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: 690380 $ $Date: 2008-08-29 21:04:38 +0100 (Fri, 29 Aug 2008) $
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         * @since 1.8.0
091         */
092        public Map getMap() {
093    
094            // cache the Map
095            if (mapDecorator == null) {
096                mapDecorator = new DynaBeanMapDecorator(this);
097            }
098            return mapDecorator;
099    
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    }