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 }