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: 926529 $ $Date: 2010-03-23 07:44:24 -0400 (Tue, 23 Mar 2010) $
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       * @since 1.8.0
91       */
92      public Map getMap() {
93  
94          // cache the Map
95          if (mapDecorator == null) {
96              mapDecorator = new DynaBeanMapDecorator(this);
97          }
98          return mapDecorator;
99  
100     }
101 
102     // ------------------------------------------------------ DynaBean Methods
103 
104 
105     /**
106      * Does the specified mapped property contain a value for the specified
107      * key value?
108      *
109      * @param name Name of the property to check
110      * @param key Name of the key to check
111      * @return <code>true<code> if the mapped property contains a value for
112      * the specified key, otherwise <code>false</code>
113      *
114      * @exception IllegalArgumentException if there is no property
115      *  of the specified name
116      */
117     public boolean contains(String name, String key) {
118 
119         Object value = values.get(name);
120         if (value == null) {
121             throw new NullPointerException
122                     ("No mapped value for '" + name + "(" + key + ")'");
123         } else if (value instanceof Map) {
124             return (((Map) value).containsKey(key));
125         } else {
126             throw new IllegalArgumentException
127                     ("Non-mapped property for '" + name + "(" + key + ")'");
128         }
129 
130     }
131 
132 
133     /**
134      * Return the value of a simple property with the specified name.
135      *
136      * @param name Name of the property whose value is to be retrieved
137      * @return The property's value
138      *
139      * @exception IllegalArgumentException if there is no property
140      *  of the specified name
141      */
142     public Object get(String name) {
143 
144         // Return any non-null value for the specified property
145         Object value = values.get(name);
146         if (value != null) {
147             return (value);
148         }
149 
150         // Return a null value for a non-primitive property
151         Class type = getDynaProperty(name).getType();
152         if (!type.isPrimitive()) {
153             return (value);
154         }
155 
156         // Manufacture default values for primitive properties
157         if (type == Boolean.TYPE) {
158             return (Boolean.FALSE);
159         } else if (type == Byte.TYPE) {
160             return (new Byte((byte) 0));
161         } else if (type == Character.TYPE) {
162             return (new Character((char) 0));
163         } else if (type == Double.TYPE) {
164             return (new Double(0.0));
165         } else if (type == Float.TYPE) {
166             return (new Float((float) 0.0));
167         } else if (type == Integer.TYPE) {
168             return (new Integer(0));
169         } else if (type == Long.TYPE) {
170             return (new Long(0));
171         } else if (type == Short.TYPE) {
172             return (new Short((short) 0));
173         } else {
174             return (null);
175         }
176 
177     }
178 
179 
180     /**
181      * Return the value of an indexed property with the specified name.
182      *
183      * @param name Name of the property whose value is to be retrieved
184      * @param index Index of the value to be retrieved
185      * @return The indexed property's value
186      *
187      * @exception IllegalArgumentException if there is no property
188      *  of the specified name
189      * @exception IllegalArgumentException if the specified property
190      *  exists, but is not indexed
191      * @exception IndexOutOfBoundsException if the specified index
192      *  is outside the range of the underlying property
193      * @exception NullPointerException if no array or List has been
194      *  initialized for this property
195      */
196     public Object get(String name, int index) {
197 
198         Object value = values.get(name);
199         if (value == null) {
200             throw new NullPointerException
201                     ("No indexed value for '" + name + "[" + index + "]'");
202         } else if (value.getClass().isArray()) {
203             return (Array.get(value, index));
204         } else if (value instanceof List) {
205             return ((List) value).get(index);
206         } else {
207             throw new IllegalArgumentException
208                     ("Non-indexed property for '" + name + "[" + index + "]'");
209         }
210 
211     }
212 
213 
214     /**
215      * Return the value of a mapped property with the specified name,
216      * or <code>null</code> if there is no value for the specified key.
217      *
218      * @param name Name of the property whose value is to be retrieved
219      * @param key Key of the value to be retrieved
220      * @return The mapped property's value
221      *
222      * @exception IllegalArgumentException if there is no property
223      *  of the specified name
224      * @exception IllegalArgumentException if the specified property
225      *  exists, but is not mapped
226      */
227     public Object get(String name, String key) {
228 
229         Object value = values.get(name);
230         if (value == null) {
231             throw new NullPointerException
232                     ("No mapped value for '" + name + "(" + key + ")'");
233         } else if (value instanceof Map) {
234             return (((Map) value).get(key));
235         } else {
236             throw new IllegalArgumentException
237                     ("Non-mapped property for '" + name + "(" + key + ")'");
238         }
239 
240     }
241 
242 
243     /**
244      * Return the <code>DynaClass</code> instance that describes the set of
245      * properties available for this DynaBean.
246      *
247      * @return The associated DynaClass
248      */
249     public DynaClass getDynaClass() {
250 
251         return (this.dynaClass);
252 
253     }
254 
255 
256     /**
257      * Remove any existing value for the specified key on the
258      * specified mapped property.
259      *
260      * @param name Name of the property for which a value is to
261      *  be removed
262      * @param key Key of the value to be removed
263      *
264      * @exception IllegalArgumentException if there is no property
265      *  of the specified name
266      */
267     public void remove(String name, String key) {
268 
269         Object value = values.get(name);
270         if (value == null) {
271             throw new NullPointerException
272                     ("No mapped value for '" + name + "(" + key + ")'");
273         } else if (value instanceof Map) {
274             ((Map) value).remove(key);
275         } else {
276             throw new IllegalArgumentException
277                     ("Non-mapped property for '" + name + "(" + key + ")'");
278         }
279 
280     }
281 
282 
283     /**
284      * Set the value of a simple property with the specified name.
285      *
286      * @param name Name of the property whose value is to be set
287      * @param value Value to which this property is to be set
288      *
289      * @exception ConversionException if the specified value cannot be
290      *  converted to the type required for this property
291      * @exception IllegalArgumentException if there is no property
292      *  of the specified name
293      * @exception NullPointerException if an attempt is made to set a
294      *  primitive property to null
295      */
296     public void set(String name, Object value) {
297 
298         DynaProperty descriptor = getDynaProperty(name);
299         if (value == null) {
300             if (descriptor.getType().isPrimitive()) {
301                 throw new NullPointerException
302                         ("Primitive value for '" + name + "'");
303             }
304         } else if (!isAssignable(descriptor.getType(), value.getClass())) {
305             throw new ConversionException
306                     ("Cannot assign value of type '" +
307                     value.getClass().getName() +
308                     "' to property '" + name + "' of type '" +
309                     descriptor.getType().getName() + "'");
310         }
311         values.put(name, value);
312 
313     }
314 
315 
316     /**
317      * Set the value of an indexed property with the specified name.
318      *
319      * @param name Name of the property whose value is to be set
320      * @param index Index of the property to be set
321      * @param value Value to which this property is to be set
322      *
323      * @exception ConversionException if the specified value cannot be
324      *  converted to the type required for this property
325      * @exception IllegalArgumentException if there is no property
326      *  of the specified name
327      * @exception IllegalArgumentException if the specified property
328      *  exists, but is not indexed
329      * @exception IndexOutOfBoundsException if the specified index
330      *  is outside the range of the underlying property
331      */
332     public void set(String name, int index, Object value) {
333 
334         Object prop = values.get(name);
335         if (prop == null) {
336             throw new NullPointerException
337                     ("No indexed value for '" + name + "[" + index + "]'");
338         } else if (prop.getClass().isArray()) {
339             Array.set(prop, index, value);
340         } else if (prop instanceof List) {
341             try {
342                 ((List) prop).set(index, value);
343             } catch (ClassCastException e) {
344                 throw new ConversionException(e.getMessage());
345             }
346         } else {
347             throw new IllegalArgumentException
348                     ("Non-indexed property for '" + name + "[" + index + "]'");
349         }
350 
351     }
352 
353 
354     /**
355      * Set the value of a mapped property with the specified name.
356      *
357      * @param name Name of the property whose value is to be set
358      * @param key Key of the property to be set
359      * @param value Value to which this property is to be set
360      *
361      * @exception ConversionException if the specified value cannot be
362      *  converted to the type required for this property
363      * @exception IllegalArgumentException if there is no property
364      *  of the specified name
365      * @exception IllegalArgumentException if the specified property
366      *  exists, but is not mapped
367      */
368     public void set(String name, String key, Object value) {
369 
370         Object prop = values.get(name);
371         if (prop == null) {
372             throw new NullPointerException
373                     ("No mapped value for '" + name + "(" + key + ")'");
374         } else if (prop instanceof Map) {
375             ((Map) prop).put(key, value);
376         } else {
377             throw new IllegalArgumentException
378                     ("Non-mapped property for '" + name + "(" + key + ")'");
379         }
380 
381     }
382 
383 
384     // ------------------------------------------------------ Protected Methods
385 
386 
387     /**
388      * Return the property descriptor for the specified property name.
389      *
390      * @param name Name of the property for which to retrieve the descriptor
391      * @return The property descriptor
392      *
393      * @exception IllegalArgumentException if this is not a valid property
394      *  name for our DynaClass
395      */
396     protected DynaProperty getDynaProperty(String name) {
397 
398         DynaProperty descriptor = getDynaClass().getDynaProperty(name);
399         if (descriptor == null) {
400             throw new IllegalArgumentException
401                     ("Invalid property name '" + name + "'");
402         }
403         return (descriptor);
404 
405     }
406 
407 
408     /**
409      * Is an object of the source class assignable to the destination class?
410      *
411      * @param dest Destination class
412      * @param source Source class
413      * @return <code>true</code> if the source class is assignable to the
414      * destination class, otherwise <code>false</code>
415      */
416     protected boolean isAssignable(Class dest, Class source) {
417 
418         if (dest.isAssignableFrom(source) ||
419                 ((dest == Boolean.TYPE) && (source == Boolean.class)) ||
420                 ((dest == Byte.TYPE) && (source == Byte.class)) ||
421                 ((dest == Character.TYPE) && (source == Character.class)) ||
422                 ((dest == Double.TYPE) && (source == Double.class)) ||
423                 ((dest == Float.TYPE) && (source == Float.class)) ||
424                 ((dest == Integer.TYPE) && (source == Integer.class)) ||
425                 ((dest == Long.TYPE) && (source == Long.class)) ||
426                 ((dest == Short.TYPE) && (source == Short.class))) {
427             return (true);
428         } else {
429             return (false);
430         }
431 
432     }
433 
434 
435 }