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