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