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