View Javadoc
1 /*
2 * $Header: /cvsroot/xwing/projects/xwing/src/java/org/apache/commons/beanutils/MappedPropertyDescriptor.java,v 1.1 2003/10/06 17:53:16 jshowlett Exp $
3 * $Revision: 1.1 $
4 * $Date: 2003/10/06 17:53:16 $
5 *
6 * ====================================================================
7 *
8 * The Apache Software License, Version 1.1
9 *
10 * Copyright (c) 1999-2003 The Apache Software Foundation. All rights
11 * reserved.
12 *
13 * Redistribution and use in source and binary forms, with or without
14 * modification, are permitted provided that the following conditions
15 * are met:
16 *
17 * 1. Redistributions of source code must retain the above copyright
18 * notice, this list of conditions and the following disclaimer.
19 *
20 * 2. Redistributions in binary form must reproduce the above copyright
21 * notice, this list of conditions and the following disclaimer in
22 * the documentation and/or other materials provided with the
23 * distribution.
24 *
25 * 3. The end-user documentation included with the redistribution, if
26 * any, must include the following acknowlegement:
27 * "This product includes software developed by the
28 * Apache Software Foundation (http://www.apache.org/)."
29 * Alternately, this acknowlegement may appear in the software itself,
30 * if and wherever such third-party acknowlegements normally appear.
31 *
32 * 4. The names "The Jakarta Project", "Commons", and "Apache Software
33 * Foundation" must not be used to endorse or promote products derived
34 * from this software without prior written permission. For written
35 * permission, please contact apache@apache.org.
36 *
37 * 5. Products derived from this software may not be called "Apache"
38 * nor may "Apache" appear in their names without prior written
39 * permission of the Apache Group.
40 *
41 * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
42 * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
43 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
44 * DISCLAIMED. IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR
45 * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
46 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
47 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
48 * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
49 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
50 * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
51 * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
52 * SUCH DAMAGE.
53 * ====================================================================
54 *
55 * This software consists of voluntary contributions made by many
56 * individuals on behalf of the Apache Software Foundation. For more
57 * information on the Apache Software Foundation, please see
58 * <http://www.apache.org/>.
59 *
60 */
61
62 package org.apache.commons.beanutils;
63
64 import java.beans.IntrospectionException;
65 import java.beans.PropertyDescriptor;
66 import java.lang.reflect.Method;
67 import java.lang.reflect.Modifier;
68 import java.security.AccessController;
69 import java.security.PrivilegedAction;
70
71 /***
72 * A MappedPropertyDescriptor describes one mapped property.
73 * Mapped properties are multivalued properties like indexed properties
74 * but that are accessed with a String key instead of an index.
75 * Such property values are typically stored in a Map collection.
76 * For this class to work properly, a mapped value must have
77 * getter and setter methods of the form
78 * <p><code>get<strong>Property</strong>(String key)<code> and
79 * <p><code>set<Property>(String key, Object value)<code>,
80 * <p>where <code><strong>Property</strong></code> must be replaced
81 * by the name of the property.
82 * @see java.beans.PropertyDescriptor
83 *
84 * @author Rey François
85 * @author Gregor Raıman
86 * @version $Revision: 1.1 $ $Date: 2003/10/06 17:53:16 $
87 */
88
89 public class MappedPropertyDescriptor extends PropertyDescriptor {
90 // ----------------------------------------------------- Instance Variables
91
92 /***
93 * The underlying data type of the property we are describing.
94 */
95 private Class mappedPropertyType;
96
97 /***
98 * The reader method for this property (if any).
99 */
100 private Method mappedReadMethod;
101
102 /***
103 * The writer method for this property (if any).
104 */
105 private Method mappedWriteMethod;
106
107 /***
108 * The parameter types array for the reader method signature.
109 */
110 private static final Class[] stringClassArray =
111 new Class[] { String.class };
112
113 // ----------------------------------------------------------- Constructors
114
115 /***
116 * Constructs a MappedPropertyDescriptor for a property that follows
117 * the standard Java convention by having getFoo and setFoo
118 * accessor methods, with the addition of a String parameter (the key).
119 * Thus if the argument name is "fred", it will
120 * assume that the writer method is "setFred" and the reader method
121 * is "getFred". Note that the property name should start with a lower
122 * case character, which will be capitalized in the method names.
123 *
124 * @param propertyName The programmatic name of the property.
125 * @param beanClass The Class object for the target bean. For
126 * example sun.beans.OurButton.class.
127 *
128 * @exception IntrospectionException if an exception occurs during
129 * introspection.
130 */
131 public MappedPropertyDescriptor(String propertyName, Class beanClass)
132 throws IntrospectionException {
133
134 super(propertyName, null, null);
135
136 if (propertyName == null || propertyName.length() == 0) {
137 throw new IntrospectionException(
138 "bad property name: "
139 + propertyName
140 + " on class: "
141 + beanClass.getClass().getName());
142 }
143
144 setName(propertyName);
145 String base = capitalizePropertyName(propertyName);
146
147 // Look for mapped read method and matching write method
148 try {
149 mappedReadMethod =
150 findMethod(beanClass, "get" + base, 1, stringClassArray);
151 Class params[] = { String.class, mappedReadMethod.getReturnType()};
152 mappedWriteMethod = findMethod(beanClass, "set" + base, 2, params);
153 } catch (IntrospectionException e) {
154 ;
155 }
156
157 // If there's no read method, then look for just a write method
158 if (mappedReadMethod == null) {
159 mappedWriteMethod = findMethod(beanClass, "set" + base, 2);
160 }
161
162 if ((mappedReadMethod == null) && (mappedWriteMethod == null)) {
163 throw new IntrospectionException(
164 "Property '"
165 + propertyName
166 + "' not found on "
167 + beanClass.getName());
168 }
169
170 findMappedPropertyType();
171 }
172
173 /***
174 * This constructor takes the name of a mapped property, and method
175 * names for reading and writing the property.
176 *
177 * @param propertyName The programmatic name of the property.
178 * @param beanClass The Class object for the target bean. For
179 * example sun.beans.OurButton.class.
180 * @param mappedGetterName The name of the method used for
181 * reading one of the property values. May be null if the
182 * property is write-only.
183 * @param mappedSetterName The name of the method used for writing
184 * one of the property values. May be null if the property is
185 * read-only.
186 *
187 * @exception IntrospectionException if an exception occurs during
188 * introspection.
189 */
190 public MappedPropertyDescriptor(
191 String propertyName,
192 Class beanClass,
193 String mappedGetterName,
194 String mappedSetterName)
195 throws IntrospectionException {
196
197 super(propertyName, null, null);
198
199 if (propertyName == null || propertyName.length() == 0) {
200 throw new IntrospectionException(
201 "bad property name: " + propertyName);
202 }
203 setName(propertyName);
204
205 // search the mapped get and set methods
206 mappedReadMethod =
207 findMethod(beanClass, mappedGetterName, 1, stringClassArray);
208
209 if (mappedReadMethod != null) {
210 Class params[] = { String.class, mappedReadMethod.getReturnType()};
211 mappedWriteMethod =
212 findMethod(beanClass, mappedSetterName, 2, params);
213 } else {
214 mappedWriteMethod = findMethod(beanClass, mappedSetterName, 2);
215 }
216
217 findMappedPropertyType();
218 }
219
220 /***
221 * This constructor takes the name of a mapped property, and Method
222 * objects for reading and writing the property.
223 *
224 * @param propertyName The programmatic name of the property.
225 * @param mappedGetter The method used for reading one of
226 * the property values. May be be null if the property
227 * is write-only.
228 * @param mappedSetter The method used for writing one the
229 * property values. May be null if the property is read-only.
230 *
231 * @exception IntrospectionException if an exception occurs during
232 * introspection.
233 */
234 public MappedPropertyDescriptor(
235 String propertyName,
236 Method mappedGetter,
237 Method mappedSetter)
238 throws IntrospectionException {
239
240 super(propertyName, mappedGetter, mappedSetter);
241
242 if (propertyName == null || propertyName.length() == 0) {
243 throw new IntrospectionException(
244 "bad property name: " + propertyName);
245 }
246
247 setName(propertyName);
248 mappedReadMethod = mappedGetter;
249 mappedWriteMethod = mappedSetter;
250 findMappedPropertyType();
251 }
252
253 // -------------------------------------------------------- Public Methods
254
255 /***
256 * Gets the Class object for the property values.
257 *
258 * @return The Java type info for the property values. Note that
259 * the "Class" object may describe a built-in Java type such as "int".
260 * The result may be "null" if this is a mapped property that
261 * does not support non-keyed access.
262 * <p>
263 * This is the type that will be returned by the mappedReadMethod.
264 */
265 public Class getMappedPropertyType() {
266 return mappedPropertyType;
267 }
268
269 /***
270 * Gets the method that should be used to read one of the property value.
271 *
272 * @return The method that should be used to read the property value.
273 * May return null if the property can't be read.
274 */
275 public Method getMappedReadMethod() {
276 return mappedReadMethod;
277 }
278
279 /***
280 * Sets the method that should be used to read one of the property value.
281 *
282 * @param getter The new getter method.
283 */
284 public void setMappedReadMethod(Method mappedGetter)
285 throws IntrospectionException {
286 mappedReadMethod = mappedGetter;
287 findMappedPropertyType();
288 }
289
290 /***
291 * Gets the method that should be used to write one of the property value.
292 *
293 * @return The method that should be used to write one of the property value.
294 * May return null if the property can't be written.
295 */
296 public Method getMappedWriteMethod() {
297 return mappedWriteMethod;
298 }
299
300 /***
301 * Sets the method that should be used to write the property value.
302 *
303 * @param setter The new setter method.
304 */
305 public void setMappedWriteMethod(Method mappedSetter)
306 throws IntrospectionException {
307 mappedWriteMethod = mappedSetter;
308 findMappedPropertyType();
309 }
310
311 // ------------------------------------------------------- Private Methods
312
313 /***
314 * Introspect our bean class to identify the corresponding getter
315 * and setter methods.
316 */
317 private void findMappedPropertyType() throws IntrospectionException {
318 try {
319 mappedPropertyType = null;
320 if (mappedReadMethod != null) {
321 if (mappedReadMethod.getParameterTypes().length != 1) {
322 throw new IntrospectionException("bad mapped read method arg count");
323 }
324 mappedPropertyType = mappedReadMethod.getReturnType();
325 if (mappedPropertyType == Void.TYPE) {
326 throw new IntrospectionException(
327 "mapped read method "
328 + mappedReadMethod.getName()
329 + " returns void");
330 }
331 }
332
333 if (mappedWriteMethod != null) {
334 Class params[] = mappedWriteMethod.getParameterTypes();
335 if (params.length != 2) {
336 throw new IntrospectionException("bad mapped write method arg count");
337 }
338 if (mappedPropertyType != null
339 && mappedPropertyType != params[1]) {
340 throw new IntrospectionException("type mismatch between mapped read and write methods");
341 }
342 mappedPropertyType = params[1];
343 }
344 } catch (IntrospectionException ex) {
345 throw ex;
346 }
347 }
348
349 /***
350 * Return a capitalized version of the specified property name.
351 *
352 * @param s The property name
353 */
354 private static String capitalizePropertyName(String s) {
355 if (s.length() == 0) {
356 return s;
357 }
358
359 char chars[] = s.toCharArray();
360 chars[0] = Character.toUpperCase(chars[0]);
361 return new String(chars);
362 }
363
364 //======================================================================
365 // Package private support methods (copied from java.beans.Introspector).
366 //======================================================================
367
368 // Cache of Class.getDeclaredMethods:
369 private static java.util.Hashtable declaredMethodCache =
370 new java.util.Hashtable();
371
372 /*
373 * Internal method to return *public* methods within a class.
374 */
375 private static synchronized Method[] getPublicDeclaredMethods(Class clz) {
376 // Looking up Class.getMethods is relatively expensive,
377 // so we cache the results.
378 final Class fclz = clz;
379 Method[] result = (Method[]) declaredMethodCache.get(fclz);
380 if (result != null) {
381 return result;
382 }
383
384 // We have to raise privilege for getMethods
385 result =
386 (Method[]) AccessController.doPrivileged(new PrivilegedAction() {
387 public Object run() {
388 return fclz.getMethods();
389 }
390 });
391
392 // Null out any methods not belonging to us
393 for (int i = 0; i < result.length; i++) {
394 Method method = result[i];
395 Class declClass = method.getDeclaringClass();
396 if (declClass != fclz) {
397 result[i] = null;
398 }
399 }
400
401 // Add it to the cache.
402 declaredMethodCache.put(clz, result);
403 return result;
404 }
405
406 /***
407 * Internal support for finding a target methodName on a given class.
408 */
409 private static Method internalFindMethod(
410 Class start,
411 String methodName,
412 int argCount) {
413 // For overridden methods we need to find the most derived version.
414 // So we start with the given class and walk up the superclass chain.
415 for (Class cl = start; cl != null; cl = cl.getSuperclass()) {
416 Method methods[] = getPublicDeclaredMethods(cl);
417 for (int i = 0; i < methods.length; i++) {
418 Method method = methods[i];
419 if (method == null) {
420 continue;
421 }
422 // skip static methods.
423 int mods = method.getModifiers();
424 if (Modifier.isStatic(mods)) {
425 continue;
426 }
427 if (method.getName().equals(methodName)
428 && method.getParameterTypes().length == argCount) {
429 return method;
430 }
431 }
432 }
433
434 // Now check any inherited interfaces. This is necessary both when
435 // the argument class is itself an interface, and when the argument
436 // class is an abstract class.
437 Class ifcs[] = start.getInterfaces();
438 for (int i = 0; i < ifcs.length; i++) {
439 Method m = internalFindMethod(ifcs[i], methodName, argCount);
440 if (m != null) {
441 return m;
442 }
443 }
444
445 return null;
446 }
447
448 /***
449 * Internal support for finding a target methodName with a given
450 * parameter list on a given class.
451 */
452 private static Method internalFindMethod(
453 Class start,
454 String methodName,
455 int argCount,
456 Class args[]) {
457 // For overriden methods we need to find the most derived version.
458 // So we start with the given class and walk up the superclass chain.
459 for (Class cl = start; cl != null; cl = cl.getSuperclass()) {
460 Method methods[] = getPublicDeclaredMethods(cl);
461 for (int i = 0; i < methods.length; i++) {
462 Method method = methods[i];
463 if (method == null) {
464 continue;
465 }
466 // skip static methods.
467 int mods = method.getModifiers();
468 if (Modifier.isStatic(mods)) {
469 continue;
470 }
471 // make sure method signature matches.
472 Class params[] = method.getParameterTypes();
473 if (method.getName().equals(methodName)
474 && params.length == argCount) {
475 boolean different = false;
476 if (argCount > 0) {
477 for (int j = 0; j < argCount; j++) {
478 if (params[j] != args[j]) {
479 different = true;
480 continue;
481 }
482 }
483 if (different) {
484 continue;
485 }
486 }
487 return method;
488 }
489 }
490 }
491
492 // Now check any inherited interfaces. This is necessary both when
493 // the argument class is itself an interface, and when the argument
494 // class is an abstract class.
495 Class ifcs[] = start.getInterfaces();
496 for (int i = 0; i < ifcs.length; i++) {
497 Method m = internalFindMethod(ifcs[i], methodName, argCount);
498 if (m != null) {
499 return m;
500 }
501 }
502
503 return null;
504 }
505
506 /***
507 * Find a target methodName on a given class.
508 */
509 static Method findMethod(Class cls, String methodName, int argCount)
510 throws IntrospectionException {
511 if (methodName == null) {
512 return null;
513 }
514
515 Method m = internalFindMethod(cls, methodName, argCount);
516 if (m != null) {
517 return m;
518 }
519
520 // We failed to find a suitable method
521 throw new IntrospectionException(
522 "No method \"" + methodName + "\" with " + argCount + " arg(s)");
523 }
524
525 /***
526 * Find a target methodName with specific parameter list on a given class.
527 */
528 static Method findMethod(
529 Class cls,
530 String methodName,
531 int argCount,
532 Class args[])
533 throws IntrospectionException {
534 if (methodName == null) {
535 return null;
536 }
537
538 Method m = internalFindMethod(cls, methodName, argCount, args);
539 if (m != null) {
540 return m;
541 }
542
543 // We failed to find a suitable method
544 throw new IntrospectionException(
545 "No method \""
546 + methodName
547 + "\" with "
548 + argCount
549 + " arg(s) of matching types.");
550 }
551
552 /***
553 * Return true if class a is either equivalent to class b, or
554 * if class a is a subclass of class b, ie if a either "extends"
555 * or "implements" b.
556 * Note tht either or both "Class" objects may represent interfaces.
557 */
558 static boolean isSubclass(Class a, Class b) {
559 // We rely on the fact that for any given java class or
560 // primtitive type there is a unqiue Class object, so
561 // we can use object equivalence in the comparisons.
562 if (a == b) {
563 return true;
564 }
565
566 if (a == null || b == null) {
567 return false;
568 }
569
570 for (Class x = a; x != null; x = x.getSuperclass()) {
571 if (x == b) {
572 return true;
573 }
574
575 if (b.isInterface()) {
576 Class interfaces[] = x.getInterfaces();
577 for (int i = 0; i < interfaces.length; i++) {
578 if (isSubclass(interfaces[i], b)) {
579 return true;
580 }
581 }
582 }
583 }
584
585 return false;
586 }
587
588 /***
589 * Return true iff the given method throws the given exception.
590 */
591
592 private boolean throwsException(Method method, Class exception) {
593
594 Class exs[] = method.getExceptionTypes();
595 for (int i = 0; i < exs.length; i++) {
596 if (exs[i] == exception) {
597 return true;
598 }
599 }
600
601 return false;
602 }
603 }
This page was automatically generated by Maven