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