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: package org.apache.commons.beanutils;
018:
019: import java.util.Map;
020: import java.util.Iterator;
021:
022: /**
023: * <p>Provides a <i>light weight</i> <code>DynaBean</code> facade to a <code>Map</code>
024: * with <i>lazy</i> map/list processing.</p>
025: *
026: * <p>Its a <i>light weight</i> <code>DynaBean</code> implementation because there is no
027: * actual <code>DynaClass</code> associated with this <code>DynaBean</code> - in fact
028: * it implements the <code>DynaClass</code> interface itself providing <i>pseudo</i> DynaClass
029: * behaviour from the actual values stored in the <code>Map</code>.</p>
030: *
031: * <p>As well providing rhe standard <code>DynaBean</code> access to the <code>Map</code>'s properties
032: * this class also provides the usual <i>Lazy</i> behaviour:</p>
033: * <ul>
034: * <li>Properties don't need to be pre-defined in a <code>DynaClass</code></li>
035: * <li>Indexed properties (<code>Lists</code> or <code>Arrays</code>) are automatically instantiated
036: * and <i>grown</i> so that they are large enough to cater for the index being set.</li>
037: * <li>Mapped properties are automatically instantiated.</li>
038: * </ul>
039: *
040: * <p><b><u><i>Restricted</i> DynaClass</u></b></p>
041: * <p>This class implements the <code>MutableDynaClass</code> interface.
042: * <code>MutableDynaClass</code> have a facility to <i>restrict</i> the <code>DynaClass</code>
043: * so that its properties cannot be modified. If the <code>MutableDynaClass</code> is
044: * restricted then calling any of the <code>set()</code> methods for a property which
045: * doesn't exist will result in a <code>IllegalArgumentException</code> being thrown.</p>
046: *
047: * @author Niall Pemberton
048: */
049: public class LazyDynaMap extends LazyDynaBean implements
050: MutableDynaClass {
051:
052: /**
053: * The name of this DynaClass (analogous to the
054: * <code>getName()</code> method of <code>java.lang.Class</code>).
055: */
056: protected String name;
057:
058: /**
059: * Controls whether changes to this DynaClass's properties are allowed.
060: */
061: protected boolean restricted;
062:
063: /**
064: * <p>Controls whether the <code>getDynaProperty()</code> method returns
065: * null if a property doesn't exist - or creates a new one.</p>
066: *
067: * <p>Default is <code>false</code>.
068: */
069: protected boolean returnNull = false;
070:
071: // ------------------- Constructors ----------------------------------
072:
073: /**
074: * Default Constructor.
075: */
076: public LazyDynaMap() {
077: this (null, (Map) null);
078: }
079:
080: /**
081: * Construct a new <code>LazyDynaMap</code> with the specified name.
082: *
083: * @param name Name of this DynaBean class
084: */
085: public LazyDynaMap(String name) {
086: this (name, (Map) null);
087: }
088:
089: /**
090: * Construct a new <code>LazyDynaMap</code> with the specified <code>Map</code>.
091: *
092: * @param values The Map backing this <code>LazyDynaMap</code>
093: */
094: public LazyDynaMap(Map values) {
095: this (null, values);
096: }
097:
098: /**
099: * 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: public LazyDynaMap(String name, Map values) {
105: this .name = name == null ? "LazyDynaMap" : name;
106: this .values = values == null ? newMap() : values;
107: this .dynaClass = this ;
108: }
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: this (null, properties);
117: }
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: this (name, (Map) null);
127: if (properties != null) {
128: for (int i = 0; i < properties.length; i++) {
129: add(properties[i]);
130: }
131: }
132: }
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: this (dynaClass.getName(), dynaClass.getDynaProperties());
141: }
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: this .values = values;
152: }
153:
154: /**
155: * Return the underlying Map backing this <code>DynaBean</code>
156: * @return the underlying Map
157: */
158: public Map getMap() {
159: return values;
160: }
161:
162: // ------------------- DynaBean Methods ----------------------------------
163:
164: /**
165: * Set the value of a simple property with the specified name.
166: *
167: * @param name Name of the property whose value is to be set
168: * @param value Value to which this property is to be set
169: */
170: public void set(String name, Object value) {
171:
172: if (isRestricted() && !values.containsKey(name)) {
173: throw new IllegalArgumentException(
174: "Invalid property name '" + name
175: + "' (DynaClass is restricted)");
176: }
177:
178: values.put(name, value);
179:
180: }
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: 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: if (name == null) {
220: throw new IllegalArgumentException(
221: "Property name is missing.");
222: }
223:
224: // If it doesn't exist and returnNull is false
225: // create a new DynaProperty
226: if (!values.containsKey(name) && isReturnNull()) {
227: return null;
228: }
229:
230: Object value = values.get(name);
231:
232: if (value == null) {
233: return new DynaProperty(name);
234: } else {
235: return new DynaProperty(name, value.getClass());
236: }
237:
238: }
239:
240: /**
241: * <p>Return an array of <code>ProperyDescriptors</code> for the properties
242: * currently defined in this DynaClass. If no properties are defined, a
243: * zero-length array will be returned.</p>
244: *
245: * <p><strong>FIXME</strong> - Should we really be implementing
246: * <code>getBeanInfo()</code> instead, which returns property descriptors
247: * and a bunch of other stuff?</p>
248: * @return the set of properties for this DynaClass
249: */
250: public DynaProperty[] getDynaProperties() {
251:
252: int i = 0;
253: DynaProperty[] properties = new DynaProperty[values.size()];
254: Iterator iterator = values.keySet().iterator();
255:
256: while (iterator.hasNext()) {
257: String name = (String) iterator.next();
258: Object value = values.get(name);
259: properties[i++] = new DynaProperty(name,
260: value == null ? null : value.getClass());
261: }
262:
263: return properties;
264:
265: }
266:
267: /**
268: * Instantiate and return a new DynaBean instance, associated
269: * with this DynaClass.
270: * @return A new <code>DynaBean</code> instance
271: */
272: public DynaBean newInstance() {
273:
274: // Create a new instance of the Map
275: Map newMap = null;
276: try {
277: newMap = (Map) getMap().getClass().newInstance();
278: } catch (Exception ex) {
279: newMap = newMap();
280: }
281:
282: // Crate new LazyDynaMap and initialize properties
283: LazyDynaMap lazyMap = new LazyDynaMap(newMap);
284: DynaProperty[] properties = this .getDynaProperties();
285: if (properties != null) {
286: for (int i = 0; i < properties.length; i++) {
287: lazyMap.add(properties[i]);
288: }
289: }
290: return lazyMap;
291: }
292:
293: // ------------------- MutableDynaClass Methods ----------------------------------
294:
295: /**
296: * <p>Is this DynaClass currently restricted.</p>
297: * <p>If restricted, no changes to the existing registration of
298: * property names, data types, readability, or writeability are allowed.</p>
299: *
300: * @return <code>true</code> if this Mutable {@link DynaClass} is restricted,
301: * otherwise <code>false</code>
302: */
303: public boolean isRestricted() {
304: return restricted;
305: }
306:
307: /**
308: * <p>Set whether this DynaClass is currently restricted.</p>
309: * <p>If restricted, no changes to the existing registration of
310: * property names, data types, readability, or writeability are allowed.</p>
311: *
312: * @param restricted The new restricted state
313: */
314: public void setRestricted(boolean restricted) {
315: this .restricted = restricted;
316: }
317:
318: /**
319: * Add a new dynamic property with no restrictions on data type,
320: * readability, or writeability.
321: *
322: * @param name Name of the new dynamic property
323: *
324: * @exception IllegalArgumentException if name is null
325: */
326: public void add(String name) {
327: add(name, null);
328: }
329:
330: /**
331: * Add a new dynamic property with the specified data type, but with
332: * no restrictions on readability or writeability.
333: *
334: * @param name Name of the new dynamic property
335: * @param type Data type of the new dynamic property (null for no
336: * restrictions)
337: *
338: * @exception IllegalArgumentException if name is null
339: * @exception IllegalStateException if this DynaClass is currently
340: * restricted, so no new properties can be added
341: */
342: public void add(String name, Class type) {
343:
344: if (name == null) {
345: throw new IllegalArgumentException(
346: "Property name is missing.");
347: }
348:
349: if (isRestricted()) {
350: throw new IllegalStateException(
351: "DynaClass is currently restricted. No new properties can be added.");
352: }
353:
354: Object value = values.get(name);
355:
356: // Check if the property already exists
357: if (value == null) {
358: values.put(name, type == null ? null : createProperty(name,
359: type));
360: }
361:
362: }
363:
364: /**
365: * <p>Add a new dynamic property with the specified data type, readability,
366: * and writeability.</p>
367: *
368: * <p><strong>N.B.</strong>Support for readable/writeable properties has not been implemented
369: * and this method always throws a <code>UnsupportedOperationException</code>.</p>
370: *
371: * <p>I'm not sure the intention of the original authors for this method, but it seems to
372: * me that readable/writable should be attributes of the <code>DynaProperty</code> class
373: * (which they are not) and is the reason this method has not been implemented.</p>
374: *
375: * @param name Name of the new dynamic property
376: * @param type Data type of the new dynamic property (null for no
377: * restrictions)
378: * @param readable Set to <code>true</code> if this property value
379: * should be readable
380: * @param writeable Set to <code>true</code> if this property value
381: * should be writeable
382: *
383: * @exception UnsupportedOperationException anytime this method is called
384: */
385: public void add(String name, Class type, boolean readable,
386: boolean writeable) {
387: throw new java.lang.UnsupportedOperationException(
388: "readable/writable properties not supported");
389: }
390:
391: /**
392: * Add a new dynamic property.
393: *
394: * @param property Property the new dynamic property to add.
395: *
396: * @exception IllegalArgumentException if name is null
397: */
398: protected void add(DynaProperty property) {
399: add(property.getName(), property.getType());
400: }
401:
402: /**
403: * Remove the specified dynamic property, and any associated data type,
404: * readability, and writeability, from this dynamic class.
405: * <strong>NOTE</strong> - This does <strong>NOT</strong> cause any
406: * corresponding property values to be removed from DynaBean instances
407: * associated with this DynaClass.
408: *
409: * @param name Name of the dynamic property to remove
410: *
411: * @exception IllegalArgumentException if name is null
412: * @exception IllegalStateException if this DynaClass is currently
413: * restricted, so no properties can be removed
414: */
415: public void remove(String name) {
416:
417: if (name == null) {
418: throw new IllegalArgumentException(
419: "Property name is missing.");
420: }
421:
422: if (isRestricted()) {
423: throw new IllegalStateException(
424: "DynaClass is currently restricted. No properties can be removed.");
425: }
426:
427: // Remove, if property doesn't exist
428: if (values.containsKey(name)) {
429: values.remove(name);
430: }
431:
432: }
433:
434: // ------------------- Additional Public Methods ----------------------------------
435:
436: /**
437: * Should this DynaClass return a <code>null</code> from
438: * the <code>getDynaProperty(name)</code> method if the property
439: * doesn't exist.
440: *
441: * @return <code>true<code> if a <code>null</code> {@link DynaProperty}
442: * should be returned if the property doesn't exist, otherwise
443: * <code>false</code> if a new {@link DynaProperty} should be created.
444: */
445: public boolean isReturnNull() {
446: return returnNull;
447: }
448:
449: /**
450: * Set whether this DynaClass should return a <code>null</code> from
451: * the <code>getDynaProperty(name)</code> method if the property
452: * doesn't exist.
453: *
454: * @param returnNull <code>true<code> if a <code>null</code> {@link DynaProperty}
455: * should be returned if the property doesn't exist, otherwise
456: * <code>false</code> if a new {@link DynaProperty} should be created.
457: */
458: public void setReturnNull(boolean returnNull) {
459: this .returnNull = returnNull;
460: }
461:
462: // ------------------- Protected Methods ----------------------------------
463:
464: /**
465: * <p>Indicate whether a property actually exists.</p>
466: *
467: * <p><strong>N.B.</strong> Using <code>getDynaProperty(name) == null</code>
468: * doesn't work in this implementation because that method might
469: * return a DynaProperty if it doesn't exist (depending on the
470: * <code>returnNull</code> indicator).</p>
471: *
472: * @param name Name of the dynamic property
473: * @return <code>true</code> if the property exists,
474: * otherwise <code>false</code>
475: * @exception IllegalArgumentException if no property name is specified
476: */
477: protected boolean isDynaProperty(String name) {
478:
479: if (name == null) {
480: throw new IllegalArgumentException(
481: "Property name is missing.");
482: }
483:
484: return values.containsKey(name);
485:
486: }
487:
488: }
|