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  */

25 package java.beans;
26
27 import java.lang.ref.Reference;
28 import java.lang.reflect.Method;
29 import java.lang.reflect.Constructor;
30 import java.util.Map.Entry;
31
32 import com.sun.beans.introspect.PropertyInfo;
33 import 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  */

40 public 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 nullif 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 nullif 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