View Javadoc

1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one or more
3    * contributor license agreements.  See the NOTICE file distributed with
4    * this work for additional information regarding copyright ownership.
5    * The ASF licenses this file to You under the Apache License, Version 2.0
6    * (the "License"); you may not use this file except in compliance with
7    * the License.  You may obtain a copy of the License at
8    *
9    *      http://www.apache.org/licenses/LICENSE-2.0
10   *
11   * Unless required by applicable law or agreed to in writing, software
12   * distributed under the License is distributed on an "AS IS" BASIS,
13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14   * See the License for the specific language governing permissions and
15   * limitations under the License.
16   */
17  
18  
19  package org.apache.commons.beanutils;
20  
21  
22  import java.io.Serializable;
23  import java.lang.reflect.Array;
24  import java.util.HashMap;
25  import java.util.List;
26  import java.util.Map;
27  
28  
29  /***
30   * <p>Minimal implementation of the <code>DynaBean</code> interface.  Can be
31   * used as a convenience base class for more sophisticated implementations.</p>
32   *
33   * <p><strong>IMPLEMENTATION NOTE</strong> - Instances of this class that are
34   * accessed from multiple threads simultaneously need to be synchronized.</p>
35   *
36   * <p><strong>IMPLEMENTATION NOTE</strong> - Instances of this class can be
37   * successfully serialized and deserialized <strong>ONLY</strong> if all
38   * property values are <code>Serializable</code>.</p>
39   *
40   * @author Craig McClanahan
41   * @version $Revision: 555824 $ $Date: 2007-07-13 01:27:15 +0100 (Fri, 13 Jul 2007) $
42   */
43  
44  public class BasicDynaBean implements DynaBean, Serializable {
45  
46  
47      // ---------------------------------------------------------- Constructors
48  
49  
50      /***
51       * Construct a new <code>DynaBean</code> associated with the specified
52       * <code>DynaClass</code> instance.
53       *
54       * @param dynaClass The DynaClass we are associated with
55       */
56      public BasicDynaBean(DynaClass dynaClass) {
57  
58          super();
59          this.dynaClass = dynaClass;
60  
61      }
62  
63  
64      // ---------------------------------------------------- Instance Variables
65  
66  
67      /***
68       * The <code>DynaClass</code> "base class" that this DynaBean
69       * is associated with.
70       */
71      protected DynaClass dynaClass = null;
72  
73  
74      /***
75       * The set of property values for this DynaBean, keyed by property name.
76       */
77      protected HashMap values = new HashMap();
78  
79      /*** Map decorator for this DynaBean */
80      private transient Map mapDecorator;
81  
82      /***
83       * Return a Map representation of this DynaBean.
84       * </p>
85       * This, for example, could be used in JSTL in the following way to access
86       * a DynaBean's <code>fooProperty</code>:
87       * <ul><li><code>${myDynaBean.<b>map</b>.fooProperty}</code></li></ul>
88       *
89       * @return a Map representation of this DynaBean
90       */
91      public Map getMap() {
92  
93          // cache the Map
94          if (mapDecorator == null) {
95              mapDecorator = new DynaBeanMapDecorator(this);
96          }
97          return mapDecorator;
98  
99      }
100 
101     // ------------------------------------------------------ DynaBean Methods
102 
103 
104     /***
105      * Does the specified mapped property contain a value for the specified
106      * key value?
107      *
108      * @param name Name of the property to check
109      * @param key Name of the key to check
110      * @return <code>true<code> if the mapped property contains a value for
111      * the specified key, otherwise <code>false</code>
112      *
113      * @exception IllegalArgumentException if there is no property
114      *  of the specified name
115      */
116     public boolean contains(String name, String key) {
117 
118         Object value = values.get(name);
119         if (value == null) {
120             throw new NullPointerException
121                     ("No mapped value for '" + name + "(" + key + ")'");
122         } else if (value instanceof Map) {
123             return (((Map) value).containsKey(key));
124         } else {
125             throw new IllegalArgumentException
126                     ("Non-mapped property for '" + name + "(" + key + ")'");
127         }
128 
129     }
130 
131 
132     /***
133      * Return the value of a simple property with the specified name.
134      *
135      * @param name Name of the property whose value is to be retrieved
136      * @return The property's value
137      *
138      * @exception IllegalArgumentException if there is no property
139      *  of the specified name
140      */
141     public Object get(String name) {
142 
143         // Return any non-null value for the specified property
144         Object value = values.get(name);
145         if (value != null) {
146             return (value);
147         }
148 
149         // Return a null value for a non-primitive property
150         Class type = getDynaProperty(name).getType();
151         if (!type.isPrimitive()) {
152             return (value);
153         }
154 
155         // Manufacture default values for primitive properties
156         if (type == Boolean.TYPE) {
157             return (Boolean.FALSE);
158         } else if (type == Byte.TYPE) {
159             return (new Byte((byte) 0));
160         } else if (type == Character.TYPE) {
161             return (new Character((char) 0));
162         } else if (type == Double.TYPE) {
163             return (new Double(0.0));
164         } else if (type == Float.TYPE) {
165             return (new Float((float) 0.0));
166         } else if (type == Integer.TYPE) {
167             return (new Integer(0));
168         } else if (type == Long.TYPE) {
169             return (new Long(0));
170         } else if (type == Short.TYPE) {
171             return (new Short((short) 0));
172         } else {
173             return (null);
174         }
175 
176     }
177 
178 
179     /***
180      * Return the value of an indexed property with the specified name.
181      *
182      * @param name Name of the property whose value is to be retrieved
183      * @param index Index of the value to be retrieved
184      * @return The indexed property's value
185      *
186      * @exception IllegalArgumentException if there is no property
187      *  of the specified name
188      * @exception IllegalArgumentException if the specified property
189      *  exists, but is not indexed
190      * @exception IndexOutOfBoundsException if the specified index
191      *  is outside the range of the underlying property
192      * @exception NullPointerException if no array or List has been
193      *  initialized for this property
194      */
195     public Object get(String name, int index) {
196 
197         Object value = values.get(name);
198         if (value == null) {
199             throw new NullPointerException
200                     ("No indexed value for '" + name + "[" + index + "]'");
201         } else if (value.getClass().isArray()) {
202             return (Array.get(value, index));
203         } else if (value instanceof List) {
204             return ((List) value).get(index);
205         } else {
206             throw new IllegalArgumentException
207                     ("Non-indexed property for '" + name + "[" + index + "]'");
208         }
209 
210     }
211 
212 
213     /***
214      * Return the value of a mapped property with the specified name,
215      * or <code>null</code> if there is no value for the specified key.
216      *
217      * @param name Name of the property whose value is to be retrieved
218      * @param key Key of the value to be retrieved
219      * @return The mapped property's value
220      *
221      * @exception IllegalArgumentException if there is no property
222      *  of the specified name
223      * @exception IllegalArgumentException if the specified property
224      *  exists, but is not mapped
225      */
226     public Object get(String name, String key) {
227 
228         Object value = values.get(name);
229         if (value == null) {
230             throw new NullPointerException
231                     ("No mapped value for '" + name + "(" + key + ")'");
232         } else if (value instanceof Map) {
233             return (((Map) value).get(key));
234         } else {
235             throw new IllegalArgumentException
236                     ("Non-mapped property for '" + name + "(" + key + ")'");
237         }
238 
239     }
240 
241 
242     /***
243      * Return the <code>DynaClass</code> instance that describes the set of
244      * properties available for this DynaBean.
245      *
246      * @return The associated DynaClass
247      */
248     public DynaClass getDynaClass() {
249 
250         return (this.dynaClass);
251 
252     }
253 
254 
255     /***
256      * Remove any existing value for the specified key on the
257      * specified mapped property.
258      *
259      * @param name Name of the property for which a value is to
260      *  be removed
261      * @param key Key of the value to be removed
262      *
263      * @exception IllegalArgumentException if there is no property
264      *  of the specified name
265      */
266     public void remove(String name, String key) {
267 
268         Object value = values.get(name);
269         if (value == null) {
270             throw new NullPointerException
271                     ("No mapped value for '" + name + "(" + key + ")'");
272         } else if (value instanceof Map) {
273             ((Map) value).remove(key);
274         } else {
275             throw new IllegalArgumentException
276                     ("Non-mapped property for '" + name + "(" + key + ")'");
277         }
278 
279     }
280 
281 
282     /***
283      * Set the value of a simple property with the specified name.
284      *
285      * @param name Name of the property whose value is to be set
286      * @param value Value to which this property is to be set
287      *
288      * @exception ConversionException if the specified value cannot be
289      *  converted to the type required for this property
290      * @exception IllegalArgumentException if there is no property
291      *  of the specified name
292      * @exception NullPointerException if an attempt is made to set a
293      *  primitive property to null
294      */
295     public void set(String name, Object value) {
296 
297         DynaProperty descriptor = getDynaProperty(name);
298         if (value == null) {
299             if (descriptor.getType().isPrimitive()) {
300                 throw new NullPointerException
301                         ("Primitive value for '" + name + "'");
302             }
303         } else if (!isAssignable(descriptor.getType(), value.getClass())) {
304             throw new ConversionException
305                     ("Cannot assign value of type '" +
306                     value.getClass().getName() +
307                     "' to property '" + name + "' of type '" +
308                     descriptor.getType().getName() + "'");
309         }
310         values.put(name, value);
311 
312     }
313 
314 
315     /***
316      * Set the value of an indexed property with the specified name.
317      *
318      * @param name Name of the property whose value is to be set
319      * @param index Index of the property to be set
320      * @param value Value to which this property is to be set
321      *
322      * @exception ConversionException if the specified value cannot be
323      *  converted to the type required for this property
324      * @exception IllegalArgumentException if there is no property
325      *  of the specified name
326      * @exception IllegalArgumentException if the specified property
327      *  exists, but is not indexed
328      * @exception IndexOutOfBoundsException if the specified index
329      *  is outside the range of the underlying property
330      */
331     public void set(String name, int index, Object value) {
332 
333         Object prop = values.get(name);
334         if (prop == null) {
335             throw new NullPointerException
336                     ("No indexed value for '" + name + "[" + index + "]'");
337         } else if (prop.getClass().isArray()) {
338             Array.set(prop, index, value);
339         } else if (prop instanceof List) {
340             try {
341                 ((List) prop).set(index, value);
342             } catch (ClassCastException e) {
343                 throw new ConversionException(e.getMessage());
344             }
345         } else {
346             throw new IllegalArgumentException
347                     ("Non-indexed property for '" + name + "[" + index + "]'");
348         }
349 
350     }
351 
352 
353     /***
354      * Set the value of a mapped property with the specified name.
355      *
356      * @param name Name of the property whose value is to be set
357      * @param key Key of the property to be set
358      * @param value Value to which this property is to be set
359      *
360      * @exception ConversionException if the specified value cannot be
361      *  converted to the type required for this property
362      * @exception IllegalArgumentException if there is no property
363      *  of the specified name
364      * @exception IllegalArgumentException if the specified property
365      *  exists, but is not mapped
366      */
367     public void set(String name, String key, Object value) {
368 
369         Object prop = values.get(name);
370         if (prop == null) {
371             throw new NullPointerException
372                     ("No mapped value for '" + name + "(" + key + ")'");
373         } else if (prop instanceof Map) {
374             ((Map) prop).put(key, value);
375         } else {
376             throw new IllegalArgumentException
377                     ("Non-mapped property for '" + name + "(" + key + ")'");
378         }
379 
380     }
381 
382 
383     // ------------------------------------------------------ Protected Methods
384 
385 
386     /***
387      * Return the property descriptor for the specified property name.
388      *
389      * @param name Name of the property for which to retrieve the descriptor
390      * @return The property descriptor
391      *
392      * @exception IllegalArgumentException if this is not a valid property
393      *  name for our DynaClass
394      */
395     protected DynaProperty getDynaProperty(String name) {
396 
397         DynaProperty descriptor = getDynaClass().getDynaProperty(name);
398         if (descriptor == null) {
399             throw new IllegalArgumentException
400                     ("Invalid property name '" + name + "'");
401         }
402         return (descriptor);
403 
404     }
405 
406 
407     /***
408      * Is an object of the source class assignable to the destination class?
409      *
410      * @param dest Destination class
411      * @param source Source class
412      * @return <code>true</code> if the source class is assignable to the
413      * destination class, otherwise <code>false</code>
414      */
415     protected boolean isAssignable(Class dest, Class source) {
416 
417         if (dest.isAssignableFrom(source) ||
418                 ((dest == Boolean.TYPE) && (source == Boolean.class)) ||
419                 ((dest == Byte.TYPE) && (source == Byte.class)) ||
420                 ((dest == Character.TYPE) && (source == Character.class)) ||
421                 ((dest == Double.TYPE) && (source == Double.class)) ||
422                 ((dest == Float.TYPE) && (source == Float.class)) ||
423                 ((dest == Integer.TYPE) && (source == Integer.class)) ||
424                 ((dest == Long.TYPE) && (source == Long.class)) ||
425                 ((dest == Short.TYPE) && (source == Short.class))) {
426             return (true);
427         } else {
428             return (false);
429         }
430 
431     }
432 
433 
434 }