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 }