View Javadoc
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