1/*
2 * Copyright (c) 1996, 2015, Oracle and/or its affiliates. All rights reserved.
3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4 *
5 * This code is free software; you can redistribute it and/or modify it
6 * under the terms of the GNU General Public License version 2 only, as
7 * published by the Free Software Foundation.  Oracle designates this
8 * particular file as subject to the "Classpath" exception as provided
9 * by Oracle in the LICENSE file that accompanied this code.
10 *
11 * This code is distributed in the hope that it will be useful, but WITHOUT
12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
14 * version 2 for more details (a copy is included in the LICENSE file that
15 * accompanied this code).
16 *
17 * You should have received a copy of the GNU General Public License version
18 * 2 along with this work; if not, write to the Free Software Foundation,
19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
20 *
21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
22 * or visit www.oracle.com if you need additional information or have any
23 * questions.
24 */
25package java.beans;
26
27import java.lang.ref.Reference;
28import java.lang.reflect.Method;
29import java.lang.reflect.Constructor;
30import java.util.Map.Entry;
31
32import com.sun.beans.introspect.PropertyInfo;
33import sun.reflect.misc.ReflectUtil;
34
35/**
36 * A PropertyDescriptor describes one property that a Java Bean
37 * exports via a pair of accessor methods.
38 * @since 1.1
39 */
40public class PropertyDescriptor extends FeatureDescriptor {
41
42    private Reference<? extends Class<?>> propertyTypeRef;
43    private final MethodRef readMethodRef = new MethodRef();
44    private final MethodRef writeMethodRef = new MethodRef();
45    private Reference<? extends Class<?>> propertyEditorClassRef;
46
47    private boolean bound;
48    private boolean constrained;
49
50    // The base name of the method name which will be prefixed with the
51    // read and write method. If name == "foo" then the baseName is "Foo"
52    private String baseName;
53
54    private String writeMethodName;
55    private String readMethodName;
56
57    /**
58     * Constructs a PropertyDescriptor for a property that follows
59     * the standard Java convention by having getFoo and setFoo
60     * accessor methods.  Thus if the argument name is "fred", it will
61     * assume that the writer method is "setFred" and the reader method
62     * is "getFred" (or "isFred" for a boolean property).  Note that the
63     * property name should start with a lower case character, which will
64     * be capitalized in the method names.
65     *
66     * @param propertyName The programmatic name of the property.
67     * @param beanClass The Class object for the target bean.  For
68     *          example sun.beans.OurButton.class.
69     * @exception IntrospectionException if an exception occurs during
70     *              introspection.
71     */
72    public PropertyDescriptor(String propertyName, Class<?> beanClass)
73                throws IntrospectionException {
74        this(propertyName, beanClass,
75                Introspector.IS_PREFIX + NameGenerator.capitalize(propertyName),
76                Introspector.SET_PREFIX + NameGenerator.capitalize(propertyName));
77    }
78
79    /**
80     * This constructor takes the name of a simple property, and method
81     * names for reading and writing the property.
82     *
83     * @param propertyName The programmatic name of the property.
84     * @param beanClass The Class object for the target bean.  For
85     *          example sun.beans.OurButton.class.
86     * @param readMethodName The name of the method used for reading the property
87     *           value.  May be null if the property is write-only.
88     * @param writeMethodName The name of the method used for writing the property
89     *           value.  May be null if the property is read-only.
90     * @exception IntrospectionException if an exception occurs during
91     *              introspection.
92     */
93    public PropertyDescriptor(String propertyName, Class<?> beanClass,
94                String readMethodName, String writeMethodName)
95                throws IntrospectionException {
96        if (beanClass == null) {
97            throw new IntrospectionException("Target Bean class is null");
98        }
99        if (propertyName == null || propertyName.length() == 0) {
100            throw new IntrospectionException("bad property name");
101        }
102        if ("".equals(readMethodName) || "".equals(writeMethodName)) {
103            throw new IntrospectionException("read or write method name should not be the empty string");
104        }
105        setName(propertyName);
106        setClass0(beanClass);
107
108        this.readMethodName = readMethodName;
109        if (readMethodName != null && getReadMethod() == null) {
110            throw new IntrospectionException("Method not found: " + readMethodName);
111        }
112        this.writeMethodName = writeMethodName;
113        if (writeMethodName != null && getWriteMethod() == null) {
114            throw new IntrospectionException("Method not found: " + writeMethodName);
115        }
116        // If this class or one of its base classes allow PropertyChangeListener,
117        // then we assume that any properties we discover are "bound".
118        // See Introspector.getTargetPropertyInfo() method.
119        Class<?>[] args = { PropertyChangeListener.class };
120        this.bound = null != Introspector.findMethod(beanClass, "addPropertyChangeListener", args.length, args);
121    }
122
123    /**
124     * This constructor takes the name of a simple property, and Method
125     * objects for reading and writing the property.
126     *
127     * @param propertyName The programmatic name of the property.
128     * @param readMethod The method used for reading the property value.
129     *          May be null if the property is write-only.
130     * @param writeMethod The method used for writing the property value.
131     *          May be null if the property is read-only.
132     * @exception IntrospectionException if an exception occurs during
133     *              introspection.
134     */
135    public PropertyDescriptor(String propertyName, Method readMethod, Method writeMethod)
136                throws IntrospectionException {
137        if (propertyName == null || propertyName.length() == 0) {
138            throw new IntrospectionException("bad property name");
139        }
140        setName(propertyName);
141        setReadMethod(readMethod);
142        setWriteMethod(writeMethod);
143    }
144
145    /**
146     * Creates {@code PropertyDescriptor} from the specified property info.
147     *
148     * @param entry  the pair of values,
149     *               where the {@code key} is the base name of the property (the rest of the method name)
150     *               and the {@code value} is the automatically generated property info
151     * @param bound  the flag indicating whether it is possible to treat this property as a bound property
152     *
153     * @since 9
154     */
155    PropertyDescriptor(Entry<String,PropertyInfo> entry, boolean bound) {
156        String base = entry.getKey();
157        PropertyInfo info = entry.getValue();
158        setName(Introspector.decapitalize(base));
159        setReadMethod0(info.getReadMethod());
160        setWriteMethod0(info.getWriteMethod());
161        setPropertyType(info.getPropertyType());
162        setConstrained(info.isConstrained());
163        setBound(bound && info.is(PropertyInfo.Name.bound));
164
165        boolean isExpert = info.is(PropertyInfo.Name.expert);
166        setValue(PropertyInfo.Name.expert.name(), isExpert); // compatibility
167        setExpert(isExpert);
168
169        boolean isHidden = info.is(PropertyInfo.Name.hidden);
170        setValue(PropertyInfo.Name.hidden.name(), isHidden); // compatibility
171        setHidden(isHidden);
172
173        setPreferred(info.is(PropertyInfo.Name.preferred));
174
175        boolean isRequired = info.is(PropertyInfo.Name.required);
176        setValue(PropertyInfo.Name.required.name(), isRequired);
177
178        boolean visual = info.is(PropertyInfo.Name.visualUpdate);
179        setValue(PropertyInfo.Name.visualUpdate.name(), visual);
180
181        Object description = info.get(PropertyInfo.Name.description);
182        if (description != null) {
183            setShortDescription(description.toString());
184        }
185        Object values = info.get(PropertyInfo.Name.enumerationValues);
186        if (values == null) {
187            values = new Object[0];
188        }
189        setValue(PropertyInfo.Name.enumerationValues.name(), values);
190        this.baseName = base;
191    }
192
193    /**
194     * Returns the Java type info for the property.
195     * Note that the {@code Class} object may describe
196     * primitive Java types such as {@code int}.
197     * This type is returned by the read method
198     * or is used as the parameter type of the write method.
199     * Returns {@code null} if the type is an indexed property
200     * that does not support non-indexed access.
201     *
202     * @return the {@code Class} object that represents the Java type info,
203     *         or {@code null} if the type cannot be determined
204     */
205    public synchronized Class<?> getPropertyType() {
206        Class<?> type = getPropertyType0();
207        if (type  == null) {
208            try {
209                type = findPropertyType(getReadMethod(), getWriteMethod());
210                setPropertyType(type);
211            } catch (IntrospectionException ex) {
212                // Fall
213            }
214        }
215        return type;
216    }
217
218    private void setPropertyType(Class<?> type) {
219        this.propertyTypeRef = getWeakReference(type);
220    }
221
222    private Class<?> getPropertyType0() {
223        return (this.propertyTypeRef != null)
224                ? this.propertyTypeRef.get()
225                : null;
226    }
227
228    /**
229     * Gets the method that should be used to read the property value.
230     *
231     * @return The method that should be used to read the property value.
232     * May return null if the property can't be read.
233     */
234    public synchronized Method getReadMethod() {
235        Method readMethod = this.readMethodRef.get();
236        if (readMethod == null) {
237            Class<?> cls = getClass0();
238            if (cls == null || (readMethodName == null && !this.readMethodRef.isSet())) {
239                // The read method was explicitly set to null.
240                return null;
241            }
242            String nextMethodName = Introspector.GET_PREFIX + getBaseName();
243            if (readMethodName == null) {
244                Class<?> type = getPropertyType0();
245                if (type == boolean.class || type == null) {
246                    readMethodName = Introspector.IS_PREFIX + getBaseName();
247                } else {
248                    readMethodName = nextMethodName;
249                }
250            }
251
252            // Since there can be multiple write methods but only one getter
253            // method, find the getter method first so that you know what the
254            // property type is.  For booleans, there can be "is" and "get"
255            // methods.  If an "is" method exists, this is the official
256            // reader method so look for this one first.
257            readMethod = Introspector.findMethod(cls, readMethodName, 0);
258            if ((readMethod == null) && !readMethodName.equals(nextMethodName)) {
259                readMethodName = nextMethodName;
260                readMethod = Introspector.findMethod(cls, readMethodName, 0);
261            }
262            try {
263                setReadMethod(readMethod);
264            } catch (IntrospectionException ex) {
265                // fall
266            }
267        }
268        return readMethod;
269    }
270
271    /**
272     * Sets the method that should be used to read the property value.
273     *
274     * @param readMethod The new read method.
275     * @throws IntrospectionException if the read method is invalid
276     * @since 1.2
277     */
278    public synchronized void setReadMethod(Method readMethod)
279                                throws IntrospectionException {
280        // The property type is determined by the read method.
281        setPropertyType(findPropertyType(readMethod, this.writeMethodRef.get()));
282        setReadMethod0(readMethod);
283    }
284
285    private void setReadMethod0(Method readMethod) {
286        this.readMethodRef.set(readMethod);
287        if (readMethod == null) {
288            readMethodName = null;
289            return;
290        }
291        setClass0(readMethod.getDeclaringClass());
292
293        readMethodName = readMethod.getName();
294        setTransient(readMethod.getAnnotation(Transient.class));
295    }
296
297    /**
298     * Gets the method that should be used to write the property value.
299     *
300     * @return The method that should be used to write the property value.
301     * May return null if the property can't be written.
302     */
303    public synchronized Method getWriteMethod() {
304        Method writeMethod = this.writeMethodRef.get();
305        if (writeMethod == null) {
306            Class<?> cls = getClass0();
307            if (cls == null || (writeMethodName == null && !this.writeMethodRef.isSet())) {
308                // The write method was explicitly set to null.
309                return null;
310            }
311
312            // We need the type to fetch the correct method.
313            Class<?> type = getPropertyType0();
314            if (type == null) {
315                try {
316                    // Can't use getPropertyType since it will lead to recursive loop.
317                    type = findPropertyType(getReadMethod(), null);
318                    setPropertyType(type);
319                } catch (IntrospectionException ex) {
320                    // Without the correct property type we can't be guaranteed
321                    // to find the correct method.
322                    return null;
323                }
324            }
325
326            if (writeMethodName == null) {
327                writeMethodName = Introspector.SET_PREFIX + getBaseName();
328            }
329
330            Class<?>[] args = (type == null) ? null : new Class<?>[] { type };
331            writeMethod = Introspector.findMethod(cls, writeMethodName, 1, args);
332            if (writeMethod != null) {
333                if (!writeMethod.getReturnType().equals(void.class)) {
334                    writeMethod = null;
335                }
336            }
337            try {
338                setWriteMethod(writeMethod);
339            } catch (IntrospectionException ex) {
340                // fall through
341            }
342        }
343        return writeMethod;
344    }
345
346    /**
347     * Sets the method that should be used to write the property value.
348     *
349     * @param writeMethod The new write method.
350     * @throws IntrospectionException if the write method is invalid
351     * @since 1.2
352     */
353    public synchronized void setWriteMethod(Method writeMethod)
354                                throws IntrospectionException {
355        // Set the property type - which validates the method
356        setPropertyType(findPropertyType(getReadMethod(), writeMethod));
357        setWriteMethod0(writeMethod);
358    }
359
360    private void setWriteMethod0(Method writeMethod) {
361        this.writeMethodRef.set(writeMethod);
362        if (writeMethod == null) {
363            writeMethodName = null;
364            return;
365        }
366        setClass0(writeMethod.getDeclaringClass());
367
368        writeMethodName = writeMethod.getName();
369        setTransient(writeMethod.getAnnotation(Transient.class));
370    }
371
372    /**
373     * Overridden to ensure that a super class doesn't take precedent
374     */
375    void setClass0(Class<?> clz) {
376        if (getClass0() != null && clz.isAssignableFrom(getClass0())) {
377            // don't replace a subclass with a superclass
378            return;
379        }
380        super.setClass0(clz);
381    }
382
383    /**
384     * Updates to "bound" properties will cause a "PropertyChange" event to
385     * get fired when the property is changed.
386     *
387     * @return True if this is a bound property.
388     */
389    public boolean isBound() {
390        return bound;
391    }
392
393    /**
394     * Updates to "bound" properties will cause a "PropertyChange" event to
395     * get fired when the property is changed.
396     *
397     * @param bound True if this is a bound property.
398     */
399    public void setBound(boolean bound) {
400        this.bound = bound;
401    }
402
403    /**
404     * Attempted updates to "Constrained" properties will cause a "VetoableChange"
405     * event to get fired when the property is changed.
406     *
407     * @return True if this is a constrained property.
408     */
409    public boolean isConstrained() {
410        return constrained;
411    }
412
413    /**
414     * Attempted updates to "Constrained" properties will cause a "VetoableChange"
415     * event to get fired when the property is changed.
416     *
417     * @param constrained True if this is a constrained property.
418     */
419    public void setConstrained(boolean constrained) {
420        this.constrained = constrained;
421    }
422
423
424    /**
425     * Normally PropertyEditors will be found using the PropertyEditorManager.
426     * However if for some reason you want to associate a particular
427     * PropertyEditor with a given property, then you can do it with
428     * this method.
429     *
430     * @param propertyEditorClass  The Class for the desired PropertyEditor.
431     */
432    public void setPropertyEditorClass(Class<?> propertyEditorClass) {
433        this.propertyEditorClassRef = getWeakReference(propertyEditorClass);
434    }
435
436    /**
437     * Gets any explicit PropertyEditor Class that has been registered
438     * for this property.
439     *
440     * @return Any explicit PropertyEditor Class that has been registered
441     *          for this property.  Normally this will return "null",
442     *          indicating that no special editor has been registered,
443     *          so the PropertyEditorManager should be used to locate
444     *          a suitable PropertyEditor.
445     */
446    public Class<?> getPropertyEditorClass() {
447        return (this.propertyEditorClassRef != null)
448                ? this.propertyEditorClassRef.get()
449                : null;
450    }
451
452    /**
453     * Constructs an instance of a property editor using the current
454     * property editor class.
455     * <p>
456     * If the property editor class has a public constructor that takes an
457     * Object argument then it will be invoked using the bean parameter
458     * as the argument. Otherwise, the default constructor will be invoked.
459     *
460     * @param bean the source object
461     * @return a property editor instance or null if a property editor has
462     *         not been defined or cannot be created
463     * @since 1.5
464     */
465    @SuppressWarnings("deprecation")
466    public PropertyEditor createPropertyEditor(Object bean) {
467        Object editor = null;
468
469        final Class<?> cls = getPropertyEditorClass();
470        if (cls != null && PropertyEditor.class.isAssignableFrom(cls)
471                && ReflectUtil.isPackageAccessible(cls)) {
472            Constructor<?> ctor = null;
473            if (bean != null) {
474                try {
475                    ctor = cls.getConstructor(new Class<?>[] { Object.class });
476                } catch (Exception ex) {
477                    // Fall through
478                }
479            }
480            try {
481                if (ctor == null) {
482                    editor = cls.newInstance();
483                } else {
484                    editor = ctor.newInstance(new Object[] { bean });
485                }
486            } catch (Exception ex) {
487                // Fall through
488            }
489        }
490        return (PropertyEditor)editor;
491    }
492
493
494    /**
495     * Compares this {@code PropertyDescriptor} against the specified object.
496     * Returns true if the objects are the same. Two {@code PropertyDescriptor}s
497     * are the same if the read, write, property types, property editor and
498     * flags  are equivalent.
499     *
500     * @since 1.4
501     */
502    public boolean equals(Object obj) {
503        if (this == obj) {
504            return true;
505        }
506        if (obj != null && obj instanceof PropertyDescriptor) {
507            PropertyDescriptor other = (PropertyDescriptor)obj;
508            Method otherReadMethod = other.getReadMethod();
509            Method otherWriteMethod = other.getWriteMethod();
510
511            if (!compareMethods(getReadMethod(), otherReadMethod)) {
512                return false;
513            }
514
515            if (!compareMethods(getWriteMethod(), otherWriteMethod)) {
516                return false;
517            }
518
519            if (getPropertyType() == other.getPropertyType() &&
520                getPropertyEditorClass() == other.getPropertyEditorClass() &&
521                bound == other.isBound() && constrained == other.isConstrained() &&
522                writeMethodName == other.writeMethodName &&
523                readMethodName == other.readMethodName) {
524                return true;
525            }
526        }
527        return false;
528    }
529
530    /**
531     * Package private helper method for Descriptor .equals methods.
532     *
533     * @param a first method to compare
534     * @param b second method to compare
535     * @return boolean to indicate that the methods are equivalent
536     */
537    boolean compareMethods(Method a, Method b) {
538        // Note: perhaps this should be a protected method in FeatureDescriptor
539        if ((a == null) != (b == null)) {
540            return false;
541        }
542
543        if (a != null && b != null) {
544            if (!a.equals(b)) {
545                return false;
546            }
547        }
548        return true;
549    }
550
551    /**
552     * Package-private constructor.
553     * Merge two property descriptors.  Where they conflict, give the
554     * second argument (y) priority over the first argument (x).
555     *
556     * @param x  The first (lower priority) PropertyDescriptor
557     * @param y  The second (higher priority) PropertyDescriptor
558     */
559    PropertyDescriptor(PropertyDescriptor x, PropertyDescriptor y) {
560        super(x,y);
561
562        if (y.baseName != null) {
563            baseName = y.baseName;
564        } else {
565            baseName = x.baseName;
566        }
567
568        if (y.readMethodName != null) {
569            readMethodName = y.readMethodName;
570        } else {
571            readMethodName = x.readMethodName;
572        }
573
574        if (y.writeMethodName != null) {
575            writeMethodName = y.writeMethodName;
576        } else {
577            writeMethodName = x.writeMethodName;
578        }
579
580        if (y.propertyTypeRef != null) {
581            propertyTypeRef = y.propertyTypeRef;
582        } else {
583            propertyTypeRef = x.propertyTypeRef;
584        }
585
586        // Figure out the merged read method.
587        Method xr = x.getReadMethod();
588        Method yr = y.getReadMethod();
589
590        // Normally give priority to y's readMethod.
591        try {
592            if (isAssignable(xr, yr)) {
593                setReadMethod(yr);
594            } else {
595                setReadMethod(xr);
596            }
597        } catch (IntrospectionException ex) {
598            // fall through
599        }
600
601        // However, if both x and y reference read methods in the same class,
602        // give priority to a boolean "is" method over a boolean "get" method.
603        if (xr != null && yr != null &&
604                   xr.getDeclaringClass() == yr.getDeclaringClass() &&
605                   getReturnType(getClass0(), xr) == boolean.class &&
606                   getReturnType(getClass0(), yr) == boolean.class &&
607                   xr.getName().indexOf(Introspector.IS_PREFIX) == 0 &&
608                   yr.getName().indexOf(Introspector.GET_PREFIX) == 0) {
609            try {
610                setReadMethod(xr);
611            } catch (IntrospectionException ex) {
612                // fall through
613            }
614        }
615
616        Method xw = x.getWriteMethod();
617        Method yw = y.getWriteMethod();
618
619        try {
620            if (yw != null) {
621                setWriteMethod(yw);
622            } else {
623                setWriteMethod(xw);
624            }
625        } catch (IntrospectionException ex) {
626            // Fall through
627        }
628
629        if (y.getPropertyEditorClass() != null) {
630            setPropertyEditorClass(y.getPropertyEditorClass());
631        } else {
632            setPropertyEditorClass(x.getPropertyEditorClass());
633        }
634
635
636        bound = x.bound | y.bound;
637        constrained = x.constrained | y.constrained;
638    }
639
640    /*
641     * Package-private dup constructor.
642     * This must isolate the new object from any changes to the old object.
643     */
644    PropertyDescriptor(PropertyDescriptor old) {
645        super(old);
646        propertyTypeRef = old.propertyTypeRef;
647        this.readMethodRef.set(old.readMethodRef.get());
648        this.writeMethodRef.set(old.writeMethodRef.get());
649        propertyEditorClassRef = old.propertyEditorClassRef;
650
651        writeMethodName = old.writeMethodName;
652        readMethodName = old.readMethodName;
653        baseName = old.baseName;
654
655        bound = old.bound;
656        constrained = old.constrained;
657    }
658
659    void updateGenericsFor(Class<?> type) {
660        setClass0(type);
661        try {
662            setPropertyType(findPropertyType(this.readMethodRef.get(), this.writeMethodRef.get()));
663        }
664        catch (IntrospectionException exception) {
665            setPropertyType(null);
666        }
667    }
668
669    /**
670     * Returns the property type that corresponds to the read and write method.
671     * The type precedence is given to the readMethod.
672     *
673     * @return the type of the property descriptor or null if both
674     *         read and write methods are null.
675     * @throws IntrospectionException if the read or write method is invalid
676     */
677    private Class<?> findPropertyType(Method readMethod, Method writeMethod)
678        throws IntrospectionException {
679        Class<?> propertyType = null;
680        try {
681            if (readMethod != null) {
682                Class<?>[] params = getParameterTypes(getClass0(), readMethod);
683                if (params.length != 0) {
684                    throw new IntrospectionException("bad read method arg count: "
685                                                     + readMethod);
686                }
687                propertyType = getReturnType(getClass0(), readMethod);
688                if (propertyType == Void.TYPE) {
689                    throw new IntrospectionException("read method " +
690                                        readMethod.getName() + " returns void");
691                }
692            }
693            if (writeMethod != null) {
694                Class<?>[] params = getParameterTypes(getClass0(), writeMethod);
695                if (params.length != 1) {
696                    throw new IntrospectionException("bad write method arg count: "
697                                                     + writeMethod);
698                }
699                if (propertyType != null && !params[0].isAssignableFrom(propertyType)) {
700                    throw new IntrospectionException("type mismatch between read and write methods");
701                }
702                propertyType = params[0];
703            }
704        } catch (IntrospectionException ex) {
705            throw ex;
706        }
707        return propertyType;
708    }
709
710
711    /**
712     * Returns a hash code value for the object.
713     * See {@link java.lang.Object#hashCode} for a complete description.
714     *
715     * @return a hash code value for this object.
716     * @since 1.5
717     */
718    public int hashCode() {
719        int result = 7;
720
721        result = 37 * result + ((getPropertyType() == null) ? 0 :
722                                getPropertyType().hashCode());
723        result = 37 * result + ((getReadMethod() == null) ? 0 :
724                                getReadMethod().hashCode());
725        result = 37 * result + ((getWriteMethod() == null) ? 0 :
726                                getWriteMethod().hashCode());
727        result = 37 * result + ((getPropertyEditorClass() == null) ? 0 :
728                                getPropertyEditorClass().hashCode());
729        result = 37 * result + ((writeMethodName == null) ? 0 :
730                                writeMethodName.hashCode());
731        result = 37 * result + ((readMethodName == null) ? 0 :
732                                readMethodName.hashCode());
733        result = 37 * result + getName().hashCode();
734        result = 37 * result + ((bound == false) ? 0 : 1);
735        result = 37 * result + ((constrained == false) ? 0 : 1);
736
737        return result;
738    }
739
740    // Calculate once since capitalize() is expensive.
741    String getBaseName() {
742        if (baseName == null) {
743            baseName = NameGenerator.capitalize(getName());
744        }
745        return baseName;
746    }
747
748    void appendTo(StringBuilder sb) {
749        appendTo(sb, "bound", this.bound);
750        appendTo(sb, "constrained", this.constrained);
751        appendTo(sb, "propertyEditorClass", this.propertyEditorClassRef);
752        appendTo(sb, "propertyType", this.propertyTypeRef);
753        appendTo(sb, "readMethod", this.readMethodRef.get());
754        appendTo(sb, "writeMethod", this.writeMethodRef.get());
755    }
756
757    boolean isAssignable(Method m1, Method m2) {
758        if (m1 == null) {
759            return true; // choose second method
760        }
761        if (m2 == null) {
762            return false; // choose first method
763        }
764        if (!m1.getName().equals(m2.getName())) {
765            return true; // choose second method by default
766        }
767        Class<?> type1 = m1.getDeclaringClass();
768        Class<?> type2 = m2.getDeclaringClass();
769        if (!type1.isAssignableFrom(type2)) {
770            return false; // choose first method: it declared later
771        }
772        type1 = getReturnType(getClass0(), m1);
773        type2 = getReturnType(getClass0(), m2);
774        if (!type1.isAssignableFrom(type2)) {
775            return false; // choose first method: it overrides return type
776        }
777        Class<?>[] args1 = getParameterTypes(getClass0(), m1);
778        Class<?>[] args2 = getParameterTypes(getClass0(), m2);
779        if (args1.length != args2.length) {
780            return true; // choose second method by default
781        }
782        for (int i = 0; i < args1.length; i++) {
783            if (!args1[i].isAssignableFrom(args2[i])) {
784                return false; // choose first method: it overrides parameter
785            }
786        }
787        return true; // choose second method
788    }
789}
790