View Javadoc

1   /*
2    * Copyright 2004 The Apache Software Foundation.
3    *
4    * Licensed under the Apache License, Version 2.0 (the "License");
5    * you may not use this file except in compliance with the License.
6    * You may obtain a copy of the License at
7    *
8    *      http://www.apache.org/licenses/LICENSE-2.0
9    *
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS,
12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   * See the License for the specific language governing permissions and
14   * limitations under the License.
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         // Check if property already exists
217         if (propertiesMap.get(property.getName()) != null) {
218            return;
219         }
220 
221         // Create a new property array with the specified property
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        // Update the properties
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         // Ignore if property doesn't exist
256         if (propertiesMap.get(name) == null) {
257             return;
258         }
259 
260 
261         // Create a new property array of without the specified property
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         // Update the properties
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         // If it doesn't exist and returnNull is false
308         // create a new DynaProperty
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 }