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: */package org.jfree.report.util.beans;
031:
032: import java.beans.BeanInfo;
033: import java.beans.IndexedPropertyDescriptor;
034: import java.beans.IntrospectionException;
035: import java.beans.Introspector;
036: import java.beans.PropertyDescriptor;
037: import java.lang.reflect.Array;
038: import java.lang.reflect.Method;
039: import java.util.ArrayList;
040: import java.util.HashMap;
041:
042: /**
043: * The BeanUtility class enables access to bean properties using the reflection
044: * API.
045: *
046: * @author Thomas Morgner
047: */
048: public final class BeanUtility {
049: /**
050: * A property specification parses a compound property name into segments
051: * and allows access to the next property.
052: */
053: private static class PropertySpecification {
054: /** The raw value of the property name. */
055: private String raw;
056: /** The next direct property that should be accessed. */
057: private String name;
058: /** The index, if the named property points to an indexed property. */
059: private String index;
060:
061: /**
062: * Creates a new PropertySpecification object for the given property string.
063: *
064: * @param raw the property string, posssibly with index specifications.
065: */
066: private PropertySpecification(final String raw) {
067: this .raw = raw;
068: this .name = getNormalizedName(raw);
069: this .index = getIndex(raw);
070: }
071:
072: /**
073: * Returns the name of the property without any index information.
074: *
075: * @param property the raw name
076: * @return the normalized name.
077: */
078: private String getNormalizedName(final String property) {
079: final int idx = property.indexOf('[');
080: if (idx < 0) {
081: return property;
082: }
083: return property.substring(0, idx);
084: }
085:
086: /**
087: * Extracts the first index from the given raw property.
088: *
089: * @param property the raw name
090: * @return the index as String.
091: */
092: private String getIndex(final String property) {
093: final int idx = property.indexOf('[');
094: if (idx < 0) {
095: return null;
096: }
097: final int end = property.indexOf(']', idx + 1);
098: if (end < 0) {
099: return null;
100: }
101: return property.substring(idx + 1, end);
102: }
103:
104: public String getRaw() {
105: return raw;
106: }
107:
108: public String getName() {
109: return name;
110: }
111:
112: public String getIndex() {
113: return index;
114: }
115:
116: public String toString() {
117: final StringBuffer b = new StringBuffer(
118: "PropertySpecification={");
119: b.append("raw=");
120: b.append(raw);
121: b.append("}");
122: return b.toString();
123: }
124: }
125:
126: private BeanInfo beanInfo;
127: private Object bean;
128: private HashMap properties;
129:
130: private BeanUtility() {
131: }
132:
133: public BeanUtility(final Object o) throws IntrospectionException {
134: bean = o;
135:
136: beanInfo = Introspector.getBeanInfo(o.getClass());
137: properties = new HashMap();
138: final PropertyDescriptor[] propertyDescriptors = beanInfo
139: .getPropertyDescriptors();
140: for (int i = 0; i < propertyDescriptors.length; i++) {
141: properties.put(propertyDescriptors[i].getName(),
142: propertyDescriptors[i]);
143: }
144: }
145:
146: public BeanUtility derive(final Object o) {
147: if (o.getClass().equals(bean.getClass()) == false) {
148: throw new IllegalArgumentException();
149: }
150: final BeanUtility bu = new BeanUtility();
151: bu.bean = o;
152: return bu;
153: }
154:
155: public PropertyDescriptor[] getPropertyInfos() {
156: return beanInfo.getPropertyDescriptors();
157: }
158:
159: public Object getProperty(final String name) throws BeanException {
160: return getPropertyForSpecification(new PropertySpecification(
161: name));
162: }
163:
164: private Object getPropertyForSpecification(
165: final PropertySpecification name) throws BeanException {
166: final PropertyDescriptor pd = (PropertyDescriptor) properties
167: .get(name.getName());
168: if (pd == null) {
169: throw new BeanException("No such property:" + name);
170: }
171:
172: if (pd instanceof IndexedPropertyDescriptor
173: && name.getIndex() != null) {
174: final IndexedPropertyDescriptor ipd = (IndexedPropertyDescriptor) pd;
175: final Method readMethod = ipd.getIndexedReadMethod();
176: if (readMethod == null) {
177: throw new BeanException("Property is not readable: "
178: + name);
179: }
180: try {
181: return readMethod.invoke(bean,
182: new Object[] { new Integer(name.getIndex()) });
183: } catch (Exception e) {
184: throw new BeanException("InvokationError", e);
185: }
186: } else {
187: final Method readMethod = pd.getReadMethod();
188: if (readMethod == null) {
189: throw new BeanException("Property is not readable: "
190: + name);
191: }
192: if (name.getIndex() != null) {
193: // handle access to array-only properties ..
194: try {
195: //System.out.println(readMethod);
196: final Object value = readMethod.invoke(bean, null);
197: // we have (possibly) an array.
198: if (value == null) {
199: throw new IndexOutOfBoundsException(
200: "No such index, property is null");
201: }
202: if (value.getClass().isArray() == false) {
203: throw new BeanException(
204: "The property contains no array.");
205: }
206: final int index = Integer.parseInt(name.getIndex());
207: return Array.get(value, index);
208: } catch (BeanException be) {
209: throw be;
210: } catch (IndexOutOfBoundsException iob) {
211: throw iob;
212: } catch (Exception e) {
213: throw new BeanException(
214: "Failed to read indexed property.");
215: }
216: }
217:
218: try {
219: return readMethod.invoke(bean, null);
220: } catch (Exception e) {
221: throw new BeanException("InvokationError", e);
222: }
223: }
224: }
225:
226: public String getPropertyAsString(final String name)
227: throws BeanException {
228: final PropertySpecification ps = new PropertySpecification(name);
229: final PropertyDescriptor pd = (PropertyDescriptor) properties
230: .get(ps.getName());
231: if (pd == null) {
232: throw new BeanException("No such property:" + name);
233: }
234: final Object o = getPropertyForSpecification(ps);
235: if (o == null) {
236: return null;
237: }
238:
239: final ValueConverter vc = ConverterRegistry.getInstance()
240: .getValueConverter(o.getClass());
241: if (vc == null) {
242: throw new BeanException(
243: "Unable to handle property of type "
244: + o.getClass().getName());
245: }
246: return vc.toAttributeValue(o);
247: }
248:
249: public void setProperty(final String name, final Object o)
250: throws BeanException {
251: if (name == null) {
252: throw new NullPointerException("Name must not be null");
253: }
254: setProperty(new PropertySpecification(name), o);
255: }
256:
257: private void setProperty(final PropertySpecification name,
258: final Object o) throws BeanException {
259: final PropertyDescriptor pd = (PropertyDescriptor) properties
260: .get(name.getName());
261: if (pd == null) {
262: throw new BeanException("No such property:" + name);
263: }
264:
265: if (pd instanceof IndexedPropertyDescriptor
266: && name.getIndex() != null) {
267: final IndexedPropertyDescriptor ipd = (IndexedPropertyDescriptor) pd;
268: final Method writeMethod = ipd.getIndexedWriteMethod();
269: if (writeMethod != null) {
270: try {
271: writeMethod.invoke(bean, new Object[] {
272: new Integer(name.getIndex()), o });
273: } catch (Exception e) {
274: throw new BeanException("InvokationError", e);
275: }
276: // we've done the job ...
277: return;
278: }
279: }
280:
281: final Method writeMethod = pd.getWriteMethod();
282: if (writeMethod == null) {
283: throw new BeanException("Property is not writeable: "
284: + name);
285: }
286:
287: if (name.getIndex() != null) {
288: // this is a indexed access, but no indexWrite method was found ...
289: updateArrayProperty(pd, name, o);
290: } else {
291: try {
292: writeMethod.invoke(bean, new Object[] { o });
293: } catch (Exception e) {
294: throw new BeanException("InvokationError", e);
295: }
296: }
297: }
298:
299: private void updateArrayProperty(final PropertyDescriptor pd,
300: final PropertySpecification name, final Object o)
301: throws BeanException {
302: final Method readMethod = pd.getReadMethod();
303: if (readMethod == null) {
304: throw new BeanException(
305: "Property is not readable, cannot perform array update: "
306: + name);
307: }
308: try {
309: //System.out.println(readMethod);
310: final Object value = readMethod.invoke(bean, null);
311: // we have (possibly) an array.
312: final int index = Integer.parseInt(name.getIndex());
313: final Object array = validateArray(getPropertyType(pd),
314: value, index);
315: Array.set(array, index, o);
316:
317: final Method writeMethod = pd.getWriteMethod();
318: writeMethod.invoke(bean, new Object[] { array });
319: } catch (BeanException e) {
320: throw e;
321: } catch (Exception e) {
322: e.printStackTrace();
323: throw new BeanException(
324: "Failed to read property, cannot perform array update: "
325: + name);
326: }
327: }
328:
329: private Object validateArray(final Class propertyType,
330: final Object o, final int size) throws BeanException {
331:
332: if (propertyType.isArray() == false) {
333: throw new BeanException("The property's value is no array.");
334: }
335:
336: if (o == null) {
337: return Array.newInstance(propertyType.getComponentType(),
338: size + 1);
339: }
340:
341: if (o.getClass().isArray() == false) {
342: throw new BeanException("The property's value is no array.");
343: }
344:
345: final int length = Array.getLength(o);
346: if (length > size) {
347: return o;
348: }
349: // we have to copy the array ..
350: final Object retval = Array.newInstance(o.getClass()
351: .getComponentType(), size + 1);
352: System.arraycopy(o, 0, retval, 0, length);
353: return o;
354: }
355:
356: public void setPropertyAsString(final String name, final String txt)
357: throws BeanException {
358: if (name == null) {
359: throw new NullPointerException("Name must not be null");
360: }
361: if (txt == null) {
362: throw new NullPointerException("Text must not be null");
363: }
364: final PropertySpecification ps = new PropertySpecification(name);
365: final PropertyDescriptor pd = (PropertyDescriptor) properties
366: .get(ps.getName());
367: if (pd == null) {
368: throw new BeanException("No such property:" + name);
369: }
370:
371: setPropertyAsString(name, getPropertyType(pd), txt);
372: }
373:
374: public Class getPropertyType(final String name)
375: throws BeanException {
376: if (name == null) {
377: throw new NullPointerException("Name must not be null");
378: }
379: final PropertySpecification ps = new PropertySpecification(name);
380: final PropertyDescriptor pd = (PropertyDescriptor) properties
381: .get(ps.getName());
382: if (pd == null) {
383: throw new BeanException("No such property:" + name);
384: }
385: return getPropertyType(pd);
386: }
387:
388: public static Class getPropertyType(final PropertyDescriptor pd)
389: throws BeanException {
390: final Class typeFromDescriptor = pd.getPropertyType();
391: if (typeFromDescriptor != null) {
392: return typeFromDescriptor;
393: }
394: if (pd instanceof IndexedPropertyDescriptor) {
395: final IndexedPropertyDescriptor idx = (IndexedPropertyDescriptor) pd;
396: return idx.getIndexedPropertyType();
397: }
398: throw new BeanException(
399: "Unable to determine the property type.");
400: }
401:
402: public void setPropertyAsString(final String name,
403: final Class type, final String txt) throws BeanException {
404: if (name == null) {
405: throw new NullPointerException("Name must not be null");
406: }
407: if (type == null) {
408: throw new NullPointerException("Type must not be null");
409: }
410: if (txt == null) {
411: throw new NullPointerException("Text must not be null");
412: }
413: final PropertySpecification ps = new PropertySpecification(name);
414: final ValueConverter vc;
415: if (ps.getIndex() != null && type.isArray()) {
416: vc = ConverterRegistry.getInstance().getValueConverter(
417: type.getComponentType());
418: } else {
419: vc = ConverterRegistry.getInstance()
420: .getValueConverter(type);
421: }
422: if (vc == null) {
423: throw new BeanException("Unable to handle '" + type
424: + "' for property '" + name + "'");
425: }
426: final Object o = vc.toPropertyValue(txt);
427: setProperty(ps, o);
428: }
429:
430: public String[] getProperties() throws BeanException {
431: final ArrayList propertyNames = new ArrayList();
432: final PropertyDescriptor[] pd = getPropertyInfos();
433: for (int i = 0; i < pd.length; i++) {
434: final PropertyDescriptor property = pd[i];
435: if (property.isHidden()) {
436: continue;
437: }
438: if (property.getReadMethod() == null
439: || property.getWriteMethod() == null) {
440: // it will make no sense to write a property now, that
441: // we can't read in later...
442: continue;
443: }
444: if (getPropertyType(property).isArray()) {
445: final int max = findMaximumIndex(property);
446: for (int idx = 0; idx < max; idx++) {
447: propertyNames.add(property.getName() + "[" + idx
448: + "]");
449: }
450: } else {
451: propertyNames.add(property.getName());
452: }
453: }
454: return (String[]) propertyNames
455: .toArray(new String[propertyNames.size()]);
456: }
457:
458: private int findMaximumIndex(final PropertyDescriptor id) {
459: try {
460: final Object o = getPropertyForSpecification(new PropertySpecification(
461: id.getName()));
462: return Array.getLength(o);
463: } catch (Exception e) {
464: // ignore, we run 'til we encounter an index out of bounds Ex.
465: }
466: return 0;
467: }
468: }
|