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