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