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