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