1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17 package org.apache.commons.beanutils;
18
19 /***
20 * <p>DynaClass which implements the <code>MutableDynaClass</code> interface.</p>
21 *
22 * <p>A <code>MutableDynaClass</code> is a specialized extension to <code>DynaClass</code>
23 * that allows properties to be added or removed dynamically.</p>
24 *
25 * <p>This implementation has one slightly unusual default behaviour - calling
26 * the <code>getDynaProperty(name)</code> method for a property which doesn't
27 * exist returns a <code>DynaProperty</code> rather than <code>null</code>. The
28 * reason for this is that <code>BeanUtils</code> calls this method to check if
29 * a property exists before trying to set the value. This would defeat the object
30 * of the <code>LazyDynaBean</code> which automatically adds missing properties
31 * when any of its <code>set()</code> methods are called. For this reason the
32 * <code>isDynaProperty(name)</code> method has been added to this implementation
33 * in order to determine if a property actually exists. If the more <i>normal</i>
34 * behaviour of returning <code>null</code> is required, then this can be achieved
35 * by calling the <code>setReturnNull(true)</code>.</p>
36 *
37 * <p>The <code>add(name, type, readable, writable)</code> method is not implemented
38 * and always throws an <code>UnsupportedOperationException</code>. I believe
39 * this attributes need to be added to the <code>DynaProperty</code> class
40 * in order to control read/write facilities.</p>
41 *
42 * @see LazyDynaBean
43 * @author Niall Pemberton
44 */
45 public class LazyDynaClass extends BasicDynaClass implements MutableDynaClass {
46
47 /***
48 * Controls whether changes to this DynaClass's properties are allowed.
49 */
50 protected boolean restricted;
51
52 /***
53 * <p>Controls whether the <code>getDynaProperty()</code> method returns
54 * null if a property doesn't exist - or creates a new one.</p>
55 *
56 * <p>Default is <code>false</code>.
57 */
58 protected boolean returnNull = false;
59
60 /***
61 * Construct a new LazyDynaClass with default parameters.
62 */
63 public LazyDynaClass() {
64 this(null, (DynaProperty[])null);
65 }
66
67 /***
68 * Construct a new LazyDynaClass with the specified name.
69 *
70 * @param name Name of this DynaBean class
71 */
72 public LazyDynaClass(String name) {
73 this(name, (DynaProperty[])null);
74 }
75
76 /***
77 * Construct a new LazyDynaClass with the specified name and DynaBean class.
78 *
79 * @param name Name of this DynaBean class
80 * @param dynaBeanClass The implementation class for new instances
81 */
82 public LazyDynaClass(String name, Class dynaBeanClass) {
83 this(name, dynaBeanClass, null);
84 }
85
86 /***
87 * Construct a new LazyDynaClass with the specified name and properties.
88 *
89 * @param name Name of this DynaBean class
90 * @param properties Property descriptors for the supported properties
91 */
92 public LazyDynaClass(String name, DynaProperty[] properties) {
93 this(name, LazyDynaBean.class, properties);
94 }
95
96 /***
97 * Construct a new LazyDynaClass with the specified name, DynaBean class and properties.
98 *
99 * @param name Name of this DynaBean class
100 * @param dynaBeanClass The implementation class for new intances
101 * @param properties Property descriptors for the supported properties
102 */
103 public LazyDynaClass(String name, Class dynaBeanClass, DynaProperty properties[]) {
104 super(name, dynaBeanClass, properties);
105 }
106
107 /***
108 * <p>Is this DynaClass currently restricted.</p>
109 * <p>If restricted, no changes to the existing registration of
110 * property names, data types, readability, or writeability are allowed.</p>
111 * @return <code>true</code> if this {@link MutableDynaClass} cannot be changed
112 * otherwise <code>false</code>
113 */
114 public boolean isRestricted() {
115 return restricted;
116 }
117
118 /***
119 * <p>Set whether this DynaClass is currently restricted.</p>
120 * <p>If restricted, no changes to the existing registration of
121 * property names, data types, readability, or writeability are allowed.</p>
122 * @param restricted <code>true</code> if this {@link MutableDynaClass} cannot
123 * be changed otherwise <code>false</code>
124 */
125 public void setRestricted(boolean restricted) {
126 this.restricted = restricted;
127 }
128
129 /***
130 * Should this DynaClass return a <code>null</code> from
131 * the <code>getDynaProperty(name)</code> method if the property
132 * doesn't exist.
133 *
134 * @return <code>true<code> if a <code>null</code> {@link DynaProperty}
135 * should be returned if the property doesn't exist, otherwise
136 * <code>false</code> if a new {@link DynaProperty} should be created.
137 */
138 public boolean isReturnNull() {
139 return returnNull;
140 }
141
142 /***
143 * Set whether this DynaClass should return a <code>null</code> from
144 * the <code>getDynaProperty(name)</code> method if the property
145 * doesn't exist.
146 * @param returnNull <code>true<code> if a <code>null</code> {@link DynaProperty}
147 * should be returned if the property doesn't exist, otherwise
148 * <code>false</code> if a new {@link DynaProperty} should be created.
149 */
150 public void setReturnNull(boolean returnNull) {
151 this.returnNull = returnNull;
152 }
153
154 /***
155 * Add a new dynamic property with no restrictions on data type,
156 * readability, or writeability.
157 *
158 * @param name Name of the new dynamic property
159 *
160 * @exception IllegalArgumentException if name is null
161 * @exception IllegalStateException if this DynaClass is currently
162 * restricted, so no new properties can be added
163 */
164 public void add(String name) {
165 add(new DynaProperty(name));
166 }
167
168 /***
169 * Add a new dynamic property with the specified data type, but with
170 * no restrictions on readability or writeability.
171 *
172 * @param name Name of the new dynamic property
173 * @param type Data type of the new dynamic property (null for no
174 * restrictions)
175 *
176 * @exception IllegalArgumentException if name is null
177 * @exception IllegalStateException if this DynaClass is currently
178 * restricted, so no new properties can be added
179 */
180 public void add(String name, Class type) {
181 if (type == null) {
182 add(name);
183 } else {
184 add(new DynaProperty(name, type));
185 }
186 }
187
188 /***
189 * <p>Add a new dynamic property with the specified data type, readability,
190 * and writeability.</p>
191 *
192 * <p><strong>N.B.</strong>Support for readable/writeable properties has not been implemented
193 * and this method always throws a <code>UnsupportedOperationException</code>.</p>
194 *
195 * <p>I'm not sure the intention of the original authors for this method, but it seems to
196 * me that readable/writable should be attributes of the <code>DynaProperty</code> class
197 * (which they are not) and is the reason this method has not been implemented.</p>
198 *
199 * @param name Name of the new dynamic property
200 * @param type Data type of the new dynamic property (null for no
201 * restrictions)
202 * @param readable Set to <code>true</code> if this property value
203 * should be readable
204 * @param writeable Set to <code>true</code> if this property value
205 * should be writeable
206 *
207 * @exception UnsupportedOperationException anytime this method is called
208 */
209 public void add(String name, Class type, boolean readable, boolean writeable) {
210 throw new java.lang.UnsupportedOperationException("readable/writable properties not supported");
211 }
212
213 /***
214 * Add a new dynamic property.
215 *
216 * @param property Property the new dynamic property to add.
217 *
218 * @exception IllegalArgumentException if name is null
219 * @exception IllegalStateException if this DynaClass is currently
220 * restricted, so no new properties can be added
221 */
222 protected void add(DynaProperty property) {
223
224 if (property.getName() == null) {
225 throw new IllegalArgumentException("Property name is missing.");
226 }
227
228 if (isRestricted()) {
229 throw new IllegalStateException("DynaClass is currently restricted. No new properties can be added.");
230 }
231
232
233 if (propertiesMap.get(property.getName()) != null) {
234 return;
235 }
236
237
238 DynaProperty[] oldProperties = getDynaProperties();
239 DynaProperty[] newProperties = new DynaProperty[oldProperties.length+1];
240 System.arraycopy(oldProperties, 0, newProperties, 0, oldProperties.length);
241 newProperties[oldProperties.length] = property;
242
243
244 setProperties(newProperties);
245
246 }
247
248 /***
249 * Remove the specified dynamic property, and any associated data type,
250 * readability, and writeability, from this dynamic class.
251 * <strong>NOTE</strong> - This does <strong>NOT</strong> cause any
252 * corresponding property values to be removed from DynaBean instances
253 * associated with this DynaClass.
254 *
255 * @param name Name of the dynamic property to remove
256 *
257 * @exception IllegalArgumentException if name is null
258 * @exception IllegalStateException if this DynaClass is currently
259 * restricted, so no properties can be removed
260 */
261 public void remove(String name) {
262
263 if (name == null) {
264 throw new IllegalArgumentException("Property name is missing.");
265 }
266
267 if (isRestricted()) {
268 throw new IllegalStateException("DynaClass is currently restricted. No properties can be removed.");
269 }
270
271
272 if (propertiesMap.get(name) == null) {
273 return;
274 }
275
276
277
278 DynaProperty[] oldProperties = getDynaProperties();
279 DynaProperty[] newProperties = new DynaProperty[oldProperties.length-1];
280 int j = 0;
281 for (int i = 0; i < oldProperties.length; i++) {
282 if (!(name.equals(oldProperties[i].getName()))) {
283 newProperties[j] = oldProperties[i];
284 j++;
285 }
286 }
287
288
289 setProperties(newProperties);
290
291 }
292
293 /***
294 * <p>Return a property descriptor for the specified property.</p>
295 *
296 * <p>If the property is not found and the <code>returnNull</code> indicator is
297 * <code>true</code>, this method always returns <code>null</code>.</p>
298 *
299 * <p>If the property is not found and the <code>returnNull</code> indicator is
300 * <code>false</code> a new property descriptor is created and returned (although
301 * its not actually added to the DynaClass's properties). This is the default
302 * beahviour.</p>
303 *
304 * <p>The reason for not returning a <code>null</code> property descriptor is that
305 * <code>BeanUtils</code> uses this method to check if a property exists
306 * before trying to set it - since these <i>Lazy</i> implementations automatically
307 * add any new properties when they are set, returning <code>null</code> from
308 * this method would defeat their purpose.</p>
309 *
310 * @param name Name of the dynamic property for which a descriptor
311 * is requested
312 * @return The dyna property for the specified name
313 *
314 * @exception IllegalArgumentException if no property name is specified
315 */
316 public DynaProperty getDynaProperty(String name) {
317
318 if (name == null) {
319 throw new IllegalArgumentException("Property name is missing.");
320 }
321
322 DynaProperty dynaProperty = (DynaProperty)propertiesMap.get(name);
323
324
325
326 if (dynaProperty == null && !isReturnNull() && !isRestricted()) {
327 dynaProperty = new DynaProperty(name);
328 }
329
330 return dynaProperty;
331
332 }
333
334 /***
335 * <p>Indicate whether a property actually exists.</p>
336 *
337 * <p><strong>N.B.</strong> Using <code>getDynaProperty(name) == null</code>
338 * doesn't work in this implementation because that method might
339 * return a DynaProperty if it doesn't exist (depending on the
340 * <code>returnNull</code> indicator).</p>
341 *
342 * @param name The name of the property to check
343 * @return <code>true<code> if there is a property of the
344 * specified name, otherwise <code>false</code>
345 * @exception IllegalArgumentException if no property name is specified
346 */
347 public boolean isDynaProperty(String name) {
348
349 if (name == null) {
350 throw new IllegalArgumentException("Property name is missing.");
351 }
352
353 return propertiesMap.get(name) == null ? false : true;
354
355 }
356
357 }