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