1 /*
2 * Copyright (c) 1996, 2013, Oracle and/or its affiliates. All rights reserved.
3 * ORACLE PROPRIETARY/CONFIDENTIAL. Use is subject to license terms.
4 *
5 *
6 *
7 *
8 *
9 *
10 *
11 *
12 *
13 *
14 *
15 *
16 *
17 *
18 *
19 *
20 *
21 *
22 *
23 *
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 }
526