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