KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > org > apache > commons > beanutils > BeanUtilsBean


1 /*
2  * Copyright 2001-2004 The Apache Software Foundation.
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  * http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */

16
17
18 package org.apache.commons.beanutils;
19
20
21 import java.beans.IndexedPropertyDescriptor JavaDoc;
22 import java.beans.PropertyDescriptor JavaDoc;
23 import java.lang.reflect.Array JavaDoc;
24 import java.lang.reflect.InvocationTargetException JavaDoc;
25 import java.lang.reflect.Method JavaDoc;
26 import java.util.ArrayList JavaDoc;
27 import java.util.Collection JavaDoc;
28 import java.util.HashMap JavaDoc;
29 import java.util.Iterator JavaDoc;
30 import java.util.Map JavaDoc;
31 import java.util.WeakHashMap JavaDoc;
32
33 import org.apache.commons.collections.FastHashMap;
34 import org.apache.commons.logging.Log;
35 import org.apache.commons.logging.LogFactory;
36
37
38 /**
39  * <p>JavaBean property population methods.</p>
40  *
41  * <p>This class provides implementations for the utility methods in
42  * {@link BeanUtils}.
43  * Different instances can be used to isolate caches between classloaders
44  * and to vary the value converters registered.</p>
45  *
46  * @author Craig R. McClanahan
47  * @author Ralph Schaer
48  * @author Chris Audley
49  * @author Rey François
50  * @author Gregor Raıman
51  * @version $Revision: 1.16 $ $Date: 2004/02/28 13:18:33 $
52  * @see BeanUtils
53  * @since 1.7
54  */

