1 /*
2 * BeanHelper.java
3 * Created on September 18, 2003
4 *
5 * The Blues Framework - A lightweight application framework
6 * Copyright (C) 2003 Lonnie Pryor
7 * http://blues.lonniepryor.com
8 *
9 * This library is free software; you can redistribute it and/or modify it under the
10 * terms of the GNU Lesser General Public License as published by the Free Software
11 * Foundation; either version 2.1 of the License, or (at your option) any later
12 * version.
13 *
14 * This library is distributed in the hope that it will be useful, but WITHOUT ANY
15 * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
16 * PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details.
17 *
18 * You should have received a copy of the GNU Lesser General Public License along
19 * with this library; if not, write to:
20 *
21 * The Free Software Foundation, Inc.
22 * 59 Temple Place, Suite 330
23 * Boston, MA 02111-1307 USA
24 *
25 */
26 package com.lonniepryor.blues.util;
27
28 import java.beans.Introspector;
29
30 import java.lang.reflect.InvocationTargetException;
31 import java.lang.reflect.Method;
32
33 import java.math.BigDecimal;
34 import java.math.BigInteger;
35
36 import java.util.Collections;
37 import java.util.HashMap;
38 import java.util.LinkedList;
39 import java.util.Map;
40 import java.util.Set;
41
42 /***
43 * Helper class that introspects and provides access to information about a
44 * JavaBean.
45 *
46 * @author Lonnie Pryor
47 * @version $Revision: 1.1 $
48 */
49 public class BeanHelper {
50 /*** Constant signifying no parameters. */
51 private static final Object[] noParameters = { };
52 /*** Thread-local helper cache. */
53 private static final ThreadLocal helperCache = new ThreadLocal();
54 /*** The constant value builder strategies. */
55 private static final Map valueBuilders;
56 /*** Null values for primitive types. */
57 private static final Map nullValues;
58
59 /***
60 * Initalize the constant value builder strategies.
61 */
62 static {
63 Map tmp = new HashMap(19);
64 tmp.put(
65 Boolean.class,
66 new ValueBuilder() {
67 public Object buildValue (String value) {
68 return "true".equalsIgnoreCase(value) ? Boolean.TRUE : Boolean.FALSE;
69 }
70 });
71 tmp.put(
72 Byte.class,
73 new ValueBuilder() {
74 public Object buildValue (String value) {
75 return new Byte(value);
76 }
77 });
78 tmp.put(
79 Character.class,
80 new ValueBuilder() {
81 public Object buildValue (String value) {
82 if ((value == null) || (value.length() != 1))
83 throw new IllegalArgumentException(
84 "Cannot convert '" + value + "' to a single character");
85 return new Character(value.charAt(0));
86 }
87 });
88 tmp.put(
89 Short.class,
90 new ValueBuilder() {
91 public Object buildValue (String value) {
92 return new Short(value);
93 }
94 });
95 tmp.put(
96 Integer.class,
97 new ValueBuilder() {
98 public Object buildValue (String value) {
99 return new Integer(value);
100 }
101 });
102 tmp.put(
103 Float.class,
104 new ValueBuilder() {
105 public Object buildValue (String value) {
106 return new Float(value);
107 }
108 });
109 tmp.put(
110 Long.class,
111 new ValueBuilder() {
112 public Object buildValue (String value) {
113 return new Long(value);
114 }
115 });
116 tmp.put(
117 Double.class,
118 new ValueBuilder() {
119 public Object buildValue (String value) {
120 return new Double(value);
121 }
122 });
123 tmp.put(
124 BigInteger.class,
125 new ValueBuilder() {
126 public Object buildValue (String value) {
127 return new BigInteger(value);
128 }
129 });
130 tmp.put(
131 BigDecimal.class,
132 new ValueBuilder() {
133 public Object buildValue (String value) {
134 return new BigDecimal(value);
135 }
136 });
137 tmp.put(
138 String.class,
139 new ValueBuilder() {
140 public Object buildValue (String value) {
141 return value;
142 }
143 });
144 tmp.put(Boolean.TYPE, tmp.get(Boolean.class));
145 tmp.put(Byte.TYPE, tmp.get(Byte.class));
146 tmp.put(Character.TYPE, tmp.get(Character.class));
147 tmp.put(Short.TYPE, tmp.get(Short.class));
148 tmp.put(Integer.TYPE, tmp.get(Integer.class));
149 tmp.put(Float.TYPE, tmp.get(Float.class));
150 tmp.put(Long.TYPE, tmp.get(Long.class));
151 tmp.put(Double.TYPE, tmp.get(Double.class));
152 valueBuilders = Collections.unmodifiableMap(tmp);
153 tmp = new HashMap(8);
154 tmp.put(Boolean.TYPE, Boolean.FALSE);
155 tmp.put(Byte.TYPE, new Byte((byte)0));
156 tmp.put(Character.TYPE, new Character('\0'));
157 tmp.put(Short.TYPE, new Short((short)0));
158 tmp.put(Integer.TYPE, new Integer(0));
159 tmp.put(Float.TYPE, new Float(0));
160 tmp.put(Long.TYPE, new Long(0));
161 tmp.put(Double.TYPE, new Double(0));
162 nullValues = Collections.unmodifiableMap(tmp);
163 }
164
165 /*** The type of the bean. */
166 private final Class beanType;
167 /***
168 * Mapping of property names to two-element Method arrays containing the accessor
169 * and mutator.
170 */
171 private final Map properties;
172
173 /***
174 * Creates a new BeanHelper object.
175 *
176 * @param beanType The class of the bean.
177 */
178 protected BeanHelper (Class beanType) {
179 if (beanType == null)
180 throw new NullPointerException("beanType");
181 if (!Types.javaBeans().isSatisfiedBy(beanType))
182 throw new IllegalArgumentException(
183 "Invalid JavaBean class '" + beanType.getName() + "'");
184 this.beanType = beanType;
185 Map tmp = new HashMap();
186 Method[] methods = Methods.propertyAccessors().selectAll(beanType.getMethods());
187 for (int i = 0; i < methods.length; ++i) {
188 String propertyName = toPropertyName(methods[i]);
189 if (!tmp.containsKey(propertyName))
190 tmp.put(propertyName, new Method[] { methods[i], null });
191 }
192 methods = Methods.propertyMutators().selectAll(beanType.getMethods());
193 for (int i = 0; i < methods.length; ++i) {
194 String propertyName = toPropertyName(methods[i]);
195 if (tmp.containsKey(propertyName)) {
196 Method[] prop = (Method[])tmp.get(propertyName);
197 if (
198 (prop[1] == null)
199 && prop[0].getReturnType().equals(methods[i].getParameterTypes()[0]))
200 prop[1] = methods[i];
201 } else
202 tmp.put(propertyName, new Method[] { null, methods[i] });
203 }
204 properties = Collections.unmodifiableMap(tmp);
205 }
206
207 /***
208 * Determines the name of the property implied by the supplied method.
209 *
210 * @param method The method to determine a property name for.
211 *
212 * @return The name of the property implied by the supplied method.
213 */
214 public static String toPropertyName (Method method) {
215 if (method == null)
216 throw new NullPointerException("method");
217 return Introspector.decapitalize(
218 method.getName().substring(method.getName().startsWith("is") ? 2 : 3));
219 }
220
221 /***
222 * Introspects the specified bean class, using the local cache if one is active.
223 *
224 * @param beanType The class of the bean.
225 *
226 * @return The helper instance for the specified class.
227 */
228 public static BeanHelper introspect (Class beanType) {
229 LinkedList stack = (LinkedList)helperCache.get();
230 if (stack == null)
231 return new BeanHelper(beanType);
232 Map cache = (Map)stack.getLast();
233 if (cache.containsKey(beanType))
234 return (BeanHelper)cache.get(beanType);
235 BeanHelper helper = new BeanHelper(beanType);
236 cache.put(beanType, helper);
237 return helper;
238 }
239
240 /***
241 * Activates the thread-local helper cache (must always be followed by a call to
242 * {@link BeanHelper#deactivateCache()}).
243 */
244 public static void activateCache () {
245 LinkedList stack = (LinkedList)helperCache.get();
246 if (stack == null)
247 helperCache.set(stack = new LinkedList());
248 stack.addLast(new HashMap());
249 }
250
251 /***
252 * Deactivates the thread-local helper cache (must always be preceeded by a call
253 * to {@link BeanHelper#activateCache()}).
254 */
255 public static void deactivateCache () {
256 LinkedList stack = (LinkedList)helperCache.get();
257 if (stack == null)
258 throw new IllegalStateException("No active cache");
259 stack.removeLast();
260 if (stack.isEmpty())
261 helperCache.set(null);
262 }
263
264 /***
265 * Invokes the specified method on the supplied object.
266 *
267 * @param target The target to invoke on.
268 * @param method The method to invoke.
269 * @param parameters The parameters to pass.
270 *
271 * @return The return value of the method.
272 */
273 public static Object invoke (Object target, Method method, Object[] parameters) {
274 try {
275 return method.invoke(target, parameters);
276 } catch (IllegalAccessException iae) {
277 throw new ReflectionException(iae);
278 } catch (InvocationTargetException ite) {
279 throw new ReflectionException(ite.getTargetException());
280 }
281 }
282
283 /***
284 * Converts a string into a basic value type.
285 *
286 * @param valueType The value type to convert to.
287 * @param string The string to convert.
288 *
289 * @return The resulting instance of the value type representing the supplied
290 * string.
291 */
292 public static Object stringToValue (Class valueType, String string) {
293 if (valueType == null)
294 throw new NullPointerException("valueType");
295 if (string == null)
296 return null;
297 ValueBuilder builder = (ValueBuilder)valueBuilders.get(valueType);
298 if (builder == null)
299 throw new IllegalArgumentException(
300 "Cannot convert strings to " + valueType.getName());
301 return builder.buildValue(string);
302 }
303
304 /***
305 * Returns the type of the bean.
306 *
307 * @return The type of the bean.
308 */
309 public Class getBeanType () {
310 return beanType;
311 }
312
313 /***
314 * Returns the names of all of the properties the bean type exposes.
315 *
316 * @return The names of all of the properties the bean type exposes.
317 */
318 public Set getPropertyNames () {
319 return properties.keySet();
320 }
321
322 /***
323 * Returns the accessor method for the named property, or null if one is not
324 * found.
325 *
326 * @param propertyName The name of the property.
327 *
328 * @return The accessor method for the named property, or null if one is not
329 * found.
330 */
331 public Method getPropertyAccessor (String propertyName) {
332 if (propertyName == null)
333 throw new NullPointerException("propertyName");
334 Method[] methods = (Method[])properties.get(propertyName);
335 if (methods == null)
336 return null;
337 return methods[0];
338 }
339
340 /***
341 * Returns the mutator method for the named property, or null if one is not
342 * found.
343 *
344 * @param propertyName The name of the property.
345 *
346 * @return The mutator method for the named property, or null if one is not
347 * found.
348 */
349 public Method getPropertyMutator (String propertyName) {
350 if (propertyName == null)
351 throw new NullPointerException("propertyName");
352 Method[] methods = (Method[])properties.get(propertyName);
353 if (methods == null)
354 return null;
355 return methods[1];
356 }
357
358 /***
359 * Returns the type of the named property, or null if one is not found.
360 *
361 * @param propertyName The name of the property.
362 *
363 * @return The type of the named property, or null if one is not found.
364 */
365 public Class getPropertyType (String propertyName) {
366 if (propertyName == null)
367 throw new NullPointerException("propertyName");
368 Method[] methods = (Method[])properties.get(propertyName);
369 if (methods == null)
370 return null;
371 if (methods[0] == null)
372 return methods[1].getParameterTypes()[0];
373 return methods[0].getReturnType();
374 }
375
376 /***
377 * Returns true if the bean helper can write strings to the specified property.
378 *
379 * @param propertyName The name of the property.
380 *
381 * @return True if the bean helper can write strings to the specified property.
382 */
383 public boolean canWriteStringsTo (String propertyName) {
384 if (propertyName == null)
385 throw new NullPointerException("propertyName");
386 Class propertyType = getPropertyType(propertyName);
387 if (propertyType == null)
388 return false;
389 return valueBuilders.containsKey(propertyType);
390 }
391
392 /***
393 * Creates a new instance of this helper's JavaBean class.
394 *
395 * @return A new bean instance.
396 */
397 public Object newBeanInstance () {
398 try {
399 return beanType.newInstance();
400 } catch (Exception e) {
401 throw new ReflectionException(e);
402 }
403 }
404
405 /***
406 * Returns the value of the named property on the specified instance, or null if
407 * it is not found.
408 *
409 * @param instance The instance to return the property value from.
410 * @param propertyName The name of the property to return.
411 *
412 * @return The value of the property or null if it is not found.
413 */
414 public Object getProperty (Object instance, String propertyName) {
415 if (instance == null)
416 throw new NullPointerException("instance");
417 if (propertyName == null)
418 throw new NullPointerException("propertyName");
419 Method method = getPropertyAccessor(propertyName);
420 if (method != null)
421 return invoke(instance, method, noParameters);
422 return null;
423 }
424
425 /***
426 * Sets the value of the named property on the specified instance.
427 *
428 * @param instance The instance to set the property value on.
429 * @param propertyName The name of the property to set.
430 * @param propertyValue The value to set the property to.
431 *
432 * @return True if the property was successfully set.
433 */
434 public boolean setProperty (
435 Object instance, String propertyName, Object propertyValue) {
436 if (instance == null)
437 throw new NullPointerException("instance");
438 if (propertyName == null)
439 throw new NullPointerException("propertyName");
440 if (propertyValue == null)
441 return clearProperty(instance, propertyName);
442 Method method = getPropertyMutator(propertyName);
443 if (method == null)
444 return false;
445 invoke(instance, method, new Object[] { propertyValue });
446 return true;
447 }
448
449 /***
450 * Sets the value of the named property on the specified instance as a string.
451 *
452 * @param instance The instance to set the property value on.
453 * @param propertyName The name of the property to set.
454 * @param propertyValue The string value to set the property to.
455 *
456 * @return True if the property was successfully set.
457 */
458 public boolean setPropertyAsString (
459 Object instance, String propertyName, String propertyValue) {
460 if (instance == null)
461 throw new NullPointerException("instance");
462 if (propertyName == null)
463 throw new NullPointerException("propertyName");
464 if (propertyValue == null)
465 return clearProperty(instance, propertyName);
466 Class propertyType = getPropertyType(propertyName);
467 if (propertyType == null)
468 return false;
469 return setProperty(
470 instance, propertyName, stringToValue(propertyType, propertyValue));
471 }
472
473 /***
474 * Clears the value of the named property on the specified instance.
475 *
476 * @param instance The instance to clear the property value on.
477 * @param propertyName The name of the property to clear.
478 *
479 * @return True if the property was successfully cleared.
480 */
481 public boolean clearProperty (Object instance, String propertyName) {
482 if (instance == null)
483 throw new NullPointerException("instance");
484 if (propertyName == null)
485 throw new NullPointerException("propertyName");
486 Method method = getPropertyMutator(propertyName);
487 if (method == null)
488 return false;
489 invoke(
490 instance, method,
491 new Object[] { nullValues.get(method.getParameterTypes()[0]) });
492 return true;
493 }
494
495 /* (non-Javadoc)
496 * @see java.lang.Object#equals(java.lang.Object)
497 */
498 public boolean equals (Object obj) {
499 return obj instanceof BeanHelper
500 && beanType.equals(((BeanHelper)obj).getBeanType());
501 }
502
503 /* (non-Javadoc)
504 * @see java.lang.Object#hashCode()
505 */
506 public int hashCode () {
507 return beanType.hashCode();
508 }
509
510 /* (non-Javadoc)
511 * @see java.lang.Object#toString()
512 */
513 public String toString () {
514 return beanType.toString();
515 }
516
517 /***
518 * Strategy class for transforming a string to a specific type.
519 *
520 * @author Lonnie Pryor
521 * @version $Revision: 1.1 $
522 */
523 private interface ValueBuilder {
524 /***
525 * Transforms the supplied string into an instance of this builder's type.
526 *
527 * @param value The string to resolve.
528 *
529 * @return An instance of this builder's type from the supplied string.
530 */
531 Object buildValue (String value);
532 }
533 }
This page was automatically generated by Maven