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 }