001 /** 002 * ======================================== 003 * JFreeReport : a free Java report library 004 * ======================================== 005 * 006 * Project Info: http://reporting.pentaho.org/ 007 * 008 * (C) Copyright 2000-2007, by Object Refinery Limited, Pentaho Corporation and Contributors. 009 * 010 * This library is free software; you can redistribute it and/or modify it under the terms 011 * of the GNU Lesser General Public License as published by the Free Software Foundation; 012 * either version 2.1 of the License, or (at your option) any later version. 013 * 014 * This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; 015 * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. 016 * See the GNU Lesser General Public License for more details. 017 * 018 * You should have received a copy of the GNU Lesser General Public License along with this 019 * library; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, 020 * Boston, MA 02111-1307, USA. 021 * 022 * [Java is a trademark or registered trademark of Sun Microsystems, Inc. 023 * in the United States and other countries.] 024 * 025 * ------------ 026 * $Id: BeanUtility.java 3525 2007-10-16 11:43:48Z tmorgner $ 027 * ------------ 028 * (C) Copyright 2000-2005, by Object Refinery Limited. 029 * (C) Copyright 2005-2007, by Pentaho Corporation. 030 */ 031 package org.jfree.report.util.beans; 032 033 import java.beans.BeanInfo; 034 import java.beans.IndexedPropertyDescriptor; 035 import java.beans.IntrospectionException; 036 import java.beans.Introspector; 037 import java.beans.PropertyDescriptor; 038 import java.lang.reflect.Array; 039 import java.lang.reflect.Method; 040 import java.util.ArrayList; 041 import java.util.HashMap; 042 043 /** 044 * The BeanUtility class enables access to bean properties using the reflection 045 * API. 046 * 047 * @author Thomas Morgner 048 */ 049 public final class BeanUtility 050 { 051 /** 052 * A property specification parses a compound property name into segments 053 * and allows access to the next property. 054 */ 055 private static class PropertySpecification 056 { 057 /** The raw value of the property name. */ 058 private String raw; 059 /** The next direct property that should be accessed. */ 060 private String name; 061 /** The index, if the named property points to an indexed property. */ 062 private String index; 063 064 /** 065 * Creates a new PropertySpecification object for the given property string. 066 * 067 * @param raw the property string, posssibly with index specifications. 068 */ 069 private PropertySpecification (final String raw) 070 { 071 this.raw = raw; 072 this.name = getNormalizedName(raw); 073 this.index = getIndex(raw); 074 } 075 076 /** 077 * Returns the name of the property without any index information. 078 * 079 * @param property the raw name 080 * @return the normalized name. 081 */ 082 private String getNormalizedName (final String property) 083 { 084 final int idx = property.indexOf('['); 085 if (idx < 0) 086 { 087 return property; 088 } 089 return property.substring(0, idx); 090 } 091 092 /** 093 * Extracts the first index from the given raw property. 094 * 095 * @param property the raw name 096 * @return the index as String. 097 */ 098 private String getIndex (final String property) 099 { 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 }