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,v 1.13 2007/04/01 18:49:34 taqua Exp $
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 public 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 }