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: /**
020: * <p>DynaClass which implements the <code>MutableDynaClass</code> interface.</p>
021: *
022: * <p>A <code>MutableDynaClass</code> is a specialized extension to <code>DynaClass</code>
023: * that allows properties to be added or removed dynamically.</p>
024: *
025: * <p>This implementation has one slightly unusual default behaviour - calling
026: * the <code>getDynaProperty(name)</code> method for a property which doesn't
027: * exist returns a <code>DynaProperty</code> rather than <code>null</code>. The
028: * reason for this is that <code>BeanUtils</code> calls this method to check if
029: * a property exists before trying to set the value. This would defeat the object
030: * of the <code>LazyDynaBean</code> which automatically adds missing properties
031: * when any of its <code>set()</code> methods are called. For this reason the
032: * <code>isDynaProperty(name)</code> method has been added to this implementation
033: * in order to determine if a property actually exists. If the more <i>normal</i>
034: * behaviour of returning <code>null</code> is required, then this can be achieved
035: * by calling the <code>setReturnNull(true)</code>.</p>
036: *
037: * <p>The <code>add(name, type, readable, writable)</code> method is not implemented
038: * and always throws an <code>UnsupportedOperationException</code>. I believe
039: * this attributes need to be added to the <code>DynaProperty</code> class
040: * in order to control read/write facilities.</p>
041: *
042: * @see LazyDynaBean
043: * @author Niall Pemberton
044: */
045: public class LazyDynaClass extends BasicDynaClass implements
046: MutableDynaClass {
047:
048: /**
049: * Controls whether changes to this DynaClass's properties are allowed.
050: */
051: protected boolean restricted;
052:
053: /**
054: * <p>Controls whether the <code>getDynaProperty()</code> method returns
055: * null if a property doesn't exist - or creates a new one.</p>
056: *
057: * <p>Default is <code>false</code>.
058: */
059: protected boolean returnNull = false;
060:
061: /**
062: * Construct a new LazyDynaClass with default parameters.
063: */
064: public LazyDynaClass() {
065: this (null, (DynaProperty[]) null);
066: }
067:
068: /**
069: * Construct a new LazyDynaClass with the specified name.
070: *
071: * @param name Name of this DynaBean class
072: */
073: public LazyDynaClass(String name) {
074: this (name, (DynaProperty[]) null);
075: }
076:
077: /**
078: * Construct a new LazyDynaClass with the specified name and DynaBean class.
079: *
080: * @param name Name of this DynaBean class
081: * @param dynaBeanClass The implementation class for new instances
082: */
083: public LazyDynaClass(String name, Class dynaBeanClass) {
084: this (name, dynaBeanClass, null);
085: }
086:
087: /**
088: * Construct a new LazyDynaClass with the specified name and properties.
089: *
090: * @param name Name of this DynaBean class
091: * @param properties Property descriptors for the supported properties
092: */
093: public LazyDynaClass(String name, DynaProperty[] properties) {
094: this (name, LazyDynaBean.class, properties);
095: }
096:
097: /**
098: * Construct a new LazyDynaClass with the specified name, DynaBean class and properties.
099: *
100: * @param name Name of this DynaBean class
101: * @param dynaBeanClass The implementation class for new intances
102: * @param properties Property descriptors for the supported properties
103: */
104: public LazyDynaClass(String name, Class dynaBeanClass,
105: DynaProperty properties[]) {
106: super (name, dynaBeanClass, properties);
107: }
108:
109: /**
110: * <p>Is this DynaClass currently restricted.</p>
111: * <p>If restricted, no changes to the existing registration of
112: * property names, data types, readability, or writeability are allowed.</p>
113: * @return <code>true</code> if this {@link MutableDynaClass} cannot be changed
114: * otherwise <code>false</code>
115: */
116: public boolean isRestricted() {
117: return restricted;
118: }
119:
120: /**
121: * <p>Set whether this DynaClass is currently restricted.</p>
122: * <p>If restricted, no changes to the existing registration of
123: * property names, data types, readability, or writeability are allowed.</p>
124: * @param restricted <code>true</code> if this {@link MutableDynaClass} cannot
125: * be changed otherwise <code>false</code>
126: */
127: public void setRestricted(boolean restricted) {
128: this .restricted = restricted;
129: }
130:
131: /**
132: * Should this DynaClass return a <code>null</code> from
133: * the <code>getDynaProperty(name)</code> method if the property
134: * doesn't exist.
135: *
136: * @return <code>true<code> if a <code>null</code> {@link DynaProperty}
137: * should be returned if the property doesn't exist, otherwise
138: * <code>false</code> if a new {@link DynaProperty} should be created.
139: */
140: public boolean isReturnNull() {
141: return returnNull;
142: }
143:
144: /**
145: * Set whether this DynaClass should return a <code>null</code> from
146: * the <code>getDynaProperty(name)</code> method if the property
147: * doesn't exist.
148: * @param returnNull <code>true<code> if a <code>null</code> {@link DynaProperty}
149: * should be returned if the property doesn't exist, otherwise
150: * <code>false</code> if a new {@link DynaProperty} should be created.
151: */
152: public void setReturnNull(boolean returnNull) {
153: this .returnNull = returnNull;
154: }
155:
156: /**
157: * Add a new dynamic property with no restrictions on data type,
158: * readability, or writeability.
159: *
160: * @param name Name of the new dynamic property
161: *
162: * @exception IllegalArgumentException if name is null
163: * @exception IllegalStateException if this DynaClass is currently
164: * restricted, so no new properties can be added
165: */
166: public void add(String name) {
167: add(new DynaProperty(name));
168: }
169:
170: /**
171: * Add a new dynamic property with the specified data type, but with
172: * no restrictions on readability or writeability.
173: *
174: * @param name Name of the new dynamic property
175: * @param type Data type of the new dynamic property (null for no
176: * restrictions)
177: *
178: * @exception IllegalArgumentException if name is null
179: * @exception IllegalStateException if this DynaClass is currently
180: * restricted, so no new properties can be added
181: */
182: public void add(String name, Class type) {
183: if (type == null) {
184: add(name);
185: } else {
186: add(new DynaProperty(name, type));
187: }
188: }
189:
190: /**
191: * <p>Add a new dynamic property with the specified data type, readability,
192: * and writeability.</p>
193: *
194: * <p><strong>N.B.</strong>Support for readable/writeable properties has not been implemented
195: * and this method always throws a <code>UnsupportedOperationException</code>.</p>
196: *
197: * <p>I'm not sure the intention of the original authors for this method, but it seems to
198: * me that readable/writable should be attributes of the <code>DynaProperty</code> class
199: * (which they are not) and is the reason this method has not been implemented.</p>
200: *
201: * @param name Name of the new dynamic property
202: * @param type Data type of the new dynamic property (null for no
203: * restrictions)
204: * @param readable Set to <code>true</code> if this property value
205: * should be readable
206: * @param writeable Set to <code>true</code> if this property value
207: * should be writeable
208: *
209: * @exception UnsupportedOperationException anytime this method is called
210: */
211: public void add(String name, Class type, boolean readable,
212: boolean writeable) {
213: throw new java.lang.UnsupportedOperationException(
214: "readable/writable properties not supported");
215: }
216:
217: /**
218: * Add a new dynamic property.
219: *
220: * @param property Property the new dynamic property to add.
221: *
222: * @exception IllegalArgumentException if name is null
223: * @exception IllegalStateException if this DynaClass is currently
224: * restricted, so no new properties can be added
225: */
226: protected void add(DynaProperty property) {
227:
228: if (property.getName() == null) {
229: throw new IllegalArgumentException(
230: "Property name is missing.");
231: }
232:
233: if (isRestricted()) {
234: throw new IllegalStateException(
235: "DynaClass is currently restricted. No new properties can be added.");
236: }
237:
238: // Check if property already exists
239: if (propertiesMap.get(property.getName()) != null) {
240: return;
241: }
242:
243: // Create a new property array with the specified property
244: DynaProperty[] oldProperties = getDynaProperties();
245: DynaProperty[] newProperties = new DynaProperty[oldProperties.length + 1];
246: System.arraycopy(oldProperties, 0, newProperties, 0,
247: oldProperties.length);
248: newProperties[oldProperties.length] = property;
249:
250: // Update the properties
251: setProperties(newProperties);
252:
253: }
254:
255: /**
256: * Remove the specified dynamic property, and any associated data type,
257: * readability, and writeability, from this dynamic class.
258: * <strong>NOTE</strong> - This does <strong>NOT</strong> cause any
259: * corresponding property values to be removed from DynaBean instances
260: * associated with this DynaClass.
261: *
262: * @param name Name of the dynamic property to remove
263: *
264: * @exception IllegalArgumentException if name is null
265: * @exception IllegalStateException if this DynaClass is currently
266: * restricted, so no properties can be removed
267: */
268: public void remove(String name) {
269:
270: if (name == null) {
271: throw new IllegalArgumentException(
272: "Property name is missing.");
273: }
274:
275: if (isRestricted()) {
276: throw new IllegalStateException(
277: "DynaClass is currently restricted. No properties can be removed.");
278: }
279:
280: // Ignore if property doesn't exist
281: if (propertiesMap.get(name) == null) {
282: return;
283: }
284:
285: // Create a new property array of without the specified property
286: DynaProperty[] oldProperties = getDynaProperties();
287: DynaProperty[] newProperties = new DynaProperty[oldProperties.length - 1];
288: int j = 0;
289: for (int i = 0; i < oldProperties.length; i++) {
290: if (!(name.equals(oldProperties[i].getName()))) {
291: newProperties[j] = oldProperties[i];
292: j++;
293: }
294: }
295:
296: // Update the properties
297: setProperties(newProperties);
298:
299: }
300:
301: /**
302: * <p>Return a property descriptor for the specified property.</p>
303: *
304: * <p>If the property is not found and the <code>returnNull</code> indicator is
305: * <code>true</code>, this method always returns <code>null</code>.</p>
306: *
307: * <p>If the property is not found and the <code>returnNull</code> indicator is
308: * <code>false</code> a new property descriptor is created and returned (although
309: * its not actually added to the DynaClass's properties). This is the default
310: * beahviour.</p>
311: *
312: * <p>The reason for not returning a <code>null</code> property descriptor is that
313: * <code>BeanUtils</code> uses this method to check if a property exists
314: * before trying to set it - since these <i>Lazy</i> implementations automatically
315: * add any new properties when they are set, returning <code>null</code> from
316: * this method would defeat their purpose.</p>
317: *
318: * @param name Name of the dynamic property for which a descriptor
319: * is requested
320: * @return The dyna property for the specified name
321: *
322: * @exception IllegalArgumentException if no property name is specified
323: */
324: public DynaProperty getDynaProperty(String name) {
325:
326: if (name == null) {
327: throw new IllegalArgumentException(
328: "Property name is missing.");
329: }
330:
331: DynaProperty dynaProperty = (DynaProperty) propertiesMap
332: .get(name);
333:
334: // If it doesn't exist and returnNull is false
335: // create a new DynaProperty
336: if (dynaProperty == null && !isReturnNull() && !isRestricted()) {
337: dynaProperty = new DynaProperty(name);
338: }
339:
340: return dynaProperty;
341:
342: }
343:
344: /**
345: * <p>Indicate whether a property actually exists.</p>
346: *
347: * <p><strong>N.B.</strong> Using <code>getDynaProperty(name) == null</code>
348: * doesn't work in this implementation because that method might
349: * return a DynaProperty if it doesn't exist (depending on the
350: * <code>returnNull</code> indicator).</p>
351: *
352: * @param name The name of the property to check
353: * @return <code>true<code> if there is a property of the
354: * specified name, otherwise <code>false</code>
355: * @exception IllegalArgumentException if no property name is specified
356: */
357: public boolean isDynaProperty(String name) {
358:
359: if (name == null) {
360: throw new IllegalArgumentException(
361: "Property name is missing.");
362: }
363:
364: return propertiesMap.get(name) == null ? false : true;
365:
366: }
367:
368: }
|