001    /*
002     * Licensed to the Apache Software Foundation (ASF) under one or more
003     * contributor license agreements.  See the NOTICE file distributed with
004     * this work for additional information regarding copyright ownership.
005     * The ASF licenses this file to You under the Apache License, Version 2.0
006     * (the "License"); you may not use this file except in compliance with
007     * the License.  You may obtain a copy of the License at
008     *
009     *      http://www.apache.org/licenses/LICENSE-2.0
010     *
011     * Unless required by applicable law or agreed to in writing, software
012     * distributed under the License is distributed on an "AS IS" BASIS,
013     * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014     * See the License for the specific language governing permissions and
015     * limitations under the License.
016     */
017    
018    
019    package org.apache.commons.beanutils;
020    
021    
022    import java.io.Serializable;
023    import java.lang.reflect.Array;
024    import java.util.HashMap;
025    import java.util.List;
026    import java.util.Map;
027    
028    
029    /**
030     * <p>Minimal implementation of the <code>DynaBean</code> interface.  Can be
031     * used as a convenience base class for more sophisticated implementations.</p>
032     *
033     * <p><strong>IMPLEMENTATION NOTE</strong> - Instances of this class that are
034     * accessed from multiple threads simultaneously need to be synchronized.</p>
035     *
036     * <p><strong>IMPLEMENTATION NOTE</strong> - Instances of this class can be
037     * successfully serialized and deserialized <strong>ONLY</strong> if all
038     * property values are <code>Serializable</code>.</p>
039     *
040     * @author Craig McClanahan
041     * @version $Revision: 555824 $ $Date: 2007-07-13 01:27:15 +0100 (Fri, 13 Jul 2007) $
042     */
043    
044    public class BasicDynaBean implements DynaBean, Serializable {
045    
046    
047        // ---------------------------------------------------------- Constructors
048    
049    
050        /**
051         * Construct a new <code>DynaBean</code> associated with the specified
052         * <code>DynaClass</code> instance.
053         *
054         * @param dynaClass The DynaClass we are associated with
055         */
056        public BasicDynaBean(DynaClass dynaClass) {
057    
058            super();
059            this.dynaClass = dynaClass;
060    
061        }
062    
063    
064        // ---------------------------------------------------- Instance Variables
065    
066    
067        /**
068         * The <code>DynaClass</code> "base class" that this DynaBean
069         * is associated with.
070         */
071        protected DynaClass dynaClass = null;
072    
073    
074        /**
075         * The set of property values for this DynaBean, keyed by property name.
076         */
077        protected HashMap values = new HashMap();
078    
079        /** Map decorator for this DynaBean */
080        private transient Map mapDecorator;
081    
082        /**
083         * Return a Map representation of this DynaBean.
084         * </p>
085         * This, for example, could be used in JSTL in the following way to access
086         * a DynaBean's <code>fooProperty</code>:
087         * <ul><li><code>${myDynaBean.<b>map</b>.fooProperty}</code></li></ul>
088         *
089         * @return a Map representation of this DynaBean
090         */
091        public Map getMap() {
092    
093            // cache the Map
094            if (mapDecorator == null) {
095                mapDecorator = new DynaBeanMapDecorator(this);
096            }
097            return mapDecorator;
098    
099        }
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    }