1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16 package org.apache.commons.beanutils;
17
18 import java.util.Map;
19 import java.util.Iterator;
20
21 /**
22 * <p>Provides a <i>light weight</i> <code>DynaBean</code> facade to a <code>Map</code> with <i>lazy</i> map/list processing.</p>
23 *
24 * <p>Its a <i>light weight</i> <code>DynaBean</code> implementation because there is no
25 * actual <code>DynaClass</code> associated with this <code>DynaBean</code> - in fact
26 * it implements the <code>DynaClass</code> interface itself providing <i>pseudo</i> DynaClass
27 * behaviour from the actual values stored in the <code>Map</code>.</p>
28 *
29 * <p>As well providing rhe standard <code>DynaBean</code> access to the <code>Map</code>'s properties
30 * this class also provides the usual <i>Lazy</i> behaviour:</p>
31 * <ul>
32 * <li>Properties don't need to be pre-defined in a <code>DynaClass</code></li>
33 * <li>Indexed properties (<code>Lists</code> or <code>Arrays</code>) are automatically instantiated
34 * and <i>grown</i> so that they are large enough to cater for the index being set.</li>
35 * <li>Mapped properties are automatically instantiated.</li>
36 * </ul>
37 *
38 * <p><b><u><i>Restricted</i> DynaClass</u></b></p>
39 * <p>This class implements the <code>MutableDynaClass</code> interface.
40 * <code>MutableDynaClass</code> have a facility to <i>restrict</i> the <code>DynaClass</code>
41 * so that its properties cannot be modified. If the <code>MutableDynaClass</code> is
42 * restricted then calling any of the <code>set()</code> methods for a property which
43 * doesn't exist will result in a <code>IllegalArgumentException</code> being thrown.</p>
44 *
45 * @author Niall Pemberton
46 */
47 public class LazyDynaMap extends LazyDynaBean implements MutableDynaClass {
48
49 /**
50 * The name of this DynaClass (analogous to the
51 * <code>getName()</code> method of <code>java.lang.Class</code>).
52 */
53 protected String name;
54
55 /**
56 * Controls whether changes to this DynaClass's properties are allowed.
57 */
58 protected boolean restricted;
59
60 /**
61 * <p>Controls whether the <code>getDynaProperty()</code> method returns
62 * null if a property doesn't exist - or creates a new one.</p>
63 *
64 * <p>Default is <code>false</code>.
65 */
66 protected boolean returnNull = false;
67
68
69
70
71 /**
72 * Default Constructor.
73 */
74 public LazyDynaMap() {
75 this(null, (Map)null);
76 }
77
78 /**
79 * Construct a new <code>LazyDynaMap</code> with the specified name.
80 *
81 * @param name Name of this DynaBean class
82 */
83 public LazyDynaMap(String name) {
84 this(name, (Map)null);
85 }
86
87 /**
88 * Construct a new <code>LazyDynaMap</code> with the specified <code>Map</code>.
89 *
90 * @param values The Map backing this <code>LazyDynaMap</code>
91 */
92 public LazyDynaMap(Map values) {
93 this(null, values);
94 }
95
96 /**
97 * Construct a new <code>LazyDynaMap</code> with the specified name and <code>Map</code>.
98 *
99 * @param name Name of this DynaBean class
100 * @param values The Map backing this <code>LazyDynaMap</code>
101 */
102 public LazyDynaMap(String name, Map values) {
103 this.name = name == null ? "LazyDynaMap" : name;
104 this.values = values == null ? newMap() : values;
105 this.dynaClass = this;
106 }
107
108 /**
109 * Construct a new <code>LazyDynaMap</code> with the specified properties.
110 *
111 * @param properties Property descriptors for the supported properties
112 */
113 public LazyDynaMap(DynaProperty[] properties) {
114 this(null, properties);
115 }
116
117 /**
118 * Construct a new <code>LazyDynaMap</code> with the specified name and properties.
119 *
120 * @param name Name of this DynaBean class
121 * @param properties Property descriptors for the supported properties
122 */
123 public LazyDynaMap(String name, DynaProperty[] properties) {
124 this(name, (Map)null);
125 if (properties != null) {
126 for (int i = 0; i < properties.length; i++) {
127 add(properties[i]);
128 }
129 }
130 }
131
132 /**
133 * Construct a new <code>LazyDynaMap</code> based on an exisiting DynaClass
134 *
135 * @param dynaClass DynaClass to copy the name and properties from
136 */
137 public LazyDynaMap(DynaClass dynaClass) {
138 this(dynaClass.getName(), dynaClass.getDynaProperties());
139 }
140
141
142
143 /**
144 * Set the Map backing this <code>DynaBean</code>
145 */
146 public void setMap(Map values) {
147 this.values = values;
148 }
149
150
151
152 /**
153 * Set the value of a simple property with the specified name.
154 *
155 * @param name Name of the property whose value is to be set
156 * @param value Value to which this property is to be set
157 */
158 public void set(String name, Object value) {
159
160 if (isRestricted() && !values.containsKey(name)) {
161 throw new IllegalArgumentException
162 ("Invalid property name '" + name + "' (DynaClass is restricted)");
163 }
164
165 values.put(name, value);
166
167 }
168
169
170
171 /**
172 * Return the name of this DynaClass (analogous to the
173 * <code>getName()</code> method of <code>java.lang.Class</code)
174 */
175 public String getName() {
176 return this.name;
177 }
178
179 /**
180 * <p>Return a property descriptor for the specified property.</p>
181 *
182 * <p>If the property is not found and the <code>returnNull</code> indicator is
183 * <code>true</code>, this method always returns <code>null</code>.</p>
184 *
185 * <p>If the property is not found and the <code>returnNull</code> indicator is
186 * <code>false</code> a new property descriptor is created and returned (although
187 * its not actually added to the DynaClass's properties). This is the default
188 * beahviour.</p>
189 *
190 * <p>The reason for not returning a <code>null</code> property descriptor is that
191 * <code>BeanUtils</code> uses this method to check if a property exists
192 * before trying to set it - since these <i>Map</i> implementations automatically
193 * add any new properties when they are set, returning <code>null</code> from
194 * this method would defeat their purpose.</p>
195 *
196 * @param name Name of the dynamic property for which a descriptor
197 * is requested
198 *
199 * @exception IllegalArgumentException if no property name is specified
200 */
201 public DynaProperty getDynaProperty(String name) {
202
203 if (name == null)
204 throw new IllegalArgumentException("Property name is missing.");
205
206
207
208 if (!values.containsKey(name) && isReturnNull()) {
209 return null;
210 }
211
212 Object value = values.get(name);
213
214 if (value == null) {
215 return new DynaProperty(name);
216 } else {
217 return new DynaProperty(name, value.getClass());
218 }
219
220 }
221
222 /**
223 * <p>Return an array of <code>ProperyDescriptors</code> for the properties
224 * currently defined in this DynaClass. If no properties are defined, a
225 * zero-length array will be returned.</p>
226 *
227 * <p><strong>FIXME</strong> - Should we really be implementing
228 * <code>getBeanInfo()</code> instead, which returns property descriptors
229 * and a bunch of other stuff?</p>
230 */
231 public DynaProperty[] getDynaProperties() {
232
233 int i = 0;
234 DynaProperty[] properties = new DynaProperty[values.size()];
235 Iterator iterator = values.keySet().iterator();
236
237 while (iterator.hasNext()) {
238 String name = (String)iterator.next();
239 Object value = values.get(name);
240 properties[i++] = new DynaProperty(name, value == null ? null : value.getClass());
241 }
242
243 return properties;
244
245 }
246
247 /**
248 * Instantiate and return a new DynaBean instance, associated
249 * with this DynaClass.
250 */
251 public DynaBean newInstance() {
252 return new LazyDynaMap(this);
253 }
254
255
256
257
258 /**
259 * <p>Is this DynaClass currently restricted.</p>
260 * <p>If restricted, no changes to the existing registration of
261 * property names, data types, readability, or writeability are allowed.</p>
262 */
263 public boolean isRestricted() {
264 return restricted;
265 }
266
267 /**
268 * <p>Set whether this DynaClass is currently restricted.</p>
269 * <p>If restricted, no changes to the existing registration of
270 * property names, data types, readability, or writeability are allowed.</p>
271 */
272 public void setRestricted(boolean restricted) {
273 this.restricted = restricted;
274 }
275
276 /**
277 * Add a new dynamic property with no restrictions on data type,
278 * readability, or writeability.
279 *
280 * @param name Name of the new dynamic property
281 *
282 * @exception IllegalArgumentException if name is null
283 */
284 public void add(String name) {
285 add(name, null);
286 }
287
288 /**
289 * Add a new dynamic property with the specified data type, but with
290 * no restrictions on readability or writeability.
291 *
292 * @param name Name of the new dynamic property
293 * @param type Data type of the new dynamic property (null for no
294 * restrictions)
295 *
296 * @exception IllegalArgumentException if name is null
297 * @exception IllegalStateException if this DynaClass is currently
298 * restricted, so no new properties can be added
299 */
300 public void add(String name, Class type) {
301
302 if (name == null) {
303 throw new IllegalArgumentException("Property name is missing.");
304 }
305
306 if (isRestricted())
307 throw new IllegalStateException("DynaClass is currently restricted. No new properties can be added.");
308
309 Object value = values.get(name);
310
311
312 if (value == null) {
313 values.put(name, type == null ? null : createProperty(name, type));
314 }
315
316 }
317
318 /**
319 * <p>Add a new dynamic property with the specified data type, readability,
320 * and writeability.</p>
321 *
322 * <p><strong>N.B.</strong>Support for readable/writeable properties has not been implemented
323 * and this method always throws a <code>UnsupportedOperationException</code>.</p>
324 *
325 * <p>I'm not sure the intention of the original authors for this method, but it seems to
326 * me that readable/writable should be attributes of the <code>DynaProperty</code> class
327 * (which they are not) and is the reason this method has not been implemented.</p>
328 *
329 * @param name Name of the new dynamic property
330 * @param type Data type of the new dynamic property (null for no
331 * restrictions)
332 * @param readable Set to <code>true</code> if this property value
333 * should be readable
334 * @param writeable Set to <code>true</code> if this property value
335 * should be writeable
336 *
337 * @exception UnsupportedOperationException anytime this method is called
338 */
339 public void add(String name, Class type, boolean readable, boolean writeable) {
340 throw new java.lang.UnsupportedOperationException("readable/writable properties not supported");
341 }
342
343 /**
344 * Add a new dynamic property.
345 *
346 * @param property Property the new dynamic property to add.
347 *
348 * @exception IllegalArgumentException if name is null
349 */
350 protected void add(DynaProperty property) {
351 add(property.getName(), property.getType());
352 }
353
354 /**
355 * Remove the specified dynamic property, and any associated data type,
356 * readability, and writeability, from this dynamic class.
357 * <strong>NOTE</strong> - This does <strong>NOT</strong> cause any
358 * corresponding property values to be removed from DynaBean instances
359 * associated with this DynaClass.
360 *
361 * @param name Name of the dynamic property to remove
362 *
363 * @exception IllegalArgumentException if name is null
364 * @exception IllegalStateException if this DynaClass is currently
365 * restricted, so no properties can be removed
366 */
367 public void remove(String name) {
368
369 if (name == null) {
370 throw new IllegalArgumentException("Property name is missing.");
371 }
372
373 if (isRestricted()) {
374 throw new IllegalStateException("DynaClass is currently restricted. No properties can be removed.");
375 }
376
377
378 if (values.containsKey(name)) {
379 values.remove(name);
380 }
381
382 }
383
384
385
386
387 /**
388 * Should this DynaClass return a <code>null</code> from
389 * the <code>getDynaProperty(name)</code> method if the property
390 * doesn't exist.
391 */
392 public boolean isReturnNull() {
393 return returnNull;
394 }
395
396 /**
397 * Set whether this DynaClass should return a <code>null</code> from
398 * the <code>getDynaProperty(name)</code> method if the property
399 * doesn't exist.
400 */
401 public void setReturnNull(boolean returnNull) {
402 this.returnNull = returnNull;
403 }
404
405
406
407
408 /**
409 * <p>Indicate whether a property actually exists.</p>
410 *
411 * <p><strong>N.B.</strong> Using <code>getDynaProperty(name) == null</code>
412 * doesn't work in this implementation because that method might
413 * return a DynaProperty if it doesn't exist (depending on the
414 * <code>returnNull</code> indicator).</p>
415 *
416 * @exception IllegalArgumentException if no property name is specified
417 */
418 protected boolean isDynaProperty(String name) {
419
420 if (name == null) {
421 throw new IllegalArgumentException("Property name is missing.");
422 }
423
424 return values.containsKey(name);
425
426 }
427
428 }