View Javadoc

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