1 /*
   2  * Copyright (c) 1996, 2013, Oracle and/or its affiliates. All rights reserved.
   3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
   4  *
   5  * This code is free software; you can redistribute it and/or modify it
   6  * under the terms of the GNU General Public License version 2 only, as
   7  * published by the Free Software Foundation.  Oracle designates this
   8  * particular file as subject to the "Classpath" exception as provided
   9  * by Oracle in the LICENSE file that accompanied this code.
  10  *
  11  * This code is distributed in the hope that it will be useful, but WITHOUT
  12  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  13  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
  14  * version 2 for more details (a copy is included in the LICENSE file that
  15  * accompanied this code).
  16  *
  17  * You should have received a copy of the GNU General Public License version
  18  * 2 along with this work; if not, write to the Free Software Foundation,
  19  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
  20  *
  21  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
  22  * or visit www.oracle.com if you need additional information or have any
  23  * questions.
  24  */
  25 
  26 package java.beans;
  27 
  28 import java.lang.ref.Reference;
  29 import java.lang.reflect.Method;
  30 
  31 /**
  32  * An IndexedPropertyDescriptor describes a property that acts like an
  33  * array and has an indexed read and/or indexed write method to access
  34  * specific elements of the array.
  35  * <p>
  36  * An indexed property may also provide simple non-indexed read and write
  37  * methods.  If these are present, they read and write arrays of the type
  38  * returned by the indexed read method.
  39  */
  40 
  41 public class IndexedPropertyDescriptor extends PropertyDescriptor {
  42 
  43     private Reference<? extends Class<?>> indexedPropertyTypeRef;
  44     private final MethodRef indexedReadMethodRef = new MethodRef();
  45     private final MethodRef indexedWriteMethodRef = new MethodRef();
  46 
  47     private String indexedReadMethodName;
  48     private String indexedWriteMethodName;
  49 
  50     /**
  51      * This constructor constructs an IndexedPropertyDescriptor for a property
  52      * that follows the standard Java conventions by having getFoo and setFoo
  53      * accessor methods, for both indexed access and array access.
  54      * <p>
  55      * Thus if the argument name is "fred", it will assume that there
  56      * is an indexed reader method "getFred", a non-indexed (array) reader
  57      * method also called "getFred", an indexed writer method "setFred",
  58      * and finally a non-indexed writer method "setFred".
  59      *
  60      * @param propertyName The programmatic name of the property.
  61      * @param beanClass The Class object for the target bean.
  62      * @exception IntrospectionException if an exception occurs during
  63      *              introspection.
  64      */
  65     public IndexedPropertyDescriptor(String propertyName, Class<?> beanClass)
  66                 throws IntrospectionException {
  67         this(propertyName, beanClass,
  68              Introspector.GET_PREFIX + NameGenerator.capitalize(propertyName),
  69              Introspector.SET_PREFIX + NameGenerator.capitalize(propertyName),
  70              Introspector.GET_PREFIX + NameGenerator.capitalize(propertyName),
  71              Introspector.SET_PREFIX + NameGenerator.capitalize(propertyName));
  72     }
  73 
  74     /**
  75      * This constructor takes the name of a simple property, and method
  76      * names for reading and writing the property, both indexed
  77      * and non-indexed.
  78      *
  79      * @param propertyName The programmatic name of the property.
  80      * @param beanClass  The Class object for the target bean.
  81      * @param readMethodName The name of the method used for reading the property
  82      *           values as an array.  May be null if the property is write-only
  83      *           or must be indexed.
  84      * @param writeMethodName The name of the method used for writing the property
  85      *           values as an array.  May be null if the property is read-only
  86      *           or must be indexed.
  87      * @param indexedReadMethodName The name of the method used for reading
  88      *          an indexed property value.
  89      *          May be null if the property is write-only.
  90      * @param indexedWriteMethodName The name of the method used for writing
  91      *          an indexed property value.
  92      *          May be null if the property is read-only.
  93      * @exception IntrospectionException if an exception occurs during
  94      *              introspection.
  95      */
  96     public IndexedPropertyDescriptor(String propertyName, Class<?> beanClass,
  97                 String readMethodName, String writeMethodName,
  98                 String indexedReadMethodName, String indexedWriteMethodName)
  99                 throws IntrospectionException {
 100         super(propertyName, beanClass, readMethodName, writeMethodName);
 101 
 102         this.indexedReadMethodName = indexedReadMethodName;
 103         if (indexedReadMethodName != null && getIndexedReadMethod() == null) {
 104             throw new IntrospectionException("Method not found: " + indexedReadMethodName);
 105         }
 106 
 107         this.indexedWriteMethodName = indexedWriteMethodName;
 108         if (indexedWriteMethodName != null && getIndexedWriteMethod() == null) {
 109             throw new IntrospectionException("Method not found: " + indexedWriteMethodName);
 110         }
 111         // Implemented only for type checking.
 112         findIndexedPropertyType(getIndexedReadMethod(), getIndexedWriteMethod());
 113     }
 114 
 115     /**
 116      * This constructor takes the name of a simple property, and Method
 117      * objects for reading and writing the property.
 118      *
 119      * @param propertyName The programmatic name of the property.
 120      * @param readMethod The method used for reading the property values as an array.
 121      *          May be null if the property is write-only or must be indexed.
 122      * @param writeMethod The method used for writing the property values as an array.
 123      *          May be null if the property is read-only or must be indexed.
 124      * @param indexedReadMethod The method used for reading an indexed property value.
 125      *          May be null if the property is write-only.
 126      * @param indexedWriteMethod The method used for writing an indexed property value.
 127      *          May be null if the property is read-only.
 128      * @exception IntrospectionException if an exception occurs during
 129      *              introspection.
 130      */
 131     public IndexedPropertyDescriptor(String propertyName, Method readMethod, Method writeMethod,
 132                                             Method indexedReadMethod, Method indexedWriteMethod)
 133                 throws IntrospectionException {
 134         super(propertyName, readMethod, writeMethod);
 135 
 136         setIndexedReadMethod0(indexedReadMethod);
 137         setIndexedWriteMethod0(indexedWriteMethod);
 138 
 139         // Type checking
 140         setIndexedPropertyType(findIndexedPropertyType(indexedReadMethod, indexedWriteMethod));
 141     }
 142 
 143     /**
 144      * Creates <code>PropertyDescriptor</code> for the specified bean
 145      * with the specified name and methods to read/write the property value.
 146      *
 147      * @param bean          the type of the target bean
 148      * @param base          the base name of the property (the rest of the method name)
 149      * @param read          the method used for reading the property value
 150      * @param write         the method used for writing the property value
 151      * @param readIndexed   the method used for reading an indexed property value
 152      * @param writeIndexed  the method used for writing an indexed property value
 153      * @exception IntrospectionException if an exception occurs during introspection
 154      *
 155      * @since 1.7
 156      */
 157     IndexedPropertyDescriptor(Class<?> bean, String base, Method read, Method write, Method readIndexed, Method writeIndexed) throws IntrospectionException {
 158         super(bean, base, read, write);
 159 
 160         setIndexedReadMethod0(readIndexed);
 161         setIndexedWriteMethod0(writeIndexed);
 162 
 163         // Type checking
 164         setIndexedPropertyType(findIndexedPropertyType(readIndexed, writeIndexed));
 165     }
 166 
 167     /**
 168      * Gets the method that should be used to read an indexed
 169      * property value.
 170      *
 171      * @return The method that should be used to read an indexed
 172      * property value.
 173      * May return null if the property isn't indexed or is write-only.
 174      */
 175     public synchronized Method getIndexedReadMethod() {
 176         Method indexedReadMethod = this.indexedReadMethodRef.get();
 177         if (indexedReadMethod == null) {
 178             Class<?> cls = getClass0();
 179             if (cls == null ||
 180                 (indexedReadMethodName == null && !this.indexedReadMethodRef.isSet())) {
 181                 // the Indexed readMethod was explicitly set to null.
 182                 return null;
 183             }
 184             String nextMethodName = Introspector.GET_PREFIX + getBaseName();
 185             if (indexedReadMethodName == null) {
 186                 Class<?> type = getIndexedPropertyType0();
 187                 if (type == boolean.class || type == null) {
 188                     indexedReadMethodName = Introspector.IS_PREFIX + getBaseName();
 189                 } else {
 190                     indexedReadMethodName = nextMethodName;
 191                 }
 192             }
 193 
 194             Class<?>[] args = { int.class };
 195             indexedReadMethod = Introspector.findMethod(cls, indexedReadMethodName, 1, args);
 196             if ((indexedReadMethod == null) && !indexedReadMethodName.equals(nextMethodName)) {
 197                 // no "is" method, so look for a "get" method.
 198                 indexedReadMethodName = nextMethodName;
 199                 indexedReadMethod = Introspector.findMethod(cls, indexedReadMethodName, 1, args);
 200             }
 201             setIndexedReadMethod0(indexedReadMethod);
 202         }
 203         return indexedReadMethod;
 204     }
 205 
 206     /**
 207      * Sets the method that should be used to read an indexed property value.
 208      *
 209      * @param readMethod The new indexed read method.
 210      * @throws IntrospectionException if an exception occurs during
 211      * introspection.
 212      */
 213     public synchronized void setIndexedReadMethod(Method readMethod)
 214         throws IntrospectionException {
 215 
 216         // the indexed property type is set by the reader.
 217         setIndexedPropertyType(findIndexedPropertyType(readMethod,
 218                                                        this.indexedWriteMethodRef.get()));
 219         setIndexedReadMethod0(readMethod);
 220     }
 221 
 222     private void setIndexedReadMethod0(Method readMethod) {
 223         this.indexedReadMethodRef.set(readMethod);
 224         if (readMethod == null) {
 225             indexedReadMethodName = null;
 226             return;
 227         }
 228         setClass0(readMethod.getDeclaringClass());
 229 
 230         indexedReadMethodName = readMethod.getName();
 231         setTransient(readMethod.getAnnotation(Transient.class));
 232     }
 233 
 234 
 235     /**
 236      * Gets the method that should be used to write an indexed property value.
 237      *
 238      * @return The method that should be used to write an indexed
 239      * property value.
 240      * May return null if the property isn't indexed or is read-only.
 241      */
 242     public synchronized Method getIndexedWriteMethod() {
 243         Method indexedWriteMethod = this.indexedWriteMethodRef.get();
 244         if (indexedWriteMethod == null) {
 245             Class<?> cls = getClass0();
 246             if (cls == null ||
 247                 (indexedWriteMethodName == null && !this.indexedWriteMethodRef.isSet())) {
 248                 // the Indexed writeMethod was explicitly set to null.
 249                 return null;
 250             }
 251 
 252             // We need the indexed type to ensure that we get the correct method.
 253             // Cannot use the getIndexedPropertyType method since that could
 254             // result in an infinite loop.
 255             Class<?> type = getIndexedPropertyType0();
 256             if (type == null) {
 257                 try {
 258                     type = findIndexedPropertyType(getIndexedReadMethod(), null);
 259                     setIndexedPropertyType(type);
 260                 } catch (IntrospectionException ex) {
 261                     // Set iprop type to be the classic type
 262                     Class<?> propType = getPropertyType();
 263                     if (propType.isArray()) {
 264                         type = propType.getComponentType();
 265                     }
 266                 }
 267             }
 268 
 269             if (indexedWriteMethodName == null) {
 270                 indexedWriteMethodName = Introspector.SET_PREFIX + getBaseName();
 271             }
 272 
 273             Class<?>[] args = (type == null) ? null : new Class<?>[] { int.class, type };
 274             indexedWriteMethod = Introspector.findMethod(cls, indexedWriteMethodName, 2, args);
 275             if (indexedWriteMethod != null) {
 276                 if (!indexedWriteMethod.getReturnType().equals(void.class)) {
 277                     indexedWriteMethod = null;
 278                 }
 279             }
 280             setIndexedWriteMethod0(indexedWriteMethod);
 281         }
 282         return indexedWriteMethod;
 283     }
 284 
 285     /**
 286      * Sets the method that should be used to write an indexed property value.
 287      *
 288      * @param writeMethod The new indexed write method.
 289      * @throws IntrospectionException if an exception occurs during
 290      * introspection.
 291      */
 292     public synchronized void setIndexedWriteMethod(Method writeMethod)
 293         throws IntrospectionException {
 294 
 295         // If the indexed property type has not been set, then set it.
 296         Class<?> type = findIndexedPropertyType(getIndexedReadMethod(),
 297                                              writeMethod);
 298         setIndexedPropertyType(type);
 299         setIndexedWriteMethod0(writeMethod);
 300     }
 301 
 302     private void setIndexedWriteMethod0(Method writeMethod) {
 303         this.indexedWriteMethodRef.set(writeMethod);
 304         if (writeMethod == null) {
 305             indexedWriteMethodName = null;
 306             return;
 307         }
 308         setClass0(writeMethod.getDeclaringClass());
 309 
 310         indexedWriteMethodName = writeMethod.getName();
 311         setTransient(writeMethod.getAnnotation(Transient.class));
 312     }
 313 
 314     /**
 315      * Returns the Java type info for the indexed property.
 316      * Note that the {@code Class} object may describe
 317      * primitive Java types such as {@code int}.
 318      * This type is returned by the indexed read method
 319      * or is used as the parameter type of the indexed write method.
 320      *
 321      * @return the {@code Class} object that represents the Java type info,
 322      *         or {@code null} if the type cannot be determined
 323      */
 324     public synchronized Class<?> getIndexedPropertyType() {
 325         Class<?> type = getIndexedPropertyType0();
 326         if (type == null) {
 327             try {
 328                 type = findIndexedPropertyType(getIndexedReadMethod(),
 329                                                getIndexedWriteMethod());
 330                 setIndexedPropertyType(type);
 331             } catch (IntrospectionException ex) {
 332                 // fall
 333             }
 334         }
 335         return type;
 336     }
 337 
 338     // Private methods which set get/set the Reference objects
 339 
 340     private void setIndexedPropertyType(Class<?> type) {
 341         this.indexedPropertyTypeRef = getWeakReference(type);
 342     }
 343 
 344     private Class<?> getIndexedPropertyType0() {
 345         return (this.indexedPropertyTypeRef != null)
 346                 ? this.indexedPropertyTypeRef.get()
 347                 : null;
 348     }
 349 
 350     private Class<?> findIndexedPropertyType(Method indexedReadMethod,
 351                                           Method indexedWriteMethod)
 352         throws IntrospectionException {
 353         Class<?> indexedPropertyType = null;
 354 
 355         if (indexedReadMethod != null) {
 356             Class<?>[] params = getParameterTypes(getClass0(), indexedReadMethod);
 357             if (params.length != 1) {
 358                 throw new IntrospectionException("bad indexed read method arg count");
 359             }
 360             if (params[0] != Integer.TYPE) {
 361                 throw new IntrospectionException("non int index to indexed read method");
 362             }
 363             indexedPropertyType = getReturnType(getClass0(), indexedReadMethod);
 364             if (indexedPropertyType == Void.TYPE) {
 365                 throw new IntrospectionException("indexed read method returns void");
 366             }
 367         }
 368         if (indexedWriteMethod != null) {
 369             Class<?>[] params = getParameterTypes(getClass0(), indexedWriteMethod);
 370             if (params.length != 2) {
 371                 throw new IntrospectionException("bad indexed write method arg count");
 372             }
 373             if (params[0] != Integer.TYPE) {
 374                 throw new IntrospectionException("non int index to indexed write method");
 375             }
 376             if (indexedPropertyType == null || params[1].isAssignableFrom(indexedPropertyType)) {
 377                 indexedPropertyType = params[1];
 378             } else if (!indexedPropertyType.isAssignableFrom(params[1])) {
 379                 throw new IntrospectionException(
 380                                                  "type mismatch between indexed read and indexed write methods: "
 381                                                  + getName());
 382             }
 383         }
 384         Class<?> propertyType = getPropertyType();
 385         if (propertyType != null && (!propertyType.isArray() ||
 386                                      propertyType.getComponentType() != indexedPropertyType)) {
 387             throw new IntrospectionException("type mismatch between indexed and non-indexed methods: "
 388                                              + getName());
 389         }
 390         return indexedPropertyType;
 391     }
 392 
 393     /**
 394      * Compares this <code>PropertyDescriptor</code> against the specified object.
 395      * Returns true if the objects are the same. Two <code>PropertyDescriptor</code>s
 396      * are the same if the read, write, property types, property editor and
 397      * flags  are equivalent.
 398      *
 399      * @since 1.4
 400      */
 401     public boolean equals(Object obj) {
 402         // Note: This would be identical to PropertyDescriptor but they don't
 403         // share the same fields.
 404         if (this == obj) {
 405             return true;
 406         }
 407 
 408         if (obj != null && obj instanceof IndexedPropertyDescriptor) {
 409             IndexedPropertyDescriptor other = (IndexedPropertyDescriptor)obj;
 410             Method otherIndexedReadMethod = other.getIndexedReadMethod();
 411             Method otherIndexedWriteMethod = other.getIndexedWriteMethod();
 412 
 413             if (!compareMethods(getIndexedReadMethod(), otherIndexedReadMethod)) {
 414                 return false;
 415             }
 416 
 417             if (!compareMethods(getIndexedWriteMethod(), otherIndexedWriteMethod)) {
 418                 return false;
 419             }
 420 
 421             if (getIndexedPropertyType() != other.getIndexedPropertyType()) {
 422                 return false;
 423             }
 424             return super.equals(obj);
 425         }
 426         return false;
 427     }
 428 
 429     /**
 430      * Package-private constructor.
 431      * Merge two property descriptors.  Where they conflict, give the
 432      * second argument (y) priority over the first argumnnt (x).
 433      *
 434      * @param x  The first (lower priority) PropertyDescriptor
 435      * @param y  The second (higher priority) PropertyDescriptor
 436      */
 437 
 438     IndexedPropertyDescriptor(PropertyDescriptor x, PropertyDescriptor y) {
 439         super(x,y);
 440         if (x instanceof IndexedPropertyDescriptor) {
 441             IndexedPropertyDescriptor ix = (IndexedPropertyDescriptor)x;
 442             try {
 443                 Method xr = ix.getIndexedReadMethod();
 444                 if (xr != null) {
 445                     setIndexedReadMethod(xr);
 446                 }
 447 
 448                 Method xw = ix.getIndexedWriteMethod();
 449                 if (xw != null) {
 450                     setIndexedWriteMethod(xw);
 451                 }
 452             } catch (IntrospectionException ex) {
 453                 // Should not happen
 454                 throw new AssertionError(ex);
 455             }
 456         }
 457         if (y instanceof IndexedPropertyDescriptor) {
 458             IndexedPropertyDescriptor iy = (IndexedPropertyDescriptor)y;
 459             try {
 460                 Method yr = iy.getIndexedReadMethod();
 461                 if (yr != null && yr.getDeclaringClass() == getClass0()) {
 462                     setIndexedReadMethod(yr);
 463                 }
 464 
 465                 Method yw = iy.getIndexedWriteMethod();
 466                 if (yw != null && yw.getDeclaringClass() == getClass0()) {
 467                     setIndexedWriteMethod(yw);
 468                 }
 469             } catch (IntrospectionException ex) {
 470                 // Should not happen
 471                 throw new AssertionError(ex);
 472             }
 473         }
 474     }
 475 
 476     /*
 477      * Package-private dup constructor
 478      * This must isolate the new object from any changes to the old object.
 479      */
 480     IndexedPropertyDescriptor(IndexedPropertyDescriptor old) {
 481         super(old);
 482         this.indexedReadMethodRef.set(old.indexedReadMethodRef.get());
 483         this.indexedWriteMethodRef.set(old.indexedWriteMethodRef.get());
 484         indexedPropertyTypeRef = old.indexedPropertyTypeRef;
 485         indexedWriteMethodName = old.indexedWriteMethodName;
 486         indexedReadMethodName = old.indexedReadMethodName;
 487     }
 488 
 489     void updateGenericsFor(Class<?> type) {
 490         super.updateGenericsFor(type);
 491         try {
 492             setIndexedPropertyType(findIndexedPropertyType(this.indexedReadMethodRef.get(), this.indexedWriteMethodRef.get()));
 493         }
 494         catch (IntrospectionException exception) {
 495             setIndexedPropertyType(null);
 496         }
 497     }
 498 
 499     /**
 500      * Returns a hash code value for the object.
 501      * See {@link java.lang.Object#hashCode} for a complete description.
 502      *
 503      * @return a hash code value for this object.
 504      * @since 1.5
 505      */
 506     public int hashCode() {
 507         int result = super.hashCode();
 508 
 509         result = 37 * result + ((indexedWriteMethodName == null) ? 0 :
 510                                 indexedWriteMethodName.hashCode());
 511         result = 37 * result + ((indexedReadMethodName == null) ? 0 :
 512                                 indexedReadMethodName.hashCode());
 513         result = 37 * result + ((getIndexedPropertyType() == null) ? 0 :
 514                                 getIndexedPropertyType().hashCode());
 515 
 516         return result;
 517     }
 518 
 519     void appendTo(StringBuilder sb) {
 520         super.appendTo(sb);
 521         appendTo(sb, "indexedPropertyType", this.indexedPropertyTypeRef);
 522         appendTo(sb, "indexedReadMethod", this.indexedReadMethodRef.get());
 523         appendTo(sb, "indexedWriteMethod", this.indexedWriteMethodRef.get());
 524     }
 525 }