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