1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18 package org.apache.commons.beanutils;
19
20
21 import java.beans.IntrospectionException;
22 import java.beans.PropertyDescriptor;
23 import java.lang.reflect.Method;
24 import java.lang.reflect.Modifier;
25
26
27 /***
28 * A MappedPropertyDescriptor describes one mapped property.
29 * Mapped properties are multivalued properties like indexed properties
30 * but that are accessed with a String key instead of an index.
31 * Such property values are typically stored in a Map collection.
32 * For this class to work properly, a mapped value must have
33 * getter and setter methods of the form
34 * <p><code>get<strong>Property</strong>(String key)<code> and
35 * <p><code>set<strong>Property</strong>(String key, Object value)<code>,
36 * <p>where <code><strong>Property</strong></code> must be replaced
37 * by the name of the property.
38 * @see java.beans.PropertyDescriptor
39 *
40 * @author Rey Francois
41 * @author Gregor Rayman
42 * @version $Revision: 556229 $ $Date: 2007-07-14 07:11:19 +0100 (Sat, 14 Jul 2007) $
43 */
44
45
46 public class MappedPropertyDescriptor extends PropertyDescriptor {
47
48
49 /***
50 * The underlying data type of the property we are describing.
51 */
52 private Class mappedPropertyType;
53
54 /***
55 * The reader method for this property (if any).
56 */
57 private Method mappedReadMethod;
58
59 /***
60 * The writer method for this property (if any).
61 */
62 private Method mappedWriteMethod;
63
64 /***
65 * The parameter types array for the reader method signature.
66 */
67 private static final Class[] STRING_CLASS_PARAMETER = new Class[]{String.class};
68
69
70
71 /***
72 * Constructs a MappedPropertyDescriptor for a property that follows
73 * the standard Java convention by having getFoo and setFoo
74 * accessor methods, with the addition of a String parameter (the key).
75 * Thus if the argument name is "fred", it will
76 * assume that the writer method is "setFred" and the reader method
77 * is "getFred". Note that the property name should start with a lower
78 * case character, which will be capitalized in the method names.
79 *
80 * @param propertyName The programmatic name of the property.
81 * @param beanClass The Class object for the target bean. For
82 * example sun.beans.OurButton.class.
83 *
84 * @exception IntrospectionException if an exception occurs during
85 * introspection.
86 */
87 public MappedPropertyDescriptor(String propertyName, Class beanClass)
88 throws IntrospectionException {
89
90 super(propertyName, null, null);
91
92 if (propertyName == null || propertyName.length() == 0) {
93 throw new IntrospectionException("bad property name: " +
94 propertyName + " on class: " + beanClass.getClass().getName());
95 }
96
97 setName(propertyName);
98 String base = capitalizePropertyName(propertyName);
99
100
101 try {
102 try {
103 mappedReadMethod = getMethod(beanClass, "get" + base,
104 STRING_CLASS_PARAMETER);
105 } catch (IntrospectionException e) {
106 mappedReadMethod = getMethod(beanClass, "is" + base,
107 STRING_CLASS_PARAMETER);
108 }
109 Class[] params = { String.class, mappedReadMethod.getReturnType() };
110 mappedWriteMethod = getMethod(beanClass, "set" + base, params);
111 } catch (IntrospectionException e) {
112
113
114
115 }
116
117
118 if (mappedReadMethod == null) {
119 mappedWriteMethod = getMethod(beanClass, "set" + base, 2);
120 }
121
122 if ((mappedReadMethod == null) && (mappedWriteMethod == null)) {
123 throw new IntrospectionException("Property '" + propertyName +
124 "' not found on " +
125 beanClass.getName());
126 }
127
128 findMappedPropertyType();
129 }
130
131
132 /***
133 * This constructor takes the name of a mapped property, and method
134 * names for reading and writing the property.
135 *
136 * @param propertyName The programmatic name of the property.
137 * @param beanClass The Class object for the target bean. For
138 * example sun.beans.OurButton.class.
139 * @param mappedGetterName The name of the method used for
140 * reading one of the property values. May be null if the
141 * property is write-only.
142 * @param mappedSetterName The name of the method used for writing
143 * one of the property values. May be null if the property is
144 * read-only.
145 *
146 * @exception IntrospectionException if an exception occurs during
147 * introspection.
148 */
149 public MappedPropertyDescriptor(String propertyName, Class beanClass,
150 String mappedGetterName, String mappedSetterName)
151 throws IntrospectionException {
152
153 super(propertyName, null, null);
154
155 if (propertyName == null || propertyName.length() == 0) {
156 throw new IntrospectionException("bad property name: " +
157 propertyName);
158 }
159 setName(propertyName);
160
161
162 mappedReadMethod =
163 getMethod(beanClass, mappedGetterName, STRING_CLASS_PARAMETER);
164
165 if (mappedReadMethod != null) {
166 Class[] params = { String.class, mappedReadMethod.getReturnType() };
167 mappedWriteMethod =
168 getMethod(beanClass, mappedSetterName, params);
169 } else {
170 mappedWriteMethod =
171 getMethod(beanClass, mappedSetterName, 2);
172 }
173
174 findMappedPropertyType();
175 }
176
177 /***
178 * This constructor takes the name of a mapped property, and Method
179 * objects for reading and writing the property.
180 *
181 * @param propertyName The programmatic name of the property.
182 * @param mappedGetter The method used for reading one of
183 * the property values. May be be null if the property
184 * is write-only.
185 * @param mappedSetter The method used for writing one the
186 * property values. May be null if the property is read-only.
187 *
188 * @exception IntrospectionException if an exception occurs during
189 * introspection.
190 */
191 public MappedPropertyDescriptor(String propertyName,
192 Method mappedGetter, Method mappedSetter)
193 throws IntrospectionException {
194
195 super(propertyName, mappedGetter, mappedSetter);
196
197 if (propertyName == null || propertyName.length() == 0) {
198 throw new IntrospectionException("bad property name: " +
199 propertyName);
200 }
201
202 setName(propertyName);
203 mappedReadMethod = mappedGetter;
204 mappedWriteMethod = mappedSetter;
205 findMappedPropertyType();
206 }
207
208
209
210 /***
211 * Gets the Class object for the property values.
212 *
213 * @return The Java type info for the property values. Note that
214 * the "Class" object may describe a built-in Java type such as "int".
215 * The result may be "null" if this is a mapped property that
216 * does not support non-keyed access.
217 * <p>
218 * This is the type that will be returned by the mappedReadMethod.
219 */
220 public Class getMappedPropertyType() {
221 return mappedPropertyType;
222 }
223
224 /***
225 * Gets the method that should be used to read one of the property value.
226 *
227 * @return The method that should be used to read the property value.
228 * May return null if the property can't be read.
229 */
230 public Method getMappedReadMethod() {
231 return mappedReadMethod;
232 }
233
234 /***
235 * Sets the method that should be used to read one of the property value.
236 *
237 * @param mappedGetter The mapped getter method.
238 * @throws IntrospectionException If an error occurs finding the
239 * mapped property
240 */
241 public void setMappedReadMethod(Method mappedGetter)
242 throws IntrospectionException {
243 mappedReadMethod = mappedGetter;
244 findMappedPropertyType();
245 }
246
247 /***
248 * Gets the method that should be used to write one of the property value.
249 *
250 * @return The method that should be used to write one of the property value.
251 * May return null if the property can't be written.
252 */
253 public Method getMappedWriteMethod() {
254 return mappedWriteMethod;
255 }
256
257 /***
258 * Sets the method that should be used to write the property value.
259 *
260 * @param mappedSetter The mapped setter method.
261 * @throws IntrospectionException If an error occurs finding the
262 * mapped property
263 */
264 public void setMappedWriteMethod(Method mappedSetter)
265 throws IntrospectionException {
266 mappedWriteMethod = mappedSetter;
267 findMappedPropertyType();
268 }
269
270
271
272 /***
273 * Introspect our bean class to identify the corresponding getter
274 * and setter methods.
275 */
276 private void findMappedPropertyType() throws IntrospectionException {
277 try {
278 mappedPropertyType = null;
279 if (mappedReadMethod != null) {
280 if (mappedReadMethod.getParameterTypes().length != 1) {
281 throw new IntrospectionException
282 ("bad mapped read method arg count");
283 }
284 mappedPropertyType = mappedReadMethod.getReturnType();
285 if (mappedPropertyType == Void.TYPE) {
286 throw new IntrospectionException
287 ("mapped read method " +
288 mappedReadMethod.getName() + " returns void");
289 }
290 }
291
292 if (mappedWriteMethod != null) {
293 Class[] params = mappedWriteMethod.getParameterTypes();
294 if (params.length != 2) {
295 throw new IntrospectionException
296 ("bad mapped write method arg count");
297 }
298 if (mappedPropertyType != null &&
299 mappedPropertyType != params[1]) {
300 throw new IntrospectionException
301 ("type mismatch between mapped read and write methods");
302 }
303 mappedPropertyType = params[1];
304 }
305 } catch (IntrospectionException ex) {
306 throw ex;
307 }
308 }
309
310
311 /***
312 * Return a capitalized version of the specified property name.
313 *
314 * @param s The property name
315 */
316 private static String capitalizePropertyName(String s) {
317 if (s.length() == 0) {
318 return s;
319 }
320
321 char[] chars = s.toCharArray();
322 chars[0] = Character.toUpperCase(chars[0]);
323 return new String(chars);
324 }
325
326 /***
327 * Find a method on a class with a specified number of parameters.
328 */
329 private static Method internalGetMethod(Class initial, String methodName,
330 int parameterCount) {
331
332
333 for (Class clazz = initial; clazz != null; clazz = clazz.getSuperclass()) {
334 Method[] methods = clazz.getDeclaredMethods();
335 for (int i = 0; i < methods.length; i++) {
336 Method method = methods[i];
337 if (method == null) {
338 continue;
339 }
340
341 int mods = method.getModifiers();
342 if (!Modifier.isPublic(mods) ||
343 Modifier.isStatic(mods)) {
344 continue;
345 }
346 if (method.getName().equals(methodName) &&
347 method.getParameterTypes().length == parameterCount) {
348 return method;
349 }
350 }
351 }
352
353
354
355
356 Class[] interfaces = initial.getInterfaces();
357 for (int i = 0; i < interfaces.length; i++) {
358 Method method = internalGetMethod(interfaces[i], methodName, parameterCount);
359 if (method != null) {
360 return method;
361 }
362 }
363
364 return null;
365 }
366
367 /***
368 * Find a method on a class with a specified number of parameters.
369 */
370 private static Method getMethod(Class clazz, String methodName, int parameterCount)
371 throws IntrospectionException {
372 if (methodName == null) {
373 return null;
374 }
375
376 Method method = internalGetMethod(clazz, methodName, parameterCount);
377 if (method != null) {
378 return method;
379 }
380
381
382 throw new IntrospectionException("No method \"" + methodName +
383 "\" with " + parameterCount + " parameter(s)");
384 }
385
386 /***
387 * Find a method on a class with a specified parameter list.
388 */
389 private static Method getMethod(Class clazz, String methodName, Class[] parameterTypes)
390 throws IntrospectionException {
391 if (methodName == null) {
392 return null;
393 }
394
395 Method method = MethodUtils.getMatchingAccessibleMethod(clazz, methodName, parameterTypes);
396 if (method != null) {
397 return method;
398 }
399
400 int parameterCount = (parameterTypes == null) ? 0 : parameterTypes.length;
401
402
403 throw new IntrospectionException("No method \"" + methodName +
404 "\" with " + parameterCount + " parameter(s) of matching types.");
405 }
406
407 }