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