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