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 }