Source for org.jfree.report.util.beans.BeanUtility

   1: /**
   2:  * ========================================
   3:  * JFreeReport : a free Java report library
   4:  * ========================================
   5:  *
   6:  * Project Info:  http://reporting.pentaho.org/
   7:  *
   8:  * (C) Copyright 2000-2007, by Object Refinery Limited, Pentaho Corporation and Contributors.
   9:  *
  10:  * This library is free software; you can redistribute it and/or modify it under the terms
  11:  * of the GNU Lesser General Public License as published by the Free Software Foundation;
  12:  * either version 2.1 of the License, or (at your option) any later version.
  13:  *
  14:  * This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
  15:  * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
  16:  * 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 with this
  19:  * library; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330,
  20:  * Boston, MA 02111-1307, USA.
  21:  *
  22:  * [Java is a trademark or registered trademark of Sun Microsystems, Inc.
  23:  * in the United States and other countries.]
  24:  *
  25:  * ------------
  26:  * $Id: BeanUtility.java 3525 2007-10-16 11:43:48Z tmorgner $
  27:  * ------------
  28:  * (C) Copyright 2000-2005, by Object Refinery Limited.
  29:  * (C) Copyright 2005-2007, by Pentaho Corporation.
  30:  */
  31: package org.jfree.report.util.beans;
  32: 
  33: import java.beans.BeanInfo;
  34: import java.beans.IndexedPropertyDescriptor;
  35: import java.beans.IntrospectionException;
  36: import java.beans.Introspector;
  37: import java.beans.PropertyDescriptor;
  38: import java.lang.reflect.Array;
  39: import java.lang.reflect.Method;
  40: import java.util.ArrayList;
  41: import java.util.HashMap;
  42: 
  43: /**
  44:  * The BeanUtility class enables access to bean properties using the reflection
  45:  * API.
  46:  *
  47:  * @author Thomas Morgner
  48:  */
  49: public final class BeanUtility
  50: {
  51:   /**
  52:    * A property specification parses a compound property name into segments
  53:    * and allows access to the next property.
  54:    */
  55:   private static class PropertySpecification
  56:   {
  57:     /** The raw value of the property name. */
  58:     private String raw;
  59:     /** The next direct property that should be accessed. */
  60:     private String name;
  61:     /** The index, if the named property points to an indexed property. */
  62:     private String index;
  63: 
  64:     /**
  65:      * Creates a new PropertySpecification object for the given property string.
  66:      *
  67:      * @param raw the property string, posssibly with index specifications.
  68:      */
  69:     private PropertySpecification (final String raw)
  70:     {
  71:       this.raw = raw;
  72:       this.name = getNormalizedName(raw);
  73:       this.index = getIndex(raw);
  74:     }
  75: 
  76:     /**
  77:      * Returns the name of the property without any index information.
  78:      *
  79:      * @param property the raw name
  80:      * @return the normalized name.
  81:      */
  82:     private String getNormalizedName (final String property)
  83:     {
  84:       final int idx = property.indexOf('[');
  85:       if (idx < 0)
  86:       {
  87:         return property;
  88:       }
  89:       return property.substring(0, idx);
  90:     }
  91: 
  92:     /**
  93:      * Extracts the first index from the given raw property.
  94:      *
  95:      * @param property the raw name
  96:      * @return the index as String.
  97:      */
  98:     private String getIndex (final String property)
  99:     {
 100:       final int idx = property.indexOf('[');
 101:       if (idx < 0)
 102:       {
 103:         return null;
 104:       }
 105:       final int end = property.indexOf(']', idx + 1);
 106:       if (end < 0)
 107:       {
 108:         return null;
 109:       }
 110:       return property.substring(idx + 1, end);
 111:     }
 112: 
 113:     public String getRaw ()
 114:     {
 115:       return raw;
 116:     }
 117: 
 118:     public String getName ()
 119:     {
 120:       return name;
 121:     }
 122: 
 123:     public String getIndex ()
 124:     {
 125:       return index;
 126:     }
 127: 
 128:     public String toString ()
 129:     {
 130:       final StringBuffer b = new StringBuffer("PropertySpecification={");
 131:       b.append("raw=");
 132:       b.append(raw);
 133:       b.append("}");
 134:       return b.toString();
 135:     }
 136:   }
 137: 
 138:   private BeanInfo beanInfo;
 139:   private Object bean;
 140:   private HashMap properties;
 141: 
 142:   private BeanUtility()
 143:   {
 144:   }
 145: 
 146:   public BeanUtility (final Object o)
 147:           throws IntrospectionException
 148:   {
 149:     bean = o;
 150: 
 151:     beanInfo = Introspector.getBeanInfo(o.getClass());
 152:     properties = new HashMap();
 153:     final PropertyDescriptor[] propertyDescriptors =
 154:             beanInfo.getPropertyDescriptors();
 155:     for (int i = 0; i < propertyDescriptors.length; i++)
 156:     {
 157:       properties.put(propertyDescriptors[i].getName(), propertyDescriptors[i]);
 158:     }
 159:   }
 160: 
 161: 
 162: 
 163:   public BeanUtility derive (final Object o)
 164:   {
 165:     if (o.getClass().equals(bean.getClass()) == false)
 166:     {
 167:       throw new IllegalArgumentException();
 168:     }
 169:     final BeanUtility bu = new BeanUtility();
 170:     bu.bean = o;
 171:     return bu;
 172:   }
 173: 
 174:   public PropertyDescriptor[] getPropertyInfos ()
 175:   {
 176:     return beanInfo.getPropertyDescriptors();
 177:   }
 178: 
 179:   public Object getProperty (final String name)
 180:           throws BeanException
 181:   {
 182:     return getPropertyForSpecification(new PropertySpecification(name));
 183:   }
 184: 
 185:   private Object getPropertyForSpecification (final PropertySpecification name)
 186:           throws BeanException
 187:   {
 188:     final PropertyDescriptor pd = (PropertyDescriptor) properties.get(name.getName());
 189:     if (pd == null)
 190:     {
 191:       throw new BeanException("No such property:" + name);
 192:     }
 193: 
 194:     if (pd instanceof IndexedPropertyDescriptor && name.getIndex() != null)
 195:     {
 196:       final IndexedPropertyDescriptor ipd = (IndexedPropertyDescriptor) pd;
 197:       final Method readMethod = ipd.getIndexedReadMethod();
 198:       if (readMethod == null)
 199:       {
 200:         throw new BeanException("Property is not readable: " + name);
 201:       }
 202:       try
 203:       {
 204:         return readMethod.invoke(bean, new Object[]{new Integer(name.getIndex())});
 205:       }
 206:       catch (Exception e)
 207:       {
 208:         throw new BeanException("InvokationError", e);
 209:       }
 210:     }
 211:     else
 212:     {
 213:       final Method readMethod = pd.getReadMethod();
 214:       if (readMethod == null)
 215:       {
 216:         throw new BeanException("Property is not readable: " + name);
 217:       }
 218:       if (name.getIndex() != null)
 219:       {
 220:         // handle access to array-only properties ..
 221:         try
 222:         {
 223:           //System.out.println(readMethod);
 224:           final Object value = readMethod.invoke(bean, null);
 225:           // we have (possibly) an array.
 226:           if (value == null)
 227:           {
 228:             throw new IndexOutOfBoundsException("No such index, property is null");
 229:           }
 230:           if (value.getClass().isArray() == false)
 231:           {
 232:             throw new BeanException("The property contains no array.");
 233:           }
 234:           final int index = Integer.parseInt(name.getIndex());
 235:           return Array.get(value, index);
 236:         }
 237:         catch(BeanException be)
 238:         {
 239:           throw be;
 240:         }
 241:         catch(IndexOutOfBoundsException iob)
 242:         {
 243:           throw iob;
 244:         }
 245:         catch(Exception e)
 246:         {
 247:           throw new BeanException("Failed to read indexed property.");
 248:         }
 249:       }
 250: 
 251:       try
 252:       {
 253:         return readMethod.invoke(bean, null);
 254:       }
 255:       catch (Exception e)
 256:       {
 257:         throw new BeanException("InvokationError", e);
 258:       }
 259:     }
 260:   }
 261: 
 262:   public String getPropertyAsString (final String name)
 263:           throws BeanException
 264:   {
 265:     final PropertySpecification ps = new PropertySpecification(name);
 266:     final PropertyDescriptor pd = (PropertyDescriptor) properties.get(ps.getName());
 267:     if (pd == null)
 268:     {
 269:       throw new BeanException("No such property:" + name);
 270:     }
 271:     final Object o = getPropertyForSpecification(ps);
 272:     if (o == null)
 273:     {
 274:       return null;
 275:     }
 276: 
 277:     final ValueConverter vc =
 278:             ConverterRegistry.getInstance().getValueConverter(o.getClass());
 279:     if (vc == null)
 280:     {
 281:       throw new BeanException("Unable to handle property of type " + o.getClass()
 282:               .getName());
 283:     }
 284:     return vc.toAttributeValue(o);
 285:   }
 286: 
 287:   public void setProperty (final String name, final Object o)
 288:           throws BeanException
 289:   {
 290:     if (name == null)
 291:     {
 292:       throw new NullPointerException("Name must not be null");
 293:     }
 294:     setProperty(new PropertySpecification(name), o);
 295:   }
 296: 
 297:   private void setProperty (final PropertySpecification name, final Object o)
 298:           throws BeanException
 299:   {
 300:     final PropertyDescriptor pd = (PropertyDescriptor) properties.get(name.getName());
 301:     if (pd == null)
 302:     {
 303:       throw new BeanException("No such property:" + name);
 304:     }
 305: 
 306:     if (pd instanceof IndexedPropertyDescriptor && name.getIndex() != null)
 307:     {
 308:       final IndexedPropertyDescriptor ipd = (IndexedPropertyDescriptor) pd;
 309:       final Method writeMethod = ipd.getIndexedWriteMethod();
 310:       if (writeMethod != null)
 311:       {
 312:         try
 313:         {
 314:           writeMethod.invoke(bean, new Object[]{new Integer(name.getIndex()), o});
 315:         }
 316:         catch (Exception e)
 317:         {
 318:           throw new BeanException("InvokationError", e);
 319:         }
 320:         // we've done the job ...
 321:         return;
 322:       }
 323:     }
 324: 
 325:     final Method writeMethod = pd.getWriteMethod();
 326:     if (writeMethod == null)
 327:     {
 328:       throw new BeanException("Property is not writeable: " + name);
 329:     }
 330: 
 331:     if (name.getIndex() != null)
 332:     {
 333:       // this is a indexed access, but no indexWrite method was found ...
 334:       updateArrayProperty(pd, name, o);
 335:     }
 336:     else
 337:     {
 338:       try
 339:       {
 340:         writeMethod.invoke(bean, new Object[]{o});
 341:       }
 342:       catch (Exception e)
 343:       {
 344:         throw new BeanException("InvokationError", e);
 345:       }
 346:     }
 347:   }
 348: 
 349:   private void updateArrayProperty (final PropertyDescriptor pd,
 350:                                     final PropertySpecification name,
 351:                                     final Object o)
 352:           throws BeanException
 353:   {
 354:     final Method readMethod = pd.getReadMethod();
 355:     if (readMethod == null)
 356:     {
 357:       throw new BeanException("Property is not readable, cannot perform array update: " + name);
 358:     }
 359:     try
 360:     {
 361:       //System.out.println(readMethod);
 362:       final Object value = readMethod.invoke(bean, null);
 363:       // we have (possibly) an array.
 364:       final int index = Integer.parseInt(name.getIndex());
 365:       final Object array = validateArray(getPropertyType(pd), value, index);
 366:       Array.set(array, index, o);
 367: 
 368:       final Method writeMethod = pd.getWriteMethod();
 369:       writeMethod.invoke(bean, new Object[]{array});
 370:     }
 371:     catch(BeanException e)
 372:     {
 373:       throw e;
 374:     }
 375:     catch(Exception e)
 376:     {
 377:       e.printStackTrace();
 378:       throw new BeanException("Failed to read property, cannot perform array update: " + name);
 379:     }
 380:   }
 381: 
 382:   private Object validateArray (final Class propertyType,
 383:                                 final Object o, final int size)
 384:           throws BeanException
 385:   {
 386: 
 387:     if (propertyType.isArray() == false)
 388:     {
 389:       throw new BeanException("The property's value is no array.");
 390:     }
 391: 
 392:     if (o == null)
 393:     {
 394:       return Array.newInstance(propertyType.getComponentType(), size + 1);
 395:     }
 396: 
 397:     if (o.getClass().isArray() == false)
 398:     {
 399:       throw new BeanException("The property's value is no array.");
 400:     }
 401: 
 402:     final int length = Array.getLength(o);
 403:     if (length > size)
 404:     {
 405:       return o;
 406:     }
 407:     // we have to copy the array ..
 408:     final Object retval = Array.newInstance(o.getClass().getComponentType(), size + 1);
 409:     System.arraycopy(o, 0, retval, 0, length);
 410:     return o;
 411:   }
 412: 
 413:   public void setPropertyAsString (final String name, final String txt)
 414:           throws BeanException
 415:   {
 416:     if (name == null)
 417:     {
 418:       throw new NullPointerException("Name must not be null");
 419:     }
 420:     if (txt == null)
 421:     {
 422:       throw new NullPointerException("Text must not be null");
 423:     }
 424:     final PropertySpecification ps = new PropertySpecification(name);
 425:     final PropertyDescriptor pd = (PropertyDescriptor) properties.get(ps.getName());
 426:     if (pd == null)
 427:     {
 428:       throw new BeanException("No such property:" + name);
 429:     }
 430: 
 431:     setPropertyAsString(name, getPropertyType(pd), txt);
 432:   }
 433: 
 434:   public Class getPropertyType (final String name) throws BeanException
 435:   {
 436:     if (name == null)
 437:     {
 438:       throw new NullPointerException("Name must not be null");
 439:     }
 440:     final PropertySpecification ps = new PropertySpecification(name);
 441:     final PropertyDescriptor pd = (PropertyDescriptor) properties.get(ps.getName());
 442:     if (pd == null)
 443:     {
 444:       throw new BeanException("No such property:" + name);
 445:     }
 446:     return getPropertyType(pd);
 447:   }
 448: 
 449:   public static Class getPropertyType (final PropertyDescriptor pd)
 450:           throws BeanException
 451:   {
 452:     final Class typeFromDescriptor = pd.getPropertyType();
 453:     if (typeFromDescriptor != null)
 454:     {
 455:       return typeFromDescriptor;
 456:     }
 457:     if (pd instanceof IndexedPropertyDescriptor)
 458:     {
 459:       final IndexedPropertyDescriptor idx = (IndexedPropertyDescriptor) pd;
 460:       return idx.getIndexedPropertyType();
 461:     }
 462:     throw new BeanException("Unable to determine the property type.");
 463:   }
 464: 
 465:   public void setPropertyAsString (final String name, final Class type, final String txt)
 466:           throws BeanException
 467:   {
 468:     if (name == null)
 469:     {
 470:       throw new NullPointerException("Name must not be null");
 471:     }
 472:     if (type == null)
 473:     {
 474:       throw new NullPointerException("Type must not be null");
 475:     }
 476:     if (txt == null)
 477:     {
 478:       throw new NullPointerException("Text must not be null");
 479:     }
 480:     final PropertySpecification ps = new PropertySpecification(name);
 481:     final ValueConverter vc;
 482:     if (ps.getIndex() != null && type.isArray())
 483:     {
 484:       vc = ConverterRegistry.getInstance().getValueConverter(type.getComponentType());
 485:     }
 486:     else
 487:     {
 488:       vc = ConverterRegistry.getInstance().getValueConverter(type);
 489:     }
 490:     if (vc == null)
 491:     {
 492:       throw new BeanException
 493:               ("Unable to handle '" + type + "' for property '" + name + "'");
 494:     }
 495:     final Object o = vc.toPropertyValue(txt);
 496:     setProperty(ps, o);
 497:   }
 498: 
 499:   public String[] getProperties ()
 500:           throws BeanException
 501:   {
 502:     final ArrayList propertyNames = new ArrayList();
 503:     final PropertyDescriptor[] pd = getPropertyInfos();
 504:     for (int i = 0; i < pd.length; i++)
 505:     {
 506:       final PropertyDescriptor property = pd[i];
 507:       if (property.isHidden())
 508:       {
 509:         continue;
 510:       }
 511:       if (property.getReadMethod() == null ||
 512:               property.getWriteMethod() == null)
 513:       {
 514:         // it will make no sense to write a property now, that
 515:         // we can't read in later...
 516:         continue;
 517:       }
 518:       if (getPropertyType(property).isArray())
 519:       {
 520:         final int max = findMaximumIndex(property);
 521:         for (int idx = 0; idx < max; idx++)
 522:         {
 523:           propertyNames.add(property.getName() + "[" + idx + "]");
 524:         }
 525:       }
 526:       else
 527:       {
 528:         propertyNames.add(property.getName());
 529:       }
 530:     }
 531:     return (String[]) propertyNames.toArray(new String[propertyNames.size()]);
 532:   }
 533: 
 534:   private int findMaximumIndex (final PropertyDescriptor id)
 535:   {
 536:     try
 537:     {
 538:       final Object o = getPropertyForSpecification
 539:               (new PropertySpecification(id.getName()));
 540:       return Array.getLength(o);
 541:     }
 542:     catch (Exception e)
 543:     {
 544:       // ignore, we run 'til we encounter an index out of bounds Ex.
 545:     }
 546:     return 0;
 547:   }
 548: }