PropertyDescriptor.java revision 16136:a6eaacd1d678
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            setValue(PropertyInfo.Name.enumerationValues.name(), values);
188        }
189        this.baseName = base;
190    }
191
192    /**
193     * Returns the Java type info for the property.
194     * Note that the {@code Class} object may describe
195     * primitive Java types such as {@code int}.
196     * This type is returned by the read method
197     * or is used as the parameter type of the write method.
198     * Returns {@code null} if the type is an indexed property
199     * that does not support non-indexed access.
200     *
201     * @return the {@code Class} object that represents the Java type info,
202     *         or {@code null} if the type cannot be determined
203     */
204    public synchronized Class<?> getPropertyType() {
205        Class<?> type = getPropertyType0();
206        if (type  == null) {
207            try {
208                type = findPropertyType(getReadMethod(), getWriteMethod());
209                setPropertyType(type);
210            } catch (IntrospectionException ex) {
211                // Fall
212            }
213        }
214        return type;
215    }
216
217    private void setPropertyType(Class<?> type) {
218        this.propertyTypeRef = getWeakReference(type);
219    }
220
221    private Class<?> getPropertyType0() {
222        return (this.propertyTypeRef != null)
223                ? this.propertyTypeRef.get()
224                : null;
225    }
226
227    /**
228     * Gets the method that should be used to read the property value.
229     *
230     * @return The method that should be used to read the property value.
231     * May return null if the property can't be read.
232     */
233    public synchronized Method getReadMethod() {
234        Method readMethod = this.readMethodRef.get();
235        if (readMethod == null) {
236            Class<?> cls = getClass0();
237            if (cls == null || (readMethodName == null && !this.readMethodRef.isSet())) {
238                // The read method was explicitly set to null.
239                return null;
240            }
241            String nextMethodName = Introspector.GET_PREFIX + getBaseName();
242            if (readMethodName == null) {
243                Class<?> type = getPropertyType0();
244                if (type == boolean.class || type == null) {
245                    readMethodName = Introspector.IS_PREFIX + getBaseName();
246                } else {
247                    readMethodName = nextMethodName;
248                }
249            }
250
251            // Since there can be multiple write methods but only one getter
252            // method, find the getter method first so that you know what the
253            // property type is.  For booleans, there can be "is" and "get"
254            // methods.  If an "is" method exists, this is the official
255            // reader method so look for this one first.
256            readMethod = Introspector.findMethod(cls, readMethodName, 0);
257            if ((readMethod == null) && !readMethodName.equals(nextMethodName)) {
258                readMethodName = nextMethodName;
259                readMethod = Introspector.findMethod(cls, readMethodName, 0);
260            }
261            try {
262                setReadMethod(readMethod);
263            } catch (IntrospectionException ex) {
264                // fall
265            }
266        }
267        return readMethod;
268    }
269
270    /**
271     * Sets the method that should be used to read the property value.
272     *
273     * @param readMethod The new read method.
274     * @throws IntrospectionException if the read method is invalid
275     * @since 1.2
276     */
277    public synchronized void setReadMethod(Method readMethod)
278                                throws IntrospectionException {
279        // The property type is determined by the read method.
280        setPropertyType(findPropertyType(readMethod, this.writeMethodRef.get()));
281        setReadMethod0(readMethod);
282    }
283
284    private void setReadMethod0(Method readMethod) {
285        this.readMethodRef.set(readMethod);
286        if (readMethod == null) {
287            readMethodName = null;
288            return;
289        }
290        setClass0(readMethod.getDeclaringClass());
291
292        readMethodName = readMethod.getName();
293        setTransient(readMethod.getAnnotation(Transient.class));
294    }
295
296    /**
297     * Gets the method that should be used to write the property value.
298     *
299     * @return The method that should be used to write the property value.
300     * May return null if the property can't be written.
301     */
302    public synchronized Method getWriteMethod() {
303        Method writeMethod = this.writeMethodRef.get();
304        if (writeMethod == null) {
305            Class<?> cls = getClass0();
306            if (cls == null || (writeMethodName == null && !this.writeMethodRef.isSet())) {
307                // The write method was explicitly set to null.
308                return null;
309            }
310
311            // We need the type to fetch the correct method.
312            Class<?> type = getPropertyType0();
313            if (type == null) {
314                try {
315                    // Can't use getPropertyType since it will lead to recursive loop.
316                    type = findPropertyType(getReadMethod(), null);
317                    setPropertyType(type);
318                } catch (IntrospectionException ex) {
319                    // Without the correct property type we can't be guaranteed
320                    // to find the correct method.
321                    return null;
322                }
323            }
324
325            if (writeMethodName == null) {
326                writeMethodName = Introspector.SET_PREFIX + getBaseName();
327            }
328
329            Class<?>[] args = (type == null) ? null : new Class<?>[] { type };
330            writeMethod = Introspector.findMethod(cls, writeMethodName, 1, args);
331            if (writeMethod != null) {
332                if (!writeMethod.getReturnType().equals(void.class)) {
333                    writeMethod = null;
334                }
335            }
336            try {
337                setWriteMethod(writeMethod);
338            } catch (IntrospectionException ex) {
339                // fall through
340            }
341        }
342        return writeMethod;
343    }
344
345    /**
346     * Sets the method that should be used to write the property value.
347     *
348     * @param writeMethod The new write method.
349     * @throws IntrospectionException if the write method is invalid
350     * @since 1.2
351     */
352    public synchronized void setWriteMethod(Method writeMethod)
353                                throws IntrospectionException {
354        // Set the property type - which validates the method
355        setPropertyType(findPropertyType(getReadMethod(), writeMethod));
356        setWriteMethod0(writeMethod);
357    }
358
359    private void setWriteMethod0(Method writeMethod) {
360        this.writeMethodRef.set(writeMethod);
361        if (writeMethod == null) {
362            writeMethodName = null;
363            return;
364        }
365        setClass0(writeMethod.getDeclaringClass());
366
367        writeMethodName = writeMethod.getName();
368        setTransient(writeMethod.getAnnotation(Transient.class));
369    }
370
371    /**
372     * Overridden to ensure that a super class doesn't take precedent
373     */
374    void setClass0(Class<?> clz) {
375        if (getClass0() != null && clz.isAssignableFrom(getClass0())) {
376            // don't replace a subclass with a superclass
377            return;
378        }
379        super.setClass0(clz);
380    }
381
382    /**
383     * Updates to "bound" properties will cause a "PropertyChange" event to
384     * get fired when the property is changed.
385     *
386     * @return True if this is a bound property.
387     */
388    public boolean isBound() {
389        return bound;
390    }
391
392    /**
393     * Updates to "bound" properties will cause a "PropertyChange" event to
394     * get fired when the property is changed.
395     *
396     * @param bound True if this is a bound property.
397     */
398    public void setBound(boolean bound) {
399        this.bound = bound;
400    }
401
402    /**
403     * Attempted updates to "Constrained" properties will cause a "VetoableChange"
404     * event to get fired when the property is changed.
405     *
406     * @return True if this is a constrained property.
407     */
408    public boolean isConstrained() {
409        return constrained;
410    }
411
412    /**
413     * Attempted updates to "Constrained" properties will cause a "VetoableChange"
414     * event to get fired when the property is changed.
415     *
416     * @param constrained True if this is a constrained property.
417     */
418    public void setConstrained(boolean constrained) {
419        this.constrained = constrained;
420    }
421
422
423    /**
424     * Normally PropertyEditors will be found using the PropertyEditorManager.
425     * However if for some reason you want to associate a particular
426     * PropertyEditor with a given property, then you can do it with
427     * this method.
428     *
429     * @param propertyEditorClass  The Class for the desired PropertyEditor.
430     */
431    public void setPropertyEditorClass(Class<?> propertyEditorClass) {
432        this.propertyEditorClassRef = getWeakReference(propertyEditorClass);
433    }
434
435    /**
436     * Gets any explicit PropertyEditor Class that has been registered
437     * for this property.
438     *
439     * @return Any explicit PropertyEditor Class that has been registered
440     *          for this property.  Normally this will return "null",
441     *          indicating that no special editor has been registered,
442     *          so the PropertyEditorManager should be used to locate
443     *          a suitable PropertyEditor.
444     */
445    public Class<?> getPropertyEditorClass() {
446        return (this.propertyEditorClassRef != null)
447                ? this.propertyEditorClassRef.get()
448                : null;
449    }
450
451    /**
452     * Constructs an instance of a property editor using the current
453     * property editor class.
454     * <p>
455     * If the property editor class has a public constructor that takes an
456     * Object argument then it will be invoked using the bean parameter
457     * as the argument. Otherwise, the default constructor will be invoked.
458     *
459     * @param bean the source object
460     * @return a property editor instance or null if a property editor has
461     *         not been defined or cannot be created
462     * @since 1.5
463     */
464    @SuppressWarnings("deprecation")
465    public PropertyEditor createPropertyEditor(Object bean) {
466        Object editor = null;
467
468        final Class<?> cls = getPropertyEditorClass();
469        if (cls != null && PropertyEditor.class.isAssignableFrom(cls)
470                && ReflectUtil.isPackageAccessible(cls)) {
471            Constructor<?> ctor = null;
472            if (bean != null) {
473                try {
474                    ctor = cls.getConstructor(new Class<?>[] { Object.class });
475                } catch (Exception ex) {
476                    // Fall through
477                }
478            }
479            try {
480                if (ctor == null) {
481                    editor = cls.newInstance();
482                } else {
483                    editor = ctor.newInstance(new Object[] { bean });
484                }
485            } catch (Exception ex) {
486                // Fall through
487            }
488        }
489        return (PropertyEditor)editor;
490    }
491
492
493    /**
494     * Compares this {@code PropertyDescriptor} against the specified object.
495     * Returns true if the objects are the same. Two {@code PropertyDescriptor}s
496     * are the same if the read, write, property types, property editor and
497     * flags  are equivalent.
498     *
499     * @since 1.4
500     */
501    public boolean equals(Object obj) {
502        if (this == obj) {
503            return true;
504        }
505        if (obj != null && obj instanceof PropertyDescriptor) {
506            PropertyDescriptor other = (PropertyDescriptor)obj;
507            Method otherReadMethod = other.getReadMethod();
508            Method otherWriteMethod = other.getWriteMethod();
509
510            if (!compareMethods(getReadMethod(), otherReadMethod)) {
511                return false;
512            }
513
514            if (!compareMethods(getWriteMethod(), otherWriteMethod)) {
515                return false;
516            }
517
518            if (getPropertyType() == other.getPropertyType() &&
519                getPropertyEditorClass() == other.getPropertyEditorClass() &&
520                bound == other.isBound() && constrained == other.isConstrained() &&
521                writeMethodName == other.writeMethodName &&
522                readMethodName == other.readMethodName) {
523                return true;
524            }
525        }
526        return false;
527    }
528
529    /**
530     * Package private helper method for Descriptor .equals methods.
531     *
532     * @param a first method to compare
533     * @param b second method to compare
534     * @return boolean to indicate that the methods are equivalent
535     */
536    boolean compareMethods(Method a, Method b) {
537        // Note: perhaps this should be a protected method in FeatureDescriptor
538        if ((a == null) != (b == null)) {
539            return false;
540        }
541
542        if (a != null && b != null) {
543            if (!a.equals(b)) {
544                return false;
545            }
546        }
547        return true;
548    }
549
550    /**
551     * Package-private constructor.
552     * Merge two property descriptors.  Where they conflict, give the
553     * second argument (y) priority over the first argument (x).
554     *
555     * @param x  The first (lower priority) PropertyDescriptor
556     * @param y  The second (higher priority) PropertyDescriptor
557     */
558    PropertyDescriptor(PropertyDescriptor x, PropertyDescriptor y) {
559        super(x,y);
560
561        if (y.baseName != null) {
562            baseName = y.baseName;
563        } else {
564            baseName = x.baseName;
565        }
566
567        if (y.readMethodName != null) {
568            readMethodName = y.readMethodName;
569        } else {
570            readMethodName = x.readMethodName;
571        }
572
573        if (y.writeMethodName != null) {
574            writeMethodName = y.writeMethodName;
575        } else {
576            writeMethodName = x.writeMethodName;
577        }
578
579        if (y.propertyTypeRef != null) {
580            propertyTypeRef = y.propertyTypeRef;
581        } else {
582            propertyTypeRef = x.propertyTypeRef;
583        }
584
585        // Figure out the merged read method.
586        Method xr = x.getReadMethod();
587        Method yr = y.getReadMethod();
588
589        // Normally give priority to y's readMethod.
590        try {
591            if (isAssignable(xr, yr)) {
592                setReadMethod(yr);
593            } else {
594                setReadMethod(xr);
595            }
596        } catch (IntrospectionException ex) {
597            // fall through
598        }
599
600        // However, if both x and y reference read methods in the same class,
601        // give priority to a boolean "is" method over a boolean "get" method.
602        if (xr != null && yr != null &&
603                   xr.getDeclaringClass() == yr.getDeclaringClass() &&
604                   getReturnType(getClass0(), xr) == boolean.class &&
605                   getReturnType(getClass0(), yr) == boolean.class &&
606                   xr.getName().indexOf(Introspector.IS_PREFIX) == 0 &&
607                   yr.getName().indexOf(Introspector.GET_PREFIX) == 0) {
608            try {
609                setReadMethod(xr);
610            } catch (IntrospectionException ex) {
611                // fall through
612            }
613        }
614
615        Method xw = x.getWriteMethod();
616        Method yw = y.getWriteMethod();
617
618        try {
619            if (yw != null) {
620                setWriteMethod(yw);
621            } else {
622                setWriteMethod(xw);
623            }
624        } catch (IntrospectionException ex) {
625            // Fall through
626        }
627
628        if (y.getPropertyEditorClass() != null) {
629            setPropertyEditorClass(y.getPropertyEditorClass());
630        } else {
631            setPropertyEditorClass(x.getPropertyEditorClass());
632        }
633
634
635        bound = x.bound | y.bound;
636        constrained = x.constrained | y.constrained;
637    }
638
639    /*
640     * Package-private dup constructor.
641     * This must isolate the new object from any changes to the old object.
642     */
643    PropertyDescriptor(PropertyDescriptor old) {
644        super(old);
645        propertyTypeRef = old.propertyTypeRef;
646        this.readMethodRef.set(old.readMethodRef.get());
647        this.writeMethodRef.set(old.writeMethodRef.get());
648        propertyEditorClassRef = old.propertyEditorClassRef;
649
650        writeMethodName = old.writeMethodName;
651        readMethodName = old.readMethodName;
652        baseName = old.baseName;
653
654        bound = old.bound;
655        constrained = old.constrained;
656    }
657
658    void updateGenericsFor(Class<?> type) {
659        setClass0(type);
660        try {
661            setPropertyType(findPropertyType(this.readMethodRef.get(), this.writeMethodRef.get()));
662        }
663        catch (IntrospectionException exception) {
664            setPropertyType(null);
665        }
666    }
667
668    /**
669     * Returns the property type that corresponds to the read and write method.
670     * The type precedence is given to the readMethod.
671     *
672     * @return the type of the property descriptor or null if both
673     *         read and write methods are null.
674     * @throws IntrospectionException if the read or write method is invalid
675     */
676    private Class<?> findPropertyType(Method readMethod, Method writeMethod)
677        throws IntrospectionException {
678        Class<?> propertyType = null;
679        try {
680            if (readMethod != null) {
681                Class<?>[] params = getParameterTypes(getClass0(), readMethod);
682                if (params.length != 0) {
683                    throw new IntrospectionException("bad read method arg count: "
684                                                     + readMethod);
685                }
686                propertyType = getReturnType(getClass0(), readMethod);
687                if (propertyType == Void.TYPE) {
688                    throw new IntrospectionException("read method " +
689                                        readMethod.getName() + " returns void");
690                }
691            }
692            if (writeMethod != null) {
693                Class<?>[] params = getParameterTypes(getClass0(), writeMethod);
694                if (params.length != 1) {
695                    throw new IntrospectionException("bad write method arg count: "
696                                                     + writeMethod);
697                }
698                if (propertyType != null && !params[0].isAssignableFrom(propertyType)) {
699                    throw new IntrospectionException("type mismatch between read and write methods");
700                }
701                propertyType = params[0];
702            }
703        } catch (IntrospectionException ex) {
704            throw ex;
705        }
706        return propertyType;
707    }
708
709
710    /**
711     * Returns a hash code value for the object.
712     * See {@link java.lang.Object#hashCode} for a complete description.
713     *
714     * @return a hash code value for this object.
715     * @since 1.5
716     */
717    public int hashCode() {
718        int result = 7;
719
720        result = 37 * result + ((getPropertyType() == null) ? 0 :
721                                getPropertyType().hashCode());
722        result = 37 * result + ((getReadMethod() == null) ? 0 :
723                                getReadMethod().hashCode());
724        result = 37 * result + ((getWriteMethod() == null) ? 0 :
725                                getWriteMethod().hashCode());
726        result = 37 * result + ((getPropertyEditorClass() == null) ? 0 :
727                                getPropertyEditorClass().hashCode());
728        result = 37 * result + ((writeMethodName == null) ? 0 :
729                                writeMethodName.hashCode());
730        result = 37 * result + ((readMethodName == null) ? 0 :
731                                readMethodName.hashCode());
732        result = 37 * result + getName().hashCode();
733        result = 37 * result + ((bound == false) ? 0 : 1);
734        result = 37 * result + ((constrained == false) ? 0 : 1);
735
736        return result;
737    }
738
739    // Calculate once since capitalize() is expensive.
740    String getBaseName() {
741        if (baseName == null) {
742            baseName = NameGenerator.capitalize(getName());
743        }
744        return baseName;
745    }
746
747    void appendTo(StringBuilder sb) {
748        appendTo(sb, "bound", this.bound);
749        appendTo(sb, "constrained", this.constrained);
750        appendTo(sb, "propertyEditorClass", this.propertyEditorClassRef);
751        appendTo(sb, "propertyType", this.propertyTypeRef);
752        appendTo(sb, "readMethod", this.readMethodRef.get());
753        appendTo(sb, "writeMethod", this.writeMethodRef.get());
754    }
755
756    boolean isAssignable(Method m1, Method m2) {
757        if (m1 == null) {
758            return true; // choose second method
759        }
760        if (m2 == null) {
761            return false; // choose first method
762        }
763        if (!m1.getName().equals(m2.getName())) {
764            return true; // choose second method by default
765        }
766        Class<?> type1 = m1.getDeclaringClass();
767        Class<?> type2 = m2.getDeclaringClass();
768        if (!type1.isAssignableFrom(type2)) {
769            return false; // choose first method: it declared later
770        }
771        type1 = getReturnType(getClass0(), m1);
772        type2 = getReturnType(getClass0(), m2);
773        if (!type1.isAssignableFrom(type2)) {
774            return false; // choose first method: it overrides return type
775        }
776        Class<?>[] args1 = getParameterTypes(getClass0(), m1);
777        Class<?>[] args2 = getParameterTypes(getClass0(), m2);
778        if (args1.length != args2.length) {
779            return true; // choose second method by default
780        }
781        for (int i = 0; i < args1.length; i++) {
782            if (!args1[i].isAssignableFrom(args2[i])) {
783                return false; // choose first method: it overrides parameter
784            }
785        }
786        return true; // choose second method
787    }
788}
789