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 }