| Classes in this File | Line Coverage | Branch Coverage | Complexity | ||||
| LazyDynaBean |
|
| 6.25;6.25 |
| 1 | /* | |
| 2 | * Licensed to the Apache Software Foundation (ASF) under one or more | |
| 3 | * contributor license agreements. See the NOTICE file distributed with | |
| 4 | * this work for additional information regarding copyright ownership. | |
| 5 | * The ASF licenses this file to You under the Apache License, Version 2.0 | |
| 6 | * (the "License"); you may not use this file except in compliance with | |
| 7 | * the License. You may obtain a copy of the License at | |
| 8 | * | |
| 9 | * http://www.apache.org/licenses/LICENSE-2.0 | |
| 10 | * | |
| 11 | * Unless required by applicable law or agreed to in writing, software | |
| 12 | * distributed under the License is distributed on an "AS IS" BASIS, | |
| 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
| 14 | * See the License for the specific language governing permissions and | |
| 15 | * limitations under the License. | |
| 16 | */ | |
| 17 | package org.apache.commons.beanutils; | |
| 18 | ||
| 19 | import java.util.List; | |
| 20 | import java.util.ArrayList; | |
| 21 | import java.util.Map; | |
| 22 | import java.util.HashMap; | |
| 23 | import java.util.Date; | |
| 24 | import java.lang.reflect.Array; | |
| 25 | import java.math.BigDecimal; | |
| 26 | import java.math.BigInteger; | |
| 27 | import java.io.Serializable; | |
| 28 | import org.apache.commons.logging.Log; | |
| 29 | import org.apache.commons.logging.LogFactory; | |
| 30 | ||
| 31 | /** | |
| 32 | * <p>DynaBean which automatically adds properties to the <code>DynaClass</code> | |
| 33 | * and provides <i>Lazy List</i> and <i>Lazy Map</i> features.</p> | |
| 34 | * | |
| 35 | * <p>DynaBeans deal with three types of properties - <i>simple</i>, <i>indexed</i> and <i>mapped</i> and | |
| 36 | * have the following <code>get()</code> and <code>set()</code> methods for | |
| 37 | * each of these types:</p> | |
| 38 | * <ul> | |
| 39 | * <li><i>Simple</i> property methods - <code>get(name)</code> and | |
| 40 | * <code>set(name, value)</code></li> | |
| 41 | * <li><i>Indexed</i> property methods - <code>get(name, index)</code> and | |
| 42 | * <code>set(name, index, value)</code></li> | |
| 43 | * <li><i>Mapped</i> property methods - <code>get(name, key)</code> and | |
| 44 | * <code>set(name, key, value)</code></li> | |
| 45 | * </ul> | |
| 46 | * | |
| 47 | * <p><b><u>Getting Property Values</u></b></p> | |
| 48 | * <p>Calling any of the <code>get()</code> methods, for a property which | |
| 49 | * doesn't exist, returns <code>null</code> in this implementation.</p> | |
| 50 | * | |
| 51 | * <p><b><u>Setting Simple Properties</u></b></p> | |
| 52 | * <p>The <code>LazyDynaBean</code> will automatically add a property to the <code>DynaClass</code> | |
| 53 | * if it doesn't exist when the <code>set(name, value)</code> method is called.</p> | |
| 54 | * | |
| 55 | * <code>DynaBean myBean = new LazyDynaBean();</code></br> | |
| 56 | * <code>myBean.set("myProperty", "myValue");</code></br> | |
| 57 | * | |
| 58 | * <p><b><u>Setting Indexed Properties</u></b></p> | |
| 59 | * <p>If the property <b>doesn't</b> exist, the <code>LazyDynaBean</code> will automatically add | |
| 60 | * a property with an <code>ArrayList</code> type to the <code>DynaClass</code> when | |
| 61 | * the <code>set(name, index, value)</code> method is called. | |
| 62 | * It will also instantiate a new <code>ArrayList</code> and automatically <i>grow</i> | |
| 63 | * the <code>List</code> so that it is big enough to accomodate the index being set. | |
| 64 | * <code>ArrayList</code> is the default indexed property that LazyDynaBean uses but | |
| 65 | * this can be easily changed by overriding the <code>defaultIndexedProperty(name)</code> | |
| 66 | * method.</p> | |
| 67 | * | |
| 68 | * <code>DynaBean myBean = new LazyDynaBean();</code></br> | |
| 69 | * <code>myBean.set("myIndexedProperty", 0, "myValue1");</code></br> | |
| 70 | * <code>myBean.set("myIndexedProperty", 1, "myValue2");</code></br> | |
| 71 | * | |
| 72 | * <p>If the indexed property <b>does</b> exist in the <code>DynaClass</code> but is set to | |
| 73 | * <code>null</code> in the <code>LazyDynaBean</code>, then it will instantiate a | |
| 74 | * new <code>List</code> or <code>Array</code> as specified by the property's type | |
| 75 | * in the <code>DynaClass</code> and automatically <i>grow</i> the <code>List</code> | |
| 76 | * or <code>Array</code> so that it is big enough to accomodate the index being set.</p> | |
| 77 | * | |
| 78 | * <code>DynaBean myBean = new LazyDynaBean();</code></br> | |
| 79 | * <code>MutableDynaClass myClass = (MutableDynaClass)myBean.getDynaClass();</code></br> | |
| 80 | * <code>myClass.add("myIndexedProperty", int[].class);</code></br> | |
| 81 | * <code>myBean.set("myIndexedProperty", 0, new Integer(10));</code></br> | |
| 82 | * <code>myBean.set("myIndexedProperty", 1, new Integer(20));</code></br> | |
| 83 | * | |
| 84 | * <p><b><u>Setting Mapped Properties</u></b></p> | |
| 85 | * <p>If the property <b>doesn't</b> exist, the <code>LazyDynaBean</code> will automatically add | |
| 86 | * a property with a <code>HashMap</code> type to the <code>DynaClass</code> and | |
| 87 | * instantiate a new <code>HashMap</code> in the DynaBean when the | |
| 88 | * <code>set(name, key, value)</code> method is called. <code>HashMap</code> is the default | |
| 89 | * mapped property that LazyDynaBean uses but this can be easily changed by overriding | |
| 90 | * the <code>defaultMappedProperty(name)</code> method.</p> | |
| 91 | * | |
| 92 | * <code>DynaBean myBean = new LazyDynaBean();</code></br> | |
| 93 | * <code>myBean.set("myMappedProperty", "myKey", "myValue");</code></br> | |
| 94 | * | |
| 95 | * <p>If the mapped property <b>does</b> exist in the <code>DynaClass</code> but is set to | |
| 96 | * <code>null</code> in the <code>LazyDynaBean</code>, then it will instantiate a | |
| 97 | * new <code>Map</code> as specified by the property's type in the <code>DynaClass</code>.</p> | |
| 98 | * | |
| 99 | * <code>DynaBean myBean = new LazyDynaBean();</code></br> | |
| 100 | * <code>MutableDynaClass myClass = (MutableDynaClass)myBean.getDynaClass();</code></br> | |
| 101 | * <code>myClass.add("myMappedProperty", TreeMap.class);</code></br> | |
| 102 | * <code>myBean.set("myMappedProperty", "myKey", "myValue");</code></br> | |
| 103 | * | |
| 104 | * <p><b><u><i>Restricted</i> DynaClass</u></b></p> | |
| 105 | * <p><code>MutableDynaClass</code> have a facility to <i>restrict</i> the <code>DynaClass</code> | |
| 106 | * so that its properties cannot be modified. If the <code>MutableDynaClass</code> is | |
| 107 | * restricted then calling any of the <code>set()</code> methods for a property which | |
| 108 | * doesn't exist will result in a <code>IllegalArgumentException</code> being thrown.</p> | |
| 109 | * | |
| 110 | * @see LazyDynaClass | |
| 111 | * @author Niall Pemberton | |
| 112 | */ | |
| 113 | public class LazyDynaBean implements DynaBean, Serializable { | |
| 114 | ||
| 115 | ||
| 116 | /** | |
| 117 | * Commons Logging | |
| 118 | */ | |
| 119 | 91 | private transient Log logger = LogFactory.getLog(LazyDynaBean.class); |
| 120 | ||
| 121 | /** BigInteger Zero */ | |
| 122 | 1 | protected static final BigInteger BigInteger_ZERO = new BigInteger("0"); |
| 123 | /** BigDecimal Zero */ | |
| 124 | 1 | protected static final BigDecimal BigDecimal_ZERO = new BigDecimal("0"); |
| 125 | /** Character Space */ | |
| 126 | 1 | protected static final Character Character_SPACE = new Character(' '); |
| 127 | /** Byte Zero */ | |
| 128 | 1 | protected static final Byte Byte_ZERO = new Byte((byte)0); |
| 129 | /** Short Zero */ | |
| 130 | 1 | protected static final Short Short_ZERO = new Short((short)0); |
| 131 | /** Integer Zero */ | |
| 132 | 1 | protected static final Integer Integer_ZERO = new Integer(0); |
| 133 | /** Long Zero */ | |
| 134 | 1 | protected static final Long Long_ZERO = new Long(0); |
| 135 | /** Float Zero */ | |
| 136 | 1 | protected static final Float Float_ZERO = new Float((byte)0); |
| 137 | /** Double Zero */ | |
| 138 | 1 | protected static final Double Double_ZERO = new Double((byte)0); |
| 139 | ||
| 140 | /** | |
| 141 | * The <code>MutableDynaClass</code> "base class" that this DynaBean | |
| 142 | * is associated with. | |
| 143 | */ | |
| 144 | protected Map values; | |
| 145 | ||
| 146 | /** Map decorator for this DynaBean */ | |
| 147 | private transient Map mapDecorator; | |
| 148 | ||
| 149 | /** | |
| 150 | * The <code>MutableDynaClass</code> "base class" that this DynaBean | |
| 151 | * is associated with. | |
| 152 | */ | |
| 153 | protected MutableDynaClass dynaClass; | |
| 154 | ||
| 155 | ||
| 156 | // ------------------- Constructors ---------------------------------- | |
| 157 | ||
| 158 | /** | |
| 159 | * Construct a new <code>LazyDynaBean</code> with a <code>LazyDynaClass</code> instance. | |
| 160 | */ | |
| 161 | public LazyDynaBean() { | |
| 162 | 81 | this(new LazyDynaClass()); |
| 163 | 81 | } |
| 164 | ||
| 165 | /** | |
| 166 | * Construct a new <code>LazyDynaBean</code> with a <code>LazyDynaClass</code> instance. | |
| 167 | * | |
| 168 | * @param name Name of this DynaBean class | |
| 169 | */ | |
| 170 | public LazyDynaBean(String name) { | |
| 171 | 0 | this(new LazyDynaClass(name)); |
| 172 | 0 | } |
| 173 | ||
| 174 | /** | |
| 175 | * Construct a new <code>DynaBean</code> associated with the specified | |
| 176 | * <code>DynaClass</code> instance - if its not a <code>MutableDynaClass</code> | |
| 177 | * then a new <code>LazyDynaClass</code> is created and the properties copied. | |
| 178 | * | |
| 179 | * @param dynaClass The DynaClass we are associated with | |
| 180 | */ | |
| 181 | 91 | public LazyDynaBean(DynaClass dynaClass) { |
| 182 | ||
| 183 | 91 | values = newMap(); |
| 184 | ||
| 185 | 91 | if (dynaClass instanceof MutableDynaClass) { |
| 186 | 91 | this.dynaClass = (MutableDynaClass)dynaClass; |
| 187 | } else { | |
| 188 | 0 | this.dynaClass = new LazyDynaClass(dynaClass.getName(), dynaClass.getDynaProperties()); |
| 189 | } | |
| 190 | ||
| 191 | 91 | } |
| 192 | ||
| 193 | ||
| 194 | // ------------------- Public Methods ---------------------------------- | |
| 195 | ||
| 196 | /** | |
| 197 | * Return a Map representation of this DynaBean. | |
| 198 | * </p> | |
| 199 | * This, for example, could be used in JSTL in the following way to access | |
| 200 | * a DynaBean's <code>fooProperty</code>: | |
| 201 | * <ul><li><code>${myDynaBean.<b>map</b>.fooProperty}</code></li></ul> | |
| 202 | * | |
| 203 | * @return a Map representation of this DynaBean | |
| 204 | */ | |
| 205 | public Map getMap() { | |
| 206 | // cache the Map | |
| 207 | 0 | if (mapDecorator == null) { |
| 208 | 0 | mapDecorator = new DynaBeanMapDecorator(this); |
| 209 | } | |
| 210 | 0 | return mapDecorator; |
| 211 | } | |
| 212 | ||
| 213 | /** | |
| 214 | * <p>Return the size of an indexed or mapped property.</p> | |
| 215 | * | |
| 216 | * @param name Name of the property | |
| 217 | * @return The indexed or mapped property size | |
| 218 | * @exception IllegalArgumentException if no property name is specified | |
| 219 | */ | |
| 220 | public int size(String name) { | |
| 221 | ||
| 222 | 0 | if (name == null) { |
| 223 | 0 | throw new IllegalArgumentException("No property name specified"); |
| 224 | } | |
| 225 | ||
| 226 | 0 | Object value = values.get(name); |
| 227 | 0 | if (value == null) { |
| 228 | 0 | return 0; |
| 229 | } | |
| 230 | ||
| 231 | 0 | if (value instanceof Map) { |
| 232 | 0 | return ((Map)value).size(); |
| 233 | } | |
| 234 | ||
| 235 | 0 | if (value instanceof List) { |
| 236 | 0 | return ((List)value).size(); |
| 237 | } | |
| 238 | ||
| 239 | 0 | if ((value.getClass().isArray())) { |
| 240 | 0 | return Array.getLength(value); |
| 241 | } | |
| 242 | ||
| 243 | 0 | return 0; |
| 244 | ||
| 245 | } | |
| 246 | ||
| 247 | // ------------------- DynaBean Methods ---------------------------------- | |
| 248 | ||
| 249 | /** | |
| 250 | * Does the specified mapped property contain a value for the specified | |
| 251 | * key value? | |
| 252 | * | |
| 253 | * @param name Name of the property to check | |
| 254 | * @param key Name of the key to check | |
| 255 | * @return <code>true<code> if the mapped property contains a value for | |
| 256 | * the specified key, otherwise <code>false</code> | |
| 257 | * | |
| 258 | * @exception IllegalArgumentException if no property name is specified | |
| 259 | */ | |
| 260 | public boolean contains(String name, String key) { | |
| 261 | ||
| 262 | 0 | if (name == null) { |
| 263 | 0 | throw new IllegalArgumentException("No property name specified"); |
| 264 | } | |
| 265 | ||
| 266 | 0 | Object value = values.get(name); |
| 267 | 0 | if (value == null) { |
| 268 | 0 | return false; |
| 269 | } | |
| 270 | ||
| 271 | 0 | if (value instanceof Map) { |
| 272 | 0 | return (((Map) value).containsKey(key)); |
| 273 | } | |
| 274 | ||
| 275 | 0 | return false; |
| 276 | ||
| 277 | } | |
| 278 | ||
| 279 | /** | |
| 280 | * <p>Return the value of a simple property with the specified name.</p> | |
| 281 | * | |
| 282 | * <p><strong>N.B.</strong> Returns <code>null</code> if there is no property | |
| 283 | * of the specified name.</p> | |
| 284 | * | |
| 285 | * @param name Name of the property whose value is to be retrieved. | |
| 286 | * @return The property's value | |
| 287 | * @exception IllegalArgumentException if no property name is specified | |
| 288 | */ | |
| 289 | public Object get(String name) { | |
| 290 | ||
| 291 | 192 | if (name == null) { |
| 292 | 0 | throw new IllegalArgumentException("No property name specified"); |
| 293 | } | |
| 294 | ||
| 295 | // Value found | |
| 296 | 192 | Object value = values.get(name); |
| 297 | 192 | if (value != null) { |
| 298 | 155 | return value; |
| 299 | } | |
| 300 | ||
| 301 | // Property doesn't exist | |
| 302 | 37 | if (!isDynaProperty(name)) { |
| 303 | 28 | return null; |
| 304 | } | |
| 305 | ||
| 306 | // Property doesn't exist | |
| 307 | 9 | value = createProperty(name, dynaClass.getDynaProperty(name).getType()); |
| 308 | ||
| 309 | 9 | if (value != null) { |
| 310 | 5 | set(name, value); |
| 311 | } | |
| 312 | ||
| 313 | 9 | return value; |
| 314 | ||
| 315 | } | |
| 316 | ||
| 317 | /** | |
| 318 | * <p>Return the value of an indexed property with the specified name.</p> | |
| 319 | * | |
| 320 | * <p><strong>N.B.</strong> Returns <code>null</code> if there is no 'indexed' | |
| 321 | * property of the specified name.</p> | |
| 322 | * | |
| 323 | * @param name Name of the property whose value is to be retrieved | |
| 324 | * @param index Index of the value to be retrieved | |
| 325 | * @return The indexed property's value | |
| 326 | * | |
| 327 | * @exception IllegalArgumentException if the specified property | |
| 328 | * exists, but is not indexed | |
| 329 | * @exception IndexOutOfBoundsException if the specified index | |
| 330 | * is outside the range of the underlying property | |
| 331 | */ | |
| 332 | public Object get(String name, int index) { | |
| 333 | ||
| 334 | // If its not a property, then create default indexed property | |
| 335 | 32 | if (!isDynaProperty(name)) { |
| 336 | 4 | set(name, defaultIndexedProperty(name)); |
| 337 | } | |
| 338 | ||
| 339 | // Get the indexed property | |
| 340 | 32 | Object indexedProperty = get(name); |
| 341 | ||
| 342 | // Check that the property is indexed | |
| 343 | 32 | if (!dynaClass.getDynaProperty(name).isIndexed()) { |
| 344 | 0 | throw new IllegalArgumentException |
| 345 | ("Non-indexed property for '" + name + "[" + index + "]' " | |
| 346 | + dynaClass.getDynaProperty(name).getName()); | |
| 347 | } | |
| 348 | ||
| 349 | // Grow indexed property to appropriate size | |
| 350 | 32 | indexedProperty = growIndexedProperty(name, indexedProperty, index); |
| 351 | ||
| 352 | // Return the indexed value | |
| 353 | 32 | if (indexedProperty.getClass().isArray()) { |
| 354 | 16 | return Array.get(indexedProperty, index); |
| 355 | 16 | } else if (indexedProperty instanceof List) { |
| 356 | 16 | return ((List)indexedProperty).get(index); |
| 357 | } else { | |
| 358 | 0 | throw new IllegalArgumentException |
| 359 | ("Non-indexed property for '" + name + "[" + index + "]' " | |
| 360 | + indexedProperty.getClass().getName()); | |
| 361 | } | |
| 362 | ||
| 363 | } | |
| 364 | ||
| 365 | /** | |
| 366 | * <p>Return the value of a mapped property with the specified name.</p> | |
| 367 | * | |
| 368 | * <p><strong>N.B.</strong> Returns <code>null</code> if there is no 'mapped' | |
| 369 | * property of the specified name.</p> | |
| 370 | * | |
| 371 | * @param name Name of the property whose value is to be retrieved | |
| 372 | * @param key Key of the value to be retrieved | |
| 373 | * @return The mapped property's value | |
| 374 | * | |
| 375 | * @exception IllegalArgumentException if the specified property | |
| 376 | * exists, but is not mapped | |
| 377 | */ | |
| 378 | public Object get(String name, String key) { | |
| 379 | ||
| 380 | // If its not a property, then create default mapped property | |
| 381 | 14 | if (!isDynaProperty(name)) { |
| 382 | 4 | set(name, defaultMappedProperty(name)); |
| 383 | } | |
| 384 | ||
| 385 | // Get the mapped property | |
| 386 | 14 | Object mappedProperty = get(name); |
| 387 | ||
| 388 | // Check that the property is mapped | |
| 389 | 14 | if (!dynaClass.getDynaProperty(name).isMapped()) { |
| 390 | 0 | throw new IllegalArgumentException |
| 391 | ("Non-mapped property for '" + name + "(" + key + ")' " | |
| 392 | + dynaClass.getDynaProperty(name).getType().getName()); | |
| 393 | } | |
| 394 | ||
| 395 | // Get the value from the Map | |
| 396 | 14 | if (mappedProperty instanceof Map) { |
| 397 | 14 | return (((Map) mappedProperty).get(key)); |
| 398 | } else { | |
| 399 | 0 | throw new IllegalArgumentException |
| 400 | ("Non-mapped property for '" + name + "(" + key + ")'" | |
| 401 | + mappedProperty.getClass().getName()); | |
| 402 | } | |
| 403 | ||
| 404 | } | |
| 405 | ||
| 406 | ||
| 407 | /** | |
| 408 | * Return the <code>DynaClass</code> instance that describes the set of | |
| 409 | * properties available for this DynaBean. | |
| 410 | * | |
| 411 | * @return The associated DynaClass | |
| 412 | */ | |
| 413 | public DynaClass getDynaClass() { | |
| 414 | 65 | return dynaClass; |
| 415 | } | |
| 416 | ||
| 417 | /** | |
| 418 | * Remove any existing value for the specified key on the | |
| 419 | * specified mapped property. | |
| 420 | * | |
| 421 | * @param name Name of the property for which a value is to | |
| 422 | * be removed | |
| 423 | * @param key Key of the value to be removed | |
| 424 | * | |
| 425 | * @exception IllegalArgumentException if there is no property | |
| 426 | * of the specified name | |
| 427 | */ | |
| 428 | public void remove(String name, String key) { | |
| 429 | ||
| 430 | 0 | if (name == null) { |
| 431 | 0 | throw new IllegalArgumentException("No property name specified"); |
| 432 | } | |
| 433 | ||
| 434 | 0 | Object value = values.get(name); |
| 435 | 0 | if (value == null) { |
| 436 | 0 | return; |
| 437 | } | |
| 438 | ||
| 439 | 0 | if (value instanceof Map) { |
| 440 | 0 | ((Map) value).remove(key); |
| 441 | } else { | |
| 442 | 0 | throw new IllegalArgumentException |
| 443 | ("Non-mapped property for '" + name + "(" + key + ")'" | |
| 444 | + value.getClass().getName()); | |
| 445 | } | |
| 446 | ||
| 447 | 0 | } |
| 448 | ||
| 449 | /** | |
| 450 | * Set the value of a simple property with the specified name. | |
| 451 | * | |
| 452 | * @param name Name of the property whose value is to be set | |
| 453 | * @param value Value to which this property is to be set | |
| 454 | * | |
| 455 | * @exception IllegalArgumentException if this is not an existing property | |
| 456 | * name for our DynaClass and the MutableDynaClass is restricted | |
| 457 | * @exception ConversionException if the specified value cannot be | |
| 458 | * converted to the type required for this property | |
| 459 | * @exception NullPointerException if an attempt is made to set a | |
| 460 | * primitive property to null | |
| 461 | */ | |
| 462 | public void set(String name, Object value) { | |
| 463 | ||
| 464 | // If the property doesn't exist, then add it | |
| 465 | 33 | if (!isDynaProperty(name)) { |
| 466 | ||
| 467 | 14 | if (dynaClass.isRestricted()) { |
| 468 | 3 | throw new IllegalArgumentException |
| 469 | ("Invalid property name '" + name + "' (DynaClass is restricted)"); | |
| 470 | } | |
| 471 | 11 | if (value == null) { |
| 472 | 1 | dynaClass.add(name); |
| 473 | } else { | |
| 474 | 10 | dynaClass.add(name, value.getClass()); |
| 475 | } | |
| 476 | ||
| 477 | } | |
| 478 | ||
| 479 | 30 | DynaProperty descriptor = dynaClass.getDynaProperty(name); |
| 480 | ||
| 481 | 30 | if (value == null) { |
| 482 | 1 | if (descriptor.getType().isPrimitive()) { |
| 483 | 0 | throw new NullPointerException |
| 484 | ("Primitive value for '" + name + "'"); | |
| 485 | } | |
| 486 | 29 | } else if (!isAssignable(descriptor.getType(), value.getClass())) { |
| 487 | 1 | throw new ConversionException |
| 488 | ("Cannot assign value of type '" + | |
| 489 | value.getClass().getName() + | |
| 490 | "' to property '" + name + "' of type '" + | |
| 491 | descriptor.getType().getName() + "'"); | |
| 492 | } | |
| 493 | ||
| 494 | // Set the property's value | |
| 495 | 29 | values.put(name, value); |
| 496 | ||
| 497 | 29 | } |
| 498 | ||
| 499 | /** | |
| 500 | * Set the value of an indexed property with the specified name. | |
| 501 | * | |
| 502 | * @param name Name of the property whose value is to be set | |
| 503 | * @param index Index of the property to be set | |
| 504 | * @param value Value to which this property is to be set | |
| 505 | * | |
| 506 | * @exception ConversionException if the specified value cannot be | |
| 507 | * converted to the type required for this property | |
| 508 | * @exception IllegalArgumentException if there is no property | |
| 509 | * of the specified name | |
| 510 | * @exception IllegalArgumentException if the specified property | |
| 511 | * exists, but is not indexed | |
| 512 | * @exception IndexOutOfBoundsException if the specified index | |
| 513 | * is outside the range of the underlying property | |
| 514 | */ | |
| 515 | public void set(String name, int index, Object value) { | |
| 516 | ||
| 517 | // If its not a property, then create default indexed property | |
| 518 | 26 | if (!isDynaProperty(name)) { |
| 519 | 5 | set(name, defaultIndexedProperty(name)); |
| 520 | } | |
| 521 | ||
| 522 | // Get the indexed property | |
| 523 | 24 | Object indexedProperty = get(name); |
| 524 | ||
| 525 | // Check that the property is indexed | |
| 526 | 24 | if (!dynaClass.getDynaProperty(name).isIndexed()) { |
| 527 | 2 | throw new IllegalArgumentException |
| 528 | ("Non-indexed property for '" + name + "[" + index + "]'" | |
| 529 | + dynaClass.getDynaProperty(name).getType().getName()); | |
| 530 | } | |
| 531 | ||
| 532 | // Grow indexed property to appropriate size | |
| 533 | 22 | indexedProperty = growIndexedProperty(name, indexedProperty, index); |
| 534 | ||
| 535 | // Set the value in an array | |
| 536 | 22 | if (indexedProperty.getClass().isArray()) { |
| 537 | 8 | Array.set(indexedProperty, index, value); |
| 538 | 14 | } else if (indexedProperty instanceof List) { |
| 539 | 14 | ((List)indexedProperty).set(index, value); |
| 540 | } else { | |
| 541 | 0 | throw new IllegalArgumentException |
| 542 | ("Non-indexed property for '" + name + "[" + index + "]' " | |
| 543 | + indexedProperty.getClass().getName()); | |
| 544 | } | |
| 545 | ||
| 546 | 22 | } |
| 547 | ||
| 548 | /** | |
| 549 | * Set the value of a mapped property with the specified name. | |
| 550 | * | |
| 551 | * @param name Name of the property whose value is to be set | |
| 552 | * @param key Key of the property to be set | |
| 553 | * @param value Value to which this property is to be set | |
| 554 | * | |
| 555 | * @exception ConversionException if the specified value cannot be | |
| 556 | * converted to the type required for this property | |
| 557 | * @exception IllegalArgumentException if there is no property | |
| 558 | * of the specified name | |
| 559 | * @exception IllegalArgumentException if the specified property | |
| 560 | * exists, but is not mapped | |
| 561 | */ | |
| 562 | public void set(String name, String key, Object value) { | |
| 563 | ||
| 564 | // If the 'mapped' property doesn't exist, then add it | |
| 565 | 14 | if (!isDynaProperty(name)) { |
| 566 | 2 | set(name, defaultMappedProperty(name)); |
| 567 | } | |
| 568 | ||
| 569 | // Get the mapped property | |
| 570 | 12 | Object mappedProperty = get(name); |
| 571 | ||
| 572 | // Check that the property is mapped | |
| 573 | 12 | if (!dynaClass.getDynaProperty(name).isMapped()) { |
| 574 | 2 | throw new IllegalArgumentException |
| 575 | ("Non-mapped property for '" + name + "(" + key + ")'" | |
| 576 | + dynaClass.getDynaProperty(name).getType().getName()); | |
| 577 | } | |
| 578 | ||
| 579 | // Set the value in the Map | |
| 580 | 10 | ((Map)mappedProperty).put(key, value); |
| 581 | ||
| 582 | 10 | } |
| 583 | ||
| 584 | // ------------------- protected Methods ---------------------------------- | |
| 585 | ||
| 586 | /** | |
| 587 | * Grow the size of an indexed property | |
| 588 | * @param name The name of the property | |
| 589 | * @param indexedProperty The current property value | |
| 590 | * @param index The indexed value to grow the property to (i.e. one less than | |
| 591 | * the required size) | |
| 592 | * @return The new property value (grown to the appropriate size) | |
| 593 | */ | |
| 594 | protected Object growIndexedProperty(String name, Object indexedProperty, int index) { | |
| 595 | ||
| 596 | // Grow a List to the appropriate size | |
| 597 | 54 | if (indexedProperty instanceof List) { |
| 598 | ||
| 599 | 30 | List list = (List)indexedProperty; |
| 600 | 72 | while (index >= list.size()) { |
| 601 | 42 | Class contentType = getDynaClass().getDynaProperty(name).getContentType(); |
| 602 | 42 | Object value = null; |
| 603 | 42 | if (contentType != null) { |
| 604 | 0 | value = createProperty(name+"["+list.size()+"]", contentType); |
| 605 | } | |
| 606 | 42 | list.add(value); |
| 607 | 42 | } |
| 608 | ||
| 609 | } | |
| 610 | ||
| 611 | // Grow an Array to the appropriate size | |
| 612 | 54 | if ((indexedProperty.getClass().isArray())) { |
| 613 | ||
| 614 | 24 | int length = Array.getLength(indexedProperty); |
| 615 | 24 | if (index >= length) { |
| 616 | 10 | Class componentType = indexedProperty.getClass().getComponentType(); |
| 617 | 10 | Object newArray = Array.newInstance(componentType, (index + 1)); |
| 618 | 10 | System.arraycopy(indexedProperty, 0, newArray, 0, length); |
| 619 | 10 | indexedProperty = newArray; |
| 620 | 10 | set(name, indexedProperty); |
| 621 | 10 | int newLength = Array.getLength(indexedProperty); |
| 622 | 42 | for (int i = length; i < newLength; i++) { |
| 623 | 32 | Array.set(indexedProperty, i, createProperty(name+"["+i+"]", componentType)); |
| 624 | } | |
| 625 | } | |
| 626 | } | |
| 627 | ||
| 628 | 54 | return indexedProperty; |
| 629 | ||
| 630 | } | |
| 631 | ||
| 632 | /** | |
| 633 | * Create a new Instance of a Property | |
| 634 | * @param name The name of the property | |
| 635 | * @param type The class of the property | |
| 636 | * @return The new value | |
| 637 | */ | |
| 638 | protected Object createProperty(String name, Class type) { | |
| 639 | 48 | if (type == null) { |
| 640 | 0 | return null; |
| 641 | } | |
| 642 | ||
| 643 | // Create Lists, arrays or DynaBeans | |
| 644 | 48 | if (type.isArray() || List.class.isAssignableFrom(type)) { |
| 645 | 10 | return createIndexedProperty(name, type); |
| 646 | } | |
| 647 | ||
| 648 | 38 | if (Map.class.isAssignableFrom(type)) { |
| 649 | 2 | return createMappedProperty(name, type); |
| 650 | } | |
| 651 | ||
| 652 | 36 | if (DynaBean.class.isAssignableFrom(type)) { |
| 653 | 8 | return createDynaBeanProperty(name, type); |
| 654 | } | |
| 655 | ||
| 656 | 28 | if (type.isPrimitive()) { |
| 657 | 12 | return createPrimitiveProperty(name, type); |
| 658 | } | |
| 659 | ||
| 660 | 16 | if (Number.class.isAssignableFrom(type)) { |
| 661 | 0 | return createNumberProperty(name, type); |
| 662 | } | |
| 663 | ||
| 664 | 16 | return createOtherProperty(name, type); |
| 665 | ||
| 666 | } | |
| 667 | ||
| 668 | /** | |
| 669 | * Create a new Instance of an 'Indexed' Property | |
| 670 | * @param name The name of the property | |
| 671 | * @param type The class of the property | |
| 672 | * @return The new value | |
| 673 | */ | |
| 674 | protected Object createIndexedProperty(String name, Class type) { | |
| 675 | ||
| 676 | // Create the indexed object | |
| 677 | 10 | Object indexedProperty = null; |
| 678 | ||
| 679 | 10 | if (type == null) { |
| 680 | ||
| 681 | 0 | indexedProperty = defaultIndexedProperty(name); |
| 682 | ||
| 683 | 10 | } else if (type.isArray()) { |
| 684 | ||
| 685 | 7 | indexedProperty = Array.newInstance(type.getComponentType(), 0); |
| 686 | ||
| 687 | 3 | } else if (List.class.isAssignableFrom(type)) { |
| 688 | 3 | if (type.isInterface()) { |
| 689 | 0 | indexedProperty = defaultIndexedProperty(name); |
| 690 | } else { | |
| 691 | try { | |
| 692 | 3 | indexedProperty = type.newInstance(); |
| 693 | } | |
| 694 | 0 | catch (Exception ex) { |
| 695 | 0 | throw new IllegalArgumentException |
| 696 | ("Error instantiating indexed property of type '" + | |
| 697 | type.getName() + "' for '" + name + "' " + ex); | |
| 698 | 3 | } |
| 699 | } | |
| 700 | } else { | |
| 701 | ||
| 702 | 0 | throw new IllegalArgumentException |
| 703 | ("Non-indexed property of type '" + type.getName() + "' for '" + name + "'"); | |
| 704 | } | |
| 705 | ||
| 706 | 10 | return indexedProperty; |
| 707 | ||
| 708 | } | |
| 709 | ||
| 710 | /** | |
| 711 | * Create a new Instance of a 'Mapped' Property | |
| 712 | * @param name The name of the property | |
| 713 | * @param type The class of the property | |
| 714 | * @return The new value | |
| 715 | */ | |
| 716 | protected Object createMappedProperty(String name, Class type) { | |
| 717 | ||
| 718 | // Create the mapped object | |
| 719 | 2 | Object mappedProperty = null; |
| 720 | ||
| 721 | 2 | if (type == null) { |
| 722 | ||
| 723 | 0 | mappedProperty = defaultMappedProperty(name); |
| 724 | ||
| 725 | 2 | } else if (type.isInterface()) { |
| 726 | ||
| 727 | 0 | mappedProperty = defaultMappedProperty(name); |
| 728 | ||
| 729 | 2 | } else if (Map.class.isAssignableFrom(type)) { |
| 730 | try { | |
| 731 | 2 | mappedProperty = type.newInstance(); |
| 732 | } | |
| 733 | 0 | catch (Exception ex) { |
| 734 | 0 | throw new IllegalArgumentException |
| 735 | ("Error instantiating mapped property of type '" + | |
| 736 | type.getName() + "' for '" + name + "' " + ex); | |
| 737 | 2 | } |
| 738 | } else { | |
| 739 | ||
| 740 | 0 | throw new IllegalArgumentException |
| 741 | ("Non-mapped property of type '" + type.getName() + "' for '" + name + "'"); | |
| 742 | } | |
| 743 | ||
| 744 | 2 | return mappedProperty; |
| 745 | ||
| 746 | } | |
| 747 | ||
| 748 | /** | |
| 749 | * Create a new Instance of a 'DynaBean' Property. | |
| 750 | * @param name The name of the property | |
| 751 | * @param type The class of the property | |
| 752 | * @return The new value | |
| 753 | */ | |
| 754 | protected Object createDynaBeanProperty(String name, Class type) { | |
| 755 | try { | |
| 756 | 8 | return type.newInstance(); |
| 757 | } | |
| 758 | 0 | catch (Exception ex) { |
| 759 | 0 | if (logger().isWarnEnabled()) { |
| 760 | 0 | logger().warn("Error instantiating DynaBean property of type '" + |
| 761 | type.getName() + "' for '" + name + "' " + ex); | |
| 762 | } | |
| 763 | 0 | return null; |
| 764 | } | |
| 765 | } | |
| 766 | ||
| 767 | /** | |
| 768 | * Create a new Instance of a 'Primitive' Property. | |
| 769 | * @param name The name of the property | |
| 770 | * @param type The class of the property | |
| 771 | * @return The new value | |
| 772 | */ | |
| 773 | protected Object createPrimitiveProperty(String name, Class type) { | |
| 774 | ||
| 775 | 12 | if (type == Boolean.TYPE) { |
| 776 | 0 | return Boolean.FALSE; |
| 777 | 12 | } else if (type == Integer.TYPE) { |
| 778 | 12 | return Integer_ZERO; |
| 779 | 0 | } else if (type == Long.TYPE) { |
| 780 | 0 | return Long_ZERO; |
| 781 | 0 | } else if (type == Double.TYPE) { |
| 782 | 0 | return Double_ZERO; |
| 783 | 0 | } else if (type == Float.TYPE) { |
| 784 | 0 | return Float_ZERO; |
| 785 | 0 | } else if (type == Byte.TYPE) { |
| 786 | 0 | return Byte_ZERO; |
| 787 | 0 | } else if (type == Short.TYPE) { |
| 788 | 0 | return Short_ZERO; |
| 789 | 0 | } else if (type == Character.TYPE) { |
| 790 | 0 | return Character_SPACE; |
| 791 | } else { | |
| 792 | 0 | return null; |
| 793 | } | |
| 794 | ||
| 795 | } | |
| 796 | ||
| 797 | /** | |
| 798 | * Create a new Instance of a <code>java.lang.Number</code> Property. | |
| 799 | * @param name The name of the property | |
| 800 | * @param type The class of the property | |
| 801 | * @return The new value | |
| 802 | */ | |
| 803 | protected Object createNumberProperty(String name, Class type) { | |
| 804 | ||
| 805 | 0 | return null; |
| 806 | ||
| 807 | } | |
| 808 | ||
| 809 | /** | |
| 810 | * Create a new Instance of other Property types | |
| 811 | * @param name The name of the property | |
| 812 | * @param type The class of the property | |
| 813 | * @return The new value | |
| 814 | */ | |
| 815 | protected Object createOtherProperty(String name, Class type) { | |
| 816 | ||
| 817 | 16 | if (type == Object.class || |
| 818 | type == String.class || | |
| 819 | type == Boolean.class || | |
| 820 | type == Character.class || | |
| 821 | Date.class.isAssignableFrom(type)) { | |
| 822 | ||
| 823 | 16 | return null; |
| 824 | ||
| 825 | } | |
| 826 | ||
| 827 | try { | |
| 828 | 0 | return type.newInstance(); |
| 829 | } | |
| 830 | 0 | catch (Exception ex) { |
| 831 | 0 | if (logger().isWarnEnabled()) { |
| 832 | 0 | logger().warn("Error instantiating property of type '" + type.getName() + "' for '" + name + "' " + ex); |
| 833 | } | |
| 834 | 0 | return null; |
| 835 | } | |
| 836 | } | |
| 837 | ||
| 838 | /** | |
| 839 | * <p>Creates a new <code>ArrayList</code> for an 'indexed' property | |
| 840 | * which doesn't exist.</p> | |
| 841 | * | |
| 842 | * <p>This method shouls be overriden if an alternative <code>List</code> | |
| 843 | * or <code>Array</code> implementation is required for 'indexed' properties.</p> | |
| 844 | * | |
| 845 | * @param name Name of the 'indexed property. | |
| 846 | * @return The default value for an indexed property (java.util.ArrayList) | |
| 847 | */ | |
| 848 | protected Object defaultIndexedProperty(String name) { | |
| 849 | 9 | return new ArrayList(); |
| 850 | } | |
| 851 | ||
| 852 | /** | |
| 853 | * <p>Creates a new <code>HashMap</code> for a 'mapped' property | |
| 854 | * which doesn't exist.</p> | |
| 855 | * | |
| 856 | * <p>This method can be overriden if an alternative <code>Map</code> | |
| 857 | * implementation is required for 'mapped' properties.</p> | |
| 858 | * | |
| 859 | * @param name Name of the 'mapped property. | |
| 860 | * @return The default value for a mapped property (java.util.HashMap) | |
| 861 | */ | |
| 862 | protected Map defaultMappedProperty(String name) { | |
| 863 | 6 | return new HashMap(); |
| 864 | } | |
| 865 | ||
| 866 | /** | |
| 867 | * Indicates if there is a property with the specified name. | |
| 868 | * @param name The name of the property to check | |
| 869 | * @return <code>true<code> if there is a property of the | |
| 870 | * specified name, otherwise <code>false</code> | |
| 871 | */ | |
| 872 | protected boolean isDynaProperty(String name) { | |
| 873 | ||
| 874 | 98 | if (name == null) { |
| 875 | 0 | throw new IllegalArgumentException("No property name specified"); |
| 876 | } | |
| 877 | ||
| 878 | // Handle LazyDynaClasses | |
| 879 | 98 | if (dynaClass instanceof LazyDynaClass) { |
| 880 | 98 | return ((LazyDynaClass)dynaClass).isDynaProperty(name); |
| 881 | } | |
| 882 | ||
| 883 | // Handle other MutableDynaClass | |
| 884 | 0 | return dynaClass.getDynaProperty(name) == null ? false : true; |
| 885 | ||
| 886 | } | |
| 887 | ||
| 888 | /** | |
| 889 | * Is an object of the source class assignable to the destination class? | |
| 890 | * | |
| 891 | * @param dest Destination class | |
| 892 | * @param source Source class | |
| 893 | * @return <code>true<code> if the source class is assignable to the | |
| 894 | * destination class, otherwise <code>false</code> | |
| 895 | */ | |
| 896 | protected boolean isAssignable(Class dest, Class source) { | |
| 897 | ||
| 898 | 29 | if (dest.isAssignableFrom(source) || |
| 899 | ((dest == Boolean.TYPE) && (source == Boolean.class)) || | |
| 900 | ((dest == Byte.TYPE) && (source == Byte.class)) || | |
| 901 | ((dest == Character.TYPE) && (source == Character.class)) || | |
| 902 | ((dest == Double.TYPE) && (source == Double.class)) || | |
| 903 | ((dest == Float.TYPE) && (source == Float.class)) || | |
| 904 | ((dest == Integer.TYPE) && (source == Integer.class)) || | |
| 905 | ((dest == Long.TYPE) && (source == Long.class)) || | |
| 906 | ((dest == Short.TYPE) && (source == Short.class))) { | |
| 907 | 28 | return (true); |
| 908 | } else { | |
| 909 | 1 | return (false); |
| 910 | } | |
| 911 | ||
| 912 | } | |
| 913 | ||
| 914 | /** | |
| 915 | * <p>Creates a new instance of the <code>Map</code>.</p> | |
| 916 | * @return a new Map instance | |
| 917 | */ | |
| 918 | protected Map newMap() { | |
| 919 | 114 | return new HashMap(); |
| 920 | } | |
| 921 | ||
| 922 | /** | |
| 923 | * <p>Returns the <code>Log</code>. | |
| 924 | */ | |
| 925 | private Log logger() { | |
| 926 | 0 | if (logger == null) { |
| 927 | 0 | logger = LogFactory.getLog(LazyDynaBean.class); |
| 928 | } | |
| 929 | 0 | return logger; |
| 930 | } | |
| 931 | ||
| 932 | } |