org.apache.commons.beanutils.LazyDynaBean Maven / Gradle / Ivy
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.commons.beanutils;
import java.io.Serializable;
import java.lang.reflect.Array;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
/**
* DynaBean which automatically adds properties to the DynaClass
* and provides Lazy List and Lazy Map features.
*
* DynaBeans deal with three types of properties - simple, indexed and mapped and
* have the following get()
and set()
methods for
* each of these types:
*
* - Simple property methods -
get(name)
and
* set(name, value)
* - Indexed property methods -
get(name, index)
and
* set(name, index, value)
* - Mapped property methods -
get(name, key)
and
* set(name, key, value)
*
*
* Getting Property Values
* Calling any of the get()
methods, for a property which
* doesn't exist, returns null
in this implementation.
*
* Setting Simple Properties
* The LazyDynaBean
will automatically add a property to the DynaClass
* if it doesn't exist when the set(name, value)
method is called.
*
* DynaBean myBean = new LazyDynaBean();
* myBean.set("myProperty", "myValue");
*
* Setting Indexed Properties
* If the property doesn't exist, the LazyDynaBean
will automatically add
* a property with an ArrayList
type to the DynaClass
when
* the set(name, index, value)
method is called.
* It will also instantiate a new ArrayList
and automatically grow
* the List
so that it is big enough to accomodate the index being set.
* ArrayList
is the default indexed property that LazyDynaBean uses but
* this can be easily changed by overriding the defaultIndexedProperty(name)
* method.
*
* DynaBean myBean = new LazyDynaBean();
* myBean.set("myIndexedProperty", 0, "myValue1");
* myBean.set("myIndexedProperty", 1, "myValue2");
*
* If the indexed property does exist in the DynaClass
but is set to
* null
in the LazyDynaBean
, then it will instantiate a
* new List
or Array
as specified by the property's type
* in the DynaClass
and automatically grow the List
* or Array
so that it is big enough to accomodate the index being set.
*
* DynaBean myBean = new LazyDynaBean();
* MutableDynaClass myClass = (MutableDynaClass)myBean.getDynaClass();
* myClass.add("myIndexedProperty", int[].class);
* myBean.set("myIndexedProperty", 0, new Integer(10));
* myBean.set("myIndexedProperty", 1, new Integer(20));
*
* Setting Mapped Properties
* If the property doesn't exist, the LazyDynaBean
will automatically add
* a property with a HashMap
type to the DynaClass
and
* instantiate a new HashMap
in the DynaBean when the
* set(name, key, value)
method is called. HashMap
is the default
* mapped property that LazyDynaBean uses but this can be easily changed by overriding
* the defaultMappedProperty(name)
method.
*
* DynaBean myBean = new LazyDynaBean();
* myBean.set("myMappedProperty", "myKey", "myValue");
*
* If the mapped property does exist in the DynaClass
but is set to
* null
in the LazyDynaBean
, then it will instantiate a
* new Map
as specified by the property's type in the DynaClass
.
*
* DynaBean myBean = new LazyDynaBean();
* MutableDynaClass myClass = (MutableDynaClass)myBean.getDynaClass();
* myClass.add("myMappedProperty", TreeMap.class);
* myBean.set("myMappedProperty", "myKey", "myValue");
*
* Restricted DynaClass
* MutableDynaClass
have a facility to restrict the DynaClass
* so that its properties cannot be modified. If the MutableDynaClass
is
* restricted then calling any of the set()
methods for a property which
* doesn't exist will result in a IllegalArgumentException
being thrown.
*
* @version $Id$
* @see LazyDynaClass
*/
public class LazyDynaBean implements DynaBean, Serializable {
/**
* Commons Logging
*/
private transient Log logger = LogFactory.getLog(LazyDynaBean.class);
/** BigInteger Zero */
protected static final BigInteger BigInteger_ZERO = new BigInteger("0");
/** BigDecimal Zero */
protected static final BigDecimal BigDecimal_ZERO = new BigDecimal("0");
/** Character Space */
protected static final Character Character_SPACE = new Character(' ');
/** Byte Zero */
protected static final Byte Byte_ZERO = new Byte((byte)0);
/** Short Zero */
protected static final Short Short_ZERO = new Short((short)0);
/** Integer Zero */
protected static final Integer Integer_ZERO = new Integer(0);
/** Long Zero */
protected static final Long Long_ZERO = new Long(0);
/** Float Zero */
protected static final Float Float_ZERO = new Float((byte)0);
/** Double Zero */
protected static final Double Double_ZERO = new Double((byte)0);
/**
* The MutableDynaClass
"base class" that this DynaBean
* is associated with.
*/
protected Map values;
/** Map decorator for this DynaBean */
private transient Map mapDecorator;
/**
* The MutableDynaClass
"base class" that this DynaBean
* is associated with.
*/
protected MutableDynaClass dynaClass;
// ------------------- Constructors ----------------------------------
/**
* Construct a new LazyDynaBean
with a LazyDynaClass
instance.
*/
public LazyDynaBean() {
this(new LazyDynaClass());
}
/**
* Construct a new LazyDynaBean
with a LazyDynaClass
instance.
*
* @param name Name of this DynaBean class
*/
public LazyDynaBean(final String name) {
this(new LazyDynaClass(name));
}
/**
* Construct a new DynaBean
associated with the specified
* DynaClass
instance - if its not a MutableDynaClass
* then a new LazyDynaClass
is created and the properties copied.
*
* @param dynaClass The DynaClass we are associated with
*/
public LazyDynaBean(final DynaClass dynaClass) {
values = newMap();
if (dynaClass instanceof MutableDynaClass) {
this.dynaClass = (MutableDynaClass)dynaClass;
} else {
this.dynaClass = new LazyDynaClass(dynaClass.getName(), dynaClass.getDynaProperties());
}
}
// ------------------- Public Methods ----------------------------------
/**
* Return a Map representation of this DynaBean.
*
* This, for example, could be used in JSTL in the following way to access
* a DynaBean's fooProperty
:
* ${myDynaBean.map.fooProperty}
*
* @return a Map representation of this DynaBean
*/
public Map getMap() {
// cache the Map
if (mapDecorator == null) {
mapDecorator = new DynaBeanPropertyMapDecorator(this);
}
return mapDecorator;
}
/**
* Return the size of an indexed or mapped property.
*
* @param name Name of the property
* @return The indexed or mapped property size
* @throws IllegalArgumentException if no property name is specified
*/
public int size(final String name) {
if (name == null) {
throw new IllegalArgumentException("No property name specified");
}
final Object value = values.get(name);
if (value == null) {
return 0;
}
if (value instanceof Map) {
return ((Map, ?>)value).size();
}
if (value instanceof List) {
return ((List>)value).size();
}
if ((value.getClass().isArray())) {
return Array.getLength(value);
}
return 0;
}
// ------------------- DynaBean Methods ----------------------------------
/**
* Does the specified mapped property contain a value for the specified
* key value?
*
* @param name Name of the property to check
* @param key Name of the key to check
* @return true
if the mapped property contains a value for
* the specified key, otherwise false
*
* @throws IllegalArgumentException if no property name is specified
*/
public boolean contains(final String name, final String key) {
if (name == null) {
throw new IllegalArgumentException("No property name specified");
}
final Object value = values.get(name);
if (value == null) {
return false;
}
if (value instanceof Map) {
return (((Map, ?>) value).containsKey(key));
}
return false;
}
/**
* Return the value of a simple property with the specified name.
*
* N.B. Returns null
if there is no property
* of the specified name.
*
* @param name Name of the property whose value is to be retrieved.
* @return The property's value
* @throws IllegalArgumentException if no property name is specified
*/
public Object get(final String name) {
if (name == null) {
throw new IllegalArgumentException("No property name specified");
}
// Value found
Object value = values.get(name);
if (value != null) {
return value;
}
// Property doesn't exist
if (!isDynaProperty(name)) {
return null;
}
// Property doesn't exist
value = createProperty(name, dynaClass.getDynaProperty(name).getType());
if (value != null) {
set(name, value);
}
return value;
}
/**
* Return the value of an indexed property with the specified name.
*
* N.B. Returns null
if there is no 'indexed'
* property of the specified name.
*
* @param name Name of the property whose value is to be retrieved
* @param index Index of the value to be retrieved
* @return The indexed property's value
*
* @throws IllegalArgumentException if the specified property
* exists, but is not indexed
* @throws IndexOutOfBoundsException if the specified index
* is outside the range of the underlying property
*/
public Object get(final String name, final int index) {
// If its not a property, then create default indexed property
if (!isDynaProperty(name)) {
set(name, defaultIndexedProperty(name));
}
// Get the indexed property
Object indexedProperty = get(name);
// Check that the property is indexed
if (!dynaClass.getDynaProperty(name).isIndexed()) {
throw new IllegalArgumentException
("Non-indexed property for '" + name + "[" + index + "]' "
+ dynaClass.getDynaProperty(name).getName());
}
// Grow indexed property to appropriate size
indexedProperty = growIndexedProperty(name, indexedProperty, index);
// Return the indexed value
if (indexedProperty.getClass().isArray()) {
return Array.get(indexedProperty, index);
} else if (indexedProperty instanceof List) {
return ((List>)indexedProperty).get(index);
} else {
throw new IllegalArgumentException
("Non-indexed property for '" + name + "[" + index + "]' "
+ indexedProperty.getClass().getName());
}
}
/**
* Return the value of a mapped property with the specified name.
*
* N.B. Returns null
if there is no 'mapped'
* property of the specified name.
*
* @param name Name of the property whose value is to be retrieved
* @param key Key of the value to be retrieved
* @return The mapped property's value
*
* @throws IllegalArgumentException if the specified property
* exists, but is not mapped
*/
public Object get(final String name, final String key) {
// If its not a property, then create default mapped property
if (!isDynaProperty(name)) {
set(name, defaultMappedProperty(name));
}
// Get the mapped property
final Object mappedProperty = get(name);
// Check that the property is mapped
if (!dynaClass.getDynaProperty(name).isMapped()) {
throw new IllegalArgumentException
("Non-mapped property for '" + name + "(" + key + ")' "
+ dynaClass.getDynaProperty(name).getType().getName());
}
// Get the value from the Map
if (mappedProperty instanceof Map) {
return (((Map, ?>) mappedProperty).get(key));
} else {
throw new IllegalArgumentException
("Non-mapped property for '" + name + "(" + key + ")'"
+ mappedProperty.getClass().getName());
}
}
/**
* Return the DynaClass
instance that describes the set of
* properties available for this DynaBean.
*
* @return The associated DynaClass
*/
public DynaClass getDynaClass() {
return dynaClass;
}
/**
* Remove any existing value for the specified key on the
* specified mapped property.
*
* @param name Name of the property for which a value is to
* be removed
* @param key Key of the value to be removed
*
* @throws IllegalArgumentException if there is no property
* of the specified name
*/
public void remove(final String name, final String key) {
if (name == null) {
throw new IllegalArgumentException("No property name specified");
}
final Object value = values.get(name);
if (value == null) {
return;
}
if (value instanceof Map) {
((Map, ?>) value).remove(key);
} else {
throw new IllegalArgumentException
("Non-mapped property for '" + name + "(" + key + ")'"
+ value.getClass().getName());
}
}
/**
* Set the value of a simple property with the specified name.
*
* @param name Name of the property whose value is to be set
* @param value Value to which this property is to be set
*
* @throws IllegalArgumentException if this is not an existing property
* name for our DynaClass and the MutableDynaClass is restricted
* @throws ConversionException if the specified value cannot be
* converted to the type required for this property
* @throws NullPointerException if an attempt is made to set a
* primitive property to null
*/
public void set(final String name, final Object value) {
// If the property doesn't exist, then add it
if (!isDynaProperty(name)) {
if (dynaClass.isRestricted()) {
throw new IllegalArgumentException
("Invalid property name '" + name + "' (DynaClass is restricted)");
}
if (value == null) {
dynaClass.add(name);
} else {
dynaClass.add(name, value.getClass());
}
}
final DynaProperty descriptor = dynaClass.getDynaProperty(name);
if (value == null) {
if (descriptor.getType().isPrimitive()) {
throw new NullPointerException
("Primitive value for '" + name + "'");
}
} else if (!isAssignable(descriptor.getType(), value.getClass())) {
throw new ConversionException
("Cannot assign value of type '" +
value.getClass().getName() +
"' to property '" + name + "' of type '" +
descriptor.getType().getName() + "'");
}
// Set the property's value
values.put(name, value);
}
/**
* Set the value of an indexed property with the specified name.
*
* @param name Name of the property whose value is to be set
* @param index Index of the property to be set
* @param value Value to which this property is to be set
*
* @throws ConversionException if the specified value cannot be
* converted to the type required for this property
* @throws IllegalArgumentException if there is no property
* of the specified name
* @throws IllegalArgumentException if the specified property
* exists, but is not indexed
* @throws IndexOutOfBoundsException if the specified index
* is outside the range of the underlying property
*/
public void set(final String name, final int index, final Object value) {
// If its not a property, then create default indexed property
if (!isDynaProperty(name)) {
set(name, defaultIndexedProperty(name));
}
// Get the indexed property
Object indexedProperty = get(name);
// Check that the property is indexed
if (!dynaClass.getDynaProperty(name).isIndexed()) {
throw new IllegalArgumentException
("Non-indexed property for '" + name + "[" + index + "]'"
+ dynaClass.getDynaProperty(name).getType().getName());
}
// Grow indexed property to appropriate size
indexedProperty = growIndexedProperty(name, indexedProperty, index);
// Set the value in an array
if (indexedProperty.getClass().isArray()) {
Array.set(indexedProperty, index, value);
} else if (indexedProperty instanceof List) {
@SuppressWarnings("unchecked")
final
// Indexed properties are stored in a List