55
56 public class BeanUtilsBean {
57
58
59     // ------------------------------------------------------ Private Class Variables
60

61     /**
62      * Contains <code>BeanUtilsBean</code> instances indexed by context classloader.
63      */

64     private static final ContextClassLoaderLocal
65             beansByClassLoader = new ContextClassLoaderLocal() {
66                         // Creates the default instance used when the context classloader is unavailable
67
protected Object JavaDoc initialValue() {
68                             return new BeanUtilsBean();
69                         }
70                     };
71     
72     /**
73      * Gets the instance which provides the functionality for {@link BeanUtils}.
74      * This is a pseudo-singleton - an single instance is provided per (thread) context classloader.
75      * This mechanism provides isolation for web apps deployed in the same container.
76      */

77     public synchronized static BeanUtilsBean getInstance() {
78         return (BeanUtilsBean) beansByClassLoader.get();
79     }
80
81     /**
82      * Sets the instance which provides the functionality for {@link BeanUtils}.
83      * This is a pseudo-singleton - an single instance is provided per (thread) context classloader.
84      * This mechanism provides isolation for web apps deployed in the same container.
85      */

86     public synchronized static void setInstance(BeanUtilsBean newInstance) {
87         beansByClassLoader.set(newInstance);
88     }
89
90     // --------------------------------------------------------- Attributes
91

92     /**
93      * Logging for this instance
94      */

95     private Log log = LogFactory.getLog(BeanUtils.class);
96     
97     /** Used to perform conversions between object types when setting properties */
98     private ConvertUtilsBean convertUtilsBean;
99     
100     /** Used to access properties*/
101     private PropertyUtilsBean propertyUtilsBean;
102
103     // --------------------------------------------------------- Constuctors
104

105     /**
106      * <p>Constructs an instance using new property
107      * and conversion instances.</p>
108      */

109     public BeanUtilsBean() {
110         this(new ConvertUtilsBean(), new PropertyUtilsBean());
111     }
112
113     /**
114      * <p>Constructs an instance using given property and conversion instances.</p>
115      *
116      * @param convertUtilsBean use this <code>ConvertUtilsBean</code>
117      * to perform conversions from one object to another
118      * @param propertyUtilsBean use this <code>PropertyUtilsBean</code>
119      * to access properties
120      */

121     public BeanUtilsBean(
122                             ConvertUtilsBean convertUtilsBean,
123                             PropertyUtilsBean propertyUtilsBean) {
124                             
125         this.convertUtilsBean = convertUtilsBean;
126         this.propertyUtilsBean = propertyUtilsBean;
127     }
128
129     // --------------------------------------------------------- Public Methods
130

131     /**
132      * <p>Clone a bean based on the available property getters and setters,
133      * even if the bean class itself does not implement Cloneable.</p>
134      *
135      * <p>
136      * <strong>Note:</strong> this method creates a <strong>shallow</strong> clone.
137      * In other words, any objects referred to by the bean are shared with the clone
138      * rather than being cloned in turn.
139      * </p>
140      *
141      * @param bean Bean to be cloned
142      *
143      * @exception IllegalAccessException if the caller does not have
144      * access to the property accessor method
145      * @exception InstantiationException if a new instance of the bean's
146      * class cannot be instantiated
147      * @exception InvocationTargetException if the property accessor method
148      * throws an exception
149      * @exception NoSuchMethodException if an accessor method for this
150      * property cannot be found
151      */

152     public Object JavaDoc cloneBean(Object JavaDoc bean)
153             throws IllegalAccessException JavaDoc, InstantiationException JavaDoc,
154             InvocationTargetException JavaDoc, NoSuchMethodException JavaDoc {
155
156         if (log.isDebugEnabled()) {
157             log.debug("Cloning bean: " + bean.getClass().getName());
158         }
159         Class JavaDoc clazz = bean.getClass();
160         Object JavaDoc newBean = null;
161         if (bean instanceof DynaBean) {
162             newBean = ((DynaBean) bean).getDynaClass().newInstance();
163         } else {
164             newBean = bean.getClass().newInstance();
165         }
166         getPropertyUtils().copyProperties(newBean, bean);
167         return (newBean);
168
169     }
170
171
172     /**
173      * <p>Copy property values from the origin bean to the destination bean
174      * for all cases where the property names are the same. For each
175      * property, a conversion is attempted as necessary. All combinations of
176      * standard JavaBeans and DynaBeans as origin and destination are
177      * supported. Properties that exist in the origin bean, but do not exist
178      * in the destination bean (or are read-only in the destination bean) are
179      * silently ignored.</p>
180      *
181      * <p>If the origin "bean" is actually a <code>Map</code>, it is assumed
182      * to contain String-valued <strong>simple</strong> property names as the keys, pointing at
183      * the corresponding property values that will be converted (if necessary)
184      * and set in the destination bean. <strong>Note</strong> that this method
185      * is intended to perform a "shallow copy" of the properties and so complex
186      * properties (for example, nested ones) will not be copied.</p>
187      *
188      * <p>This method differs from <code>populate()</code>, which
189      * was primarily designed for populating JavaBeans from the map of request
190      * parameters retrieved on an HTTP request, is that no scalar->indexed
191      * or indexed->scalar manipulations are performed. If the origin property
192      * is indexed, the destination property must be also.</p>
193      *
194      * <p>If you know that no type conversions are required, the
195      * <code>copyProperties()</code> method in {@link PropertyUtils} will
196      * execute faster than this method.</p>
197      *
198      * <p><strong>FIXME</strong> - Indexed and mapped properties that do not
199      * have getter and setter methods for the underlying array or Map are not
200      * copied by this method.</p>
201      *
202      * @param dest Destination bean whose properties are modified
203      * @param orig Origin bean whose properties are retrieved
204      *
205      * @exception IllegalAccessException if the caller does not have
206      * access to the property accessor method
207      * @exception IllegalArgumentException if the <code>dest</code> or
208      * <code>orig</code> argument is null
209      * @exception InvocationTargetException if the property accessor method
210      * throws an exception
211      */

212     public void copyProperties(Object JavaDoc dest, Object JavaDoc orig)
213         throws IllegalAccessException JavaDoc, InvocationTargetException JavaDoc {
214
215         // Validate existence of the specified beans
216
if (dest == null) {
217             throw new IllegalArgumentException JavaDoc
218                     ("No destination bean specified");
219         }
220         if (orig == null) {
221             throw new IllegalArgumentException JavaDoc("No origin bean specified");
222         }
223         if (log.isDebugEnabled()) {
224             log.debug("BeanUtils.copyProperties(" + dest + ", " +
225                       orig + ")");
226         }
227
228         // Copy the properties, converting as necessary
229
if (orig instanceof DynaBean) {
230             DynaProperty origDescriptors[] =
231                 ((DynaBean) orig).getDynaClass().getDynaProperties();
232             for (int i = 0; i < origDescriptors.length; i++) {
233                 String JavaDoc name = origDescriptors[i].getName();
234                 if (getPropertyUtils().isWriteable(dest, name)) {
235                     Object JavaDoc value = ((DynaBean) orig).get(name);
236                     copyProperty(dest, name, value);
237                 }
238             }
239         } else if (orig instanceof Map JavaDoc) {
240             Iterator JavaDoc names = ((Map JavaDoc) orig).keySet().iterator();
241             while (names.hasNext()) {
242                 String JavaDoc name = (String JavaDoc) names.next();
243                 if (getPropertyUtils().isWriteable(dest, name)) {
244                     Object JavaDoc value = ((Map JavaDoc) orig).get(name);
245                     copyProperty(dest, name, value);
246                 }
247             }
248         } else /* if (orig is a standard JavaBean) */ {
249             PropertyDescriptor JavaDoc origDescriptors[] =
250                 getPropertyUtils().getPropertyDescriptors(orig);
251             for (int i = 0; i < origDescriptors.length; i++) {
252                 String JavaDoc name = origDescriptors[i].getName();
253                 if ("class".equals(name)) {
254                     continue; // No point in trying to set an object's class
255
}
256                 if (getPropertyUtils().isReadable(orig, name) &&
257                     getPropertyUtils().isWriteable(dest, name)) {
258                     try {
259                         Object JavaDoc value =
260                             getPropertyUtils().getSimpleProperty(orig, name);
261                         copyProperty(dest, name, value);
262                     } catch (NoSuchMethodException JavaDoc e) {
263                         ; // Should not happen
264
}
265                 }
266             }
267         }
268
269     }
270
271
272     /**
273      * <p>Copy the specified property value to the specified destination bean,
274      * performing any type conversion that is required. If the specified
275      * bean does not have a property of the specified name, or the property
276      * is read only on the destination bean, return without
277      * doing anything. If you have custom destination property types, register
278      * {@link Converter}s for them by calling the <code>register()</code>
279      * method of {@link ConvertUtils}.</p>
280      *
281      * <p><strong>IMPLEMENTATION RESTRICTIONS</strong>:</p>
282      * <ul>
283      * <li>Does not support destination properties that are indexed,
284      * but only an indexed setter (as opposed to an array setter)
285      * is available.</li>
286      * <li>Does not support destination properties that are mapped,
287      * but only a keyed setter (as opposed to a Map setter)
288      * is available.</li>
289      * <li>The desired property type of a mapped setter cannot be
290      * determined (since Maps support any data type), so no conversion
291      * will be performed.</li>
292      * </ul>
293      *
294      * @param bean Bean on which setting is to be performed
295      * @param name Property name (can be nested/indexed/mapped/combo)
296      * @param value Value to be set
297      *
298      * @exception IllegalAccessException if the caller does not have
299      * access to the property accessor method
300      * @exception InvocationTargetException if the property accessor method
301      * throws an exception
302      */

303     public void copyProperty(Object JavaDoc bean, String JavaDoc name, Object JavaDoc value)
304         throws IllegalAccessException JavaDoc, InvocationTargetException JavaDoc {
305
306         // Trace logging (if enabled)
307
if (log.isTraceEnabled()) {
308             StringBuffer JavaDoc sb = new StringBuffer JavaDoc(" copyProperty(");
309             sb.append(bean);
310             sb.append(", ");
311             sb.append(name);
312             sb.append(", ");
313             if (value == null) {
314                 sb.append("<NULL>");
315             } else if (value instanceof String JavaDoc) {
316                 sb.append((String JavaDoc) value);
317             } else if (value instanceof String JavaDoc[]) {
318                 String JavaDoc values[] = (String JavaDoc[]) value;
319                 sb.append('[');
320                 for (int i = 0; i < values.length; i++) {
321                     if (i > 0) {
322                         sb.append(',');
323                     }
324                     sb.append(values[i]);
325                 }
326                 sb.append(']');
327             } else {
328                 sb.append(value.toString());
329             }
330             sb.append(')');
331             log.trace(sb.toString());
332         }
333
334         // Resolve any nested expression to get the actual target bean
335
Object JavaDoc target = bean;
336         int delim = name.lastIndexOf(PropertyUtils.NESTED_DELIM);
337         if (delim >= 0) {
338             try {
339                 target =
340                     getPropertyUtils().getProperty(bean, name.substring(0, delim));
341             } catch (NoSuchMethodException JavaDoc e) {
342                 return; // Skip this property setter
343
}
344             name = name.substring(delim + 1);
345             if (log.isTraceEnabled()) {
346                 log.trace(" Target bean = " + target);
347                 log.trace(" Target name = " + name);
348             }
349         }
350
351         // Declare local variables we will require
352
String JavaDoc propName = null; // Simple name of target property
353
Class JavaDoc type = null; // Java type of target property
354
int index = -1; // Indexed subscript value (if any)
355
String JavaDoc key = null; // Mapped key value (if any)
356

357         // Calculate the target property name, index, and key values
358
propName = name;
359         int i = propName.indexOf(PropertyUtils.INDEXED_DELIM);
360         if (i >= 0) {
361             int k = propName.indexOf(PropertyUtils.INDEXED_DELIM2);
362             try {
363                 index =
364                     Integer.parseInt(propName.substring(i + 1, k));
365             } catch (NumberFormatException JavaDoc e) {
366                 ;
367             }
368             propName = propName.substring(0, i);
369         }
370         int j = propName.indexOf(PropertyUtils.MAPPED_DELIM);
371         if (j >= 0) {
372             int k = propName.indexOf(PropertyUtils.MAPPED_DELIM2);
373             try {
374                 key = propName.substring(j + 1, k);
375             } catch (IndexOutOfBoundsException JavaDoc e) {
376                 ;
377             }
378             propName = propName.substring(0, j);
379         }
380
381         // Calculate the target property type
382
if (target instanceof DynaBean) {
383             DynaClass dynaClass = ((DynaBean) target).getDynaClass();
384             DynaProperty dynaProperty = dynaClass.getDynaProperty(propName);
385             if (dynaProperty == null) {
386                 return; // Skip this property setter
387
}
388             type = dynaProperty.getType();
389         } else {
390             PropertyDescriptor JavaDoc descriptor = null;
391             try {
392                 descriptor =
393                     getPropertyUtils().getPropertyDescriptor(target, name);
394                 if (descriptor == null) {
395                     return; // Skip this property setter
396
}
397             } catch (NoSuchMethodException JavaDoc e) {
398                 return; // Skip this property setter
399
}
400             type = descriptor.getPropertyType();
401             if (type == null) {
402                 // Most likely an indexed setter on a POJB only
403
if (log.isTraceEnabled()) {
404                     log.trace(" target type for property '" +
405                               propName + "' is null, so skipping ths setter");
406                 }
407                 return;
408             }
409         }
410         if (log.isTraceEnabled()) {
411             log.trace(" target propName=" + propName + ", type=" +
412                       type + ", index=" + index + ", key=" + key);
413         }
414
415         // Convert the specified value to the required type and store it
416
if (index >= 0) { // Destination must be indexed
417
Converter converter = getConvertUtils().lookup(type.getComponentType());
418             if (converter != null) {
419                 log.trace(" USING CONVERTER " + converter);
420                 value = converter.convert(type, value);
421             }
422             try {
423                 getPropertyUtils().setIndexedProperty(target, propName,
424                                                  index, value);
425             } catch (NoSuchMethodException JavaDoc e) {
426                 throw new InvocationTargetException JavaDoc
427                     (e, "Cannot set " + propName);
428             }
429         } else if (key != null) { // Destination must be mapped
430
// Maps do not know what the preferred data type is,
431
// so perform no conversions at all
432
// FIXME - should we create or support a TypedMap?
433
try {
434                 getPropertyUtils().setMappedProperty(target, propName,
435                                                 key, value);
436             } catch (NoSuchMethodException JavaDoc e) {
437                 throw new InvocationTargetException JavaDoc
438                     (e, "Cannot set " + propName);
439             }
440         } else { // Destination must be simple
441
Converter converter = getConvertUtils().lookup(type);
442             if (converter != null) {
443                 log.trace(" USING CONVERTER " + converter);
444                 value = converter.convert(type, value);
445             }
446             try {
447                 getPropertyUtils().setSimpleProperty(target, propName, value);
448             } catch (NoSuchMethodException JavaDoc e) {
449                 throw new InvocationTargetException JavaDoc
450                     (e, "Cannot set " + propName);
451             }
452         }
453
454     }
455
456
457     /**
458      * <p>Return the entire set of properties for which the specified bean
459      * provides a read method. This map contains the to <code>String</code>
460      * converted property values for all properties for which a read method
461      * is provided (i.e. where the getReadMethod() returns non-null).</p>
462      *
463      * <p>This map can be fed back to a call to
464      * <code>BeanUtils.populate()</code> to reconsitute the same set of
465      * properties, modulo differences for read-only and write-only
466      * properties, but only if there are no indexed properties.</p>
467      *
468      * @param bean Bean whose properties are to be extracted
469      *
470      * @exception IllegalAccessException if the caller does not have
471      * access to the property accessor method
472      * @exception InvocationTargetException if the property accessor method
473      * throws an exception
474      * @exception NoSuchMethodException if an accessor method for this
475      * property cannot be found
476      */

477     public Map JavaDoc describe(Object JavaDoc bean)
478             throws IllegalAccessException JavaDoc, InvocationTargetException JavaDoc,
479             NoSuchMethodException JavaDoc {
480
481         if (bean == null) {
482         // return (Collections.EMPTY_MAP);
483
return (new java.util.HashMap JavaDoc());
484         }
485         
486         if (log.isDebugEnabled()) {
487             log.debug("Describing bean: " + bean.getClass().getName());
488         }
489             
490         Map JavaDoc description = new HashMap JavaDoc();
491         if (bean instanceof DynaBean) {
492             DynaProperty descriptors[] =
493                 ((DynaBean) bean).getDynaClass().getDynaProperties();
494             for (int i = 0; i < descriptors.length; i++) {
495                 String JavaDoc name = descriptors[i].getName();
496                 description.put(name, getProperty(bean, name));
497             }
498         } else {
499             PropertyDescriptor JavaDoc descriptors[] =
500                 getPropertyUtils().getPropertyDescriptors(bean);
501             for (int i = 0; i < descriptors.length; i++) {
502                 String JavaDoc name = descriptors[i].getName();
503                 if (descriptors[i].getReadMethod() != null)
504                     description.put(name, getProperty(bean, name));
505             }
506         }
507         return (description);
508
509     }
510
511
512     /**
513      * Return the value of the specified array property of the specified
514      * bean, as a String array.
515      *
516      * @param bean Bean whose property is to be extracted
517      * @param name Name of the property to be extracted
518      *
519      * @exception IllegalAccessException if the caller does not have
520      * access to the property accessor method
521      * @exception InvocationTargetException if the property accessor method
522      * throws an exception
523      * @exception NoSuchMethodException if an accessor method for this
524      * property cannot be found
525      */

526     public String JavaDoc[] getArrayProperty(Object JavaDoc bean, String JavaDoc name)
527             throws IllegalAccessException JavaDoc, InvocationTargetException JavaDoc,
528             NoSuchMethodException JavaDoc {
529
530         Object JavaDoc value = getPropertyUtils().getProperty(bean, name);
531         if (value == null) {
532             return (null);
533         } else if (value instanceof Collection JavaDoc) {
534             ArrayList JavaDoc values = new ArrayList JavaDoc();
535             Iterator JavaDoc items = ((Collection JavaDoc) value).iterator();
536             while (items.hasNext()) {
537                 Object JavaDoc item = items.next();
538                 if (item == null) {
539                     values.add((String JavaDoc) null);
540                 } else {
541                     // convert to string using convert utils
542
values.add(getConvertUtils().convert(item));
543                 }
544             }
545             return ((String JavaDoc[]) values.toArray(new String JavaDoc[values.size()]));
546         } else if (value.getClass().isArray()) {
547             int n = Array.getLength(value);
548             String JavaDoc results[] = new String JavaDoc[n];
549             for (int i = 0; i < n; i++) {
550                 Object JavaDoc item = Array.get(value, i);
551                 if (item == null) {
552                     results[i] = null;
553                 } else {
554                     // convert to string using convert utils
555
results[i] = getConvertUtils().convert(item);
556                 }
557             }
558             return (results);
559         } else {
560             String JavaDoc results[] = new String JavaDoc[1];
561             results[0] = value.toString();
562             return (results);
563         }
564
565     }
566
567
568     /**
569      * Return the value of the specified indexed property of the specified
570      * bean, as a String. The zero-relative index of the
571      * required value must be included (in square brackets) as a suffix to
572      * the property name, or <code>IllegalArgumentException</code> will be
573      * thrown.
574      *
575      * @param bean Bean whose property is to be extracted
576      * @param name <code>propertyname[index]</code> of the property value
577      * to be extracted
578      *
579      * @exception IllegalAccessException if the caller does not have
580      * access to the property accessor method
581      * @exception InvocationTargetException if the property accessor method
582      * throws an exception
583      * @exception NoSuchMethodException if an accessor method for this
584      * property cannot be found
585      */

586     public String JavaDoc getIndexedProperty(Object JavaDoc bean, String JavaDoc name)
587             throws IllegalAccessException JavaDoc, InvocationTargetException JavaDoc,
588             NoSuchMethodException JavaDoc {
589
590         Object JavaDoc value = getPropertyUtils().getIndexedProperty(bean, name);
591         return (getConvertUtils().convert(value));
592
593     }
594
595
596     /**
597      * Return the value of the specified indexed property of the specified
598      * bean, as a String. The index is specified as a method parameter and
599      * must *not* be included in the property name expression
600      *
601      * @param bean Bean whose property is to be extracted
602      * @param name Simple property name of the property value to be extracted
603      * @param index Index of the property value to be extracted
604      *
605      * @exception IllegalAccessException if the caller does not have
606      * access to the property accessor method
607      * @exception InvocationTargetException if the property accessor method
608      * throws an exception
609      * @exception NoSuchMethodException if an accessor method for this
610      * property cannot be found
611      */

612     public String JavaDoc getIndexedProperty(Object JavaDoc bean,
613                                             String JavaDoc name, int index)
614             throws IllegalAccessException JavaDoc, InvocationTargetException JavaDoc,
615             NoSuchMethodException JavaDoc {
616
617         Object JavaDoc value = getPropertyUtils().getIndexedProperty(bean, name, index);
618         return (getConvertUtils().convert(value));
619
620     }
621
622
623     /**
624      * Return the value of the specified indexed property of the specified
625      * bean, as a String. The String-valued key of the required value
626      * must be included (in parentheses) as a suffix to
627      * the property name, or <code>IllegalArgumentException</code> will be
628      * thrown.
629      *
630      * @param bean Bean whose property is to be extracted
631      * @param name <code>propertyname(index)</code> of the property value
632      * to be extracted
633      *
634      * @exception IllegalAccessException if the caller does not have
635      * access to the property accessor method
636      * @exception InvocationTargetException if the property accessor method
637      * throws an exception
638      * @exception NoSuchMethodException if an accessor method for this
639      * property cannot be found
640      */

641     public String JavaDoc getMappedProperty(Object JavaDoc bean, String JavaDoc name)
642             throws IllegalAccessException JavaDoc, InvocationTargetException JavaDoc,
643             NoSuchMethodException JavaDoc {
644
645         Object JavaDoc value = getPropertyUtils().getMappedProperty(bean, name);
646         return (getConvertUtils().convert(value));
647
648     }
649
650
651     /**
652      * Return the value of the specified mapped property of the specified
653      * bean, as a String. The key is specified as a method parameter and
654      * must *not* be included in the property name expression
655      *
656      * @param bean Bean whose property is to be extracted
657      * @param name Simple property name of the property value to be extracted
658      * @param key Lookup key of the property value to be extracted
659      *
660      * @exception IllegalAccessException if the caller does not have
661      * access to the property accessor method
662      * @exception InvocationTargetException if the property accessor method
663      * throws an exception
664      * @exception NoSuchMethodException if an accessor method for this
665      * property cannot be found
666      */

667     public String JavaDoc getMappedProperty(Object JavaDoc bean,
668                                            String JavaDoc name, String JavaDoc key)
669             throws IllegalAccessException JavaDoc, InvocationTargetException JavaDoc,
670             NoSuchMethodException JavaDoc {
671
672         Object JavaDoc value = getPropertyUtils().getMappedProperty(bean, name, key);
673         return (getConvertUtils().convert(value));
674
675     }
676
677
678     /**
679      * Return the value of the (possibly nested) property of the specified
680      * name, for the specified bean, as a String.
681      *
682      * @param bean Bean whose property is to be extracted
683      * @param name Possibly nested name of the property to be extracted
684      *
685      * @exception IllegalAccessException if the caller does not have
686      * access to the property accessor method
687      * @exception IllegalArgumentException if a nested reference to a
688      * property returns null
689      * @exception InvocationTargetException if the property accessor method
690      * throws an exception
691      * @exception NoSuchMethodException if an accessor method for this
692      * property cannot be found
693      */

694     public String JavaDoc getNestedProperty(Object JavaDoc bean, String JavaDoc name)
695             throws IllegalAccessException JavaDoc, InvocationTargetException JavaDoc,
696             NoSuchMethodException JavaDoc {
697
698         Object JavaDoc value = getPropertyUtils().getNestedProperty(bean, name);
699         return (getConvertUtils().convert(value));
700
701     }
702
703
704     /**
705      * Return the value of the specified property of the specified bean,
706      * no matter which property reference format is used, as a String.
707      *
708      * @param bean Bean whose property is to be extracted
709      * @param name Possibly indexed and/or nested name of the property
710      * to be extracted
711      *
712      * @exception IllegalAccessException if the caller does not have
713      * access to the property accessor method
714      * @exception InvocationTargetException if the property accessor method
715      * throws an exception
716      * @exception NoSuchMethodException if an accessor method for this
717      * property cannot be found
718      */

719     public String JavaDoc getProperty(Object JavaDoc bean, String JavaDoc name)
720             throws IllegalAccessException JavaDoc, InvocationTargetException JavaDoc,
721             NoSuchMethodException JavaDoc {
722
723         return (getNestedProperty(bean, name));
724
725     }
726
727
728     /**
729      * Return the value of the specified simple property of the specified
730      * bean, converted to a String.
731      *
732      * @param bean Bean whose property is to be extracted
733      * @param name Name of the property to be extracted
734      *
735      * @exception IllegalAccessException if the caller does not have
736      * access to the property accessor method
737      * @exception InvocationTargetException if the property accessor method
738      * throws an exception
739      * @exception NoSuchMethodException if an accessor method for this
740      * property cannot be found
741      */

742     public String JavaDoc getSimpleProperty(Object JavaDoc bean, String JavaDoc name)
743             throws IllegalAccessException JavaDoc, InvocationTargetException JavaDoc,
744             NoSuchMethodException JavaDoc {
745
746         Object JavaDoc value = getPropertyUtils().getSimpleProperty(bean, name);
747         return (getConvertUtils().convert(value));
748
749     }
750
751
752     /**
753      * <p>Populate the JavaBeans properties of the specified bean, based on
754      * the specified name/value pairs. This method uses Java reflection APIs
755      * to identify corresponding "property setter" method names, and deals
756      * with setter arguments of type <code>String</code>, <code>boolean</code>,
757      * <code>int</code>, <code>long</code>, <code>float</code>, and
758      * <code>double</code>. In addition, array setters for these types (or the
759      * corresponding primitive types) can also be identified.</p>
760      *
761      * <p>The particular setter method to be called for each property is
762      * determined using the usual JavaBeans introspection mechanisms. Thus,
763      * you may identify custom setter methods using a BeanInfo class that is
764      * associated with the class of the bean itself. If no such BeanInfo
765      * class is available, the standard method name conversion ("set" plus
766      * the capitalized name of the property in question) is used.</p>
767      *
768      * <p><strong>NOTE</strong>: It is contrary to the JavaBeans Specification
769      * to have more than one setter method (with different argument
770      * signatures) for the same property.</p>
771      *
772      * <p><strong>WARNING</strong> - The logic of this method is customized
773      * for extracting String-based request parameters from an HTTP request.
774      * It is probably not what you want for general property copying with
775      * type conversion. For that purpose, check out the
776      * <code>copyProperties()</code> method instead.</p>
777      *
778      * @param bean JavaBean whose properties are being populated
779      * @param properties Map keyed by property name, with the
780      * corresponding (String or String[]) value(s) to be set
781      *
782      * @exception IllegalAccessException if the caller does not have
783      * access to the property accessor method
784      * @exception InvocationTargetException if the property accessor method
785      * throws an exception
786      */

787     public void populate(Object JavaDoc bean, Map JavaDoc properties)
788         throws IllegalAccessException JavaDoc, InvocationTargetException JavaDoc {
789
790         // Do nothing unless both arguments have been specified
791
if ((bean == null) || (properties == null)) {
792             return;
793         }
794         if (log.isDebugEnabled()) {
795             log.debug("BeanUtils.populate(" + bean + ", " +
796                     properties + ")");
797         }
798
799         // Loop through the property name/value pairs to be set
800
Iterator JavaDoc names = properties.keySet().iterator();
801         while (names.hasNext()) {
802
803             // Identify the property name and value(s) to be assigned
804
String JavaDoc name = (String JavaDoc) names.next();
805             if (name == null) {
806                 continue;
807             }
808             Object JavaDoc value = properties.get(name);
809
810             // Perform the assignment for this property
811
setProperty(bean, name, value);
812
813         }
814
815     }
816
817
818     /**
819      * <p>Set the specified property value, performing type conversions as
820      * required to conform to the type of the destination property.</p>
821      *
822      * <p>If the property is read only then the method returns
823      * without throwing an exception.</p>
824      *
825      * <p>If <code>null</code> is passed into a property expecting a primitive value,
826      * then this will be converted as if it were a <code>null</code> string.</p>
827      *
828      * <p><strong>WARNING</strong> - The logic of this method is customized
829      * to meet the needs of <code>populate()</code>, and is probably not what
830      * you want for general property copying with type conversion. For that
831      * purpose, check out the <code>copyProperty()</code> method instead.</p>
832      *
833      * <p><strong>WARNING</strong> - PLEASE do not modify the behavior of this
834      * method without consulting with the Struts developer community. There
835      * are some subtleties to its functionality that are not documented in the
836      * Javadoc description above, yet are vital to the way that Struts utilizes
837      * this method.</p>
838      *
839      * @param bean Bean on which setting is to be performed
840      * @param name Property name (can be nested/indexed/mapped/combo)
841      * @param value Value to be set
842      *
843      * @exception IllegalAccessException if the caller does not have
844      * access to the property accessor method
845      * @exception InvocationTargetException if the property accessor method
846      * throws an exception
847      */

848     public void setProperty(Object JavaDoc bean, String JavaDoc name, Object JavaDoc value)
849         throws IllegalAccessException JavaDoc, InvocationTargetException JavaDoc {
850
851         // Trace logging (if enabled)
852
if (log.isTraceEnabled()) {
853             StringBuffer JavaDoc sb = new StringBuffer JavaDoc(" setProperty(");
854             sb.append(bean);
855             sb.append(", ");
856             sb.append(name);
857             sb.append(", ");
858             if (value == null) {
859                 sb.append("<NULL>");
860             } else if (value instanceof String JavaDoc) {
861                 sb.append((String JavaDoc) value);
862             } else if (value instanceof String JavaDoc[]) {
863                 String JavaDoc values[] = (String JavaDoc[]) value;
864                 sb.append('[');
865                 for (int i = 0; i < values.length; i++) {
866                     if (i > 0) {
867                         sb.append(',');
868                     }
869                     sb.append(values[i]);
870                 }
871                 sb.append(']');
872             } else {
873                 sb.append(value.toString());
874             }
875             sb.append(')');
876             log.trace(sb.toString());
877         }
878
879         // Resolve any nested expression to get the actual target bean
880
Object JavaDoc target = bean;
881         int delim = findLastNestedIndex(name);
882         if (delim >= 0) {
883             try {
884                 target =
885                     getPropertyUtils().getProperty(bean, name.substring(0, delim));
886             } catch (NoSuchMethodException JavaDoc e) {
887                 return; // Skip this property setter
888
}
889             name = name.substring(delim + 1);
890             if (log.isTraceEnabled()) {
891                 log.trace(" Target bean = " + target);
892                 log.trace(" Target name = " + name);
893             }
894         }
895
896         // Declare local variables we will require
897
String JavaDoc propName = null; // Simple name of target property
898
Class JavaDoc type = null; // Java type of target property
899
int index = -1; // Indexed subscript value (if any)
900
String JavaDoc key = null; // Mapped key value (if any)
901

902         // Calculate the property name, index, and key values
903
propName = name;
904         int i = propName.indexOf(PropertyUtils.INDEXED_DELIM);
905         if (i >= 0) {
906             int k = propName.indexOf(PropertyUtils.INDEXED_DELIM2);
907             try {
908                 index =
909                     Integer.parseInt(propName.substring(i + 1, k));
910             } catch (NumberFormatException JavaDoc e) {
911                 ;
912             }
913             propName = propName.substring(0, i);
914         }
915         int j = propName.indexOf(PropertyUtils.MAPPED_DELIM);
916         if (j >= 0) {
917             int k = propName.indexOf(PropertyUtils.MAPPED_DELIM2);
918             try {
919                 key = propName.substring(j + 1, k);
920             } catch (IndexOutOfBoundsException JavaDoc e) {
921                 ;
922             }
923             propName = propName.substring(0, j);
924         }
925
926         // Calculate the property type
927
if (target instanceof DynaBean) {
928             DynaClass dynaClass = ((DynaBean) target).getDynaClass();
929             DynaProperty dynaProperty = dynaClass.getDynaProperty(propName);
930             if (dynaProperty == null) {
931                 return; // Skip this property setter
932
}
933             type = dynaProperty.getType();
934         } else {
935             PropertyDescriptor JavaDoc descriptor = null;
936             try {
937                 descriptor =
938                     getPropertyUtils().getPropertyDescriptor(target, name);
939                 if (descriptor == null) {
940                     return; // Skip this property setter
941
}
942             } catch (NoSuchMethodException JavaDoc e) {
943                 return; // Skip this property setter
944
}
945             if (descriptor instanceof MappedPropertyDescriptor) {
946                 if (((MappedPropertyDescriptor) descriptor).getMappedWriteMethod() == null) {
947                     if (log.isDebugEnabled()) {
948                         log.debug("Skipping read-only property");
949                     }
950                     return; // Read-only, skip this property setter
951
}
952                 type = ((MappedPropertyDescriptor) descriptor).
953                     getMappedPropertyType();
954             } else if (descriptor instanceof IndexedPropertyDescriptor JavaDoc) {
955                 if (((IndexedPropertyDescriptor JavaDoc) descriptor).getIndexedWriteMethod() == null) {
956                     if (log.isDebugEnabled()) {
957                         log.debug("Skipping read-only property");
958                     }
959                     return; // Read-only, skip this property setter
960
}
961                 type = ((IndexedPropertyDescriptor JavaDoc) descriptor).
962                     getIndexedPropertyType();
963             } else {
964                 if (descriptor.getWriteMethod() == null) {
965                     if (log.isDebugEnabled()) {
966                         log.debug("Skipping read-only property");
967                     }
968                     return; // Read-only, skip this property setter
969
}
970                 type = descriptor.getPropertyType();
971             }
972         }
973
974         // Convert the specified value to the required type
975
Object JavaDoc newValue = null;
976         if (type.isArray() && (index < 0)) { // Scalar value into array
977
if (value == null) {
978                 String JavaDoc values[] = new String JavaDoc[1];
979                 values[0] = (String JavaDoc) value;
980                 newValue = getConvertUtils().convert((String JavaDoc[]) values, type);
981             } else if (value instanceof String JavaDoc) {
982                 String JavaDoc values[] = new String JavaDoc[1];
983                 values[0] = (String JavaDoc) value;
984                 newValue = getConvertUtils().convert((String JavaDoc[]) values, type);
985             } else if (value instanceof String JavaDoc[]) {
986                 newValue = getConvertUtils().convert((String JavaDoc[]) value, type);
987             } else {
988                 newValue = value;
989             }
990         } else if (type.isArray()) { // Indexed value into array
991
if (value instanceof String JavaDoc) {
992                 newValue = getConvertUtils().convert((String JavaDoc) value,
993                                                 type.getComponentType());
994             } else if (value instanceof String JavaDoc[]) {
995                 newValue = getConvertUtils().convert(((String JavaDoc[]) value)[0],
996                                                 type.getComponentType());
997             } else {
998                 newValue = value;
999             }
1000        } else { // Value into scalar
1001
if ((value instanceof String JavaDoc) || (value == null)) {
1002                newValue = getConvertUtils().convert((String JavaDoc) value, type);
1003            } else if (value instanceof String JavaDoc[]) {
1004                newValue = getConvertUtils().convert(((String JavaDoc[]) value)[0],
1005                                                type);
1006            } else if (getConvertUtils().lookup(value.getClass()) != null) {
1007                newValue = getConvertUtils().convert(value.toString(), type);
1008            } else {
1009                newValue = value;
1010            }
1011        }
1012
1013        // Invoke the setter method
1014
try {
1015            if (index >= 0) {
1016                getPropertyUtils().setIndexedProperty(target, propName,
1017                                                 index, newValue);
1018            } else if (key != null) {
1019                getPropertyUtils().setMappedProperty(target, propName,
1020                                                key, newValue);
1021            } else {
1022                getPropertyUtils().setProperty(target, propName, newValue);
1023            }
1024        } catch (NoSuchMethodException JavaDoc e) {
1025            throw new InvocationTargetException JavaDoc
1026                (e, "Cannot set " + propName);
1027        }
1028
1029    }
1030    
1031    private int findLastNestedIndex(String JavaDoc expression)
1032    {
1033        // walk back from the end to the start
1034
// and find the first index that
1035
int bracketCount = 0;
1036        for (int i = expression.length() - 1; i>=0 ; i--) {
1037            char at = expression.charAt(i);
1038            switch (at) {
1039                case PropertyUtils.NESTED_DELIM:
1040                    if (bracketCount < 1) {
1041                        return i;
1042                    }
1043                    break;
1044                    
1045                case PropertyUtils.MAPPED_DELIM:
1046                case PropertyUtils.INDEXED_DELIM:
1047                    // not bothered which
1048
--bracketCount;
1049                    break;
1050                
1051                case PropertyUtils.MAPPED_DELIM2:
1052                case PropertyUtils.INDEXED_DELIM2:
1053                    // not bothered which
1054
++bracketCount;
1055                    break;
1056            }
1057        }
1058        // can't find any
1059
return -1;
1060    }
1061    
1062    /**
1063     * Gets the <code>ConvertUtilsBean</code> instance used to perform the conversions.
1064     */

1065    public ConvertUtilsBean getConvertUtils() {
1066        return convertUtilsBean;
1067    }
1068    
1069    /**
1070     * Gets the <code>PropertyUtilsBean</code> instance used to access properties.
1071     */

1072    public PropertyUtilsBean getPropertyUtils() {
1073        return propertyUtilsBean;
1074    }
1075}
1076
Popular Tags