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. import java.util.Map;
  18. import java.util.Iterator;
  19. /**
  20. * <p>Provides a <i>light weight</i> <code>DynaBean</code> facade to a <code>Map</code> with <i>lazy</i> map/list processing.</p>
  21. *
  22. * <p>Its a <i>light weight</i> <code>DynaBean</code> implementation because there is no
  23. * actual <code>DynaClass</code> associated with this <code>DynaBean</code> - in fact
  24. * it implements the <code>DynaClass</code> interface itself providing <i>pseudo</i> DynaClass
  25. * behaviour from the actual values stored in the <code>Map</code>.</p>
  26. *
  27. * <p>As well providing rhe standard <code>DynaBean</code> access to the <code>Map</code>'s properties
  28. * this class also provides the usual <i>Lazy</i> behaviour:</p>
  29. * <ul>
  30. * <li>Properties don't need to be pre-defined in a <code>DynaClass</code></li>
  31. * <li>Indexed properties (<code>Lists</code> or <code>Arrays</code>) are automatically instantiated
  32. * and <i>grown</i> so that they are large enough to cater for the index being set.</li>
  33. * <li>Mapped properties are automatically instantiated.</li>
  34. * </ul>
  35. *
  36. * <p><b><u><i>Restricted</i> DynaClass</u></b></p>
  37. * <p>This class implements the <code>MutableDynaClass</code> interface.
  38. * <code>MutableDynaClass</code> have a facility to <i>restrict</i> the <code>DynaClass</code>
  39. * so that its properties cannot be modified. If the <code>MutableDynaClass</code> is
  40. * restricted then calling any of the <code>set()</code> methods for a property which
  41. * doesn't exist will result in a <code>IllegalArgumentException</code> being thrown.</p>
  42. *
  43. * @author Niall Pemberton
  44. */
  45. public class LazyDynaMap extends LazyDynaBean implements MutableDynaClass {
  46. /**
  47. * The name of this DynaClass (analogous to the
  48. * <code>getName()</code> method of <code>java.lang.Class</code>).
  49. */
  50. protected String name;
  51. /**
  52. * Controls whether changes to this DynaClass's properties are allowed.
  53. */
  54. protected boolean restricted;
  55. /**
  56. * <p>Controls whether the <code>getDynaProperty()</code> method returns
  57. * null if a property doesn't exist - or creates a new one.</p>
  58. *
  59. * <p>Default is <code>false</code>.
  60. */
  61. protected boolean returnNull = false;
  62. // ------------------- Constructors ----------------------------------
  63. /**
  64. * Default Constructor.
  65. */
  66. public LazyDynaMap() {
  67. this(null, (Map)null);
  68. }
  69. /**
  70. * Construct a new <code>LazyDynaMap</code> with the specified name.
  71. *
  72. * @param name Name of this DynaBean class
  73. */
  74. public LazyDynaMap(String name) {
  75. this(name, (Map)null);
  76. }
  77. /**
  78. * Construct a new <code>LazyDynaMap</code> with the specified <code>Map</code>.
  79. *
  80. * @param values The Map backing this <code>LazyDynaMap</code>
  81. */
  82. public LazyDynaMap(Map values) {
  83. this(null, values);
  84. }
  85. /**
  86. * Construct a new <code>LazyDynaMap</code> with the specified name and <code>Map</code>.
  87. *
  88. * @param name Name of this DynaBean class
  89. * @param values The Map backing this <code>LazyDynaMap</code>
  90. */
  91. public LazyDynaMap(String name, Map values) {
  92. this.name = name == null ? "LazyDynaMap" : name;
  93. this.values = values == null ? newMap() : values;
  94. this.dynaClass = this;
  95. }
  96. /**
  97. * Construct a new <code>LazyDynaMap</code> with the specified properties.
  98. *
  99. * @param properties Property descriptors for the supported properties
  100. */
  101. public LazyDynaMap(DynaProperty[] properties) {
  102. this(null, properties);
  103. }
  104. /**
  105. * Construct a new <code>LazyDynaMap</code> with the specified name and properties.
  106. *
  107. * @param name Name of this DynaBean class
  108. * @param properties Property descriptors for the supported properties
  109. */
  110. public LazyDynaMap(String name, DynaProperty[] properties) {
  111. this(name, (Map)null);
  112. if (properties != null) {
  113. for (int i = 0; i < properties.length; i++) {
  114. add(properties[i]);
  115. }
  116. }
  117. }
  118. /**
  119. * Construct a new <code>LazyDynaMap</code> based on an exisiting DynaClass
  120. *
  121. * @param dynaClass DynaClass to copy the name and properties from
  122. */
  123. public LazyDynaMap(DynaClass dynaClass) {
  124. this(dynaClass.getName(), dynaClass.getDynaProperties());
  125. }
  126. // ------------------- Public Methods ----------------------------------
  127. /**
  128. * Set the Map backing this <code>DynaBean</code>
  129. */
  130. public void setMap(Map values) {
  131. this.values = values;
  132. }
  133. // ------------------- DynaBean Methods ----------------------------------
  134. /**
  135. * Set the value of a simple property with the specified name.
  136. *
  137. * @param name Name of the property whose value is to be set
  138. * @param value Value to which this property is to be set
  139. */
  140. public void set(String name, Object value) {
  141. if (isRestricted() && !values.containsKey(name)) {
  142. throw new IllegalArgumentException
  143. ("Invalid property name '" + name + "' (DynaClass is restricted)");
  144. }
  145. values.put(name, value);
  146. }
  147. // ------------------- DynaClass Methods ----------------------------------
  148. /**
  149. * Return the name of this DynaClass (analogous to the
  150. * <code>getName()</code> method of <code>java.lang.Class</code)
  151. */
  152. public String getName() {
  153. return this.name;
  154. }
  155. /**
  156. * <p>Return a property descriptor for the specified property.</p>
  157. *
  158. * <p>If the property is not found and the <code>returnNull</code> indicator is
  159. * <code>true</code>, this method always returns <code>null</code>.</p>
  160. *
  161. * <p>If the property is not found and the <code>returnNull</code> indicator is
  162. * <code>false</code> a new property descriptor is created and returned (although
  163. * its not actually added to the DynaClass's properties). This is the default
  164. * beahviour.</p>
  165. *
  166. * <p>The reason for not returning a <code>null</code> property descriptor is that
  167. * <code>BeanUtils</code> uses this method to check if a property exists
  168. * before trying to set it - since these <i>Map</i> implementations automatically
  169. * add any new properties when they are set, returning <code>null</code> from
  170. * this method would defeat their purpose.</p>
  171. *
  172. * @param name Name of the dynamic property for which a descriptor
  173. * is requested
  174. *
  175. * @exception IllegalArgumentException if no property name is specified
  176. */
  177. public DynaProperty getDynaProperty(String name) {
  178. if (name == null)
  179. throw new IllegalArgumentException("Property name is missing.");
  180. // If it doesn't exist and returnNull is false
  181. // create a new DynaProperty
  182. if (!values.containsKey(name) && isReturnNull()) {
  183. return null;
  184. }
  185. Object value = values.get(name);
  186. if (value == null) {
  187. return new DynaProperty(name);
  188. } else {
  189. return new DynaProperty(name, value.getClass());
  190. }
  191. }
  192. /**
  193. * <p>Return an array of <code>ProperyDescriptors</code> for the properties
  194. * currently defined in this DynaClass. If no properties are defined, a
  195. * zero-length array will be returned.</p>
  196. *
  197. * <p><strong>FIXME</strong> - Should we really be implementing
  198. * <code>getBeanInfo()</code> instead, which returns property descriptors
  199. * and a bunch of other stuff?</p>
  200. */
  201. public DynaProperty[] getDynaProperties() {
  202. int i = 0;
  203. DynaProperty[] properties = new DynaProperty[values.size()];
  204. Iterator iterator = values.keySet().iterator();
  205. while (iterator.hasNext()) {
  206. String name = (String)iterator.next();
  207. Object value = values.get(name);
  208. properties[i++] = new DynaProperty(name, value == null ? null : value.getClass());
  209. }
  210. return properties;
  211. }
  212. /**
  213. * Instantiate and return a new DynaBean instance, associated
  214. * with this DynaClass.
  215. */
  216. public DynaBean newInstance() {
  217. return new LazyDynaMap(this);
  218. }
  219. // ------------------- MutableDynaClass Methods ----------------------------------
  220. /**
  221. * <p>Is this DynaClass currently restricted.</p>
  222. * <p>If restricted, no changes to the existing registration of
  223. * property names, data types, readability, or writeability are allowed.</p>
  224. */
  225. public boolean isRestricted() {
  226. return restricted;
  227. }
  228. /**
  229. * <p>Set whether this DynaClass is currently restricted.</p>
  230. * <p>If restricted, no changes to the existing registration of
  231. * property names, data types, readability, or writeability are allowed.</p>
  232. */
  233. public void setRestricted(boolean restricted) {
  234. this.restricted = restricted;
  235. }
  236. /**
  237. * Add a new dynamic property with no restrictions on data type,
  238. * readability, or writeability.
  239. *
  240. * @param name Name of the new dynamic property
  241. *
  242. * @exception IllegalArgumentException if name is null
  243. */
  244. public void add(String name) {
  245. add(name, null);
  246. }
  247. /**
  248. * Add a new dynamic property with the specified data type, but with
  249. * no restrictions on readability or writeability.
  250. *
  251. * @param name Name of the new dynamic property
  252. * @param type Data type of the new dynamic property (null for no
  253. * restrictions)
  254. *
  255. * @exception IllegalArgumentException if name is null
  256. * @exception IllegalStateException if this DynaClass is currently
  257. * restricted, so no new properties can be added
  258. */
  259. public void add(String name, Class type) {
  260. if (name == null) {
  261. throw new IllegalArgumentException("Property name is missing.");
  262. }
  263. if (isRestricted())
  264. throw new IllegalStateException("DynaClass is currently restricted. No new properties can be added.");
  265. Object value = values.get(name);
  266. // Check if the property already exists
  267. if (value == null) {
  268. values.put(name, type == null ? null : createProperty(name, type));
  269. }
  270. }
  271. /**
  272. * <p>Add a new dynamic property with the specified data type, readability,
  273. * and writeability.</p>
  274. *
  275. * <p><strong>N.B.</strong>Support for readable/writeable properties has not been implemented
  276. * and this method always throws a <code>UnsupportedOperationException</code>.</p>
  277. *
  278. * <p>I'm not sure the intention of the original authors for this method, but it seems to
  279. * me that readable/writable should be attributes of the <code>DynaProperty</code> class
  280. * (which they are not) and is the reason this method has not been implemented.</p>
  281. *
  282. * @param name Name of the new dynamic property
  283. * @param type Data type of the new dynamic property (null for no
  284. * restrictions)
  285. * @param readable Set to <code>true</code> if this property value
  286. * should be readable
  287. * @param writeable Set to <code>true</code> if this property value
  288. * should be writeable
  289. *
  290. * @exception UnsupportedOperationException anytime this method is called
  291. */
  292. public void add(String name, Class type, boolean readable, boolean writeable) {
  293. throw new java.lang.UnsupportedOperationException("readable/writable properties not supported");
  294. }
  295. /**
  296. * Add a new dynamic property.
  297. *
  298. * @param property Property the new dynamic property to add.
  299. *
  300. * @exception IllegalArgumentException if name is null
  301. */
  302. protected void add(DynaProperty property) {
  303. add(property.getName(), property.getType());
  304. }
  305. /**
  306. * Remove the specified dynamic property, and any associated data type,
  307. * readability, and writeability, from this dynamic class.
  308. * <strong>NOTE</strong> - This does <strong>NOT</strong> cause any
  309. * corresponding property values to be removed from DynaBean instances
  310. * associated with this DynaClass.
  311. *
  312. * @param name Name of the dynamic property to remove
  313. *
  314. * @exception IllegalArgumentException if name is null
  315. * @exception IllegalStateException if this DynaClass is currently
  316. * restricted, so no properties can be removed
  317. */
  318. public void remove(String name) {
  319. if (name == null) {
  320. throw new IllegalArgumentException("Property name is missing.");
  321. }
  322. if (isRestricted()) {
  323. throw new IllegalStateException("DynaClass is currently restricted. No properties can be removed.");
  324. }
  325. // Remove, if property doesn't exist
  326. if (values.containsKey(name)) {
  327. values.remove(name);
  328. }
  329. }
  330. // ------------------- Additional Public Methods ----------------------------------
  331. /**
  332. * Should this DynaClass return a <code>null</code> from
  333. * the <code>getDynaProperty(name)</code> method if the property
  334. * doesn't exist.
  335. */
  336. public boolean isReturnNull() {
  337. return returnNull;
  338. }
  339. /**
  340. * Set whether this DynaClass should return a <code>null</code> from
  341. * the <code>getDynaProperty(name)</code> method if the property
  342. * doesn't exist.
  343. */
  344. public void setReturnNull(boolean returnNull) {
  345. this.returnNull = returnNull;
  346. }
  347. // ------------------- Protected Methods ----------------------------------
  348. /**
  349. * <p>Indicate whether a property actually exists.</p>
  350. *
  351. * <p><strong>N.B.</strong> Using <code>getDynaProperty(name) == null</code>
  352. * doesn't work in this implementation because that method might
  353. * return a DynaProperty if it doesn't exist (depending on the
  354. * <code>returnNull</code> indicator).</p>
  355. *
  356. * @exception IllegalArgumentException if no property name is specified
  357. */
  358. protected boolean isDynaProperty(String name) {
  359. if (name == null) {
  360. throw new IllegalArgumentException("Property name is missing.");
  361. }
  362. return values.containsKey(name);
  363. }
  364. }