KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > com > inversoft > beans > JavaBean


1 /*
2  * Copyright (c) 2003, Inversoft
3  *
4  * This software is distribuable under the GNU Lesser General Public License.
5  * For more information visit gnu.org.
6  */

7 package com.inversoft.beans;
8
9
10 import java.lang.reflect.Array JavaDoc;
11 import java.util.ArrayList JavaDoc;
12 import java.util.List JavaDoc;
13 import java.util.Map JavaDoc;
14 import java.util.WeakHashMap JavaDoc;
15
16 import com.inversoft.util.ObjectTools;
17 import com.inversoft.util.ReflectionException;
18 import com.inversoft.util.ReflectionTools;
19 import com.inversoft.util.typeconverter.TypeConversionException;
20
21
22 /**
23  * <p>
24  * This class can be used to facilitate getting and setting
25  * of JavaBean properties. It is an object representation
26  * of a JavaBean and allows the developer to add new
27  * properties, get property values and set property values.
28  * This class also models indexed and nested JavaBeans
29  * properties as well as nested JavaBeans themselves
30  * </p>
31  *
32  * <p>
33  * Nested JavaBeans are when a JavaBean property is another
34  * JavaBean. In normal Java code, retrieval would look
35  * something like:
36  * </p>
37  *
38  * <p>
39  * <code>bean1.getProperty().getProperty2()...getPropertyN().</code>
40  * </p>
41  *
42  * <p>
43  * The bean1 instance is the base JavaBean for this example.
44  * Within this JavaBean there is a property named property1,
45  * whose return type is another JavaBean. This is repeated
46  * until a leaf property is hit. Leaf properties return
47  * object instances that do not contain any JavaBean
48  * properties. Although all leaf properties do not meet
49  * this requirement a property that does is automatically a
50  * leaf property.
51  * </p>
52  *
53  * <p>
54  * A property might return an instance of an object that
55  * does contain JavaBean properties, but in the context of
56  * a particular application, these are not accessed using
57  * the JBeans package or through reflection. These
58  * properties are also considered leaf properties.
59  * </p>
60  *
61  * <p>
62  * Using this class these properties can be retrieved using
63  * the standard JavaBean notation of property.property2...propertyN
64  * (for the example above)
65  * </p>
66  *
67  * <p>
68  * It should also be noted that the properties already
69  * accessed or added are stored so that continued access is
70  * quicker than using the NestedBeanProperty class. This
71  * also means that this class will only store the information
72  * about the properties and nested JavaBeans requested. It
73  * does NOT build a complete list of the properties and
74  * nested JavaBeans of a particular class at any time. It
75  * is not recommended that this class be heavily used
76  * because of the space usage of storing all the properties
77  * and nested JavaBeans. If it the local and nested
78  * properties are relatively few, then this class can offer
79  * speed benefits. If not, this class is not a good solution
80  * because of its memory consumption.
81  * </p>
82  *
83  * <p>
84  * <b>NOTE:</b><br>
85  * When this class creates new JavaBean instances for nested
86  * properties, the new instances have the same value for the
87  * strict property as the parent JavaBean instance.
88  * </p>
89  *
90  * @author Brian Pontarelli
91  */

