1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17 package org.apache.commons.beanutils;
18
19
20 import java.beans.IntrospectionException;
21 import java.beans.PropertyDescriptor;
22 import java.lang.reflect.Method;
23 import java.lang.reflect.Modifier;
24 import java.security.AccessController;
25 import java.security.PrivilegedAction;
26
27
28 /**
29 * A MappedPropertyDescriptor describes one mapped property.
30 * Mapped properties are multivalued properties like indexed properties
31 * but that are accessed with a String key instead of an index.
32 * Such property values are typically stored in a Map collection.
33 * For this class to work properly, a mapped value must have
34 * getter and setter methods of the form
35 * <p><code>get<strong>Property</strong>(String key)<code> and
36 * <p><code>set<Property>(String key, Object value)<code>,
37 * <p>where <code><strong>Property</strong></code> must be replaced
38 * by the name of the property.
39 * @see java.beans.PropertyDescriptor
40 *
41 * @author Rey Fran?ois
42 * @author Gregor Ra?man
43 * @version $Revision: 1.18.2.1 $ $Date: 2004/07/27 21:44:26 $
44 */
45
46
47 public class MappedPropertyDescriptor extends PropertyDescriptor {
48
49
50 /**
51 * The underlying data type of the property we are describing.
52 */
53 private Class mappedPropertyType;
54
55 /**
56 * The reader method for this property (if any).
57 */
58 private Method mappedReadMethod;
59
60 /**
61 * The writer method for this property (if any).
62 */
63 private Method mappedWriteMethod;
64
65 /**
66 * The parameter types array for the reader method signature.
67 */
68 private static final Class[] stringClassArray = new Class[]{String.class};
69
70
71
72 /**
73 * Constructs a MappedPropertyDescriptor for a property that follows
74 * the standard Java convention by having getFoo and setFoo
75 * accessor methods, with the addition of a String parameter (the key).
76 * Thus if the argument name is "fred", it will
77 * assume that the writer method is "setFred" and the reader method
78 * is "getFred". Note that the property name should start with a lower
79 * case character, which will be capitalized in the method names.
80 *
81 * @param propertyName The programmatic name of the property.
82 * @param beanClass The Class object for the target bean. For
83 * example sun.beans.OurButton.class.
84 *
85 * @exception IntrospectionException if an exception occurs during
86 * introspection.
87 */
88 public MappedPropertyDescriptor(String propertyName, Class beanClass)
89 throws IntrospectionException {
90
91 super(propertyName, null, null);
92
93 if (propertyName == null || propertyName.length() == 0) {
94 throw new IntrospectionException("bad property name: " +
95 propertyName + " on class: " + beanClass.getClass().getName());
96 }
97
98 setName(propertyName);
99 String base = capitalizePropertyName(propertyName);
100
101
102 try {
103 mappedReadMethod = findMethod(beanClass, "get" + base, 1,
104 stringClassArray);
105 Class params[] = { String.class, mappedReadMethod.getReturnType() };
106 mappedWriteMethod = findMethod(beanClass, "set" + base, 2, params);
107 } catch (IntrospectionException e) {
108 ;
109 }
110
111
112 if (mappedReadMethod == null) {
113 mappedWriteMethod = findMethod(beanClass, "set" + base, 2);
114 }
115
116 if ((mappedReadMethod == null) && (mappedWriteMethod == null)) {
117 throw new IntrospectionException("Property '" + propertyName +
118 "' not found on " +
119 beanClass.getName());
120 }
121
122 findMappedPropertyType();
123 }
124
125
126 /**
127 * This constructor takes the name of a mapped property, and method
128 * names for reading and writing the property.
129 *
130 * @param propertyName The programmatic name of the property.
131 * @param beanClass The Class object for the target bean. For
132 * example sun.beans.OurButton.class.
133 * @param mappedGetterName The name of the method used for
134 * reading one of the property values. May be null if the
135 * property is write-only.
136 * @param mappedSetterName The name of the method used for writing
137 * one of the property values. May be null if the property is
138 * read-only.
139 *
140 * @exception IntrospectionException if an exception occurs during
141 * introspection.
142 */
143 public MappedPropertyDescriptor(String propertyName, Class beanClass,
144 String mappedGetterName, String mappedSetterName)
145 throws IntrospectionException {
146
147 super(propertyName, null, null);
148
149 if (propertyName == null || propertyName.length() == 0) {
150 throw new IntrospectionException("bad property name: " +
151 propertyName);
152 }
153 setName(propertyName);
154
155
156 mappedReadMethod =
157 findMethod(beanClass, mappedGetterName, 1, stringClassArray);
158
159 if (mappedReadMethod != null) {
160 Class params[] = { String.class, mappedReadMethod.getReturnType() };
161 mappedWriteMethod =
162 findMethod(beanClass, mappedSetterName, 2, params);
163 } else {
164 mappedWriteMethod =
165 findMethod(beanClass, mappedSetterName, 2);
166 }
167
168 findMappedPropertyType();
169 }
170
171 /**
172 * This constructor takes the name of a mapped property, and Method
173 * objects for reading and writing the property.
174 *
175 * @param propertyName The programmatic name of the property.
176 * @param mappedGetter The method used for reading one of
177 * the property values. May be be null if the property
178 * is write-only.
179 * @param mappedSetter The method used for writing one the
180 * property values. May be null if the property is read-only.
181 *
182 * @exception IntrospectionException if an exception occurs during
183 * introspection.
184 */
185 public MappedPropertyDescriptor(String propertyName,
186 Method mappedGetter, Method mappedSetter)
187 throws IntrospectionException {
188
189 super(propertyName, mappedGetter, mappedSetter);
190
191 if (propertyName == null || propertyName.length() == 0) {
192 throw new IntrospectionException("bad property name: " +
193 propertyName);
194 }
195
196 setName(propertyName);
197 mappedReadMethod = mappedGetter;
198 mappedWriteMethod = mappedSetter;
199 findMappedPropertyType();
200 }
201
202
203
204 /**
205 * Gets the Class object for the property values.
206 *
207 * @return The Java type info for the property values. Note that
208 * the "Class" object may describe a built-in Java type such as "int".
209 * The result may be "null" if this is a mapped property that
210 * does not support non-keyed access.
211 * <p>
212 * This is the type that will be returned by the mappedReadMethod.
213 */
214 public Class getMappedPropertyType() {
215 return mappedPropertyType;
216 }
217
218 /**
219 * Gets the method that should be used to read one of the property value.
220 *
221 * @return The method that should be used to read the property value.
222 * May return null if the property can't be read.
223 */
224 public Method getMappedReadMethod() {
225 return mappedReadMethod;
226 }
227
228 /**
229 * Sets the method that should be used to read one of the property value.
230 *
231 * @param mappedGetter The new getter method.
232 */
233 public void setMappedReadMethod(Method mappedGetter)
234 throws IntrospectionException {
235 mappedReadMethod = mappedGetter;
236 findMappedPropertyType();
237 }
238
239 /**
240 * Gets the method that should be used to write one of the property value.
241 *
242 * @return The method that should be used to write one of the property value.
243 * May return null if the property can't be written.
244 */
245 public Method getMappedWriteMethod() {
246 return mappedWriteMethod;
247 }
248
249 /**
250 * Sets the method that should be used to write the property value.
251 *
252 * @param mappedSetter The new setter method.
253 */
254 public void setMappedWriteMethod(Method mappedSetter)
255 throws IntrospectionException {
256 mappedWriteMethod = mappedSetter;
257 findMappedPropertyType();
258 }
259
260
261
262 /**
263 * Introspect our bean class to identify the corresponding getter
264 * and setter methods.
265 */
266 private void findMappedPropertyType() throws IntrospectionException {
267 try {
268 mappedPropertyType = null;
269 if (mappedReadMethod != null) {
270 if (mappedReadMethod.getParameterTypes().length != 1) {
271 throw new IntrospectionException
272 ("bad mapped read method arg count");
273 }
274 mappedPropertyType = mappedReadMethod.getReturnType();
275 if (mappedPropertyType == Void.TYPE) {
276 throw new IntrospectionException
277 ("mapped read method " +
278 mappedReadMethod.getName() + " returns void");
279 }
280 }
281
282 if (mappedWriteMethod != null) {
283 Class params[] = mappedWriteMethod.getParameterTypes();
284 if (params.length != 2) {
285 throw new IntrospectionException
286 ("bad mapped write method arg count");
287 }
288 if (mappedPropertyType != null &&
289 mappedPropertyType != params[1]) {
290 throw new IntrospectionException
291 ("type mismatch between mapped read and write methods");
292 }
293 mappedPropertyType = params[1];
294 }
295 } catch (IntrospectionException ex) {
296 throw ex;
297 }
298 }
299
300
301 /**
302 * Return a capitalized version of the specified property name.
303 *
304 * @param s The property name
305 */
306 private static String capitalizePropertyName(String s) {
307 if (s.length() == 0) {
308 return s;
309 }
310
311 char chars[] = s.toCharArray();
312 chars[0] = Character.toUpperCase(chars[0]);
313 return new String(chars);
314 }
315
316
317
318
319
320
321 private static java.util.Hashtable
322 declaredMethodCache = new java.util.Hashtable();
323
324
325
326
327 private static synchronized Method[] getPublicDeclaredMethods(Class clz) {
328
329
330 final Class fclz = clz;
331 Method[] result = (Method[]) declaredMethodCache.get(fclz);
332 if (result != null) {
333 return result;
334 }
335
336
337 result = (Method[])
338 AccessController.doPrivileged(new PrivilegedAction() {
339 public Object run() {
340 try{
341
342 return fclz.getDeclaredMethods();
343
344 } catch (SecurityException ex) {
345
346
347
348
349 Method[] methods = fclz.getMethods();
350 for (int i = 0, size = methods.length; i < size; i++) {
351 Method method = methods[i];
352 if (!(fclz.equals(method.getDeclaringClass()))) {
353 methods[i] = null;
354 }
355 }
356 return methods;
357 }
358 }
359 });
360
361
362 for (int i = 0; i < result.length; i++) {
363 Method method = result[i];
364 if (method != null) {
365 int mods = method.getModifiers();
366 if (!Modifier.isPublic(mods)) {
367 result[i] = null;
368 }
369 }
370 }
371
372
373 declaredMethodCache.put(clz, result);
374 return result;
375 }
376
377 /**
378 * Internal support for finding a target methodName on a given class.
379 */
380 private static Method internalFindMethod(Class start, String methodName,
381 int argCount) {
382
383
384 for (Class cl = start; cl != null; cl = cl.getSuperclass()) {
385 Method methods[] = getPublicDeclaredMethods(cl);
386 for (int i = 0; i < methods.length; i++) {
387 Method method = methods[i];
388 if (method == null) {
389 continue;
390 }
391
392 int mods = method.getModifiers();
393 if (Modifier.isStatic(mods)) {
394 continue;
395 }
396 if (method.getName().equals(methodName) &&
397 method.getParameterTypes().length == argCount) {
398 return method;
399 }
400 }
401 }
402
403
404
405
406 Class ifcs[] = start.getInterfaces();
407 for (int i = 0; i < ifcs.length; i++) {
408 Method m = internalFindMethod(ifcs[i], methodName, argCount);
409 if (m != null) {
410 return m;
411 }
412 }
413
414 return null;
415 }
416
417 /**
418 * Internal support for finding a target methodName with a given
419 * parameter list on a given class.
420 */
421 private static Method internalFindMethod(Class start, String methodName,
422 int argCount, Class args[]) {
423
424
425 for (Class cl = start; cl != null; cl = cl.getSuperclass()) {
426 Method methods[] = getPublicDeclaredMethods(cl);
427 for (int i = 0; i < methods.length; i++) {
428 Method method = methods[i];
429 if (method == null) {
430 continue;
431 }
432
433 int mods = method.getModifiers();
434 if (Modifier.isStatic(mods)) {
435 continue;
436 }
437
438 Class params[] = method.getParameterTypes();
439 if (method.getName().equals(methodName) &&
440 params.length == argCount) {
441 boolean different = false;
442 if (argCount > 0) {
443 for (int j = 0; j < argCount; j++) {
444 if (params[j] != args[j]) {
445 different = true;
446 continue;
447 }
448 }
449 if (different) {
450 continue;
451 }
452 }
453 return method;
454 }
455 }
456 }
457
458
459
460
461 Class ifcs[] = start.getInterfaces();
462 for (int i = 0; i < ifcs.length; i++) {
463 Method m = internalFindMethod(ifcs[i], methodName, argCount);
464 if (m != null) {
465 return m;
466 }
467 }
468
469 return null;
470 }
471
472 /**
473 * Find a target methodName on a given class.
474 */
475 static Method findMethod(Class cls, String methodName, int argCount)
476 throws IntrospectionException {
477 if (methodName == null) {
478 return null;
479 }
480
481 Method m = internalFindMethod(cls, methodName, argCount);
482 if (m != null) {
483 return m;
484 }
485
486
487 throw new IntrospectionException("No method \"" + methodName +
488 "\" with " + argCount + " arg(s)");
489 }
490
491 /**
492 * Find a target methodName with specific parameter list on a given class.
493 */
494 static Method findMethod(Class cls, String methodName, int argCount,
495 Class args[]) throws IntrospectionException {
496 if (methodName == null) {
497 return null;
498 }
499
500 Method m = internalFindMethod(cls, methodName, argCount, args);
501 if (m != null) {
502 return m;
503 }
504
505
506 throw new IntrospectionException("No method \"" + methodName +
507 "\" with " + argCount + " arg(s) of matching types.");
508 }
509
510 /**
511 * Return true if class a is either equivalent to class b, or
512 * if class a is a subclass of class b, ie if a either "extends"
513 * or "implements" b.
514 * Note tht either or both "Class" objects may represent interfaces.
515 */
516 static boolean isSubclass(Class a, Class b) {
517
518
519
520 if (a == b) {
521 return true;
522 }
523
524 if (a == null || b == null) {
525 return false;
526 }
527
528 for (Class x = a; x != null; x = x.getSuperclass()) {
529 if (x == b) {
530 return true;
531 }
532
533 if (b.isInterface()) {
534 Class interfaces[] = x.getInterfaces();
535 for (int i = 0; i < interfaces.length; i++) {
536 if (isSubclass(interfaces[i], b)) {
537 return true;
538 }
539 }
540 }
541 }
542
543 return false;
544 }
545
546
547 /**
548 * Return true iff the given method throws the given exception.
549 */
550
551 private boolean throwsException(Method method, Class exception) {
552
553 Class exs[] = method.getExceptionTypes();
554 for (int i = 0; i < exs.length; i++) {
555 if (exs[i] == exception) {
556 return true;
557 }
558 }
559
560 return false;
561 }
562 }