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: package org.apache.commons.beanutils;
019:
020: import java.io.Serializable;
021: import java.lang.reflect.Array;
022: import java.util.HashMap;
023: import java.util.List;
024: import java.util.Map;
025:
026: /**
027: * <p>Minimal implementation of the <code>DynaBean</code> interface. Can be
028: * used as a convenience base class for more sophisticated implementations.</p>
029: *
030: * <p><strong>IMPLEMENTATION NOTE</strong> - Instances of this class that are
031: * accessed from multiple threads simultaneously need to be synchronized.</p>
032: *
033: * <p><strong>IMPLEMENTATION NOTE</strong> - Instances of this class can be
034: * successfully serialized and deserialized <strong>ONLY</strong> if all
035: * property values are <code>Serializable</code>.</p>
036: *
037: * @author Craig McClanahan
038: * @version $Revision: 555824 $ $Date: 2007-07-13 01:27:15 +0100 (Fri, 13 Jul 2007) $
039: */
040:
041: public class BasicDynaBean implements DynaBean, Serializable {
042:
043: // ---------------------------------------------------------- Constructors
044:
045: /**
046: * Construct a new <code>DynaBean</code> associated with the specified
047: * <code>DynaClass</code> instance.
048: *
049: * @param dynaClass The DynaClass we are associated with
050: */
051: public BasicDynaBean(DynaClass dynaClass) {
052:
053: super ();
054: this .dynaClass = dynaClass;
055:
056: }
057:
058: // ---------------------------------------------------- Instance Variables
059:
060: /**
061: * The <code>DynaClass</code> "base class" that this DynaBean
062: * is associated with.
063: */
064: protected DynaClass dynaClass = null;
065:
066: /**
067: * The set of property values for this DynaBean, keyed by property name.
068: */
069: protected HashMap values = new HashMap();
070:
071: /** Map decorator for this DynaBean */
072: private transient Map mapDecorator;
073:
074: /**
075: * Return a Map representation of this DynaBean.
076: * </p>
077: * This, for example, could be used in JSTL in the following way to access
078: * a DynaBean's <code>fooProperty</code>:
079: * <ul><li><code>${myDynaBean.<b>map</b>.fooProperty}</code></li></ul>
080: *
081: * @return a Map representation of this DynaBean
082: */
083: public Map getMap() {
084:
085: // cache the Map
086: if (mapDecorator == null) {
087: mapDecorator = new DynaBeanMapDecorator(this );
088: }
089: return mapDecorator;
090:
091: }
092:
093: // ------------------------------------------------------ DynaBean Methods
094:
095: /**
096: * Does the specified mapped property contain a value for the specified
097: * key value?
098: *
099: * @param name Name of the property to check
100: * @param key Name of the key to check
101: * @return <code>true<code> if the mapped property contains a value for
102: * the specified key, otherwise <code>false</code>
103: *
104: * @exception IllegalArgumentException if there is no property
105: * of the specified name
106: */
107: public boolean contains(String name, String key) {
108:
109: Object value = values.get(name);
110: if (value == null) {
111: throw new NullPointerException("No mapped value for '"
112: + name + "(" + key + ")'");
113: } else if (value instanceof Map) {
114: return (((Map) value).containsKey(key));
115: } else {
116: throw new IllegalArgumentException(
117: "Non-mapped property for '" + name + "(" + key
118: + ")'");
119: }
120:
121: }
122:
123: /**
124: * Return the value of a simple property with the specified name.
125: *
126: * @param name Name of the property whose value is to be retrieved
127: * @return The property's value
128: *
129: * @exception IllegalArgumentException if there is no property
130: * of the specified name
131: */
132: public Object get(String name) {
133:
134: // Return any non-null value for the specified property
135: Object value = values.get(name);
136: if (value != null) {
137: return (value);
138: }
139:
140: // Return a null value for a non-primitive property
141: Class type = getDynaProperty(name).getType();
142: if (!type.isPrimitive()) {
143: return (value);
144: }
145:
146: // Manufacture default values for primitive properties
147: if (type == Boolean.TYPE) {
148: return (Boolean.FALSE);
149: } else if (type == Byte.TYPE) {
150: return (new Byte((byte) 0));
151: } else if (type == Character.TYPE) {
152: return (new Character((char) 0));
153: } else if (type == Double.TYPE) {
154: return (new Double(0.0));
155: } else if (type == Float.TYPE) {
156: return (new Float((float) 0.0));
157: } else if (type == Integer.TYPE) {
158: return (new Integer(0));
159: } else if (type == Long.TYPE) {
160: return (new Long(0));
161: } else if (type == Short.TYPE) {
162: return (new Short((short) 0));
163: } else {
164: return (null);
165: }
166:
167: }
168:
169: /**
170: * Return the value of an indexed property with the specified name.
171: *
172: * @param name Name of the property whose value is to be retrieved
173: * @param index Index of the value to be retrieved
174: * @return The indexed property's value
175: *
176: * @exception IllegalArgumentException if there is no property
177: * of the specified name
178: * @exception IllegalArgumentException if the specified property
179: * exists, but is not indexed
180: * @exception IndexOutOfBoundsException if the specified index
181: * is outside the range of the underlying property
182: * @exception NullPointerException if no array or List has been
183: * initialized for this property
184: */
185: public Object get(String name, int index) {
186:
187: Object value = values.get(name);
188: if (value == null) {
189: throw new NullPointerException("No indexed value for '"
190: + name + "[" + index + "]'");
191: } else if (value.getClass().isArray()) {
192: return (Array.get(value, index));
193: } else if (value instanceof List) {
194: return ((List) value).get(index);
195: } else {
196: throw new IllegalArgumentException(
197: "Non-indexed property for '" + name + "[" + index
198: + "]'");
199: }
200:
201: }
202:
203: /**
204: * Return the value of a mapped property with the specified name,
205: * or <code>null</code> if there is no value for the specified key.
206: *
207: * @param name Name of the property whose value is to be retrieved
208: * @param key Key of the value to be retrieved
209: * @return The mapped property's value
210: *
211: * @exception IllegalArgumentException if there is no property
212: * of the specified name
213: * @exception IllegalArgumentException if the specified property
214: * exists, but is not mapped
215: */
216: public Object get(String name, String key) {
217:
218: Object value = values.get(name);
219: if (value == null) {
220: throw new NullPointerException("No mapped value for '"
221: + name + "(" + key + ")'");
222: } else if (value instanceof Map) {
223: return (((Map) value).get(key));
224: } else {
225: throw new IllegalArgumentException(
226: "Non-mapped property for '" + name + "(" + key
227: + ")'");
228: }
229:
230: }
231:
232: /**
233: * Return the <code>DynaClass</code> instance that describes the set of
234: * properties available for this DynaBean.
235: *
236: * @return The associated DynaClass
237: */
238: public DynaClass getDynaClass() {
239:
240: return (this .dynaClass);
241:
242: }
243:
244: /**
245: * Remove any existing value for the specified key on the
246: * specified mapped property.
247: *
248: * @param name Name of the property for which a value is to
249: * be removed
250: * @param key Key of the value to be removed
251: *
252: * @exception IllegalArgumentException if there is no property
253: * of the specified name
254: */
255: public void remove(String name, String key) {
256:
257: Object value = values.get(name);
258: if (value == null) {
259: throw new NullPointerException("No mapped value for '"
260: + name + "(" + key + ")'");
261: } else if (value instanceof Map) {
262: ((Map) value).remove(key);
263: } else {
264: throw new IllegalArgumentException(
265: "Non-mapped property for '" + name + "(" + key
266: + ")'");
267: }
268:
269: }
270:
271: /**
272: * Set the value of a simple property with the specified name.
273: *
274: * @param name Name of the property whose value is to be set
275: * @param value Value to which this property is to be set
276: *
277: * @exception ConversionException if the specified value cannot be
278: * converted to the type required for this property
279: * @exception IllegalArgumentException if there is no property
280: * of the specified name
281: * @exception NullPointerException if an attempt is made to set a
282: * primitive property to null
283: */
284: public void set(String name, Object value) {
285:
286: DynaProperty descriptor = getDynaProperty(name);
287: if (value == null) {
288: if (descriptor.getType().isPrimitive()) {
289: throw new NullPointerException("Primitive value for '"
290: + name + "'");
291: }
292: } else if (!isAssignable(descriptor.getType(), value.getClass())) {
293: throw new ConversionException(
294: "Cannot assign value of type '"
295: + value.getClass().getName()
296: + "' to property '" + name + "' of type '"
297: + descriptor.getType().getName() + "'");
298: }
299: values.put(name, value);
300:
301: }
302:
303: /**
304: * Set the value of an indexed property with the specified name.
305: *
306: * @param name Name of the property whose value is to be set
307: * @param index Index of the property to be set
308: * @param value Value to which this property is to be set
309: *
310: * @exception ConversionException if the specified value cannot be
311: * converted to the type required for this property
312: * @exception IllegalArgumentException if there is no property
313: * of the specified name
314: * @exception IllegalArgumentException if the specified property
315: * exists, but is not indexed
316: * @exception IndexOutOfBoundsException if the specified index
317: * is outside the range of the underlying property
318: */
319: public void set(String name, int index, Object value) {
320:
321: Object prop = values.get(name);
322: if (prop == null) {
323: throw new NullPointerException("No indexed value for '"
324: + name + "[" + index + "]'");
325: } else if (prop.getClass().isArray()) {
326: Array.set(prop, index, value);
327: } else if (prop instanceof List) {
328: try {
329: ((List) prop).set(index, value);
330: } catch (ClassCastException e) {
331: throw new ConversionException(e.getMessage());
332: }
333: } else {
334: throw new IllegalArgumentException(
335: "Non-indexed property for '" + name + "[" + index
336: + "]'");
337: }
338:
339: }
340:
341: /**
342: * Set the value of a mapped property with the specified name.
343: *
344: * @param name Name of the property whose value is to be set
345: * @param key Key of the property to be set
346: * @param value Value to which this property is to be set
347: *
348: * @exception ConversionException if the specified value cannot be
349: * converted to the type required for this property
350: * @exception IllegalArgumentException if there is no property
351: * of the specified name
352: * @exception IllegalArgumentException if the specified property
353: * exists, but is not mapped
354: */
355: public void set(String name, String key, Object value) {
356:
357: Object prop = values.get(name);
358: if (prop == null) {
359: throw new NullPointerException("No mapped value for '"
360: + name + "(" + key + ")'");
361: } else if (prop instanceof Map) {
362: ((Map) prop).put(key, value);
363: } else {
364: throw new IllegalArgumentException(
365: "Non-mapped property for '" + name + "(" + key
366: + ")'");
367: }
368:
369: }
370:
371: // ------------------------------------------------------ Protected Methods
372:
373: /**
374: * Return the property descriptor for the specified property name.
375: *
376: * @param name Name of the property for which to retrieve the descriptor
377: * @return The property descriptor
378: *
379: * @exception IllegalArgumentException if this is not a valid property
380: * name for our DynaClass
381: */
382: protected DynaProperty getDynaProperty(String name) {
383:
384: DynaProperty descriptor = getDynaClass().getDynaProperty(name);
385: if (descriptor == null) {
386: throw new IllegalArgumentException(
387: "Invalid property name '" + name + "'");
388: }
389: return (descriptor);
390:
391: }
392:
393: /**
394: * Is an object of the source class assignable to the destination class?
395: *
396: * @param dest Destination class
397: * @param source Source class
398: * @return <code>true</code> if the source class is assignable to the
399: * destination class, otherwise <code>false</code>
400: */
401: protected boolean isAssignable(Class dest, Class source) {
402:
403: if (dest.isAssignableFrom(source)
404: || ((dest == Boolean.TYPE) && (source == Boolean.class))
405: || ((dest == Byte.TYPE) && (source == Byte.class))
406: || ((dest == Character.TYPE) && (source == Character.class))
407: || ((dest == Double.TYPE) && (source == Double.class))
408: || ((dest == Float.TYPE) && (source == Float.class))
409: || ((dest == Integer.TYPE) && (source == Integer.class))
410: || ((dest == Long.TYPE) && (source == Long.class))
411: || ((dest == Short.TYPE) && (source == Short.class))) {
412: return (true);
413: } else {
414: return (false);
415: }
416:
417: }
418:
419: }
|