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.util.Map.Entry;
  30 
  31 import com.sun.beans.introspect.PropertyInfo;
  32 
  33 /**
  34  * An IndexedPropertyDescriptor describes a property that acts like an
  35  * array and has an indexed read and/or indexed write method to access
  36  * specific elements of the array.
  37  * <p>
  38  * An indexed property may also provide simple non-indexed read and write
  39  * methods.  If these are present, they read and write arrays of the type
  40  * returned by the indexed read method.
  41  *
  42  * @since 1.1
  43  */
  44 
  45 public class IndexedPropertyDescriptor extends PropertyDescriptor {
  46 
  47     private Reference<? extends Class<?>> indexedPropertyTypeRef;
  48     private final MethodRef indexedReadMethodRef = new MethodRef();
  49     private final MethodRef indexedWriteMethodRef = new MethodRef();
  50 
  51     private String indexedReadMethodName;
  52     private String indexedWriteMethodName;
  53 
  54     /**
  55      * This constructor constructs an IndexedPropertyDescriptor for a property
  56      * that follows the standard Java conventions by having getFoo and setFoo
  57      * accessor methods, for both indexed access and array access.
  58      * <p>
  59      * Thus if the argument name is "fred", it will assume that there
  60      * is an indexed reader method "getFred", a non-indexed (array) reader
  61      * method also called "getFred", an indexed writer method "setFred",
  62      * and finally a non-indexed writer method "setFred".
  63      *
  64      * @param propertyName The programmatic name of the property.
  65      * @param beanClass The Class object for the target bean.
  66      * @exception IntrospectionException if an exception occurs during
  67      *              introspection.
  68      */
  69     public IndexedPropertyDescriptor(String propertyName, Class<?> beanClass)
  70                 throws IntrospectionException {
  71         this(propertyName, beanClass,
  72              Introspector.GET_PREFIX + NameGenerator.capitalize(propertyName),
  73              Introspector.SET_PREFIX + NameGenerator.capitalize(propertyName),
  74              Introspector.GET_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, both indexed
  81      * and non-indexed.
  82      *
  83      * @param propertyName The programmatic name of the property.
  84      * @param beanClass  The Class object for the target bean.
  85      * @param readMethodName The name of the method used for reading the property
  86      *           values as an array.  May be null if the property is write-only
  87      *           or must be indexed.
  88      * @param writeMethodName The name of the method used for writing the property
  89      *           values as an array.  May be null if the property is read-only
  90      *           or must be indexed.
  91      * @param indexedReadMethodName The name of the method used for reading
  92      *          an indexed property value.
  93      *          May be null if the property is write-only.
  94      * @param indexedWriteMethodName The name of the method used for writing
  95      *          an indexed property value.
  96      *          May be null if the property is read-only.
  97      * @exception IntrospectionException if an exception occurs during
  98      *              introspection.
  99      */
 100     public IndexedPropertyDescriptor(String propertyName, Class<?> beanClass,
 101                 String readMethodName, String writeMethodName,
 102                 String indexedReadMethodName, String indexedWriteMethodName)
 103                 throws IntrospectionException {
 104         super(propertyName, beanClass, readMethodName, writeMethodName);
 105 
 106         this.indexedReadMethodName = indexedReadMethodName;
 107         if (indexedReadMethodName != null && getIndexedReadMethod() == null) {
 108             throw new IntrospectionException("Method not found: " + indexedReadMethodName);
 109         }
 110 
 111         this.indexedWriteMethodName = indexedWriteMethodName;
 112         if (indexedWriteMethodName != null && getIndexedWriteMethod() == null) {
 113             throw new IntrospectionException("Method not found: " + indexedWriteMethodName);
 114         }
 115         // Implemented only for type checking.
 116         findIndexedPropertyType(getIndexedReadMethod(), getIndexedWriteMethod());
 117     }
 118 
 119     /**
 120      * This constructor takes the name of a simple property, and Method
 121      * objects for reading and writing the property.
 122      *
 123      * @param propertyName The programmatic name of the property.
 124      * @param readMethod The method used for reading the property values as an array.
 125      *          May be null if the property is write-only or must be indexed.
 126      * @param writeMethod The method used for writing the property values as an array.
 127      *          May be null if the property is read-only or must be indexed.
 128      * @param indexedReadMethod The method used for reading an indexed property value.
 129      *          May be null if the property is write-only.
 130      * @param indexedWriteMethod The method used for writing an indexed property value.
 131      *          May be null if the property is read-only.
 132      * @exception IntrospectionException if an exception occurs during
 133      *              introspection.
 134      */
 135     public IndexedPropertyDescriptor(String propertyName, Method readMethod, Method writeMethod,
 136                                             Method indexedReadMethod, Method indexedWriteMethod)
 137                 throws IntrospectionException {
 138         super(propertyName, readMethod, writeMethod);
 139 
 140         setIndexedReadMethod0(indexedReadMethod);
 141         setIndexedWriteMethod0(indexedWriteMethod);
 142 
 143         // Type checking
 144         setIndexedPropertyType(findIndexedPropertyType(indexedReadMethod, indexedWriteMethod));
 145     }
 146 
 147     /**
 148      * Creates {@code IndexedPropertyDescriptor} from the specified property info.
 149      *
 150      * @param entry  the key-value pair,
 151      *               where the {@code key} is the base name of the property (the rest of the method name)
 152      *               and the {@code value} is the automatically generated property info
 153      * @param bound  the flag indicating whether it is possible to treat this property as a bound property
 154      *
 155      * @since 1.9
 156      */
 157     IndexedPropertyDescriptor(Entry<String,PropertyInfo> entry, boolean bound) {
 158         super(entry, bound);
 159         PropertyInfo info = entry.getValue().getIndexed();
 160         setIndexedReadMethod0(info.getReadMethod());
 161         setIndexedWriteMethod0(info.getWriteMethod());
 162         setIndexedPropertyType(info.getPropertyType());
 163     }
 164 
 165     /**
 166      * Gets the method that should be used to read an indexed
 167      * property value.
 168      *
 169      * @return The method that should be used to read an indexed
 170      * property value.
 171      * May return null if the property isn't indexed or is write-only.
 172      */
 173     public synchronized Method getIndexedReadMethod() {
 174         Method indexedReadMethod = this.indexedReadMethodRef.get();
 175         if (indexedReadMethod == null) {
 176             Class<?> cls = getClass0();
 177             if (cls == null ||
 178                 (indexedReadMethodName == null && !this.indexedReadMethodRef.isSet())) {
 179                 // the Indexed readMethod was explicitly set to null.
 180                 return null;
 181             }
 182             String nextMethodName = Introspector.GET_PREFIX + getBaseName();
 183             if (indexedReadMethodName == null) {
 184                 Class<?> type = getIndexedPropertyType0();
 185                 if (type == boolean.class || type == null) {
 186                     indexedReadMethodName = Introspector.IS_PREFIX + getBaseName();
 187                 } else {
 188                     indexedReadMethodName = nextMethodName;
 189                 }
 190             }
 191 
 192             Class<?>[] args = { int.class };
 193             indexedReadMethod = Introspector.findMethod(cls, indexedReadMethodName, 1, args);
 194             if ((indexedReadMethod == null) && !indexedReadMethodName.equals(nextMethodName)) {
 195                 // no "is" method, so look for a "get" method.
 196                 indexedReadMethodName = nextMethodName;
 197                 indexedReadMethod = Introspector.findMethod(cls, indexedReadMethodName, 1, args);
 198             }
 199             setIndexedReadMethod0(indexedReadMethod);
 200         }
 201         return indexedReadMethod;
 202     }
 203 
 204     /**
 205      * Sets the method that should be used to read an indexed property value.
 206      *
 207      * @param readMethod The new indexed read method.
 208      * @throws IntrospectionException if an exception occurs during
 209      * introspection.
 210      *
 211      * @since 1.2
 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      * @since 1.2
 293      */
 294     public synchronized void setIndexedWriteMethod(Method writeMethod)
 295         throws IntrospectionException {
 296 
 297         // If the indexed property type has not been set, then set it.
 298         Class<?> type = findIndexedPropertyType(getIndexedReadMethod(),
 299                                              writeMethod);
 300         setIndexedPropertyType(type);
 301         setIndexedWriteMethod0(writeMethod);
 302     }
 303 
 304     private void setIndexedWriteMethod0(Method writeMethod) {
 305         this.indexedWriteMethodRef.set(writeMethod);
 306         if (writeMethod == null) {
 307             indexedWriteMethodName = null;
 308             return;
 309         }
 310         setClass0(writeMethod.getDeclaringClass());
 311 
 312         indexedWriteMethodName = writeMethod.getName();
 313         setTransient(writeMethod.getAnnotation(Transient.class));
 314     }
 315 
 316     /**
 317      * Returns the Java type info for the indexed property.
 318      * Note that the {@code Class} object may describe
 319      * primitive Java types such as {@code int}.
 320      * This type is returned by the indexed read method
 321      * or is used as the parameter type of the indexed write method.
 322      *
 323      * @return the {@code Class} object that represents the Java type info,
 324      *         or {@code null} if the type cannot be determined
 325      */
 326     public synchronized Class<?> getIndexedPropertyType() {
 327         Class<?> type = getIndexedPropertyType0();
 328         if (type == null) {
 329             try {
 330                 type = findIndexedPropertyType(getIndexedReadMethod(),
 331                                                getIndexedWriteMethod());
 332                 setIndexedPropertyType(type);
 333             } catch (IntrospectionException ex) {
 334                 // fall
 335             }
 336         }
 337         return type;
 338     }
 339 
 340     // Private methods which set get/set the Reference objects
 341 
 342     private void setIndexedPropertyType(Class<?> type) {
 343         this.indexedPropertyTypeRef = getWeakReference(type);
 344     }
 345 
 346     private Class<?> getIndexedPropertyType0() {
 347         return (this.indexedPropertyTypeRef != null)
 348                 ? this.indexedPropertyTypeRef.get()
 349                 : null;
 350     }
 351 
 352     private Class<?> findIndexedPropertyType(Method indexedReadMethod,
 353                                           Method indexedWriteMethod)
 354         throws IntrospectionException {
 355         Class<?> indexedPropertyType = null;
 356 
 357         if (indexedReadMethod != null) {
 358             Class<?>[] params = getParameterTypes(getClass0(), indexedReadMethod);
 359             if (params.length != 1) {
 360                 throw new IntrospectionException("bad indexed read method arg count");
 361             }
 362             if (params[0] != Integer.TYPE) {
 363                 throw new IntrospectionException("non int index to indexed read method");
 364             }
 365             indexedPropertyType = getReturnType(getClass0(), indexedReadMethod);
 366             if (indexedPropertyType == Void.TYPE) {
 367                 throw new IntrospectionException("indexed read method returns void");
 368             }
 369         }
 370         if (indexedWriteMethod != null) {
 371             Class<?>[] params = getParameterTypes(getClass0(), indexedWriteMethod);
 372             if (params.length != 2) {
 373                 throw new IntrospectionException("bad indexed write method arg count");
 374             }
 375             if (params[0] != Integer.TYPE) {
 376                 throw new IntrospectionException("non int index to indexed write method");
 377             }
 378             if (indexedPropertyType == null || params[1].isAssignableFrom(indexedPropertyType)) {
 379                 indexedPropertyType = params[1];
 380             } else if (!indexedPropertyType.isAssignableFrom(params[1])) {
 381                 throw new IntrospectionException(
 382                                                  "type mismatch between indexed read and indexed write methods: "
 383                                                  + getName());
 384             }
 385         }
 386         Class<?> propertyType = getPropertyType();
 387         if (propertyType != null && (!propertyType.isArray() ||
 388                                      propertyType.getComponentType() != indexedPropertyType)) {
 389             throw new IntrospectionException("type mismatch between indexed and non-indexed methods: "
 390                                              + getName());
 391         }
 392         return indexedPropertyType;
 393     }
 394 
 395     /**
 396      * Compares this <code>PropertyDescriptor</code> against the specified object.
 397      * Returns true if the objects are the same. Two <code>PropertyDescriptor</code>s
 398      * are the same if the read, write, property types, property editor and
 399      * flags  are equivalent.
 400      *
 401      * @since 1.4
 402      */
 403     public boolean equals(Object obj) {
 404         // Note: This would be identical to PropertyDescriptor but they don't
 405         // share the same fields.
 406         if (this == obj) {
 407             return true;
 408         }
 409 
 410         if (obj != null && obj instanceof IndexedPropertyDescriptor) {
 411             IndexedPropertyDescriptor other = (IndexedPropertyDescriptor)obj;
 412             Method otherIndexedReadMethod = other.getIndexedReadMethod();
 413             Method otherIndexedWriteMethod = other.getIndexedWriteMethod();
 414 
 415             if (!compareMethods(getIndexedReadMethod(), otherIndexedReadMethod)) {
 416                 return false;
 417             }
 418 
 419             if (!compareMethods(getIndexedWriteMethod(), otherIndexedWriteMethod)) {
 420                 return false;
 421             }
 422 
 423             if (getIndexedPropertyType() != other.getIndexedPropertyType()) {
 424                 return false;
 425             }
 426             return super.equals(obj);
 427         }
 428         return false;
 429     }
 430 
 431     /**
 432      * Package-private constructor.
 433      * Merge two property descriptors.  Where they conflict, give the
 434      * second argument (y) priority over the first argument (x).
 435      *
 436      * @param x  The first (lower priority) PropertyDescriptor
 437      * @param y  The second (higher priority) PropertyDescriptor
 438      */
 439 
 440     IndexedPropertyDescriptor(PropertyDescriptor x, PropertyDescriptor y) {
 441         super(x,y);
 442         Method tr = null;
 443         Method tw = null;
 444 
 445         if (x instanceof IndexedPropertyDescriptor) {
 446             IndexedPropertyDescriptor ix = (IndexedPropertyDescriptor) x;
 447             tr = ix.getIndexedReadMethod();
 448             tw = ix.getIndexedWriteMethod();
 449         }
 450         if (y instanceof IndexedPropertyDescriptor) {
 451             IndexedPropertyDescriptor iy = (IndexedPropertyDescriptor) y;
 452             Method yr = iy.getIndexedReadMethod();
 453             if (isAssignable(tr, yr)) {
 454                 tr = yr;
 455             }
 456 
 457             Method yw = iy.getIndexedWriteMethod();
 458             if (isAssignable(tw, yw)) {
 459                 tw = yw;
 460             }
 461         }
 462 
 463         try {
 464             if(tr != null) {
 465                 setIndexedReadMethod(tr);
 466             }
 467             if(tw != null) {
 468                 setIndexedWriteMethod(tw);
 469             }
 470         } catch(IntrospectionException ex) {
 471             // Should not happen
 472             throw new AssertionError(ex);
 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 }