92 public class JavaBean {
93
94     /**
95      * Global switch for caching
96      */

97     static final boolean CACHING = true;
98
99     /**
100      * The cache Map for the JavaBean objects
101      */

102     static Map JavaDoc cache = new WeakHashMap JavaDoc();
103
104     protected Class JavaDoc beanClass;
105
106
107     /**
108      * Fetchs a JavaBean object, which may have been cached, depending on the
109      * implementation, for the given Class instance.
110      *
111      * @param beanClass The Class to fetch the JavaBean instance for
112      * retrieving property values and a child bean is null
113      * @return The JavaBean instance and never null
114      */

115     public static JavaBean getInstance(Class JavaDoc beanClass) {
116         // If not caching, just create the objects
117
if (!CACHING) {
118             return new JavaBean(beanClass);
119         }
120
121         synchronized (cache) {
122             // Otherwise look for the property Map or create and store
123
JavaBean javaBean = (JavaBean) cache.get(beanClass);
124             if (javaBean == null) {
125                 javaBean = new JavaBean(beanClass);
126                 cache.put(beanClass, javaBean);
127             }
128
129             return javaBean;
130         }
131     }
132
133
134     /**
135      * Does nothing, this means that the values must be setup later
136      */

137     protected JavaBean() {
138     }
139
140     /**
141      * Creates new JavaBean using the given bean class object and with strict set
142      * to false. This constructor does nothing but setup the JavaBean for access. As
143      * mentioned in the class comment, this does not build a list of properties or
144      * nested JavaBeans. This occurs when either the properties are added or used
145      * to get and set values.
146      *
147      * @param beanClass The Class object used to find the properties and nested
148      * JavaBeans
149      */

150     public JavaBean(Class JavaDoc beanClass) {
151         this.beanClass = beanClass;
152     }
153
154     /**
155      * Creates new JavaBean using the given fully qualified name of the bean
156      * class object and with strict set to false. This constructor does nothing
157      * but setup the JavaBean for access. As mentioned in the class comment,
158      * this does not build a list of properties or nested JavaBeans. This occurs
159      * when either the properties are added or used to get and set values.
160      *
161      * @param beanClass The fully qualified name of the bean class used to find
162      * properties and nested JavaBeans
163      * @throws BeanException If the class name is not a valid class in the classpath
164      */

165     public JavaBean(String JavaDoc beanClass) throws BeanException {
166         try {
167             this.beanClass = Class.forName(beanClass);
168         } catch (ClassNotFoundException JavaDoc cnfe) {
169             throw new BeanException(cnfe);
170         }
171     }
172
173
174     /**
175      * Gets the bean class that this JavaBean is for
176      */

177     public Class JavaDoc getBeanClass() {
178         return beanClass;
179     }
180
181     /**
182      * Sets the bean class that this JavaBean is for. This is only available to
183      * sub-classes
184      */

185     protected void setBeanClass(Class JavaDoc beanClass) {
186         this.beanClass = beanClass;
187     }
188
189     /**
190      * Gets the bean class that this JavaBean is for as a String
191      */

192     public String JavaDoc getClassName() {
193         return beanClass.getName();
194     }
195
196     /**
197      * <p>
198      * Returns the bean property described by the propertyName String parameter.
199      * This parameter can be either a property of this JavaBean or a property
200      * of a nested JavaBean. See the class comment for a description of nested
201      * JavaBeans.
202      * </p>
203      *
204      * <p>
205      * If the BeanProperty has not been added to either this JavaBean or a child
206      * JavaBean, it will be created and likewise, any JavaBean children that do
207      * not exist yet, will also be created and added. This is done in order to
208      * allow access to the BeanProperty. Not that if the BeanProperty does not
209      * exist in the Class of this JavaBean or a child JavaBean an exception is
210      * thrown.
211      * </p>
212      *
213      * @param propertyName The name of the property that is a local or nested property
214      * @return Either the local bean property or the nested bean property and never null
215      * @throws BeanException If there was a problem finding or creating the bean
216      * property
217      */

218     public BeanProperty getBeanProperty(String JavaDoc propertyName) throws BeanException {
219
220         JavaBeanTools.NameInfo ni = JavaBeanTools.splitNameFront(propertyName);
221
222         // Determine if the property name describes a local property or a sub-property
223
if (!ni.nested) {
224             return findProperty(ni.localPropertyName);
225         }
226
227         // Attempt to look up this beans child JavaBean
228
JavaBean child = findChildJavaBean(ni.localPropertyName, null);
229
230         // Recurse into the child we just retrieved or created
231
return child.getBeanProperty(ni.nestedPropertyName);
232     }
233
234     /**
235      * <p>
236      * Returns the indexed bean property described by the propertyName String
237      * parameter. This parameter can be either a property of this JavaBean or a
238      * property of a nested JavaBean. See the class comment for a description of
239      * nested JavaBeans.
240      * </p>
241      *
242      * <p>
243      * If the IndexedBeanProperty has not been added to either this JavaBean or a
244      * child JavaBean, it will be created and likewise, any JavaBean children that
245      * do not exist yet, will also be created and added. This is done in order to
246      * allow access to the IndexedBeanProperty. Not that if the property does not
247      * exist in the Class of this JavaBean or a child JavaBean or it is not indexed
248      * an exception is thrown.
249      * </p>
250      *
251      * @param propertyName The name of the property that is a local or nested
252      * indexed property
253      * @return Either the local indexed bean property or the nested indexed bean
254      * property and never null
255      * @throws BeanException If there was a problem finding or creating the
256      * indexed bean property
257      */

258     public IndexedBeanProperty getIndexedBeanProperty(String JavaDoc propertyName)
259     throws BeanException {
260
261         JavaBeanTools.NameInfo ni = JavaBeanTools.splitNameFront(propertyName);
262
263         // Determine if the property name describes a local property or a sub-property
264
if (!ni.nested) {
265
266             // This will never throw a class cast exception because the findProperty
267
// method is guarenteed to return an IndexedBeanProperty if the boolean
268
// parameter is true
269
BeanProperty bp = findProperty(ni.localPropertyName);
270             if (!(bp instanceof IndexedBeanProperty)) {
271                 throw new BeanException("Property is not indexed");
272             }
273
274             return (IndexedBeanProperty) bp;
275         }
276
277         // Attempt to look up this beans child JavaBean
278
JavaBean child = findChildJavaBean(ni.localPropertyName, null);
279
280         // Recurse into the child we just retrieved or created
281
return child.getIndexedBeanProperty(ni.nestedPropertyName);
282     }
283
284     /**
285      * <p>
286      * Returns a NestedBeanProperty desribed by the propertyName String. This
287      * is a new instance of the NestedBeanProperty unlike using the {@link
288      * #getBeanProperty(String) getBeanProperty} method which returns an instance
289      * of BeanProperty that is being used internally to the JavaBean class.
290      * However, this uses the internal BeanProperty objects while building,
291      * thereby reducing overhead in creation.
292      * </p>
293      *
294      * @param propertyName The name of the property that is a local or nested
295      * property that will be returned as a NestedBeanProperty
296      * @return A new NestedBeanProperty for the property described
297      * @throws BeanException If the propertyName is invalid
298      */

299     public NestedBeanProperty getNestedBeanProperty(String JavaDoc propertyName)
300     throws BeanException {
301         return new NestedBeanProperty(propertyName, beanClass);
302     }
303
304     /**
305      * <p>
306      * Returns whether or not the property described by the propertyName String
307      * parameter is an indexed bean property or not. This property can be a
308      * property of this JavaBean or a property of a nested JavaBean. See the
309      * class comment for a description of nested JavaBeans.
310      * </p>
311      *
312      * <p>
313      * If the property was not added to the JavaBean yet, then this method adds
314      * it so that it can determine whether or not it is indexed. Also, if the
315      * propertyName is not valid, then an exception is thrown.
316      * </p>
317      *
318      * @param propertyName The name of the property that is a local or nested property
319      * and is being check to see if it is indexed or not
320      * @return True if the bean property is indexed, false otherwise
321      * @throws BeanException If the propertyName is not a valid local or nested
322      * property
323      */

324     public boolean isBeanPropertyIndexed(String JavaDoc propertyName) throws BeanException {
325
326         JavaBeanTools.NameInfo ni = JavaBeanTools.splitNameFront(propertyName);
327
328         // Determine if the property name describes a local property or a sub-property
329
if (!ni.nested) {
330
331             // This will return the BeanProperty which could be indexed or not
332
BeanProperty bp = findProperty(ni.localPropertyName);
333             return (bp instanceof IndexedBeanProperty);
334         }
335
336         // Attempt to look up this beans child JavaBean
337
JavaBean child = findChildJavaBean(ni.localPropertyName, null);
338
339         // Recurse into the child we just retrieved or created
340
return child.isBeanPropertyIndexed(ni.nestedPropertyName);
341     }
342
343     /**
344      * Checks if the given bean property is a valid bean property for this JavaBean
345      * or a child JavaBean. This method also checks if the given type the type
346      * or sub-type of the type for the property. If the BeanProperty has not
347      * been added to the JavaBean or to a child JavaBean it is created and added.
348      *
349      * @param propertyName The property to check and possibly add to this the
350      * java bean
351      * @param type The type to check against the property
352      * @throws BeanException If the property does not exist in this java bean or
353      * if the property type is not the same as or assignable to the given
354      * type
355      */

356     public void checkBeanProperty(String JavaDoc propertyName, Class JavaDoc type)
357     throws BeanException {
358
359         BeanProperty prop = getBeanProperty(propertyName);
360
361         if (!prop.getPropertyType().isAssignableFrom(type)) {
362             throw new BeanException(type.getName() + " is not a valid type for" +
363                 " the bean property named " + propertyName);
364         }
365     }
366
367     /**
368      * Returns the child JavaBean described by the propertyName String parameter.
369      * This parameter can be either a property of this JavaBean or a nested
370      * property name. See the class comment for a description of nested JavaBeans.
371      *
372      * @param propertyName The name of the property that is a child bean or the
373      * name of a nested property that is a child bean of one of this
374      * beans children
375      * @return Either the local child JavaBean or the nested child JavaBean and
376      * never null
377      * @throws BeanException If there was a problem finding the sub-bean or there
378      * was a problem creating BeanProperty objects for properties of this
379      * bean or one of its children or problems creating JavaBean obejcts
380      * for one of this beans child beans
381      */

382     public JavaBean getChildJavaBean(String JavaDoc propertyName) throws BeanException {
383
384         JavaBeanTools.NameInfo ni = JavaBeanTools.splitNameFront(propertyName);
385
386         // Attempt to look up this beans child JavaBean
387
JavaBean child = findChildJavaBean(ni.localPropertyName, null);
388
389         // If this is a nested child, recurse into the child we just retrieved or created.
390
// Otherwise it was local, so just return the child
391
return ((ni.nested) ?
392             child.getChildJavaBean(ni.nestedPropertyName) : child);
393     }
394
395     /**
396      * Returns a new instance of the Java bean class that this JavaBean instance was
397      * cosntructed for. This is done using the default constructor for the Java bean.
398      * If there was any problems a BeanException is thrown
399      *
400      * @return A new instance of the Java bean for this JavaBean instance
401      * @throws BeanException If there was a problem creating the instance
402      */

403     public Object JavaDoc instantiate() throws BeanException {
404         try {
405             return ReflectionTools.instantiate(beanClass);
406         } catch (ReflectionException re) {
407             throw new BeanException(re);
408         }
409     }
410
411
412     //--------------------------------------------------------------------------
413
// Property Modification and Retrieval Methods
414
//--------------------------------------------------------------------------
415

416     /**
417      * <p>
418      * Retrieves the local or nested bean property using the property name and the
419      * bean object (which will be used to retrieve and/or store child objects).
420      * </p>
421      *
422      * <p>
423      * This method assumes a non-strict handling. If you want to make this strict
424      * use {@link #getPropertyValue(String, Object, boolean) this} method.
425      * See the class comment for more information.
426      * </p>
427      *
428      * @param propertyName The name of the property to retrieve
429      * @param bean The bean to retrieve the property value from
430      * @return The value of the property
431      * @throws BeanException If there was a problem retrieving the value
432      */

433     public Object JavaDoc getPropertyValue(String JavaDoc propertyName, Object JavaDoc bean)
434     throws BeanException {
435         return getPropertyValue(propertyName, bean, (List JavaDoc) null, false);
436     }
437
438     /**
439      * <p>
440      * Retrieves the local or nested bean property using the property name and the
441      * bean object (which will be used to retrieve and/or store child objects).
442      * </p>
443      *
444      * <p>
445      * This method uses the strict parameter to determine the strictness handling.
446      * See the class comment for more information.
447      * </p>
448      *
449      * @param propertyName The name of the property to retrieve
450      * @param bean The bean to retrieve the property value from
451      * @param strict Determines the strictness of this operation
452      * @return The value of the property
453      * @throws BeanException If there was a problem retrieving the value
454      */

455     public Object JavaDoc getPropertyValue(String JavaDoc propertyName, Object JavaDoc bean, boolean strict)
456     throws BeanException {
457         return getPropertyValue(propertyName, bean, (List JavaDoc) null, strict);
458     }
459
460     /**
461      * <p>
462      * Retrieves the local or nested bean property using the property name, the
463      * bean object (which will be used to retrieve and/or store child objects)
464      * and the list of indices which will be used for any indexed properties.
465      * The array is used in sequential order as the indices whenever a indices
466      * property is encountered.
467      * </p>
468      *
469      * <p>
470      * This method assumes a non-strict handling. If you want to make this strict
471      * use {@link #getPropertyValue(String, Object, boolean) this} method.
472      * See the class comment for more information.
473      * </p>
474      *
475      * @param propertyName The name of the property to retrieve
476      * @param bean The bean to retrieve the property value from
477      * @param indices (Optional) The list of indices used for indexed properties.
478      * This is two dimensional so that it is modeled correctly. Each
479      * property that may need an indices is the first array. Each indices
480      * that that property needs is the second array. The fist array must
481      * have the same length as the number of nested properties. If a
482      * property in the nesting does not require an index, that entry in
483      * the list should be null.
484      * @return The value of the property
485      * @throws BeanException If there was a problem retrieving the value
486      */

487     public Object JavaDoc getPropertyValue(String JavaDoc propertyName, Object JavaDoc bean, int[][] indices)
488     throws BeanException {
489         List JavaDoc list = convertArray(indices);
490         return getPropertyValue(propertyName, bean, list, false);
491     }
492
493     /**
494      * <p>
495      * Retrieves the local or nested bean property using the property name, the
496      * bean object (which will be used to retrieve and/or store child objects)
497      * and the list of indices which will be used for any indexed properties.
498      * The array is used in sequential order as the indices whenever a indices
499      * property is encountered.
500      * </p>
501      *
502      * <p>
503      * This method uses the strict parameter to determine the strictness handling.
504      * See the class comment for more information.
505      * </p>
506      *
507      * @param propertyName The name of the property to retrieve
508      * @param bean The bean to retrieve the property value from
509      * @param indices (Optional) The list of indices used for indexed properties.
510      * This is two dimensional so that it is modeled correctly. Each
511      * property that may need an indices is the first array. Each indices
512      * that that property needs is the second array. The fist array must
513      * have the same length as the number of nested properties. If a
514      * property in the nesting does not require an index, that entry in
515      * the list should be null.
516      * @param strict Determines the strictness of this operation
517      * @return The value of the property
518      * @throws BeanException If there was a problem retrieving the value
519      */

520     public Object JavaDoc getPropertyValue(String JavaDoc propertyName, Object JavaDoc bean, int[][] indices,
521             boolean strict)
522     throws BeanException {
523         List JavaDoc list = convertArray(indices);
524         return getPropertyValue(propertyName, bean, list, strict);
525     }
526
527     /**
528      * <p>
529      * Retrieves the local or nested bean property using the property name, the
530      * bean object (which will be used to retrieve and/or store child objects)
531      * and the list of indices which will be used for any indexed properties. The
532      * array is used in sequential order as the indices whenever a indices property is
533      * encountered.
534      * </p>
535      *
536      * <p>
537      * This method assumes a non-strict handling. If you want to make this strict
538      * use {@link #getPropertyValue(String, Object, boolean) this} method.
539      * See the class comment for more information.
540      * </p>
541      *
542      * @param propertyName The name of the property to retrieve
543      * @param bean The bean to retrieve the property value from
544      * @param indices (Optional) The list of indices used for indexed properties.
545      * This is must bea two dimensional so that it is modeled correctly. Each
546      * property that may need an indices is the first List. Each indices
547      * that that property needs is the second List. This List must have
548      * the same length as the number of nested properties. If a property
549      * in the nesting does not require an index, that entry in the list
550      * should be null.
551      * @return The value of the property
552      * @throws BeanException If there was a problem retrieving the value
553      */

554     public Object JavaDoc getPropertyValue(String JavaDoc propertyName, Object JavaDoc bean, List JavaDoc indices)
555     throws BeanException {
556         return getPropertyValue(propertyName, bean, indices, false);
557     }
558
559     /**
560      * <p>
561      * Retrieves the local or nested bean property using the property name, the
562      * bean object (which will be used to retrieve and/or store child objects)
563      * and the list of indices which will be used for any indexed properties. The
564      * array is used in sequential order as the indices whenever a indices property is
565      * encountered.
566      * </p>
567      *
568      * <p>
569      * This method uses the strict parameter to determine the strictness handling.
570      * See the class comment for more information.
571      * </p>
572      *
573      * @param propertyName The name of the property to retrieve
574      * @param bean The bean to retrieve the property value from
575      * @param indices (Optional) The list of indices used for indexed properties.
576      * This is must be two dimensional so that it is modeled correctly. Each
577      * property that may need an indices is the first List. Each indices
578      * that that property needs is the second List. (ie List inside List)
579      * @param strict Determines the strictness of this operation
580      * @return The value of the property
581      * @throws BeanException If there was a problem retrieving the value
582      */

583     public Object JavaDoc getPropertyValue(String JavaDoc propertyName, Object JavaDoc bean, List JavaDoc indices,
584             boolean strict)
585     throws BeanException {
586         JavaBeanTools.NameInfo ni = JavaBeanTools.splitNameFront(propertyName);
587         JavaBeanTools.PropertyInfo pi =
588             JavaBeanTools.retrievePropertyInfo(ni.localPropertyName);
589
590         List JavaDoc subList = indices;
591         boolean listEmpty = (indices == null || indices.size() == 0);
592         if (pi.indices == null && !listEmpty) {
593             pi.indices = (List JavaDoc) indices.get(0);
594             subList = indices.subList(1, indices.size());
595         }
596
597         Object JavaDoc value = getLocalPropertyValue(pi, bean);
598         if (ni.nested) {
599             if (value != null) {
600
601                 JavaBean child = JavaBean.getInstance(value.getClass());
602                 value = child.getPropertyValue(ni.nestedPropertyName, value, subList,
603                     strict);
604             } else if (strict) {
605                 throw new BeanException("Null property value encountered for " +
606                     "strict JavaBean. Property name was: " + propertyName);
607             }
608         }
609
610         return value;
611     }
612
613     /**
614      * <p>
615      * Sets the local or nested property to the given value on the given bean instance.
616      * This method does no conversion and is the same as calling the other
617      * setPropertyValue method with the convert parameter set to false.
618      * </p>
619      *
620      * <p>
621      * This method assumes a non-strict handling. If you want to make this strict
622      * use {@link #setPropertyValue(String, Object, Object, boolean, boolean)
623      * this} method. See the class comment for more information.
624      * </p>
625      *
626      * @param propertyName The name of the property to set (local or nested)
627      * @param bean The bean to set the property on
628      * @param value The value to set the property to
629      * @throws BeanException If there was a problem setting the property or
630      * creating child objects
631      */

632     public void setPropertyValue(String JavaDoc propertyName, Object JavaDoc bean, Object JavaDoc value)
633     throws BeanException {
634         try {
635             setPropertyValue(propertyName, bean, (List JavaDoc) null, value, false, false);
636         } catch (TypeConversionException tce) {
637             assert false : "FATAL: should never throw TypeConversionException" +
638                 " because this method does no conversion";
639         }
640     }
641
642     /**
643      * <p>
644      * Sets the local or nested property using the property name, the bean object
645      * (which will be used to retrieve and/or store the child objects if this
646      * is a nested property), the value with which to set the property, the convert
647      * flag to determine whether or not to convert the value to the appropriate
648      * type before setting.
649      * </p>
650      *
651      * <p>
652      * This method assumes a non-strict handling. If you want to make this strict
653      * use {@link #setPropertyValue(String, Object, Object, boolean, boolean)
654      * this} method. See the class comment for more information.
655      * </p>
656      *
657      * @param propertyName The name of the property to set (local or nested)
658      * @param bean The bean to set the property on
659      * @param value The value to set the property to
660      * @param convert Determines whether or not to convert the value to the type
661      * of the property
662      * @throws BeanException If there was a problem setting the property or
663      * creating child objects
664      * @throws TypeConversionException If there was an error attempting to auto
665      * convert the value being set
666      */

667     public void setPropertyValue(String JavaDoc propertyName, Object JavaDoc bean, Object JavaDoc value,
668             boolean convert)
669     throws BeanException, TypeConversionException {
670         setPropertyValue(propertyName, bean, (List JavaDoc) null, value, convert, false);
671     }
672
673     /**
674      * <p>
675      * Sets the local or nested property using the property name, the bean object
676      * (which will be used to retrieve and/or store the child objects if this
677      * is a nested property), the value with which to set the property, the convert
678      * flag to determine whether or not to convert the value to the appropriate
679      * type before setting.
680      * </p>
681      *
682      * <p>
683      * This method uses the strict parameter to determine the strictness handling.
684      * See the class comment for more information.
685      * </p>
686      *
687      * @param propertyName The name of the property to set (local or nested)
688      * @param bean The bean to set the property on
689      * @param value The value to set the property to
690      * @param convert Determines whether or not to convert the value to the type
691      * of the property
692      * @param strict Determines the strictness of this operation
693      * @throws BeanException If there was a problem setting the property or
694      * creating child objects
695      * @throws TypeConversionException If there was an error attempting to auto
696      * convert the value being set
697      */

698     public void setPropertyValue(String JavaDoc propertyName, Object JavaDoc bean, Object JavaDoc value,
699             boolean convert, boolean strict)
700     throws BeanException, TypeConversionException {
701         setPropertyValue(propertyName, bean, (List JavaDoc) null, value, convert, strict);
702     }
703
704     /**
705      * <p>
706      * Sets the local or nested property using the property name, the bean object
707      * (which will be used to retrieve and/or store the child objects if this
708      * is a nested property), the array of indices for any indexed properties, the
709      * value with which to set the property, the convert flag to determine whether
710      * or not to convert the value to the appropriate type before setting.
711      * </p>
712      *
713      * <p>
714      * This method assumes a non-strict handling. If you want to make this strict
715      * use {@link #setPropertyValue(String, Object, Object, boolean, boolean)
716      * this} method. See the class comment for more information.
717      * </p>
718      *
719      * @param propertyName The name of the property to set (local or nested)
720      * @param bean The bean to set the property on
721      * @param indices (Optional) The array of indices that will be used for any
722      * indexed properties
723      * @param value The value to set the property to
724      * @param convert Determines whether or not to convert the value to the type
725      * of the property
726      * @throws BeanException If there was a problem setting the property or
727      * creating child objects
728      * @throws TypeConversionException If there was an error attempting to auto
729      * convert the value being set
730      */

731     public void setPropertyValue(String JavaDoc propertyName, Object JavaDoc bean, int[][] indices,
732             Object JavaDoc value, boolean convert)
733     throws BeanException, TypeConversionException {
734         List JavaDoc list = convertArray(indices);
735         setPropertyValue(propertyName, bean, list, value, convert, false);
736     }
737
738     /**
739      * <p>
740      * Sets the local or nested property using the property name, the bean object
741      * (which will be used to retrieve and/or store the child objects if this
742      * is a nested property), the array of indices for any indexed properties, the
743      * value with which to set the property, the convert flag to determine whether
744      * or not to convert the value to the appropriate type before setting.
745      * </p>
746      *
747      * <p>
748      * This method uses the strict parameter to determine the strictness handling.
749      * See the class comment for more information.
750      * </p>
751      *
752      * @param propertyName The name of the property to set (local or nested)
753      * @param bean The bean to set the property on
754      * @param indices (Optional) The array of indices that will be used for any
755      * indexed properties
756      * @param value The value to set the property to
757      * @param convert Determines whether or not to convert the value to the type
758      * of the property
759      * @param strict Determines the strictness of this operation
760      * @throws BeanException If there was a problem setting the property or
761      * creating child objects
762      * @throws TypeConversionException If there was an error attempting to auto
763      * convert the value being set
764      */

765     public void setPropertyValue(String JavaDoc propertyName, Object JavaDoc bean, int[][] indices,
766             Object JavaDoc value, boolean convert, boolean strict)
767     throws BeanException, TypeConversionException {
768         List JavaDoc list = convertArray(indices);
769         setPropertyValue(propertyName, bean, list, value, convert, strict);
770     }
771
772     /**
773      * <p>
774      * Sets the local or nested property using the property name, the bean
775      * object (which will be used to retrieve and/or store the child objects if
776      * this is a nested property), the list of indices for any indexed properties,
777      * the value with which to set the property, the convert flag to determine
778      * whether or not to convert the value to the appropriate type before setting.
779      * </p>
780      *
781      * <p>
782      * This method assumes a non-strict handling. If you want to make this strict
783      * use {@link #setPropertyValue(String, Object, Object, boolean, boolean)
784      * this} method. See the class comment for more information.
785      * </p>
786      *
787      * @param propertyName The name of the property to set (local or nested)
788      * @param bean The bean to set the property on
789      * @param indices (Optional) The array of indices that will be used for any
790      * indexed properties
791      * @param value The value to set the property to
792      * @param convert Determines whether or not to convert the value to the type
793      * of the property
794      * @throws BeanException If there was a problem setting the property or
795      * creating child objects
796      * @throws TypeConversionException If there was an error attempting to auto
797      * convert the value being set
798      */

799     public void setPropertyValue(String JavaDoc propertyName, Object JavaDoc bean, List JavaDoc indices,
800             Object JavaDoc value, boolean convert)
801     throws BeanException, TypeConversionException {
802         recurseToSet(propertyName, bean, indices, value, convert, false);
803     }
804
805     /**
806      * <p>
807      * Sets the local or nested property using the property name, the bean
808      * object (which will be used to retrieve and/or store the child objects if
809      * this is a nested property), the list of indices for any indexed properties,
810      * the value with which to set the property, the convert flag to determine
811      * whether or not to convert the value to the appropriate type before setting.
812      * </p>
813      *
814      * <p>
815      * This method uses the strict parameter to determine the strictness handling.
816      * See the class comment for more information.
817      * </p>
818      *
819      * @param propertyName The name of the property to set (local or nested)
820      * @param bean The bean to set the property on
821      * @param indices (Optional) The array of indices that will be used for any
822      * indexed properties
823      * @param value The value to set the property to
824      * @param convert Determines whether or not to convert the value to the type
825      * of the property
826      * @param strict Determines the strictness of this operation
827      * @throws BeanException If there was a problem setting the property or
828      * creating child objects
829      * @throws TypeConversionException If there was an error attempting to auto
830      * convert the value being set
831      */

832     public void setPropertyValue(String JavaDoc propertyName, Object JavaDoc bean, List JavaDoc indices,
833             Object JavaDoc value, boolean convert, boolean strict)
834     throws BeanException, TypeConversionException {
835         recurseToSet(propertyName, bean, indices, value, convert, strict);
836     }
837
838     /**
839      * This does the recursive work of setting the property. This calls the
840      * child JavaBean's until the end is reached and then calls set.
841      */

842     private void recurseToSet(String JavaDoc propertyName, Object JavaDoc bean, List JavaDoc indices,
843             Object JavaDoc value, boolean convert, boolean strict)
844     throws BeanException, TypeConversionException {
845         JavaBeanTools.NameInfo ni = JavaBeanTools.splitNameFront(propertyName);
846         JavaBeanTools.PropertyInfo pi =
847             JavaBeanTools.retrievePropertyInfo(ni.localPropertyName);
848
849         boolean hasIndices = (indices != null && indices.size() > 0);
850         List JavaDoc subList = null;
851
852         if (pi.indices == null && hasIndices) {
853             pi.indices = (List JavaDoc) indices.get(0);
854             subList = indices.subList(1, indices.size());
855         }
856
857         if (ni.nested) {
858             Object JavaDoc beanInstance = getLocalPropertyValueCreate(pi, bean, strict);
859
860             JavaBean child = JavaBean.getInstance(beanInstance.getClass());
861             child.recurseToSet(ni.nestedPropertyName, beanInstance, subList,
862                 value, convert, strict);
863         } else {
864             setLocalPropertyValue(pi, bean, value, convert, strict);
865         }
866     }
867
868
869     //-------------------------------------------------------------------------
870
// Helper Methods (not to be overridden)
871
//-------------------------------------------------------------------------
872

873     /**
874      * Converts the given int array to a List of Integer objects
875      */

876     final List JavaDoc convertArray(int[][] indices) {
877         List JavaDoc list = null;
878         if (indices != null && indices.length > 0) {
879             list = new ArrayList JavaDoc();
880             for (int i = 0; i < indices.length; i++) {
881                 List JavaDoc subList = new ArrayList JavaDoc();
882                 list.add(subList);
883
884                 for (int j = 0; j < indices[i].length; j++) {
885                     subList.add(new Integer JavaDoc(indices[i][j]));
886                 }
887             }
888         }
889
890         return list;
891     }
892
893     /*
894      * Returns the bean property with the given name and if it does not exist,
895      * it attempts to create it, store it and then return the newly created one.
896      * If the indexed parameter is true, then the property returned must be indexed
897      * or an exception is thrown. If it is false, then the property returned
898      * can be either indexed or not.
899      */

900     private BeanProperty findProperty(String JavaDoc propertyName) throws BeanException {
901         BeanProperty property;
902
903         // Try indexed properties first then normal
904
try {
905             property = IndexedBeanProperty.getInstance(propertyName, beanClass);
906         } catch (BeanException be) {
907             property = BeanProperty.getInstance(propertyName, beanClass);
908         }
909
910         return property;
911     }
912
913     /*
914      * Finds the child java bean with the given property name or creates a new
915      * java bean child with the given type, stores it with the given name and
916      * returns it
917      */

918     private JavaBean findChildJavaBean(String JavaDoc propertyName, Class JavaDoc type)
919     throws BeanException {
920
921         if (type == null) {
922             BeanProperty prop = findProperty(propertyName);
923             type = prop.getPropertyType();
924         }
925
926         // The new JavaBean has the same strictness as this JavaBean
927
return JavaBean.getInstance(type);
928     }
929
930     private boolean hasMoreIndices(List JavaDoc indices, int count) {
931         return (indices != null && count < indices.size());
932     }
933
934     /**
935      * Retrieves the local bean property value, handling arrays, collections and
936      * normal proeprties. This does no creation or setting. If a null is ever
937      * encountered, it is returned.
938      */

939     private Object JavaDoc getLocalPropertyValue(JavaBeanTools.PropertyInfo pi, Object JavaDoc bean)
940     throws BeanException {
941         BeanProperty bp = findProperty(pi.propertyName);
942
943         int count = 0;
944         boolean indexed = (bp instanceof IndexedBeanProperty);
945         Object JavaDoc value;
946         IndexedBeanProperty ip;
947         Object JavaDoc rootIndex;
948         if (indexed) {
949             if (!hasMoreIndices(pi.indices, count)) {
950                 throw new BeanException("Property named: " + pi.propertyName +
951                     " is indexed, but no indices supplied");
952             }
953
954             ip = (IndexedBeanProperty) bp;
955             rootIndex = pi.indices.get(0);
956             value = ip.getPropertyValue(bean, rootIndex);
957             count++;
958         } else {
959             value = bp.getPropertyValue(bean);
960         }
961
962         if (value != null && hasMoreIndices(pi.indices, count)) {
963             int length = pi.indices.size();
964             Object JavaDoc ds = value;
965             Object JavaDoc subDS = null;
966             while (ObjectTools.isDataStructure(ds) && (count < length)) {
967                 try {
968                     subDS = ObjectTools.getValueFromCollection(ds,
969                         pi.indices.get(count));
970                 } catch (IllegalArgumentException JavaDoc iae) {
971                     throw new BeanException(iae.getMessage() + " for property " +
972                         "named: " + pi.propertyName);
973                 }
974
975                 count++;
976                 if (ObjectTools.isDataStructure(subDS) && (count < length)) {
977                      ds = subDS;
978                 }
979             }
980
981             value = subDS;
982         }
983
984         return value;
985     }
986
987     /**
988      * Retrieves the local bean property value, handling arrays, collections and
989      * normal proeprties. This could have the after effect of creating objects
990      * and setting them into the property.
991      */

992     private Object JavaDoc getLocalPropertyValueCreate(JavaBeanTools.PropertyInfo pi,
993             Object JavaDoc bean, boolean strict)
994     throws BeanException {
995         BeanProperty bp = findProperty(pi.propertyName);
996
997         int count = 0;
998         boolean indexed = (bp instanceof IndexedBeanProperty);
999         Object JavaDoc value;
1000        IndexedBeanProperty ip = null;
1001        Object JavaDoc rootIndex = null;
1002
1003        // If the property is indexed, grab the index and cast. Also verify that
1004
// there is an index to use
1005
if (indexed) {
1006            if (!hasMoreIndices(pi.indices, count)) {
1007                throw new BeanException("Property named: " + pi.propertyName +
1008                    " is indexed, but no indices supplied");
1009            }
1010
1011            ip = (IndexedBeanProperty) bp;
1012            rootIndex = pi.indices.get(0);
1013            value = ip.getPropertyValue(bean, rootIndex);
1014            count++;
1015        } else {
1016            value = bp.getPropertyValue(bean);
1017        }
1018
1019        // If we are dealing with a collection or array of some kind, traverse
1020
// and possibly create the collection/array and possibly store this newly
1021
// created array in the property. Otherwise just return the property value
1022
// like normal
1023
if (hasMoreIndices(pi.indices, count)) {
1024            int length = pi.indices.size();
1025            ArrayAndIndex ai = traverseDataStructure(value, bp, ip, rootIndex, pi,
1026                bean, count, indexed, strict);
1027
1028            try {
1029                value = ObjectTools.getValueFromCollection(ai.array,
1030                    pi.indices.get(length - 1));
1031            } catch (IllegalArgumentException JavaDoc iae) {
1032                throw new BeanException(iae.getMessage() + " for property " +
1033                    "named: " + pi.propertyName);
1034            } catch (IndexOutOfBoundsException JavaDoc ioobe) {
1035                // This could be because the DS was null
1036
value = null;
1037            }
1038
1039            Class JavaDoc type = ai.array.getClass();
1040            if (value == null && type.isArray()) {
1041                if (strict) {
1042                    throw new BeanException("Null value encountered and strictness" +
1043                        " set for property named: " + pi.propertyName);
1044                }
1045
1046                try {
1047                    value = ReflectionTools.instantiate(type.getComponentType());
1048                    ObjectTools.setValueIntoCollection(ai.array,
1049                        pi.indices.get(length - 1), value);
1050                } catch (ReflectionException re) {
1051                    throw new BeanException(re);
1052                } catch (IllegalArgumentException JavaDoc iae) {
1053                    throw new BeanException(iae.getMessage() + " for property " +
1054                        "named: " + pi.propertyName);
1055                }
1056            } else if (value == null) {
1057                throw new BeanException("Null value encountered for property " +
1058                    "named: " + pi.propertyName + ". Unable to create because" +
1059                    " a data structure was encountered of type: " + type.getName() +
1060                    ". Only arrays can be auto-generated.");
1061            }
1062        } else if (value == null) {
1063            if (strict) {
1064                throw new BeanException("Null value encountered and strictness" +
1065                    " set for property named: " + pi.propertyName);
1066            }
1067
1068            try {
1069                Class JavaDoc type = bp.getPropertyType();
1070                value = ReflectionTools.instantiate(type);
1071            } catch (ReflectionException re) {
1072                throw new BeanException(re);
1073            }
1074
1075            if (indexed) {
1076                ip.setPropertyValue(bean, rootIndex, value, false);
1077            } else {
1078                bp.setPropertyValue(bean, value, false);
1079            }
1080        }
1081
1082        return value;
1083    }
1084
1085    /**
1086     * This method sets the value into the local property.
1087     */

1088    private void setLocalPropertyValue(JavaBeanTools.PropertyInfo pi, Object JavaDoc bean,
1089            Object JavaDoc value, boolean convert, boolean strict)
1090    throws BeanException {
1091        BeanProperty bp = findProperty(pi.propertyName);
1092        int count = 0;
1093        boolean indexed = (bp instanceof IndexedBeanProperty);
1094        IndexedBeanProperty ip = null;
1095        Object JavaDoc rootIndex = null;
1096
1097        // If the property is indexed, grab the index and cast. Also verify that
1098
// there is an index to use
1099
if (indexed) {
1100            if (!hasMoreIndices(pi.indices, count)) {
1101                throw new BeanException();
1102            }
1103            count++;
1104            rootIndex = pi.indices.get(0);
1105            ip = (IndexedBeanProperty) bp;
1106        }
1107
1108        // If we are dealing with a collection or array of some kind, traverse
1109
// and possibly create the collection/array and then store the value
1110
// into that. Otherwise just set as normal
1111
if (hasMoreIndices(pi.indices, count)) {
1112            Object JavaDoc propertyValue;
1113            if (indexed) {
1114                propertyValue = ip.getPropertyValue(bean, rootIndex);
1115            } else {
1116                propertyValue = bp.getPropertyValue(bean);
1117            }
1118
1119            ArrayAndIndex ai = traverseDataStructure(propertyValue, bp, ip,
1120                rootIndex, pi, bean, count, indexed, strict);
1121
1122            // Only convert if we can figure out the component type
1123
Class JavaDoc type = ai.array.getClass();
1124            if (convert && type != null && type.isArray() &&
1125                    type.getComponentType() != null) {
1126                value = bp.convertParameter(value, bean, type.getComponentType());
1127            }
1128
1129            try {
1130                ObjectTools.setValueIntoCollection(ai.array,
1131                    pi.indices.get(ai.lastIndex), value);
1132            } catch (IllegalArgumentException JavaDoc iae) {
1133                throw new BeanException(iae.getMessage() + " for property " +
1134                    "named: " + pi.propertyName);
1135            }
1136        } else {
1137            if (indexed) {
1138                ip.setPropertyValue(bean, rootIndex, value, convert);
1139            } else {
1140                bp.setPropertyValue(bean, value, convert);
1141            }
1142        }
1143    }
1144
1145    /**
1146     * <p>
1147     * This method traverse and optionally builds out the data structure given.
1148     * This can only build out if the data structure is at some point an array.
1149     * That point is the point directly before the construction point. For example:
1150     * </p>
1151     *
1152     * <p>
1153     * getList().get(0) instanceof Object[][] &&
1154     * ((Object[][]) getList().get(0))[0] == null
1155     * </p>
1156     *
1157     * <p>
1158     * In this case the object that is null is within an array and therefore we
1159     * can create the array and store it.
1160     * </p>
1161     *
1162     * <p>
1163     * One last thing to notice is that this method returns the Collection/array
1164     * directly before the last position (determined by the length of the indices
1165     * List). For example, if ds is a four dimensional array, this returns a
1166     * single dimensional array (ie Object[][][][] returns Object[] which could
1167     * be the value of array[0][1][2] if the indices are 0,1,2,3. The 3 index is
1168     * ignored)
1169     * </p>
1170     */

1171    private ArrayAndIndex traverseDataStructure(Object JavaDoc ds, BeanProperty bp,
1172            IndexedBeanProperty ip, Object JavaDoc rootIndex, JavaBeanTools.PropertyInfo pi,
1173            Object JavaDoc bean, int startIndex, boolean indexed, boolean strict)
1174    throws BeanException {
1175        int stopIndex = pi.indices.size() - 1;
1176        Class JavaDoc type = null;
1177        boolean create = (ds == null);
1178        boolean isNull = (ds == null);
1179        if (isNull) {
1180            type = bp.getPropertyType();
1181            if (!type.isArray()) {
1182                throw new BeanException("Unable to determine type to create for" +
1183                    " property named: " + pi.propertyName + ". Property type is " +
1184                    type.getName() + " and only properties whose type are arrays" +
1185                    " can be auto-generated.");
1186            }
1187        } else {
1188            Object JavaDoc subDS = ds;
1189            boolean inDS = ObjectTools.isDataStructure(ds);
1190            while (inDS && startIndex < stopIndex) {
1191                try {
1192                    subDS = ObjectTools.getValueFromCollection(ds,
1193                        pi.indices.get(startIndex));
1194                } catch (IllegalArgumentException JavaDoc iae) {
1195                    throw new BeanException(iae.getMessage() + " for property" +
1196                        " named: " + pi.propertyName);
1197                }
1198
1199                inDS = ObjectTools.isDataStructure(subDS);
1200                if (inDS) {
1201                    ds = subDS;
1202                    startIndex++;
1203                }
1204            }
1205
1206            if (subDS == null && ds.getClass().isArray()) {
1207                type = ds.getClass();
1208                create = true;
1209            }
1210        }
1211
1212        if (create) {
1213            if (strict) {
1214                throw new BeanException("Null value encountered and strictness" +
1215                    " set for property named: " + pi.propertyName);
1216            }
1217
1218            ObjectTools.ArrayInfo ai;
1219            try {
1220                ai = ObjectTools.createArray(type, pi.indices, startIndex);
1221            } catch (IllegalArgumentException JavaDoc iae) {
1222                throw new BeanException("Non-integer indices for property named: " +
1223                    pi.propertyName);
1224            }
1225
1226            if (isNull) {
1227                if (indexed) {
1228                    ip.setPropertyValue(bean, rootIndex, ai.array, false);
1229                } else {
1230                    bp.setPropertyValue(bean, ai.array, false);
1231                }
1232            } else {
1233                try {
1234                    ObjectTools.setValueIntoCollection(ds, pi.indices.get(startIndex),
1235                        ai.array);
1236                } catch (IllegalArgumentException JavaDoc iae) {
1237                    throw new BeanException(iae.getMessage() + " for property " +
1238                        "named: " + pi.propertyName);
1239                }
1240            }
1241
1242            ds = ai.array;
1243        }
1244
1245        while (ObjectTools.isArray(ds) && startIndex < stopIndex) {
1246            int index = ((Integer JavaDoc) pi.indices.get(startIndex)).intValue();
1247            Object JavaDoc subDS = Array.get(ds, index);
1248            if (ObjectTools.isArray(subDS)) {
1249                ds = subDS;
1250                startIndex++;
1251            }
1252        }
1253
1254        ArrayAndIndex ai = new ArrayAndIndex();
1255        ai.array = ds;
1256        ai.lastIndex = startIndex;
1257
1258        return ai;
1259    }
1260
1261    private class ArrayAndIndex {
1262        Object JavaDoc array;
1263        int lastIndex;
1264    }
1265}
Popular Tags