You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@commons.apache.org by gg...@apache.org on 2023/02/18 13:53:22 UTC
[commons-beanutils] 04/05: Sort members
This is an automated email from the ASF dual-hosted git repository.
ggregory pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/commons-beanutils.git
commit 26a7831ea71b282646a229dde2401fe12c509f2a
Author: Gary Gregory <ga...@gmail.com>
AuthorDate: Wed Feb 15 14:32:55 2023 -0500
Sort members
---
.../beanutils2/BaseDynaBeanMapDecorator.java | 176 +-
.../apache/commons/beanutils2/BasicDynaBean.java | 202 +--
.../apache/commons/beanutils2/BasicDynaClass.java | 132 +-
.../apache/commons/beanutils2/BeanComparator.java | 144 +-
.../commons/beanutils2/BeanIntrospectionData.java | 58 +-
.../org/apache/commons/beanutils2/BeanMap.java | 706 ++++----
.../apache/commons/beanutils2/BeanPredicate.java | 72 +-
.../BeanPropertyValueEqualsPredicate.java | 102 +-
.../org/apache/commons/beanutils2/BeanUtils.java | 62 +-
.../apache/commons/beanutils2/BeanUtilsBean.java | 252 +--
.../commons/beanutils2/ConstructorUtils.java | 18 +-
.../beanutils2/ContextClassLoaderLocal.java | 34 +-
.../commons/beanutils2/ConversionException.java | 4 +-
.../apache/commons/beanutils2/ConvertUtils.java | 76 +-
.../commons/beanutils2/ConvertUtilsBean.java | 572 +++----
.../beanutils2/DefaultBeanIntrospector.java | 62 +-
.../beanutils2/DefaultIntrospectionContext.java | 38 +-
.../org/apache/commons/beanutils2/DynaBean.java | 22 +-
.../beanutils2/DynaBeanPropertyMapDecorator.java | 18 +-
.../org/apache/commons/beanutils2/DynaClass.java | 30 +-
.../apache/commons/beanutils2/DynaProperty.java | 222 +--
.../beanutils2/FluentPropertyBeanIntrospector.java | 34 +-
.../commons/beanutils2/IntrospectionContext.java | 40 +-
.../apache/commons/beanutils2/LazyDynaBean.java | 966 +++++------
.../apache/commons/beanutils2/LazyDynaClass.java | 238 +--
.../apache/commons/beanutils2/LazyDynaList.java | 306 ++--
.../org/apache/commons/beanutils2/LazyDynaMap.java | 394 ++---
.../beanutils2/MappedPropertyDescriptor.java | 506 +++---
.../org/apache/commons/beanutils2/MethodUtils.java | 1800 ++++++++++----------
.../apache/commons/beanutils2/PropertyUtils.java | 88 +-
.../commons/beanutils2/PropertyUtilsBean.java | 690 ++++----
.../apache/commons/beanutils2/WeakFastHashMap.java | 1012 +++++------
.../apache/commons/beanutils2/WrapDynaBean.java | 156 +-
.../apache/commons/beanutils2/WrapDynaClass.java | 312 ++--
.../beanutils2/converters/ArrayConverter.java | 174 +-
.../beanutils2/converters/BooleanConverter.java | 68 +-
.../beanutils2/converters/CharacterConverter.java | 26 +-
.../beanutils2/converters/ClassConverter.java | 24 +-
.../beanutils2/converters/DateTimeConverter.java | 446 ++---
.../beanutils2/converters/DurationConverter.java | 22 +-
.../beanutils2/converters/EnumConverter.java | 24 +-
.../beanutils2/converters/FileConverter.java | 22 +-
.../beanutils2/converters/LocalTimeConverter.java | 22 +-
.../beanutils2/converters/MonthDayConverter.java | 22 +-
.../beanutils2/converters/NumberConverter.java | 410 ++---
.../beanutils2/converters/OffsetTimeConverter.java | 22 +-
.../beanutils2/converters/PathConverter.java | 22 +-
.../beanutils2/converters/PeriodConverter.java | 22 +-
.../beanutils2/converters/ShortConverter.java | 8 +-
.../beanutils2/converters/StringConverter.java | 22 +-
.../beanutils2/converters/URIConverter.java | 22 +-
.../beanutils2/converters/URLConverter.java | 22 +-
.../beanutils2/converters/UUIDConverter.java | 22 +-
.../beanutils2/converters/YearConverter.java | 22 +-
.../beanutils2/converters/YearMonthConverter.java | 22 +-
.../beanutils2/converters/ZoneIdConverter.java | 22 +-
.../beanutils2/converters/ZoneOffsetConverter.java | 22 +-
.../commons/beanutils2/locale/LocaleBeanUtils.java | 330 ++--
.../beanutils2/locale/LocaleBeanUtilsBean.java | 684 ++++----
.../beanutils2/locale/LocaleConvertUtils.java | 236 +--
.../beanutils2/locale/LocaleConvertUtilsBean.java | 6 +-
.../apache/commons/beanutils2/AbstractChild.java | 8 +-
.../apache/commons/beanutils2/AbstractParent.java | 20 +-
.../org/apache/commons/beanutils2/AlphaBean.java | 14 +-
.../commons/beanutils2/BasicDynaBeanTestCase.java | 154 +-
.../commons/beanutils2/BeanComparatorTestCase.java | 104 +-
.../beanutils2/BeanIntrospectionDataTestCase.java | 42 +-
.../apache/commons/beanutils2/BeanMapTestCase.java | 330 ++--
.../commons/beanutils2/BeanPredicateTestCase.java | 12 +-
.../BeanPropertyValueChangeConsumerTestCase.java | 234 +--
.../BeanPropertyValueEqualsPredicateTestCase.java | 214 +--
.../BeanToPropertyValueTransformerTestCase.java | 222 +--
.../commons/beanutils2/BeanUtilsBean2TestCase.java | 14 +-
.../commons/beanutils2/BeanUtilsBeanTestCase.java | 1624 +++++++++---------
.../commons/beanutils2/BeanUtilsBenchCase.java | 24 +-
.../commons/beanutils2/BeanWithInnerBean.java | 18 +-
.../commons/beanutils2/BeanificationTestCase.java | 548 +++---
.../org/apache/commons/beanutils2/BenchBean.java | 112 +-
.../org/apache/commons/beanutils2/BetaBean.java | 12 +-
.../beanutils2/ConstructorUtilsTestCase.java | 134 +-
.../commons/beanutils2/ConvertUtilsTestCase.java | 180 +-
.../beanutils2/DynaBeanMapDecoratorTestCase.java | 150 +-
.../commons/beanutils2/DynaBeanUtilsTestCase.java | 768 ++++-----
.../commons/beanutils2/DynaPropertyTestCase.java | 46 +-
.../beanutils2/DynaPropertyUtilsTestCase.java | 60 +-
.../beanutils2/FluentIntrospectionTestBean.java | 18 +-
.../FluentPropertyBeanIntrospectorTestCase.java | 24 +-
.../beanutils2/IndexedPropertyTestCase.java | 210 +--
.../apache/commons/beanutils2/IndexedTestBean.java | 66 +-
.../commons/beanutils2/LazyDynaBeanTestCase.java | 488 +++---
.../commons/beanutils2/LazyDynaClassTestCase.java | 46 +-
.../commons/beanutils2/LazyDynaListTestCase.java | 378 ++--
.../commons/beanutils2/LazyDynaMapTestCase.java | 456 ++---
.../commons/beanutils2/MappedPropertyTestBean.java | 60 +-
.../commons/beanutils2/MappedPropertyTestCase.java | 170 +-
.../commons/beanutils2/MethodUtilsTestCase.java | 312 ++--
.../apache/commons/beanutils2/NestedTestBean.java | 52 +-
.../apache/commons/beanutils2/PrimitiveBean.java | 36 +-
.../commons/beanutils2/PropertyUtilsBenchCase.java | 28 +-
.../commons/beanutils2/PropertyUtilsTestCase.java | 1430 ++++++++--------
...SuppressPropertiesBeanIntrospectorTestCase.java | 136 +-
.../org/apache/commons/beanutils2/TestBean.java | 694 ++++----
.../commons/beanutils2/WrapDynaBeanTestCase.java | 216 +--
.../commons/beanutils2/bugs/Jira157TestCase.java | 114 +-
.../commons/beanutils2/bugs/Jira18TestCase.java | 64 +-
.../commons/beanutils2/bugs/Jira273TestCase.java | 86 +-
.../commons/beanutils2/bugs/Jira298TestCase.java | 46 +-
.../commons/beanutils2/bugs/Jira339TestCase.java | 82 +-
.../commons/beanutils2/bugs/Jira345TestCase.java | 62 +-
.../commons/beanutils2/bugs/Jira349TestCase.java | 72 +-
.../commons/beanutils2/bugs/Jira357TestCase.java | 202 +--
.../commons/beanutils2/bugs/Jira358TestCase.java | 18 +-
.../commons/beanutils2/bugs/Jira359TestCase.java | 72 +-
.../commons/beanutils2/bugs/Jira368TestCase.java | 18 +-
.../commons/beanutils2/bugs/Jira369TestCase.java | 58 +-
.../commons/beanutils2/bugs/Jira381TestCase.java | 44 +-
.../commons/beanutils2/bugs/Jira411TestCase.java | 26 +-
.../commons/beanutils2/bugs/Jira422TestCase.java | 12 +-
.../commons/beanutils2/bugs/Jira422bTestCase.java | 12 +-
.../commons/beanutils2/bugs/Jira454TestCase.java | 14 +-
.../commons/beanutils2/bugs/Jira456TestCase.java | 30 +-
.../commons/beanutils2/bugs/Jira458TestCase.java | 12 +-
.../commons/beanutils2/bugs/Jira465TestCase.java | 108 +-
.../commons/beanutils2/bugs/Jira492TestCase.java | 74 +-
.../commons/beanutils2/bugs/Jira520TestCase.java | 22 +-
.../commons/beanutils2/bugs/Jira61TestCase.java | 186 +-
.../commons/beanutils2/bugs/Jira87TestCase.java | 18 +-
.../commons/beanutils2/bugs/Jira92TestCase.java | 14 +-
.../beanutils2/bugs/other/Jira18BeanFactory.java | 64 +-
.../beanutils2/bugs/other/Jira273BeanFactory.java | 102 +-
.../beanutils2/bugs/other/Jira298BeanFactory.java | 30 +-
.../bugs/other/Jira492IndexedListsSupport.java | 8 +-
.../beanutils2/bugs/other/Jira61BeanFactory.java | 64 +-
.../beanutils2/bugs/other/Jira87BeanFactory.java | 42 +-
.../converters/BaseLocaleConverterTestCase.java | 176 +-
.../BigDecimalLocaleConverterTestCase.java | 102 +-
.../BigIntegerLocaleConverterTestCase.java | 100 +-
.../converters/ByteLocaleConverterTestCase.java | 96 +-
.../converters/DateLocaleConverterTestCase.java | 316 ++--
.../converters/DoubleLocaleConverterTestCase.java | 102 +-
.../converters/EnumConverterTestCase.java | 8 +-
.../converters/FloatLocaleConverterTestCase.java | 102 +-
.../converters/IntegerLocaleConverterTestCase.java | 98 +-
.../converters/LongLocaleConverterTestCase.java | 100 +-
.../converters/ShortLocaleConverterTestCase.java | 100 +-
.../expression/DefaultResolverTestCase.java | 78 +-
.../beanutils2/locale/LocaleBeanUtilsTestCase.java | 42 +-
.../locale/LocaleBeanificationTestCase.java | 544 +++---
.../locale/LocaleConvertUtilsTestCase.java | 474 +++---
.../memoryleaktests/MemoryLeakTestCase.java | 342 ++--
.../memoryleaktests/pojotests/SomePojo.java | 8 +-
.../commons/beanutils2/priv/PackageBean.java | 8 +-
.../commons/beanutils2/priv/PrivateBean.java | 46 +-
.../commons/beanutils2/priv/PublicSubBean.java | 10 +-
.../beanutils2/sql/DynaResultSetTestCase.java | 62 +-
.../commons/beanutils2/sql/DynaRowSetTestCase.java | 282 +--
.../commons/beanutils2/sql/TestResultSet.java | 292 ++--
.../beanutils2/sql/TestResultSetMetaData.java | 92 +-
158 files changed, 13765 insertions(+), 13765 deletions(-)
diff --git a/src/main/java/org/apache/commons/beanutils2/BaseDynaBeanMapDecorator.java b/src/main/java/org/apache/commons/beanutils2/BaseDynaBeanMapDecorator.java
index 28794e50..bc3747d9 100644
--- a/src/main/java/org/apache/commons/beanutils2/BaseDynaBeanMapDecorator.java
+++ b/src/main/java/org/apache/commons/beanutils2/BaseDynaBeanMapDecorator.java
@@ -48,8 +48,51 @@ import java.util.Set;
*/
public abstract class BaseDynaBeanMapDecorator<K> implements Map<K, Object> {
+ /**
+ * Map.Entry implementation.
+ */
+ private static class MapEntry<K> implements Map.Entry<K, Object> {
+
+ private final K key;
+ private final Object value;
+
+ MapEntry(final K key, final Object value) {
+ this.key = key;
+ this.value = value;
+ }
+
+ @Override
+ public boolean equals(final Object o) {
+ if (!(o instanceof Map.Entry)) {
+ return false;
+ }
+ final Map.Entry<?, ?> e = (Map.Entry<?, ?>) o;
+ return key.equals(e.getKey()) && (value == null ? e.getValue() == null : value.equals(e.getValue()));
+ }
+
+ @Override
+ public K getKey() {
+ return key;
+ }
+
+ @Override
+ public Object getValue() {
+ return value;
+ }
+
+ @Override
+ public int hashCode() {
+ return key.hashCode() + (value == null ? 0 : value.hashCode());
+ }
+
+ @Override
+ public Object setValue(final Object value) {
+ throw new UnsupportedOperationException();
+ }
+ }
private final DynaBean dynaBean;
private final boolean readOnly;
+
private transient Set<K> keySet;
/**
@@ -77,15 +120,6 @@ public abstract class BaseDynaBeanMapDecorator<K> implements Map<K, Object> {
this.readOnly = readOnly;
}
- /**
- * Indicate whether the Map is read only.
- *
- * @return {@code true} if the Map is read only, otherwise {@code false}.
- */
- public boolean isReadOnly() {
- return readOnly;
- }
-
/**
* clear() operation is not supported.
*
@@ -132,6 +166,14 @@ public abstract class BaseDynaBeanMapDecorator<K> implements Map<K, Object> {
return false;
}
+ /**
+ * Converts the name of a property to the key type of this decorator.
+ *
+ * @param propertyName the name of a property
+ * @return the converted key to be used in the decorated map
+ */
+ protected abstract K convertKey(String propertyName);
+
/**
* <p>
* Returns the Set of the property/value mappings in the decorated {@link DynaBean}.
@@ -166,6 +208,24 @@ public abstract class BaseDynaBeanMapDecorator<K> implements Map<K, Object> {
return getDynaBean().get(toString(key));
}
+ /**
+ * Provide access to the underlying {@link DynaBean} this Map decorates.
+ *
+ * @return the decorated {@link DynaBean}.
+ */
+ public DynaBean getDynaBean() {
+ return dynaBean;
+ }
+
+ /**
+ * Convenience method to retrieve the {@link DynaProperty}s for this {@link DynaClass}.
+ *
+ * @return The an array of the {@link DynaProperty}s.
+ */
+ private DynaProperty[] getDynaProperties() {
+ return getDynaBean().getDynaClass().getDynaProperties();
+ }
+
/**
* Indicate whether the decorated {@link DynaBean} has any properties.
*
@@ -176,6 +236,15 @@ public abstract class BaseDynaBeanMapDecorator<K> implements Map<K, Object> {
return getDynaProperties().length == 0;
}
+ /**
+ * Indicate whether the Map is read only.
+ *
+ * @return {@code true} if the Map is read only, otherwise {@code false}.
+ */
+ public boolean isReadOnly() {
+ return readOnly;
+ }
+
/**
* <p>
* Returns the Set of the property names in the decorated {@link DynaBean}.
@@ -267,6 +336,16 @@ public abstract class BaseDynaBeanMapDecorator<K> implements Map<K, Object> {
return getDynaProperties().length;
}
+ /**
+ * Convenience method to convert an Object to a String.
+ *
+ * @param obj The Object to convert
+ * @return String representation of the object
+ */
+ private String toString(final Object obj) {
+ return obj == null ? null : obj.toString();
+ }
+
/**
* Returns the set of property values in the decorated {@link DynaBean}.
*
@@ -284,83 +363,4 @@ public abstract class BaseDynaBeanMapDecorator<K> implements Map<K, Object> {
return Collections.unmodifiableList(values);
}
- /**
- * Provide access to the underlying {@link DynaBean} this Map decorates.
- *
- * @return the decorated {@link DynaBean}.
- */
- public DynaBean getDynaBean() {
- return dynaBean;
- }
-
- /**
- * Converts the name of a property to the key type of this decorator.
- *
- * @param propertyName the name of a property
- * @return the converted key to be used in the decorated map
- */
- protected abstract K convertKey(String propertyName);
-
- /**
- * Convenience method to retrieve the {@link DynaProperty}s for this {@link DynaClass}.
- *
- * @return The an array of the {@link DynaProperty}s.
- */
- private DynaProperty[] getDynaProperties() {
- return getDynaBean().getDynaClass().getDynaProperties();
- }
-
- /**
- * Convenience method to convert an Object to a String.
- *
- * @param obj The Object to convert
- * @return String representation of the object
- */
- private String toString(final Object obj) {
- return obj == null ? null : obj.toString();
- }
-
- /**
- * Map.Entry implementation.
- */
- private static class MapEntry<K> implements Map.Entry<K, Object> {
-
- private final K key;
- private final Object value;
-
- MapEntry(final K key, final Object value) {
- this.key = key;
- this.value = value;
- }
-
- @Override
- public boolean equals(final Object o) {
- if (!(o instanceof Map.Entry)) {
- return false;
- }
- final Map.Entry<?, ?> e = (Map.Entry<?, ?>) o;
- return key.equals(e.getKey()) && (value == null ? e.getValue() == null : value.equals(e.getValue()));
- }
-
- @Override
- public int hashCode() {
- return key.hashCode() + (value == null ? 0 : value.hashCode());
- }
-
- @Override
- public K getKey() {
- return key;
- }
-
- @Override
- public Object getValue() {
- return value;
- }
-
- @Override
- public Object setValue(final Object value) {
- throw new UnsupportedOperationException();
- }
- }
-
}
diff --git a/src/main/java/org/apache/commons/beanutils2/BasicDynaBean.java b/src/main/java/org/apache/commons/beanutils2/BasicDynaBean.java
index f2515b5c..8f90c732 100644
--- a/src/main/java/org/apache/commons/beanutils2/BasicDynaBean.java
+++ b/src/main/java/org/apache/commons/beanutils2/BasicDynaBean.java
@@ -55,18 +55,6 @@ public class BasicDynaBean implements DynaBean, Serializable {
private static final long serialVersionUID = 1L;
- /**
- * Constructs a new {@code DynaBean} associated with the specified
- * {@code DynaClass} instance.
- *
- * @param dynaClass The DynaClass we are associated with
- */
- public BasicDynaBean(final DynaClass dynaClass) {
-
- this.dynaClass = dynaClass;
-
- }
-
/**
* The {@code DynaClass} "base class" that this DynaBean
* is associated with.
@@ -82,23 +70,14 @@ public class BasicDynaBean implements DynaBean, Serializable {
private transient Map<String, Object> mapDecorator;
/**
- * <p>
- * Gets a Map representation of this DynaBean.
- * <p>
- * This, for example, could be used in JSTL in the following way to access
- * a DynaBean's {@code fooProperty}:
- * <ul><li>{@code ${myDynaBean.<b>map</b>.fooProperty}}</li></ul>
+ * Constructs a new {@code DynaBean} associated with the specified
+ * {@code DynaClass} instance.
*
- * @return a Map representation of this DynaBean
- * @since 1.8.0
+ * @param dynaClass The DynaClass we are associated with
*/
- public Map<String, Object> getMap() {
+ public BasicDynaBean(final DynaClass dynaClass) {
- // cache the Map
- if (mapDecorator == null) {
- mapDecorator = new DynaBeanPropertyMapDecorator(this);
- }
- return mapDecorator;
+ this.dynaClass = dynaClass;
}
@@ -251,6 +230,72 @@ public class BasicDynaBean implements DynaBean, Serializable {
}
+ /**
+ * Gets the property descriptor for the specified property name.
+ *
+ * @param name Name of the property for which to retrieve the descriptor
+ * @return The property descriptor
+ *
+ * @throws IllegalArgumentException if this is not a valid property
+ * name for our DynaClass
+ */
+ protected DynaProperty getDynaProperty(final String name) {
+
+ final DynaProperty descriptor = getDynaClass().getDynaProperty(name);
+ if (descriptor == null) {
+ throw new IllegalArgumentException
+ ("Invalid property name '" + name + "'");
+ }
+ return descriptor;
+
+ }
+
+ /**
+ * <p>
+ * Gets a Map representation of this DynaBean.
+ * <p>
+ * This, for example, could be used in JSTL in the following way to access
+ * a DynaBean's {@code fooProperty}:
+ * <ul><li>{@code ${myDynaBean.<b>map</b>.fooProperty}}</li></ul>
+ *
+ * @return a Map representation of this DynaBean
+ * @since 1.8.0
+ */
+ public Map<String, Object> getMap() {
+
+ // cache the Map
+ if (mapDecorator == null) {
+ mapDecorator = new DynaBeanPropertyMapDecorator(this);
+ }
+ return mapDecorator;
+
+ }
+
+ /**
+ * Is an object of the source class assignable to the destination class?
+ *
+ * @param dest Destination class
+ * @param source Source class
+ * @return {@code true} if the source class is assignable to the
+ * destination class, otherwise {@code false}
+ */
+ protected boolean isAssignable(final Class<?> dest, final Class<?> source) {
+
+ if (dest.isAssignableFrom(source) ||
+ dest == Boolean.TYPE && source == Boolean.class ||
+ dest == Byte.TYPE && source == Byte.class ||
+ dest == Character.TYPE && source == Character.class ||
+ dest == Double.TYPE && source == Double.class ||
+ dest == Float.TYPE && source == Float.class ||
+ dest == Integer.TYPE && source == Integer.class ||
+ dest == Long.TYPE && source == Long.class ||
+ dest == Short.TYPE && source == Short.class) {
+ return true;
+ }
+ return false;
+
+ }
+
/**
* Remove any existing value for the specified key on the
* specified mapped property.
@@ -275,36 +320,6 @@ public class BasicDynaBean implements DynaBean, Serializable {
}
- /**
- * Sets 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 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 NullPointerException if an attempt is made to set a
- * primitive property to null
- */
- @Override
- public void set(final String name, final Object value) {
-
- final DynaProperty descriptor = getDynaProperty(name);
- if (value == null) {
- if (descriptor.getType().isPrimitive()) {
- throw new NullPointerException
- ("Primitive value for '" + name + "'");
- }
- } else if (!isAssignable(descriptor.getType(), value.getClass())) {
- throw ConversionException.format("Cannot assign value of type '%s' to property '%s' of type '%s'", value.getClass().getName(), name,
- descriptor.getType().getName());
- }
- values.put(name, value);
-
- }
-
/**
* Sets the value of an indexed property with the specified name.
*
@@ -346,6 +361,36 @@ public class BasicDynaBean implements DynaBean, Serializable {
}
+ /**
+ * Sets 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 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 NullPointerException if an attempt is made to set a
+ * primitive property to null
+ */
+ @Override
+ public void set(final String name, final Object value) {
+
+ final DynaProperty descriptor = getDynaProperty(name);
+ if (value == null) {
+ if (descriptor.getType().isPrimitive()) {
+ throw new NullPointerException
+ ("Primitive value for '" + name + "'");
+ }
+ } else if (!isAssignable(descriptor.getType(), value.getClass())) {
+ throw ConversionException.format("Cannot assign value of type '%s' to property '%s' of type '%s'", value.getClass().getName(), name,
+ descriptor.getType().getName());
+ }
+ values.put(name, value);
+
+ }
+
/**
* Sets the value of a mapped property with the specified name.
*
@@ -378,49 +423,4 @@ public class BasicDynaBean implements DynaBean, Serializable {
}
- /**
- * Gets the property descriptor for the specified property name.
- *
- * @param name Name of the property for which to retrieve the descriptor
- * @return The property descriptor
- *
- * @throws IllegalArgumentException if this is not a valid property
- * name for our DynaClass
- */
- protected DynaProperty getDynaProperty(final String name) {
-
- final DynaProperty descriptor = getDynaClass().getDynaProperty(name);
- if (descriptor == null) {
- throw new IllegalArgumentException
- ("Invalid property name '" + name + "'");
- }
- return descriptor;
-
- }
-
- /**
- * Is an object of the source class assignable to the destination class?
- *
- * @param dest Destination class
- * @param source Source class
- * @return {@code true} if the source class is assignable to the
- * destination class, otherwise {@code false}
- */
- protected boolean isAssignable(final Class<?> dest, final Class<?> source) {
-
- if (dest.isAssignableFrom(source) ||
- dest == Boolean.TYPE && source == Boolean.class ||
- dest == Byte.TYPE && source == Byte.class ||
- dest == Character.TYPE && source == Character.class ||
- dest == Double.TYPE && source == Double.class ||
- dest == Float.TYPE && source == Float.class ||
- dest == Integer.TYPE && source == Integer.class ||
- dest == Long.TYPE && source == Long.class ||
- dest == Short.TYPE && source == Short.class) {
- return true;
- }
- return false;
-
- }
-
}
diff --git a/src/main/java/org/apache/commons/beanutils2/BasicDynaClass.java b/src/main/java/org/apache/commons/beanutils2/BasicDynaClass.java
index 06486213..9be1c3c4 100644
--- a/src/main/java/org/apache/commons/beanutils2/BasicDynaClass.java
+++ b/src/main/java/org/apache/commons/beanutils2/BasicDynaClass.java
@@ -35,6 +35,47 @@ public class BasicDynaClass implements DynaClass, Serializable {
private static final long serialVersionUID = 1L;
+ /**
+ * The method signature of the constructor we will use to create
+ * new DynaBean instances.
+ */
+ private static final Class<?>[] CONSTRUCTOR_TYPES = { DynaClass.class };
+
+ /**
+ * The constructor of the {@code dynaBeanClass} that we will use
+ * for creating new instances.
+ */
+ protected transient Constructor<?> constructor;
+
+ /**
+ * The argument values to be passed to the constructor we will use
+ * to create new DynaBean instances.
+ */
+ protected Object[] constructorValues = { this };
+
+ /**
+ * The {@code DynaBean} implementation class we will use for
+ * creating new instances.
+ */
+ protected Class<?> dynaBeanClass = BasicDynaBean.class;
+
+ /**
+ * The "name" of this DynaBean class.
+ */
+ protected String name = this.getClass().getName();
+
+ /**
+ * The set of dynamic properties that are part of this DynaClass.
+ */
+ protected DynaProperty[] properties = DynaProperty.EMPTY_ARRAY;
+
+ /**
+ * The set of dynamic properties that are part of this DynaClass,
+ * keyed by the property name. Individual descriptor instances will
+ * be the same instances as those in the {@code properties} list.
+ */
+ protected HashMap<String, DynaProperty> propertiesMap = new HashMap<>();
+
/**
* Constructs a new BasicDynaClass with default parameters.
*/
@@ -74,57 +115,30 @@ public class BasicDynaClass implements DynaClass, Serializable {
}
/**
- * The constructor of the {@code dynaBeanClass} that we will use
- * for creating new instances.
- */
- protected transient Constructor<?> constructor;
-
- /**
- * The method signature of the constructor we will use to create
- * new DynaBean instances.
- */
- private static final Class<?>[] CONSTRUCTOR_TYPES = { DynaClass.class };
-
- /**
- * The argument values to be passed to the constructor we will use
- * to create new DynaBean instances.
- */
- protected Object[] constructorValues = { this };
-
- /**
- * The {@code DynaBean} implementation class we will use for
- * creating new instances.
- */
- protected Class<?> dynaBeanClass = BasicDynaBean.class;
-
- /**
- * The "name" of this DynaBean class.
- */
- protected String name = this.getClass().getName();
-
- /**
- * The set of dynamic properties that are part of this DynaClass.
- */
- protected DynaProperty[] properties = DynaProperty.EMPTY_ARRAY;
-
- /**
- * The set of dynamic properties that are part of this DynaClass,
- * keyed by the property name. Individual descriptor instances will
- * be the same instances as those in the {@code properties} list.
+ * Gets the Class object we will use to create new instances in the
+ * {@code newInstance()} method. This Class <strong>MUST</strong>
+ * implement the {@code DynaBean} interface.
+ *
+ * @return The class of the {@link DynaBean}
*/
- protected HashMap<String, DynaProperty> propertiesMap = new HashMap<>();
+ public Class<?> getDynaBeanClass() {
+ return this.dynaBeanClass;
+ }
/**
- * Gets the name of this DynaClass (analogous to the
- * {@code getName()} method of {@code java.lang.Class}, which
- * allows the same {@code DynaClass} implementation class to support
- * different dynamic classes, with different sets of properties.
+ * <p>Return an array of {@code PropertyDescriptor} for the properties
+ * currently defined in this DynaClass. If no properties are defined, a
+ * zero-length array will be returned.</p>
*
- * @return the name of the DynaClass
+ * <p><strong>FIXME</strong> - Should we really be implementing
+ * {@code getBeanInfo()} instead, which returns property descriptors
+ * and a bunch of other stuff?</p>
+ *
+ * @return the set of properties for this DynaClass
*/
@Override
- public String getName() {
- return this.name;
+ public DynaProperty[] getDynaProperties() {
+ return properties.clone();
}
/**
@@ -147,19 +161,16 @@ public class BasicDynaClass implements DynaClass, Serializable {
}
/**
- * <p>Return an array of {@code PropertyDescriptor} for the properties
- * currently defined in this DynaClass. If no properties are defined, a
- * zero-length array will be returned.</p>
- *
- * <p><strong>FIXME</strong> - Should we really be implementing
- * {@code getBeanInfo()} instead, which returns property descriptors
- * and a bunch of other stuff?</p>
+ * Gets the name of this DynaClass (analogous to the
+ * {@code getName()} method of {@code java.lang.Class}, which
+ * allows the same {@code DynaClass} implementation class to support
+ * different dynamic classes, with different sets of properties.
*
- * @return the set of properties for this DynaClass
+ * @return the name of the DynaClass
*/
@Override
- public DynaProperty[] getDynaProperties() {
- return properties.clone();
+ public String getName() {
+ return this.name;
}
/**
@@ -189,17 +200,6 @@ public class BasicDynaClass implements DynaClass, Serializable {
}
}
- /**
- * Gets the Class object we will use to create new instances in the
- * {@code newInstance()} method. This Class <strong>MUST</strong>
- * implement the {@code DynaBean} interface.
- *
- * @return The class of the {@link DynaBean}
- */
- public Class<?> getDynaBeanClass() {
- return this.dynaBeanClass;
- }
-
/**
* Sets the Class object we will use to create new instances in the
* {@code newInstance()} method. This Class <strong>MUST</strong>
diff --git a/src/main/java/org/apache/commons/beanutils2/BeanComparator.java b/src/main/java/org/apache/commons/beanutils2/BeanComparator.java
index 67859718..ce628f98 100644
--- a/src/main/java/org/apache/commons/beanutils2/BeanComparator.java
+++ b/src/main/java/org/apache/commons/beanutils2/BeanComparator.java
@@ -43,8 +43,57 @@ import java.util.Comparator;
*/
public class BeanComparator<T, V> implements Comparator<T>, Serializable {
+ /**
+ * A {@link Comparator Comparator} that compares {@link Comparable Comparable} objects.
+ * <p>
+ * This Comparator is useful, for example, for enforcing the natural order in custom implementations of
+ * {@link java.util.SortedSet SortedSet} and {@link java.util.SortedMap SortedMap}.
+ * </p>
+ *
+ * @param <E> the type of objects compared by this comparator
+ * @see java.util.Collections#reverseOrder()
+ */
+ private static class NaturalOrderComparator<E extends Comparable<? super E>>
+ implements Comparator<E>, Serializable {
+
+ /** Serialization version. */
+ private static final long serialVersionUID = -291439688585137865L;
+
+ /** The singleton instance. */
+ @SuppressWarnings("rawtypes")
+ public static final NaturalOrderComparator INSTANCE = new NaturalOrderComparator();
+
+ /**
+ * Private constructor to prevent instantiation. Only use INSTANCE.
+ */
+ private NaturalOrderComparator() {
+ }
+
+ /**
+ * Compare the two {@link Comparable Comparable} arguments. This method is equivalent to:
+ *
+ * <pre>
+ * ((Comparable) obj1).compareTo(obj2)
+ * </pre>
+ */
+ @Override
+ public int compare(final E obj1, final E obj2) {
+ return obj1.compareTo(obj2);
+ }
+
+ @Override
+ public boolean equals(final Object object) {
+ return this == object || null != object && object.getClass().equals(this.getClass());
+ }
+
+ @Override
+ public int hashCode() {
+ return "NaturalOrderComparator".hashCode();
+ }
+ }
private static final long serialVersionUID = 1L;
private String property;
+
private final Comparator<V> comparator;
/**
@@ -97,34 +146,6 @@ public class BeanComparator<T, V> implements Comparator<T>, Serializable {
this.comparator = comparator != null ? comparator : NaturalOrderComparator.INSTANCE;
}
- /**
- * Sets the method to be called to compare two JavaBeans
- *
- * @param property String method name to call to compare If the property passed in is null then the actual objects
- * will be compared
- */
- public void setProperty(final String property) {
- this.property = property;
- }
-
- /**
- * Gets the property attribute of the BeanComparator
- *
- * @return String method name to call to compare. A null value indicates that the actual objects will be compared
- */
- public String getProperty() {
- return property;
- }
-
- /**
- * Gets the Comparator being used to compare beans.
- *
- * @return the Comparator being used to compare beans
- */
- public Comparator<V> getComparator() {
- return comparator;
- }
-
/**
* Compare two JavaBeans by their shared property. If {@link #getProperty} is null then the actual objects will be
* compared.
@@ -181,6 +202,24 @@ public class BeanComparator<T, V> implements Comparator<T>, Serializable {
return true;
}
+ /**
+ * Gets the Comparator being used to compare beans.
+ *
+ * @return the Comparator being used to compare beans
+ */
+ public Comparator<V> getComparator() {
+ return comparator;
+ }
+
+ /**
+ * Gets the property attribute of the BeanComparator
+ *
+ * @return String method name to call to compare. A null value indicates that the actual objects will be compared
+ */
+ public String getProperty() {
+ return property;
+ }
+
/**
* Hashcode compatible with equals.
*
@@ -205,51 +244,12 @@ public class BeanComparator<T, V> implements Comparator<T>, Serializable {
}
/**
- * A {@link Comparator Comparator} that compares {@link Comparable Comparable} objects.
- * <p>
- * This Comparator is useful, for example, for enforcing the natural order in custom implementations of
- * {@link java.util.SortedSet SortedSet} and {@link java.util.SortedMap SortedMap}.
- * </p>
+ * Sets the method to be called to compare two JavaBeans
*
- * @param <E> the type of objects compared by this comparator
- * @see java.util.Collections#reverseOrder()
+ * @param property String method name to call to compare If the property passed in is null then the actual objects
+ * will be compared
*/
- private static class NaturalOrderComparator<E extends Comparable<? super E>>
- implements Comparator<E>, Serializable {
-
- /** Serialization version. */
- private static final long serialVersionUID = -291439688585137865L;
-
- /** The singleton instance. */
- @SuppressWarnings("rawtypes")
- public static final NaturalOrderComparator INSTANCE = new NaturalOrderComparator();
-
- /**
- * Private constructor to prevent instantiation. Only use INSTANCE.
- */
- private NaturalOrderComparator() {
- }
-
- /**
- * Compare the two {@link Comparable Comparable} arguments. This method is equivalent to:
- *
- * <pre>
- * ((Comparable) obj1).compareTo(obj2)
- * </pre>
- */
- @Override
- public int compare(final E obj1, final E obj2) {
- return obj1.compareTo(obj2);
- }
-
- @Override
- public int hashCode() {
- return "NaturalOrderComparator".hashCode();
- }
-
- @Override
- public boolean equals(final Object object) {
- return this == object || null != object && object.getClass().equals(this.getClass());
- }
+ public void setProperty(final String property) {
+ this.property = property;
}
}
diff --git a/src/main/java/org/apache/commons/beanutils2/BeanIntrospectionData.java b/src/main/java/org/apache/commons/beanutils2/BeanIntrospectionData.java
index f4992806..7ca1c382 100644
--- a/src/main/java/org/apache/commons/beanutils2/BeanIntrospectionData.java
+++ b/src/main/java/org/apache/commons/beanutils2/BeanIntrospectionData.java
@@ -42,6 +42,26 @@ import java.util.Map;
* @since 1.9.1
*/
class BeanIntrospectionData {
+ /**
+ * Initializes the map with the names of the write methods for the supported
+ * properties. The method names - if defined - need to be stored separately because
+ * they may get lost when the GC claims soft references used by the
+ * {@code PropertyDescriptor} objects.
+ *
+ * @param descs the array with the descriptors of the available properties
+ * @return the map with the names of write methods for properties
+ */
+ private static Map<String, String> setUpWriteMethodNames(final PropertyDescriptor[] descs) {
+ final Map<String, String> methods = new HashMap<>();
+ for (final PropertyDescriptor pd : descs) {
+ final Method method = pd.getWriteMethod();
+ if (method != null) {
+ methods.put(pd.getName(), method.getName());
+ }
+ }
+ return methods;
+ }
+
/** An array with property descriptors for the managed bean class. */
private final PropertyDescriptor[] descriptors;
@@ -70,15 +90,6 @@ class BeanIntrospectionData {
writeMethodNames = writeMethNames;
}
- /**
- * Returns the array with property descriptors.
- *
- * @return the property descriptors for the associated bean class
- */
- public PropertyDescriptor[] getDescriptors() {
- return descriptors;
- }
-
/**
* Returns the {@code PropertyDescriptor} for the property with the specified name. If
* this property is unknown, result is <b>null</b>.
@@ -95,6 +106,15 @@ class BeanIntrospectionData {
return null;
}
+ /**
+ * Returns the array with property descriptors.
+ *
+ * @return the property descriptors for the associated bean class
+ */
+ public PropertyDescriptor[] getDescriptors() {
+ return descriptors;
+ }
+
/**
* Returns the write method for the property determined by the given
* {@code PropertyDescriptor}. This information is normally available in the
@@ -127,24 +147,4 @@ class BeanIntrospectionData {
return method;
}
-
- /**
- * Initializes the map with the names of the write methods for the supported
- * properties. The method names - if defined - need to be stored separately because
- * they may get lost when the GC claims soft references used by the
- * {@code PropertyDescriptor} objects.
- *
- * @param descs the array with the descriptors of the available properties
- * @return the map with the names of write methods for properties
- */
- private static Map<String, String> setUpWriteMethodNames(final PropertyDescriptor[] descs) {
- final Map<String, String> methods = new HashMap<>();
- for (final PropertyDescriptor pd : descs) {
- final Method method = pd.getWriteMethod();
- if (method != null) {
- methods.put(pd.getName(), method.getName());
- }
- }
- return methods;
- }
}
diff --git a/src/main/java/org/apache/commons/beanutils2/BeanMap.java b/src/main/java/org/apache/commons/beanutils2/BeanMap.java
index 2fcfe9af..e6cb7a14 100644
--- a/src/main/java/org/apache/commons/beanutils2/BeanMap.java
+++ b/src/main/java/org/apache/commons/beanutils2/BeanMap.java
@@ -43,17 +43,48 @@ import java.util.function.Function;
*/
public class BeanMap extends AbstractMap<String, Object> implements Cloneable {
- private transient Object bean;
+ /**
+ * Map entry used by {@link BeanMap}.
+ */
+ protected static class Entry extends AbstractMap.SimpleEntry<String, Object> {
- private final transient HashMap<String, Method> readMethods = new HashMap<>();
- private final transient HashMap<String, Method> writeMethods = new HashMap<>();
- private final transient HashMap<String, Class<? extends Object>> types = new HashMap<>();
+ private static final long serialVersionUID = 1L;
+ private final BeanMap owner;
+
+ /**
+ * Constructs a new {@code Entry}.
+ *
+ * @param owner the BeanMap this entry belongs to
+ * @param key the key for this entry
+ * @param value the value for this entry
+ */
+ protected Entry(final BeanMap owner, final String key, final Object value) {
+ super(key, value);
+ this.owner = owner;
+ }
+
+ /**
+ * Sets the value.
+ *
+ * @param value the new value for the entry
+ * @return the old value for the entry
+ */
+ @Override
+ public Object setValue(final Object value) {
+ final String key = getKey();
+ final Object oldValue = owner.get(key);
+
+ owner.put(key, value);
+ final Object newValue = owner.get(key);
+ super.setValue(newValue);
+ return oldValue;
+ }
+ }
/**
* An empty array. Used to invoke accessors via reflection.
*/
public static final Object[] NULL_ARGUMENTS = {};
-
/**
* Maps primitive Class types to transformers. The transformer transform strings into the appropriate primitive
* wrapper.
@@ -61,7 +92,6 @@ public class BeanMap extends AbstractMap<String, Object> implements Cloneable {
* N.B. private & unmodifiable replacement for the (public & static) defaultTransformers instance.
*/
private static final Map<Class<? extends Object>, Function<?, ?>> typeTransformers = Collections.unmodifiableMap(createTypeTransformers());
-
private static Map<Class<? extends Object>, Function<?, ?>> createTypeTransformers() {
final Map<Class<? extends Object>, Function<?, ?>> defTransformers = new HashMap<>();
defTransformers.put(Boolean.TYPE, input -> Boolean.valueOf(input.toString()));
@@ -75,15 +105,26 @@ public class BeanMap extends AbstractMap<String, Object> implements Cloneable {
return defTransformers;
}
+ private transient Object bean;
+
+ private final transient HashMap<String, Method> readMethods = new HashMap<>();
+
+ private final transient HashMap<String, Method> writeMethods = new HashMap<>();
+
// Constructors
+ private final transient HashMap<String, Class<? extends Object>> types = new HashMap<>();
+
/**
* Constructs a new empty {@code BeanMap}.
*/
public BeanMap() {
}
+ // Map interface
+
+
/**
* Constructs a new {@code BeanMap} that operates on the specified bean. If the given bean is
* {@code null}, then this map will be empty.
@@ -95,17 +136,28 @@ public class BeanMap extends AbstractMap<String, Object> implements Cloneable {
initialize();
}
- // Map interface
-
-
/**
- * Renders a string representation of this object.
- *
- * @return a {@code String} representation of this object
+ * This method reinitializes the bean map to have default values for the bean's properties. This is accomplished by
+ * constructing a new instance of the bean which the map uses as its underlying data source. This behavior for
+ * {@code clear()} differs from the Map contract in that the mappings are not actually removed from the map
+ * (the mappings for a BeanMap are fixed).
*/
@Override
- public String toString() {
- return "BeanMap<" + bean + ">";
+ public void clear() {
+ if (bean == null) {
+ return;
+ }
+
+ Class<? extends Object> beanClass = null;
+ try {
+ beanClass = bean.getClass();
+ bean = beanClass.newInstance();
+ } catch (final Exception e) {
+ final UnsupportedOperationException uoe = new UnsupportedOperationException(
+ "Could not create new instance of class: " + beanClass);
+ BeanUtils.initCause(uoe, e);
+ throw uoe;
+ }
}
/**
@@ -179,44 +231,6 @@ public class BeanMap extends AbstractMap<String, Object> implements Cloneable {
return newMap;
}
- /**
- * Puts all of the writable properties from the given BeanMap into this BeanMap. Read-only and Write-only properties
- * will be ignored.
- *
- * @param map the BeanMap whose properties to put
- */
- public void putAllWriteable(final BeanMap map) {
- map.readMethods.keySet().forEach(key -> {
- if (getWriteMethod(key) != null) {
- this.put(key, map.get(key));
- }
- });
- }
-
- /**
- * This method reinitializes the bean map to have default values for the bean's properties. This is accomplished by
- * constructing a new instance of the bean which the map uses as its underlying data source. This behavior for
- * {@code clear()} differs from the Map contract in that the mappings are not actually removed from the map
- * (the mappings for a BeanMap are fixed).
- */
- @Override
- public void clear() {
- if (bean == null) {
- return;
- }
-
- Class<? extends Object> beanClass = null;
- try {
- beanClass = bean.getClass();
- bean = beanClass.newInstance();
- } catch (final Exception e) {
- final UnsupportedOperationException uoe = new UnsupportedOperationException(
- "Could not create new instance of class: " + beanClass);
- BeanUtils.initCause(uoe, e);
- throw uoe;
- }
- }
-
/**
* Returns true if the bean defines a property with the given name.
* <p>
@@ -248,94 +262,117 @@ public class BeanMap extends AbstractMap<String, Object> implements Cloneable {
}
/**
- * Returns the value of the bean's property with the given name.
- * <p>
- * The given name must be a {@link String} and must not be null; otherwise, this method returns {@code null}.
- * If the bean defines a property with the given name, the value of that property is returned. Otherwise,
- * {@code null} is returned.
- * <p>
- * Write-only properties will not be matched as the test operates against property read methods.
+ * Converts the given value to the given type. First, reflection is used to find a public constructor declared by
+ * the given class that takes one argument, which must be the precise type of the given value. If such a constructor
+ * is found, a new object is created by passing the given value to that constructor, and the newly constructed
+ * object is returned.
+ * <P>
*
- * @param name the name of the property whose value to return
- * @return the value of the property with that name
+ * If no such constructor exists, and the given type is a primitive type, then the given value is converted to a
+ * string using its {@link Object#toString() toString()} method, and that string is parsed into the correct
+ * primitive type using, for instance, {@link Integer#valueOf(String)} to convert the string into an
+ * {@code int}.
+ * <P>
+ *
+ * If no special constructor exists and the given type is not a primitive type, this method returns the original
+ * value.
+ *
+ * @param <R> The return type.
+ * @param newType the type to convert the value to
+ * @param value the value to convert
+ * @return the converted value
+ * @throws NumberFormatException if newType is a primitive type, and the string representation of the given value
+ * cannot be converted to that type
+ * @throws InstantiationException if the constructor found with reflection raises it
+ * @throws InvocationTargetException if the constructor found with reflection raises it
+ * @throws IllegalAccessException never
+ * @throws IllegalArgumentException never
*/
- @Override
- public Object get(final Object name) {
- if (bean != null) {
- final Method method = getReadMethod(name);
- if (method != null) {
- try {
- return method.invoke(bean, NULL_ARGUMENTS);
- } catch (final IllegalAccessException | NullPointerException |
- InvocationTargetException | IllegalArgumentException e) {
- logWarn(e);
- }
+ protected <R> Object convertType(final Class<R> newType, final Object value)
+ throws InstantiationException, IllegalAccessException, IllegalArgumentException, InvocationTargetException {
+
+ // try call constructor
+ try {
+ final Constructor<R> constructor = newType.getConstructor(value.getClass());
+ return constructor.newInstance(value);
+ } catch (final NoSuchMethodException e) {
+ // try using the transformers
+ final Function<Object, R> transformer = getTypeTransformer(newType);
+ if (transformer != null) {
+ return transformer.apply(value);
}
+ return value;
}
- return null;
}
/**
- * Sets the bean property with the given name to the given value.
+ * Creates an array of parameters to pass to the given mutator method. If the given object is not the right type to
+ * pass to the method directly, it will be converted using {@link #convertType(Class,Object)}.
*
- * @param name the name of the property to set
- * @param value the value to set that property to
- * @return the previous value of that property
- * @throws IllegalArgumentException if the given name is null; if the given name is not a {@link String}; if the
- * bean doesn't define a property with that name; or if the bean property with that name is read-only
+ * @param method the mutator method
+ * @param value the value to pass to the mutator method
+ * @return an array containing one object that is either the given value or a transformed value
+ * @throws IllegalAccessException if {@link #convertType(Class,Object)} raises it
+ * @throws IllegalArgumentException if any other exception is raised by {@link #convertType(Class,Object)}
* @throws ClassCastException if an error occurs creating the method args
*/
- @Override
- public Object put(final String name, final Object value) throws IllegalArgumentException, ClassCastException {
- if (bean != null) {
- final Object oldValue = get(name);
- final Method method = getWriteMethod(name);
- if (method == null) {
- throw new IllegalArgumentException(
- "The bean of type: " + bean.getClass().getName() + " has no property called: " + name);
- }
- try {
- final Object[] arguments = createWriteMethodArguments(method, value);
- method.invoke(bean, arguments);
-
- final Object newValue = get(name);
- firePropertyChange(name, oldValue, newValue);
- } catch (final InvocationTargetException | IllegalAccessException e) {
- final IllegalArgumentException iae = new IllegalArgumentException(e.getMessage());
- if (!BeanUtils.initCause(iae, e)) {
- logInfo(e);
+ protected Object[] createWriteMethodArguments(final Method method, Object value)
+ throws IllegalAccessException, ClassCastException {
+ try {
+ if (value != null) {
+ final Class<? extends Object>[] paramTypes = method.getParameterTypes();
+ if (paramTypes != null && paramTypes.length > 0) {
+ final Class<? extends Object> paramType = paramTypes[0];
+ if (!paramType.isAssignableFrom(value.getClass())) {
+ value = convertType(paramType, value);
+ }
}
- throw iae;
}
- return oldValue;
+ final Object[] answer = { value };
+ return answer;
+ } catch (final InvocationTargetException e) {
+ final IllegalArgumentException iae = new IllegalArgumentException(e.getMessage());
+ if (!BeanUtils.initCause(iae, e)) {
+ logInfo(e);
+ }
+ throw iae;
+ } catch (final InstantiationException e) {
+ final IllegalArgumentException iae = new IllegalArgumentException(e.getMessage());
+ if (!BeanUtils.initCause(iae, e)) {
+ logInfo(e);
+ }
+ BeanUtils.initCause(iae, e);
+ throw iae;
}
- return null;
}
/**
- * Returns the number of properties defined by the bean.
+ * Convenience method for getting an iterator over the entries.
*
- * @return the number of properties defined by the bean
+ * @return an iterator over the entries
*/
- @Override
- public int size() {
- return readMethods.size();
- }
+ public Iterator<Map.Entry<String, Object>> entryIterator() {
+ final Iterator<String> iter = keyIterator();
+ return new Iterator<Map.Entry<String, Object>>() {
+ @Override
+ public boolean hasNext() {
+ return iter.hasNext();
+ }
- /**
- * Get the keys for this BeanMap.
- * <p>
- * Write-only properties are <b>not</b> included in the returned set of property names, although it is possible to
- * set their value and to get their type.
- *
- * @return BeanMap keys. The Set returned by this method is not modifiable.
- */
- @SuppressWarnings({ "unchecked", "rawtypes" })
- // The set actually contains strings; however, because it cannot be
- // modified there is no danger in selling it as Set<Object>
- @Override
- public Set<String> keySet() {
- return Collections.unmodifiableSet((Set) readMethods.keySet());
+ @Override
+ public Map.Entry<String, Object> next() {
+ final String key = iter.next();
+ final Object value = get(key);
+ // This should not cause any problems; the key is actually a
+ // string, but it does no harm to expose it as Object
+ return new Entry(BeanMap.this, key, value);
+ }
+
+ @Override
+ public void remove() {
+ throw new UnsupportedOperationException("remove() not supported for BeanMap");
+ }
+ };
}
/**
@@ -361,99 +398,45 @@ public class BeanMap extends AbstractMap<String, Object> implements Cloneable {
}
/**
- * Returns the values for the BeanMap.
- *
- * @return values for the BeanMap. The returned collection is not modifiable.
- */
- @Override
- public Collection<Object> values() {
- final ArrayList<Object> answer = new ArrayList<>(readMethods.size());
- valueIterator().forEachRemaining(answer::add);
- return Collections.unmodifiableList(answer);
- }
-
- // Helper methods
-
-
- /**
- * Returns the type of the property with the given name.
+ * Called during a successful {@link #put(String,Object)} operation. Default implementation does nothing. Override
+ * to be notified of property changes in the bean caused by this map.
*
- * @param name the name of the property
- * @return the type of the property, or {@code null} if no such property exists
+ * @param key the name of the property that changed
+ * @param oldValue the old value for that property
+ * @param newValue the new value for that property
*/
- public Class<?> getType(final String name) {
- return types.get(name);
+ protected void firePropertyChange(final Object key, final Object oldValue, final Object newValue) {
+ // noop
}
/**
- * Convenience method for getting an iterator over the keys.
+ * Returns the value of the bean's property with the given name.
* <p>
- * Write-only properties will not be returned in the iterator.
- *
- * @return an iterator over the keys
- */
- public Iterator<String> keyIterator() {
- return readMethods.keySet().iterator();
- }
-
- /**
- * Convenience method for getting an iterator over the values.
- *
- * @return an iterator over the values
- */
- public Iterator<Object> valueIterator() {
- final Iterator<?> iter = keyIterator();
- return new Iterator<Object>() {
- @Override
- public boolean hasNext() {
- return iter.hasNext();
- }
-
- @Override
- public Object next() {
- final Object key = iter.next();
- return get(key);
- }
-
- @Override
- public void remove() {
- throw new UnsupportedOperationException("remove() not supported for BeanMap");
- }
- };
- }
-
- /**
- * Convenience method for getting an iterator over the entries.
+ * The given name must be a {@link String} and must not be null; otherwise, this method returns {@code null}.
+ * If the bean defines a property with the given name, the value of that property is returned. Otherwise,
+ * {@code null} is returned.
+ * <p>
+ * Write-only properties will not be matched as the test operates against property read methods.
*
- * @return an iterator over the entries
+ * @param name the name of the property whose value to return
+ * @return the value of the property with that name
*/
- public Iterator<Map.Entry<String, Object>> entryIterator() {
- final Iterator<String> iter = keyIterator();
- return new Iterator<Map.Entry<String, Object>>() {
- @Override
- public boolean hasNext() {
- return iter.hasNext();
- }
-
- @Override
- public Map.Entry<String, Object> next() {
- final String key = iter.next();
- final Object value = get(key);
- // This should not cause any problems; the key is actually a
- // string, but it does no harm to expose it as Object
- return new Entry(BeanMap.this, key, value);
- }
-
- @Override
- public void remove() {
- throw new UnsupportedOperationException("remove() not supported for BeanMap");
+ @Override
+ public Object get(final Object name) {
+ if (bean != null) {
+ final Method method = getReadMethod(name);
+ if (method != null) {
+ try {
+ return method.invoke(bean, NULL_ARGUMENTS);
+ } catch (final IllegalAccessException | NullPointerException |
+ InvocationTargetException | IllegalArgumentException e) {
+ logWarn(e);
+ }
}
- };
+ }
+ return null;
}
- // Properties
-
-
/**
* Returns the bean currently being operated on. The return value may be null if this map is empty.
*
@@ -463,14 +446,18 @@ public class BeanMap extends AbstractMap<String, Object> implements Cloneable {
return bean;
}
+ // Helper methods
+
+
/**
- * Sets the bean to be operated on by this map. The given value may be null, in which case this map will be empty.
+ * Returns the accessor for the property with the given name.
*
- * @param newBean the new bean to operate on
+ * @param name the name of the property
+ * @return null if the name is null; null if the name is not a {@link String}; null if no such property exists; or
+ * the accessor method for that property
*/
- public void setBean(final Object newBean) {
- bean = newBean;
- reinitialise();
+ protected Method getReadMethod(final Object name) {
+ return readMethods.get(name);
}
/**
@@ -484,29 +471,29 @@ public class BeanMap extends AbstractMap<String, Object> implements Cloneable {
}
/**
- * Returns the mutator for the property with the given name.
+ * Returns the type of the property with the given name.
*
* @param name the name of the property
- * @return the mutator method for the property, or null
+ * @return the type of the property, or {@code null} if no such property exists
*/
- public Method getWriteMethod(final String name) {
- return writeMethods.get(name);
+ public Class<?> getType(final String name) {
+ return types.get(name);
}
- // Implementation methods
-
-
/**
- * Returns the accessor for the property with the given name.
+ * Returns a transformer for the given primitive type.
*
- * @param name the name of the property
- * @return null if the name is null; null if the name is not a {@link String}; null if no such property exists; or
- * the accessor method for that property
+ * @param <R> The transformer result type.
+ * @param type the primitive type whose transformer to return
+ * @return a transformer that will convert strings into that type, or null if the given type is not a primitive type
*/
- protected Method getReadMethod(final Object name) {
- return readMethods.get(name);
+ protected <R> Function<Object, R> getTypeTransformer(final Class<R> type) {
+ return (Function<Object, R>) typeTransformers.get(type);
}
+ // Properties
+
+
/**
* Returns the mutator for the property with the given name.
*
@@ -519,13 +506,13 @@ public class BeanMap extends AbstractMap<String, Object> implements Cloneable {
}
/**
- * Reinitializes this bean. Called during {@link #setBean(Object)}. Does introspection to find properties.
+ * Returns the mutator for the property with the given name.
+ *
+ * @param name the name of the property
+ * @return the mutator method for the property, or null
*/
- protected void reinitialise() {
- readMethods.clear();
- writeMethods.clear();
- types.clear();
- initialize();
+ public Method getWriteMethod(final String name) {
+ return writeMethods.get(name);
}
private void initialize() {
@@ -562,172 +549,185 @@ public class BeanMap extends AbstractMap<String, Object> implements Cloneable {
}
/**
- * Called during a successful {@link #put(String,Object)} operation. Default implementation does nothing. Override
- * to be notified of property changes in the bean caused by this map.
+ * Convenience method for getting an iterator over the keys.
+ * <p>
+ * Write-only properties will not be returned in the iterator.
*
- * @param key the name of the property that changed
- * @param oldValue the old value for that property
- * @param newValue the new value for that property
+ * @return an iterator over the keys
*/
- protected void firePropertyChange(final Object key, final Object oldValue, final Object newValue) {
- // noop
+ public Iterator<String> keyIterator() {
+ return readMethods.keySet().iterator();
}
- // Implementation classes
+ // Implementation methods
/**
- * Map entry used by {@link BeanMap}.
+ * Get the keys for this BeanMap.
+ * <p>
+ * Write-only properties are <b>not</b> included in the returned set of property names, although it is possible to
+ * set their value and to get their type.
+ *
+ * @return BeanMap keys. The Set returned by this method is not modifiable.
*/
- protected static class Entry extends AbstractMap.SimpleEntry<String, Object> {
-
- private static final long serialVersionUID = 1L;
- private final BeanMap owner;
-
- /**
- * Constructs a new {@code Entry}.
- *
- * @param owner the BeanMap this entry belongs to
- * @param key the key for this entry
- * @param value the value for this entry
- */
- protected Entry(final BeanMap owner, final String key, final Object value) {
- super(key, value);
- this.owner = owner;
- }
+ @SuppressWarnings({ "unchecked", "rawtypes" })
+ // The set actually contains strings; however, because it cannot be
+ // modified there is no danger in selling it as Set<Object>
+ @Override
+ public Set<String> keySet() {
+ return Collections.unmodifiableSet((Set) readMethods.keySet());
+ }
- /**
- * Sets the value.
- *
- * @param value the new value for the entry
- * @return the old value for the entry
- */
- @Override
- public Object setValue(final Object value) {
- final String key = getKey();
- final Object oldValue = owner.get(key);
+ /**
+ * Logs the given exception to {@code System.out}. Used to display warnings while accessing/mutating the bean.
+ *
+ * @param ex the exception to log
+ */
+ protected void logInfo(final Exception ex) {
+ // Deliberately do not use LOG4J or Commons Logging to avoid dependencies
+ System.out.println("INFO: Exception: " + ex);
+ }
- owner.put(key, value);
- final Object newValue = owner.get(key);
- super.setValue(newValue);
- return oldValue;
- }
+ /**
+ * Logs the given exception to {@code System.err}. Used to display errors while accessing/mutating the bean.
+ *
+ * @param ex the exception to log
+ */
+ protected void logWarn(final Exception ex) {
+ // Deliberately do not use LOG4J or Commons Logging to avoid dependencies
+ System.out.println("WARN: Exception: " + ex);
+ ex.printStackTrace();
}
/**
- * Creates an array of parameters to pass to the given mutator method. If the given object is not the right type to
- * pass to the method directly, it will be converted using {@link #convertType(Class,Object)}.
+ * Sets the bean property with the given name to the given value.
*
- * @param method the mutator method
- * @param value the value to pass to the mutator method
- * @return an array containing one object that is either the given value or a transformed value
- * @throws IllegalAccessException if {@link #convertType(Class,Object)} raises it
- * @throws IllegalArgumentException if any other exception is raised by {@link #convertType(Class,Object)}
+ * @param name the name of the property to set
+ * @param value the value to set that property to
+ * @return the previous value of that property
+ * @throws IllegalArgumentException if the given name is null; if the given name is not a {@link String}; if the
+ * bean doesn't define a property with that name; or if the bean property with that name is read-only
* @throws ClassCastException if an error occurs creating the method args
*/
- protected Object[] createWriteMethodArguments(final Method method, Object value)
- throws IllegalAccessException, ClassCastException {
- try {
- if (value != null) {
- final Class<? extends Object>[] paramTypes = method.getParameterTypes();
- if (paramTypes != null && paramTypes.length > 0) {
- final Class<? extends Object> paramType = paramTypes[0];
- if (!paramType.isAssignableFrom(value.getClass())) {
- value = convertType(paramType, value);
- }
- }
- }
- final Object[] answer = { value };
- return answer;
- } catch (final InvocationTargetException e) {
- final IllegalArgumentException iae = new IllegalArgumentException(e.getMessage());
- if (!BeanUtils.initCause(iae, e)) {
- logInfo(e);
+ @Override
+ public Object put(final String name, final Object value) throws IllegalArgumentException, ClassCastException {
+ if (bean != null) {
+ final Object oldValue = get(name);
+ final Method method = getWriteMethod(name);
+ if (method == null) {
+ throw new IllegalArgumentException(
+ "The bean of type: " + bean.getClass().getName() + " has no property called: " + name);
}
- throw iae;
- } catch (final InstantiationException e) {
- final IllegalArgumentException iae = new IllegalArgumentException(e.getMessage());
- if (!BeanUtils.initCause(iae, e)) {
- logInfo(e);
+ try {
+ final Object[] arguments = createWriteMethodArguments(method, value);
+ method.invoke(bean, arguments);
+
+ final Object newValue = get(name);
+ firePropertyChange(name, oldValue, newValue);
+ } catch (final InvocationTargetException | IllegalAccessException e) {
+ final IllegalArgumentException iae = new IllegalArgumentException(e.getMessage());
+ if (!BeanUtils.initCause(iae, e)) {
+ logInfo(e);
+ }
+ throw iae;
}
- BeanUtils.initCause(iae, e);
- throw iae;
+ return oldValue;
}
+ return null;
}
/**
- * Converts the given value to the given type. First, reflection is used to find a public constructor declared by
- * the given class that takes one argument, which must be the precise type of the given value. If such a constructor
- * is found, a new object is created by passing the given value to that constructor, and the newly constructed
- * object is returned.
- * <P>
- *
- * If no such constructor exists, and the given type is a primitive type, then the given value is converted to a
- * string using its {@link Object#toString() toString()} method, and that string is parsed into the correct
- * primitive type using, for instance, {@link Integer#valueOf(String)} to convert the string into an
- * {@code int}.
- * <P>
+ * Puts all of the writable properties from the given BeanMap into this BeanMap. Read-only and Write-only properties
+ * will be ignored.
*
- * If no special constructor exists and the given type is not a primitive type, this method returns the original
- * value.
+ * @param map the BeanMap whose properties to put
+ */
+ public void putAllWriteable(final BeanMap map) {
+ map.readMethods.keySet().forEach(key -> {
+ if (getWriteMethod(key) != null) {
+ this.put(key, map.get(key));
+ }
+ });
+ }
+
+ // Implementation classes
+
+
+ /**
+ * Reinitializes this bean. Called during {@link #setBean(Object)}. Does introspection to find properties.
+ */
+ protected void reinitialise() {
+ readMethods.clear();
+ writeMethods.clear();
+ types.clear();
+ initialize();
+ }
+
+ /**
+ * Sets the bean to be operated on by this map. The given value may be null, in which case this map will be empty.
*
- * @param <R> The return type.
- * @param newType the type to convert the value to
- * @param value the value to convert
- * @return the converted value
- * @throws NumberFormatException if newType is a primitive type, and the string representation of the given value
- * cannot be converted to that type
- * @throws InstantiationException if the constructor found with reflection raises it
- * @throws InvocationTargetException if the constructor found with reflection raises it
- * @throws IllegalAccessException never
- * @throws IllegalArgumentException never
+ * @param newBean the new bean to operate on
*/
- protected <R> Object convertType(final Class<R> newType, final Object value)
- throws InstantiationException, IllegalAccessException, IllegalArgumentException, InvocationTargetException {
+ public void setBean(final Object newBean) {
+ bean = newBean;
+ reinitialise();
+ }
- // try call constructor
- try {
- final Constructor<R> constructor = newType.getConstructor(value.getClass());
- return constructor.newInstance(value);
- } catch (final NoSuchMethodException e) {
- // try using the transformers
- final Function<Object, R> transformer = getTypeTransformer(newType);
- if (transformer != null) {
- return transformer.apply(value);
- }
- return value;
- }
+ /**
+ * Returns the number of properties defined by the bean.
+ *
+ * @return the number of properties defined by the bean
+ */
+ @Override
+ public int size() {
+ return readMethods.size();
}
/**
- * Returns a transformer for the given primitive type.
+ * Renders a string representation of this object.
*
- * @param <R> The transformer result type.
- * @param type the primitive type whose transformer to return
- * @return a transformer that will convert strings into that type, or null if the given type is not a primitive type
+ * @return a {@code String} representation of this object
*/
- protected <R> Function<Object, R> getTypeTransformer(final Class<R> type) {
- return (Function<Object, R>) typeTransformers.get(type);
+ @Override
+ public String toString() {
+ return "BeanMap<" + bean + ">";
}
/**
- * Logs the given exception to {@code System.out}. Used to display warnings while accessing/mutating the bean.
+ * Convenience method for getting an iterator over the values.
*
- * @param ex the exception to log
+ * @return an iterator over the values
*/
- protected void logInfo(final Exception ex) {
- // Deliberately do not use LOG4J or Commons Logging to avoid dependencies
- System.out.println("INFO: Exception: " + ex);
+ public Iterator<Object> valueIterator() {
+ final Iterator<?> iter = keyIterator();
+ return new Iterator<Object>() {
+ @Override
+ public boolean hasNext() {
+ return iter.hasNext();
+ }
+
+ @Override
+ public Object next() {
+ final Object key = iter.next();
+ return get(key);
+ }
+
+ @Override
+ public void remove() {
+ throw new UnsupportedOperationException("remove() not supported for BeanMap");
+ }
+ };
}
/**
- * Logs the given exception to {@code System.err}. Used to display errors while accessing/mutating the bean.
+ * Returns the values for the BeanMap.
*
- * @param ex the exception to log
+ * @return values for the BeanMap. The returned collection is not modifiable.
*/
- protected void logWarn(final Exception ex) {
- // Deliberately do not use LOG4J or Commons Logging to avoid dependencies
- System.out.println("WARN: Exception: " + ex);
- ex.printStackTrace();
+ @Override
+ public Collection<Object> values() {
+ final ArrayList<Object> answer = new ArrayList<>(readMethods.size());
+ valueIterator().forEachRemaining(answer::add);
+ return Collections.unmodifiableList(answer);
}
}
diff --git a/src/main/java/org/apache/commons/beanutils2/BeanPredicate.java b/src/main/java/org/apache/commons/beanutils2/BeanPredicate.java
index 17dd7325..6bf1a62a 100644
--- a/src/main/java/org/apache/commons/beanutils2/BeanPredicate.java
+++ b/src/main/java/org/apache/commons/beanutils2/BeanPredicate.java
@@ -53,6 +53,42 @@ public class BeanPredicate<T> implements Predicate<T> {
this.predicate = predicate;
}
+ /**
+ * Gets the {@code Predicate} to be applied to the value of the named property
+ * during {@link #test(Object)}.
+ * @return {@code Predicate}, not null
+ */
+ public Predicate<T> getPredicate() {
+ return predicate;
+ }
+
+ /**
+ * Gets the name of the property whose value is to be predicated.
+ * in the evaluation.
+ * @return the property name, not null
+ */
+ public String getPropertyName() {
+ return propertyName;
+ }
+
+ /**
+ * Sets the {@code Predicate} to be applied to the value of the named property
+ * during {@link #test(Object)}.
+ * @param predicate {@code Predicate}, not null
+ */
+ public void setPredicate(final Predicate<T> predicate) {
+ this.predicate = predicate;
+ }
+
+ /**
+ * Sets the name of the property whose value is to be predicated.
+ * @param propertyName the name of the property whose value is to be predicated,
+ * not null
+ */
+ public void setPropertyName(final String propertyName) {
+ this.propertyName = propertyName;
+ }
+
/**
* Evaluates the given object by applying the {@link #getPredicate()}
* to a property value named by {@link #getPropertyName()}.
@@ -89,40 +125,4 @@ public class BeanPredicate<T> implements Predicate<T> {
return evaluation;
}
- /**
- * Gets the name of the property whose value is to be predicated.
- * in the evaluation.
- * @return the property name, not null
- */
- public String getPropertyName() {
- return propertyName;
- }
-
- /**
- * Sets the name of the property whose value is to be predicated.
- * @param propertyName the name of the property whose value is to be predicated,
- * not null
- */
- public void setPropertyName(final String propertyName) {
- this.propertyName = propertyName;
- }
-
- /**
- * Gets the {@code Predicate} to be applied to the value of the named property
- * during {@link #test(Object)}.
- * @return {@code Predicate}, not null
- */
- public Predicate<T> getPredicate() {
- return predicate;
- }
-
- /**
- * Sets the {@code Predicate} to be applied to the value of the named property
- * during {@link #test(Object)}.
- * @param predicate {@code Predicate}, not null
- */
- public void setPredicate(final Predicate<T> predicate) {
- this.predicate = predicate;
- }
-
}
diff --git a/src/main/java/org/apache/commons/beanutils2/BeanPropertyValueEqualsPredicate.java b/src/main/java/org/apache/commons/beanutils2/BeanPropertyValueEqualsPredicate.java
index c6aea087..bf835551 100644
--- a/src/main/java/org/apache/commons/beanutils2/BeanPropertyValueEqualsPredicate.java
+++ b/src/main/java/org/apache/commons/beanutils2/BeanPropertyValueEqualsPredicate.java
@@ -174,6 +174,57 @@ public class BeanPropertyValueEqualsPredicate<T, V> implements Predicate<T> {
this.ignoreNull = ignoreNull;
}
+ /**
+ * Utility method which evaluates whether the actual property value equals the expected property
+ * value.
+ *
+ * @param expected The expected value.
+ * @param actual The actual value.
+ * @return True if they are equal; false otherwise.
+ */
+ protected boolean evaluateValue(final V expected, final Object actual) {
+ return Objects.equals(expected, actual);
+ }
+
+ /**
+ * Returns the name of the property which will be evaluated when this {@code Predicate} is
+ * executed.
+ *
+ * @return The name of the property which will be evaluated when this {@code Predicate} is
+ * executed.
+ */
+ public String getPropertyName() {
+ return propertyName;
+ }
+
+ /**
+ * Returns the value that the property specified by {@code propertyName} will be compared to
+ * when this {@code Predicate} executes.
+ *
+ * @return The value that the property specified by {@code propertyName} will be compared to
+ * when this {@code Predicate} executes.
+ */
+ public V getPropertyValue() {
+ return propertyValue;
+ }
+
+ /**
+ * Returns the flag which determines whether {@code null} objects in the property path will
+ * generate an {@code IllegalArgumentException</code> or not. If set to <code>true} then
+ * if any objects in the property path evaluate to {@code null} then the
+ * {@code IllegalArgumentException</code> throw by <code>PropertyUtils} will be logged but
+ * not re-thrown and {@code false</code> will be returned. If set to <code>false} then if
+ * any objects in the property path evaluate to {@code null} then the
+ * {@code IllegalArgumentException</code> throw by <code>PropertyUtils} will be logged and
+ * re-thrown.
+ *
+ * @return The flag which determines whether {@code null} objects in the property path will
+ * generate an {@code IllegalArgumentException} or not.
+ */
+ public boolean isIgnoreNull() {
+ return ignoreNull;
+ }
+
/**
* Evaluates the object provided against the criteria specified when this
* {@code BeanPropertyValueEqualsPredicate} was constructed. Equality is based on
@@ -236,55 +287,4 @@ public class BeanPropertyValueEqualsPredicate<T, V> implements Predicate<T> {
return evaluation;
}
-
- /**
- * Utility method which evaluates whether the actual property value equals the expected property
- * value.
- *
- * @param expected The expected value.
- * @param actual The actual value.
- * @return True if they are equal; false otherwise.
- */
- protected boolean evaluateValue(final V expected, final Object actual) {
- return Objects.equals(expected, actual);
- }
-
- /**
- * Returns the name of the property which will be evaluated when this {@code Predicate} is
- * executed.
- *
- * @return The name of the property which will be evaluated when this {@code Predicate} is
- * executed.
- */
- public String getPropertyName() {
- return propertyName;
- }
-
- /**
- * Returns the value that the property specified by {@code propertyName} will be compared to
- * when this {@code Predicate} executes.
- *
- * @return The value that the property specified by {@code propertyName} will be compared to
- * when this {@code Predicate} executes.
- */
- public V getPropertyValue() {
- return propertyValue;
- }
-
- /**
- * Returns the flag which determines whether {@code null} objects in the property path will
- * generate an {@code IllegalArgumentException</code> or not. If set to <code>true} then
- * if any objects in the property path evaluate to {@code null} then the
- * {@code IllegalArgumentException</code> throw by <code>PropertyUtils} will be logged but
- * not re-thrown and {@code false</code> will be returned. If set to <code>false} then if
- * any objects in the property path evaluate to {@code null} then the
- * {@code IllegalArgumentException</code> throw by <code>PropertyUtils} will be logged and
- * re-thrown.
- *
- * @return The flag which determines whether {@code null} objects in the property path will
- * generate an {@code IllegalArgumentException} or not.
- */
- public boolean isIgnoreNull() {
- return ignoreNull;
- }
}
diff --git a/src/main/java/org/apache/commons/beanutils2/BeanUtils.java b/src/main/java/org/apache/commons/beanutils2/BeanUtils.java
index ce128e99..14e79764 100644
--- a/src/main/java/org/apache/commons/beanutils2/BeanUtils.java
+++ b/src/main/java/org/apache/commons/beanutils2/BeanUtils.java
@@ -35,6 +35,14 @@ import java.util.Map;
public class BeanUtils {
+ /** An empty class array */
+ static final Class<?>[] EMPTY_CLASS_ARRAY = {};
+
+
+ /** An empty object array */
+ static final Object[] EMPTY_OBJECT_ARRAY = {};
+
+
/**
* <p>Clone a bean based on the available property getters and setters,
* even if the bean class itself does not implement Cloneable.</p>
@@ -112,6 +120,18 @@ public class BeanUtils {
}
+ /**
+ * Create a cache.
+ * @param <K> the key type of the cache
+ * @param <V> the value type of the cache
+ * @return a new cache
+ * @since 1.8.0
+ */
+ public static <K, V> Map<K, V> createCache() {
+ return new WeakFastHashMap<>();
+ }
+
+
/**
* <p>Return the entire set of properties for which the specified bean
* provides a read method.</p>
@@ -334,7 +354,6 @@ public class BeanUtils {
}
-
/**
* <p>Return the value of the specified simple property of the specified
* bean, converted to a String.</p>
@@ -361,6 +380,17 @@ public class BeanUtils {
}
+ /**
+ * If we're running on JDK 1.4 or later, initialize the cause for the given throwable.
+ *
+ * @param throwable The throwable.
+ * @param cause The cause of the throwable.
+ * @return true if the cause was initialized, otherwise false.
+ * @since 1.8.0
+ */
+ public static boolean initCause(final Throwable throwable, final Throwable cause) {
+ return BeanUtilsBean.getInstance().initCause(throwable, cause);
+ }
/**
* <p>Populate the JavaBeans properties of the specified bean, based on
@@ -384,7 +414,6 @@ public class BeanUtils {
BeanUtilsBean.getInstance().populate(bean, properties);
}
-
/**
* <p>Set the specified property value, performing type conversions as
* required to conform to the type of the destination property.</p>
@@ -406,33 +435,4 @@ public class BeanUtils {
BeanUtilsBean.getInstance().setProperty(bean, name, value);
}
-
- /**
- * If we're running on JDK 1.4 or later, initialize the cause for the given throwable.
- *
- * @param throwable The throwable.
- * @param cause The cause of the throwable.
- * @return true if the cause was initialized, otherwise false.
- * @since 1.8.0
- */
- public static boolean initCause(final Throwable throwable, final Throwable cause) {
- return BeanUtilsBean.getInstance().initCause(throwable, cause);
- }
-
- /**
- * Create a cache.
- * @param <K> the key type of the cache
- * @param <V> the value type of the cache
- * @return a new cache
- * @since 1.8.0
- */
- public static <K, V> Map<K, V> createCache() {
- return new WeakFastHashMap<>();
- }
-
- /** An empty class array */
- static final Class<?>[] EMPTY_CLASS_ARRAY = {};
-
- /** An empty object array */
- static final Object[] EMPTY_OBJECT_ARRAY = {};
}
diff --git a/src/main/java/org/apache/commons/beanutils2/BeanUtilsBean.java b/src/main/java/org/apache/commons/beanutils2/BeanUtilsBean.java
index 946459da..ca9ff037 100644
--- a/src/main/java/org/apache/commons/beanutils2/BeanUtilsBean.java
+++ b/src/main/java/org/apache/commons/beanutils2/BeanUtilsBean.java
@@ -56,6 +56,58 @@ public class BeanUtilsBean {
}
};
+ /**
+ * Logging for this instance
+ */
+ private static final Log LOG = LogFactory.getLog(BeanUtilsBean.class);
+
+ /** A reference to Throwable's initCause method, or null if it's not there in this JVM */
+ private static final Method INIT_CAUSE_METHOD = getInitCauseMethod();
+
+ /**
+ * Determines the type of a {@code DynaProperty}. Here a special treatment
+ * is needed for mapped properties.
+ *
+ * @param dynaProperty the property descriptor
+ * @param value the value object to be set for this property
+ * @return the type of this property
+ */
+ private static Class<?> dynaPropertyType(final DynaProperty dynaProperty,
+ final Object value) {
+ if (!dynaProperty.isMapped()) {
+ return dynaProperty.getType();
+ }
+ return value == null ? String.class : value.getClass();
+ }
+
+ /**
+ * Returns a <code>Method<code> allowing access to
+ * {@link Throwable#initCause(Throwable)} method of {@link Throwable},
+ * or {@code null} if the method
+ * does not exist.
+ *
+ * @return A {@code Method<code> for <code>Throwable.initCause}, or
+ * {@code null} if unavailable.
+ */
+ private static Method getInitCauseMethod() {
+ try {
+ final Class<?>[] paramsClasses = { Throwable.class };
+ return Throwable.class.getMethod("initCause", paramsClasses);
+ } catch (final NoSuchMethodException e) {
+ final Log log = LogFactory.getLog(BeanUtils.class);
+ if (log.isWarnEnabled()) {
+ log.warn("Throwable does not have initCause() method in JDK 1.3");
+ }
+ return null;
+ } catch (final Throwable e) {
+ final Log log = LogFactory.getLog(BeanUtils.class);
+ if (log.isWarnEnabled()) {
+ log.warn("Error getting the Throwable initCause() method", e);
+ }
+ return null;
+ }
+ }
+
/**
* Gets the instance which provides the functionality for {@link BeanUtils}.
* This is a pseudo-singleton - an single instance is provided per (thread) context classloader.
@@ -78,20 +130,12 @@ public class BeanUtilsBean {
BEANS_BY_CLASSLOADER.set(newInstance);
}
- /**
- * Logging for this instance
- */
- private static final Log LOG = LogFactory.getLog(BeanUtilsBean.class);
-
/** Used to perform conversions between object types when setting properties */
private final ConvertUtilsBean convertUtilsBean;
/** Used to access properties*/
private final PropertyUtilsBean propertyUtilsBean;
- /** A reference to Throwable's initCause method, or null if it's not there in this JVM */
- private static final Method INIT_CAUSE_METHOD = getInitCauseMethod();
-
/**
* <p>Constructs an instance using new property
* and conversion instances.</p>
@@ -165,6 +209,42 @@ public class BeanUtilsBean {
return newBean;
}
+ /**
+ * <p>Converts the value to an object of the specified class (if
+ * possible).</p>
+ *
+ * @param <R> The desired return type
+ * @param value Value to be converted (may be null)
+ * @param type Class of the value to be converted to
+ * @return The converted value
+ *
+ * @throws ConversionException if thrown by an underlying Converter
+ * @since 1.8.0
+ */
+ protected <R> Object convert(final Object value, final Class<R> type) {
+ final Converter<R> converter = getConvertUtils().lookup(type);
+ if (converter != null) {
+ if (LOG.isTraceEnabled()) {
+ LOG.trace(" USING CONVERTER " + converter);
+ }
+ return converter.convert(type, value);
+ }
+ return value;
+ }
+
+ /**
+ * Performs a type conversion of a property value before it is copied to a target
+ * bean. This method delegates to {@link #convert(Object, Class)}, but <b>null</b>
+ * values are not converted. This causes <b>null</b> values to be copied verbatim.
+ *
+ * @param value the value to be converted and copied
+ * @param type the target type of the conversion
+ * @return the converted value
+ */
+ private Object convertForCopy(final Object value, final Class<?> type) {
+ return value != null ? convert(value, type) : value;
+ }
+
/**
* <p>Copy property values from the origin bean to the destination bean
* for all cases where the property names are the same. For each
@@ -534,6 +614,15 @@ public class BeanUtilsBean {
return results;
}
+ /**
+ * Gets the {@code ConvertUtilsBean} instance used to perform the conversions.
+ *
+ * @return The ConvertUtils bean instance
+ */
+ public ConvertUtilsBean getConvertUtils() {
+ return convertUtilsBean;
+ }
+
/**
* Gets the value of the specified indexed property of the specified
* bean, as a String. The zero-relative index of the
@@ -682,6 +771,15 @@ public class BeanUtilsBean {
return getNestedProperty(bean, name);
}
+ /**
+ * Gets the {@code PropertyUtilsBean} instance used to access properties.
+ *
+ * @return The ConvertUtils bean instance
+ */
+ public PropertyUtilsBean getPropertyUtils() {
+ return propertyUtilsBean;
+ }
+
/**
* Gets the value of the specified simple property of the specified
* bean, converted to a String.
@@ -704,6 +802,26 @@ public class BeanUtilsBean {
return getConvertUtils().convert(value);
}
+ /**
+ * If we're running on JDK 1.4 or later, initialize the cause for the given throwable.
+ *
+ * @param throwable The throwable.
+ * @param cause The cause of the throwable.
+ * @return true if the cause was initialized, otherwise false.
+ * @since 1.8.0
+ */
+ public boolean initCause(final Throwable throwable, final Throwable cause) {
+ if (INIT_CAUSE_METHOD != null && cause != null) {
+ try {
+ INIT_CAUSE_METHOD.invoke(throwable, cause);
+ return true;
+ } catch (final Throwable e) {
+ // can't initialize cause
+ }
+ }
+ return false;
+ }
+
/**
* <p>Populate the JavaBeans properties of the specified bean, based on
* the specified name/value pairs. This method uses Java reflection APIs
@@ -954,122 +1072,4 @@ public class BeanUtilsBean {
(e, "Cannot set " + propName);
}
}
-
- /**
- * Gets the {@code ConvertUtilsBean} instance used to perform the conversions.
- *
- * @return The ConvertUtils bean instance
- */
- public ConvertUtilsBean getConvertUtils() {
- return convertUtilsBean;
- }
-
- /**
- * Gets the {@code PropertyUtilsBean} instance used to access properties.
- *
- * @return The ConvertUtils bean instance
- */
- public PropertyUtilsBean getPropertyUtils() {
- return propertyUtilsBean;
- }
-
- /**
- * If we're running on JDK 1.4 or later, initialize the cause for the given throwable.
- *
- * @param throwable The throwable.
- * @param cause The cause of the throwable.
- * @return true if the cause was initialized, otherwise false.
- * @since 1.8.0
- */
- public boolean initCause(final Throwable throwable, final Throwable cause) {
- if (INIT_CAUSE_METHOD != null && cause != null) {
- try {
- INIT_CAUSE_METHOD.invoke(throwable, cause);
- return true;
- } catch (final Throwable e) {
- // can't initialize cause
- }
- }
- return false;
- }
-
- /**
- * <p>Converts the value to an object of the specified class (if
- * possible).</p>
- *
- * @param <R> The desired return type
- * @param value Value to be converted (may be null)
- * @param type Class of the value to be converted to
- * @return The converted value
- *
- * @throws ConversionException if thrown by an underlying Converter
- * @since 1.8.0
- */
- protected <R> Object convert(final Object value, final Class<R> type) {
- final Converter<R> converter = getConvertUtils().lookup(type);
- if (converter != null) {
- if (LOG.isTraceEnabled()) {
- LOG.trace(" USING CONVERTER " + converter);
- }
- return converter.convert(type, value);
- }
- return value;
- }
-
- /**
- * Performs a type conversion of a property value before it is copied to a target
- * bean. This method delegates to {@link #convert(Object, Class)}, but <b>null</b>
- * values are not converted. This causes <b>null</b> values to be copied verbatim.
- *
- * @param value the value to be converted and copied
- * @param type the target type of the conversion
- * @return the converted value
- */
- private Object convertForCopy(final Object value, final Class<?> type) {
- return value != null ? convert(value, type) : value;
- }
-
- /**
- * Returns a <code>Method<code> allowing access to
- * {@link Throwable#initCause(Throwable)} method of {@link Throwable},
- * or {@code null} if the method
- * does not exist.
- *
- * @return A {@code Method<code> for <code>Throwable.initCause}, or
- * {@code null} if unavailable.
- */
- private static Method getInitCauseMethod() {
- try {
- final Class<?>[] paramsClasses = { Throwable.class };
- return Throwable.class.getMethod("initCause", paramsClasses);
- } catch (final NoSuchMethodException e) {
- final Log log = LogFactory.getLog(BeanUtils.class);
- if (log.isWarnEnabled()) {
- log.warn("Throwable does not have initCause() method in JDK 1.3");
- }
- return null;
- } catch (final Throwable e) {
- final Log log = LogFactory.getLog(BeanUtils.class);
- if (log.isWarnEnabled()) {
- log.warn("Error getting the Throwable initCause() method", e);
- }
- return null;
- }
- }
-
- /**
- * Determines the type of a {@code DynaProperty}. Here a special treatment
- * is needed for mapped properties.
- *
- * @param dynaProperty the property descriptor
- * @param value the value object to be set for this property
- * @return the type of this property
- */
- private static Class<?> dynaPropertyType(final DynaProperty dynaProperty,
- final Object value) {
- if (!dynaProperty.isMapped()) {
- return dynaProperty.getType();
- }
- return value == null ? String.class : value.getClass();
- }
}
diff --git a/src/main/java/org/apache/commons/beanutils2/ConstructorUtils.java b/src/main/java/org/apache/commons/beanutils2/ConstructorUtils.java
index 22a0864e..1ee53b2f 100644
--- a/src/main/java/org/apache/commons/beanutils2/ConstructorUtils.java
+++ b/src/main/java/org/apache/commons/beanutils2/ConstructorUtils.java
@@ -423,15 +423,6 @@ public class ConstructorUtils {
return ctor.newInstance(args);
}
- private static Object[] toArray(final Object arg) {
- Object[] args = null;
- if (arg != null) {
- args = new Object[] { arg };
- }
- return args;
- }
-
-
/**
* Delegates to {@link Array#newInstance(Class, int)}.
*
@@ -446,4 +437,13 @@ public class ConstructorUtils {
return (T[]) Array.newInstance(componentType, length);
}
+
+ private static Object[] toArray(final Object arg) {
+ Object[] args = null;
+ if (arg != null) {
+ args = new Object[] { arg };
+ }
+ return args;
+ }
+
}
diff --git a/src/main/java/org/apache/commons/beanutils2/ContextClassLoaderLocal.java b/src/main/java/org/apache/commons/beanutils2/ContextClassLoaderLocal.java
index 9880421d..1ebb3c42 100644
--- a/src/main/java/org/apache/commons/beanutils2/ContextClassLoaderLocal.java
+++ b/src/main/java/org/apache/commons/beanutils2/ContextClassLoaderLocal.java
@@ -112,23 +112,6 @@ public class ContextClassLoaderLocal<T> {
public ContextClassLoaderLocal() {
}
- /**
- * Returns the initial value for this ContextClassLoaderLocal
- * variable. This method will be called once per Context ClassLoader for
- * each ContextClassLoaderLocal, the first time it is accessed
- * with get or set. If the programmer desires ContextClassLoaderLocal variables
- * to be initialized to some value other than null, ContextClassLoaderLocal must
- * be subclassed, and this method overridden. Typically, an anonymous
- * inner class will be used. Typical implementations of initialValue
- * will call an appropriate constructor and return the newly constructed
- * object.
- *
- * @return a new Object to be used as an initial value for this ContextClassLoaderLocal
- */
- protected T initialValue() {
- return null;
- }
-
/**
* Gets the instance which provides the functionality for {@link BeanUtils}.
* This is a pseudo-singleton - an single instance is provided per (thread) context classloader.
@@ -166,6 +149,23 @@ public class ContextClassLoaderLocal<T> {
return globalValue;
}
+ /**
+ * Returns the initial value for this ContextClassLoaderLocal
+ * variable. This method will be called once per Context ClassLoader for
+ * each ContextClassLoaderLocal, the first time it is accessed
+ * with get or set. If the programmer desires ContextClassLoaderLocal variables
+ * to be initialized to some value other than null, ContextClassLoaderLocal must
+ * be subclassed, and this method overridden. Typically, an anonymous
+ * inner class will be used. Typical implementations of initialValue
+ * will call an appropriate constructor and return the newly constructed
+ * object.
+ *
+ * @return a new Object to be used as an initial value for this ContextClassLoaderLocal
+ */
+ protected T initialValue() {
+ return null;
+ }
+
/**
* Sets the value - a value is provided per (thread) context classloader.
* This mechanism provides isolation for web apps deployed in the same container.
diff --git a/src/main/java/org/apache/commons/beanutils2/ConversionException.java b/src/main/java/org/apache/commons/beanutils2/ConversionException.java
index 707ab98f..fc061c97 100644
--- a/src/main/java/org/apache/commons/beanutils2/ConversionException.java
+++ b/src/main/java/org/apache/commons/beanutils2/ConversionException.java
@@ -25,6 +25,8 @@ package org.apache.commons.beanutils2;
*/
public class ConversionException extends RuntimeException {
+ private static final long serialVersionUID = 1L;
+
/**
* Constructs a new instance with a message formatted using {@link String#format(String, Object...)}.
*
@@ -37,8 +39,6 @@ public class ConversionException extends RuntimeException {
return new ConversionException(String.format(format, args));
}
- private static final long serialVersionUID = 1L;
-
/**
* Constructs a new exception with the specified message.
*
diff --git a/src/main/java/org/apache/commons/beanutils2/ConvertUtils.java b/src/main/java/org/apache/commons/beanutils2/ConvertUtils.java
index de03d847..bf2ae597 100644
--- a/src/main/java/org/apache/commons/beanutils2/ConvertUtils.java
+++ b/src/main/java/org/apache/commons/beanutils2/ConvertUtils.java
@@ -43,6 +43,20 @@ public class ConvertUtils {
return ConvertUtilsBean.getInstance().convert(value);
}
+ /**
+ * <p>Converts the value to an object of the specified class (if
+ * possible).</p>
+ *
+ * @param value Value to be converted (may be null)
+ * @param targetType Class of the value to be converted to (must not be null)
+ * @return The converted value
+ *
+ * @throws ConversionException if thrown by an underlying Converter
+ */
+ public static Object convert(final Object value, final Class<?> targetType) {
+ return ConvertUtilsBean.getInstance().convert(value, targetType);
+ }
+
/**
* <p>Converts the specified value to an object of the specified class (if
* possible). Otherwise, return a String representation of the value.</p>
@@ -75,20 +89,6 @@ public class ConvertUtils {
return ConvertUtilsBean.getInstance().convert(values, clazz);
}
- /**
- * <p>Converts the value to an object of the specified class (if
- * possible).</p>
- *
- * @param value Value to be converted (may be null)
- * @param targetType Class of the value to be converted to (must not be null)
- * @return The converted value
- *
- * @throws ConversionException if thrown by an underlying Converter
- */
- public static Object convert(final Object value, final Class<?> targetType) {
- return ConvertUtilsBean.getInstance().convert(value, targetType);
- }
-
/**
* <p>Remove all registered {@link Converter}s, and re-establish the
* standard Converters.</p>
@@ -114,22 +114,6 @@ public class ConvertUtils {
ConvertUtilsBean.getInstance().deregister(clazz);
}
- /**
- * <p>Look up and return any registered {@link Converter} for the specified
- * destination class; if there is no registered Converter, return
- * {@code null}.</p>
- *
- * <p>For more details see {@code ConvertUtilsBean}.</p>
- *
- * @param <T> The converter type.
- * @param clazz Class for which to return a registered Converter
- * @return The registered {@link Converter} or {@code null} if not found
- * @see ConvertUtilsBean#lookup(Class)
- */
- public static <T> Converter<T> lookup(final Class<T> clazz) {
- return ConvertUtilsBean.getInstance().lookup(clazz);
- }
-
/**
* Look up and return any registered {@link Converter} for the specified
* source and destination class; if there is no registered Converter,
@@ -145,19 +129,19 @@ public class ConvertUtils {
}
/**
- * <p>Register a custom {@link Converter} for the specified destination
- * {@code Class}, replacing any previously registered Converter.</p>
+ * <p>Look up and return any registered {@link Converter} for the specified
+ * destination class; if there is no registered Converter, return
+ * {@code null}.</p>
*
* <p>For more details see {@code ConvertUtilsBean}.</p>
*
* @param <T> The converter type.
- * @param converter Converter to be registered
- * @param clazz Destination class for conversions performed by this
- * Converter
- * @see ConvertUtilsBean#register(Converter, Class)
+ * @param clazz Class for which to return a registered Converter
+ * @return The registered {@link Converter} or {@code null} if not found
+ * @see ConvertUtilsBean#lookup(Class)
*/
- public static <T> void register(final Converter<T> converter, final Class<T> clazz) {
- ConvertUtilsBean.getInstance().register(converter, clazz);
+ public static <T> Converter<T> lookup(final Class<T> clazz) {
+ return ConvertUtilsBean.getInstance().lookup(clazz);
}
/**
@@ -204,4 +188,20 @@ public class ConvertUtils {
}
return type;
}
+
+ /**
+ * <p>Register a custom {@link Converter} for the specified destination
+ * {@code Class}, replacing any previously registered Converter.</p>
+ *
+ * <p>For more details see {@code ConvertUtilsBean}.</p>
+ *
+ * @param <T> The converter type.
+ * @param converter Converter to be registered
+ * @param clazz Destination class for conversions performed by this
+ * Converter
+ * @see ConvertUtilsBean#register(Converter, Class)
+ */
+ public static <T> void register(final Converter<T> converter, final Class<T> clazz) {
+ ConvertUtilsBean.getInstance().register(converter, clazz);
+ }
}
diff --git a/src/main/java/org/apache/commons/beanutils2/ConvertUtilsBean.java b/src/main/java/org/apache/commons/beanutils2/ConvertUtilsBean.java
index 46c6093c..dc9ddaa0 100644
--- a/src/main/java/org/apache/commons/beanutils2/ConvertUtilsBean.java
+++ b/src/main/java/org/apache/commons/beanutils2/ConvertUtilsBean.java
@@ -174,6 +174,11 @@ public class ConvertUtilsBean {
private static final Integer ZERO = Integer.valueOf(0);
private static final Character SPACE = Character.valueOf(' ');
+ /**
+ * The {@code Log} instance for this class.
+ */
+ private static final Log LOG = LogFactory.getLog(ConvertUtilsBean.class);
+
/**
* Get singleton instance
* @return The singleton instance
@@ -188,11 +193,6 @@ public class ConvertUtilsBean {
*/
private final WeakFastHashMap<Class<?>, Converter<?>> converters = new WeakFastHashMap<>();
- /**
- * The {@code Log} instance for this class.
- */
- private static final Log LOG = LogFactory.getLog(ConvertUtilsBean.class);
-
/** Constructs a bean with standard converters registered */
public ConvertUtilsBean() {
converters.setFast(false);
@@ -232,6 +232,61 @@ public class ConvertUtilsBean {
}
+ /**
+ * Convert the value to an object of the specified class (if
+ * possible). If no converter for the desired target type is registered,
+ * the passed in object is returned unchanged.
+ *
+ * @param <T> The Class type.
+ * @param value Value to be converted (may be null)
+ * @param targetType Class of the value to be converted to (must not be null)
+ * @return The converted value
+ *
+ * @throws ConversionException if thrown by an underlying Converter
+ */
+ public <T> Object convert(final Object value, final Class<T> targetType) {
+ final boolean nullValue = value == null;
+ final Class<?> sourceType = nullValue ? null : value.getClass();
+
+ if (LOG.isDebugEnabled()) {
+ if (nullValue) {
+ LOG.debug("Convert null value to type '" + targetType.getName() + "'");
+ } else {
+ LOG.debug("Convert type '" + sourceType.getName() + "' value '" + value + "' to type '"
+ + targetType.getName() + "'");
+ }
+ }
+
+ Object converted = value;
+ final Converter<T> converter = lookup(sourceType, targetType);
+ if (converter != null) {
+ if (LOG.isTraceEnabled()) {
+ LOG.trace(" Using converter " + converter);
+ }
+ converted = converter.convert(targetType, value);
+ }
+ if (String.class.equals(targetType) && converted != null && !(converted instanceof String)) {
+
+ // NOTE: For backwards compatibility, if the Converter
+ // doesn't handle conversion-->String then
+ // use the registered String Converter
+ final Converter<String> strConverter = lookup(String.class);
+ if (strConverter != null) {
+ if (LOG.isTraceEnabled()) {
+ LOG.trace(" Using converter " + converter);
+ }
+ converted = strConverter.convert(String.class, converted);
+ }
+
+ // If the object still isn't a String, use toString() method
+ if (converted != null && !(converted instanceof String)) {
+ converted = converted.toString();
+ }
+
+ }
+ return converted;
+ }
+
/**
* Convert the specified value to an object of the specified class (if
* possible). Otherwise, return a {@link String} representation of the value.
@@ -299,74 +354,91 @@ public class ConvertUtilsBean {
}
/**
- * Convert the value to an object of the specified class (if
- * possible). If no converter for the desired target type is registered,
- * the passed in object is returned unchanged.
+ * Remove all registered {@link Converter}s, and re-establish the
+ * standard Converters.
+ */
+ public void deregister() {
+
+ converters.clear();
+
+ registerPrimitives(false);
+ registerStandard(false, false);
+ registerOther(true);
+ registerArrays(false, 0);
+ register(BigDecimal.class, new BigDecimalConverter());
+ register(BigInteger.class, new BigIntegerConverter());
+ }
+
+ /**
+ * Remove any registered {@link Converter} for the specified destination
+ * {@code Class}.
*
- * @param <T> The Class type.
- * @param value Value to be converted (may be null)
- * @param targetType Class of the value to be converted to (must not be null)
- * @return The converted value
+ * @param clazz Class for which to remove a registered Converter
+ */
+ public void deregister(final Class<?> clazz) {
+ converters.remove(clazz);
+ }
+
+ /**
+ * Look up and return any registered {@link Converter} for the specified
+ * source and destination class; if there is no registered Converter,
+ * return {@code null}.
*
- * @throws ConversionException if thrown by an underlying Converter
+ * @param <T> The converter type.
+ * @param sourceType Class of the value being converted
+ * @param targetType Class of the value to be converted to
+ * @return The registered {@link Converter} or {@code null} if not found
*/
- public <T> Object convert(final Object value, final Class<T> targetType) {
- final boolean nullValue = value == null;
- final Class<?> sourceType = nullValue ? null : value.getClass();
+ public <T> Converter<T> lookup(final Class<?> sourceType, final Class<T> targetType) {
- if (LOG.isDebugEnabled()) {
- if (nullValue) {
- LOG.debug("Convert null value to type '" + targetType.getName() + "'");
- } else {
- LOG.debug("Convert type '" + sourceType.getName() + "' value '" + value + "' to type '"
- + targetType.getName() + "'");
- }
+ if (targetType == null) {
+ throw new IllegalArgumentException("Target type is missing");
+ }
+ if (sourceType == null) {
+ return lookup(targetType);
}
- Object converted = value;
- final Converter<T> converter = lookup(sourceType, targetType);
- if (converter != null) {
- if (LOG.isTraceEnabled()) {
- LOG.trace(" Using converter " + converter);
+ Converter converter = null;
+ // Convert --> String
+ if (targetType == String.class) {
+ converter = lookup(sourceType);
+ if (converter == null && (sourceType.isArray() ||
+ Collection.class.isAssignableFrom(sourceType))) {
+ converter = lookup(String[].class);
}
- converted = converter.convert(targetType, value);
+ if (converter == null) {
+ converter = lookup(String.class);
+ }
+ return converter;
}
- if (String.class.equals(targetType) && converted != null && !(converted instanceof String)) {
- // NOTE: For backwards compatibility, if the Converter
- // doesn't handle conversion-->String then
- // use the registered String Converter
- final Converter<String> strConverter = lookup(String.class);
- if (strConverter != null) {
- if (LOG.isTraceEnabled()) {
- LOG.trace(" Using converter " + converter);
- }
- converted = strConverter.convert(String.class, converted);
+ // Convert --> String array
+ if (targetType == String[].class) {
+ if (sourceType.isArray() || Collection.class.isAssignableFrom(sourceType)) {
+ converter = lookup(sourceType);
}
-
- // If the object still isn't a String, use toString() method
- if (converted != null && !(converted instanceof String)) {
- converted = converted.toString();
+ if (converter == null) {
+ converter = lookup(String[].class);
}
-
+ return converter;
}
- return converted;
+
+ return lookup(targetType);
+
}
/**
- * Remove all registered {@link Converter}s, and re-establish the
- * standard Converters.
+ * Look up and return any registered {@link Converter} for the specified
+ * destination class; if there is no registered Converter, return
+ * {@code null}.
+ *
+ * @param <T> The converter type.
+ * @param clazz Class for which to return a registered Converter
+ * @return The registered {@link Converter} or {@code null} if not found
*/
- public void deregister() {
-
- converters.clear();
-
- registerPrimitives(false);
- registerStandard(false, false);
- registerOther(true);
- registerArrays(false, 0);
- register(BigDecimal.class, new BigDecimalConverter());
- register(BigInteger.class, new BigIntegerConverter());
+ @SuppressWarnings("unchecked")
+ public <T> Converter<T> lookup(final Class<T> clazz) {
+ return (Converter<T>) converters.get(clazz);
}
/**
@@ -391,81 +463,109 @@ public class ConvertUtilsBean {
registerArrays(throwException, defaultArraySize);
}
+ /** strictly for convenience since it has same parameter order as Map.put */
+ private <T> void register(final Class<?> clazz, final Converter<T> converter) {
+ register(new ConverterFacade<>(converter), clazz);
+ }
+
/**
- * Register the converters for primitive types.
- * </p>
- * This method registers the following converters:
- * <ul>
- * <li>{@code Boolean.TYPE} - {@link BooleanConverter}</li>
- * <li>{@code Byte.TYPE} - {@link ByteConverter}</li>
- * <li>{@code Character.TYPE} - {@link CharacterConverter}</li>
- * <li>{@code Double.TYPE} - {@link DoubleConverter}</li>
- * <li>{@code Float.TYPE} - {@link FloatConverter}</li>
- * <li>{@code Integer.TYPE} - {@link IntegerConverter}</li>
- * <li>{@code Long.TYPE} - {@link LongConverter}</li>
- * <li>{@code Short.TYPE} - {@link ShortConverter}</li>
- * </ul>
- * @param throwException {@code true} if the converters should
- * throw an exception when a conversion error occurs, otherwise <code>
- * {@code false} if a default value should be used.
+ * Register a custom {@link Converter} for the specified destination
+ * {@code Class}, replacing any previously registered Converter.
+ *
+ * @param converter Converter to be registered
+ * @param clazz Destination class for conversions performed by this
+ * Converter
*/
- private void registerPrimitives(final boolean throwException) {
- register(Boolean.TYPE, throwException ? new BooleanConverter() : new BooleanConverter(Boolean.FALSE));
- register(Byte.TYPE, throwException ? new ByteConverter() : new ByteConverter(ZERO));
- register(Character.TYPE, throwException ? new CharacterConverter() : new CharacterConverter(SPACE));
- register(Double.TYPE, throwException ? new DoubleConverter() : new DoubleConverter(ZERO));
- register(Float.TYPE, throwException ? new FloatConverter() : new FloatConverter(ZERO));
- register(Integer.TYPE, throwException ? new IntegerConverter() : new IntegerConverter(ZERO));
- register(Long.TYPE, throwException ? new LongConverter() : new LongConverter(ZERO));
- register(Short.TYPE, throwException ? new ShortConverter() : new ShortConverter(ZERO));
+ public void register(final Converter converter, final Class<?> clazz) {
+ converters.put(clazz, converter);
}
/**
- * Register the converters for standard types.
- * </p>
- * This method registers the following converters:
- * <ul>
- * <li>{@code BigDecimal.class} - {@link BigDecimalConverter}</li>
- * <li>{@code BigInteger.class} - {@link BigIntegerConverter}</li>
- * <li>{@code Boolean.class} - {@link BooleanConverter}</li>
- * <li>{@code Byte.class} - {@link ByteConverter}</li>
- * <li>{@code Character.class} - {@link CharacterConverter}</li>
- * <li>{@code Double.class} - {@link DoubleConverter}</li>
- * <li>{@code Float.class} - {@link FloatConverter}</li>
- * <li>{@code Integer.class} - {@link IntegerConverter}</li>
- * <li>{@code Long.class} - {@link LongConverter}</li>
- * <li>{@code Short.class} - {@link ShortConverter}</li>
- * <li>{@code String.class} - {@link StringConverter}</li>
- * </ul>
- * @param throwException {@code true} if the converters should
- * throw an exception when a conversion error occurs, otherwise <code>
- * {@code false} if a default value should be used.
- * @param defaultNull {@code true}if the <i>standard</i> converters
- * (see {@link ConvertUtilsBean#registerStandard(boolean, boolean)})
- * should use a default value of {@code null</code>, otherwise <code>false}.
- * N.B. This values is ignored if {@code throwException</code> is <code>true}
+ * Register a new ArrayConverter with the specified element delegate converter
+ * that returns a default array of the specified size in the event of conversion errors.
+ *
+ * @param componentType The component type of the array
+ * @param componentConverter The converter to delegate to for the array elements
+ * @param throwException Whether a conversion exception should be thrown or a default
+ * value used in the event of a conversion error
+ * @param defaultArraySize The size of the default array
*/
- private void registerStandard(final boolean throwException, final boolean defaultNull) {
+ private <T> void registerArrayConverter(final Class<T> componentType, final Converter<T> componentConverter,
+ final boolean throwException, final int defaultArraySize) {
+ final Class<T[]> arrayType = (Class<T[]>) Array.newInstance(componentType, 0).getClass();
+ final Converter<T[]> arrayConverter;
+ if (throwException) {
+ arrayConverter = new ArrayConverter<>(arrayType, componentConverter);
+ } else {
+ arrayConverter = new ArrayConverter<>(arrayType, componentConverter, defaultArraySize);
+ }
+ register(arrayType, arrayConverter);
+ }
- final Number defaultNumber = defaultNull ? null : ZERO;
- final BigDecimal bigDecDeflt = defaultNull ? null : new BigDecimal("0.0");
- final BigInteger bigIntDeflt = defaultNull ? null : new BigInteger("0");
- final Boolean booleanDefault = defaultNull ? null : Boolean.FALSE;
- final Character charDefault = defaultNull ? null : SPACE;
- final String stringDefault = defaultNull ? null : "";
+ /**
+ * Register array converters.
+ *
+ * @param throwException {@code true} if the converters should
+ * throw an exception when a conversion error occurs, otherwise <code>
+ * {@code false} if a default value should be used.
+ * @param defaultArraySize The size of the default array value for array converters
+ * (N.B. This values is ignored if {@code throwException</code> is <code>true}).
+ * Specifying a value less than zero causes a <code>null<code> value to be used for
+ * the default.
+ */
+ private void registerArrays(final boolean throwException, final int defaultArraySize) {
+ // @formatter:off
- register(BigDecimal.class, throwException ? new BigDecimalConverter() : new BigDecimalConverter(bigDecDeflt));
- register(BigInteger.class, throwException ? new BigIntegerConverter() : new BigIntegerConverter(bigIntDeflt));
- register(Boolean.class, throwException ? new BooleanConverter() : new BooleanConverter(booleanDefault));
- register(Byte.class, throwException ? new ByteConverter() : new ByteConverter(defaultNumber));
- register(Character.class, throwException ? new CharacterConverter() : new CharacterConverter(charDefault));
- register(Double.class, throwException ? new DoubleConverter() : new DoubleConverter(defaultNumber));
- register(Float.class, throwException ? new FloatConverter() : new FloatConverter(defaultNumber));
- register(Integer.class, throwException ? new IntegerConverter() : new IntegerConverter(defaultNumber));
- register(Long.class, throwException ? new LongConverter() : new LongConverter(defaultNumber));
- register(Short.class, throwException ? new ShortConverter() : new ShortConverter(defaultNumber));
- register(String.class, throwException ? new StringConverter() : new StringConverter(stringDefault));
+ // Primitives
+ registerArrayConverter(Boolean.TYPE, new BooleanConverter(), throwException, defaultArraySize);
+ registerArrayConverter(Byte.TYPE, new ByteConverter(), throwException, defaultArraySize);
+ registerArrayConverter(Character.TYPE, new CharacterConverter(), throwException, defaultArraySize);
+ registerArrayConverter(Double.TYPE, new DoubleConverter(), throwException, defaultArraySize);
+ registerArrayConverter(Float.TYPE, new FloatConverter(), throwException, defaultArraySize);
+ registerArrayConverter(Integer.TYPE, new IntegerConverter(), throwException, defaultArraySize);
+ registerArrayConverter(Long.TYPE, new LongConverter(), throwException, defaultArraySize);
+ registerArrayConverter(Short.TYPE, new ShortConverter(), throwException, defaultArraySize);
+ // Standard
+ registerArrayConverter(BigDecimal.class, new BigDecimalConverter(), throwException, defaultArraySize);
+ registerArrayConverter(BigInteger.class, new BigIntegerConverter(), throwException, defaultArraySize);
+ registerArrayConverter(Boolean.class, new BooleanConverter(), throwException, defaultArraySize);
+ registerArrayConverter(Byte.class, new ByteConverter(), throwException, defaultArraySize);
+ registerArrayConverter(Character.class, new CharacterConverter(), throwException, defaultArraySize);
+ registerArrayConverter(Double.class, new DoubleConverter(), throwException, defaultArraySize);
+ registerArrayConverter(Float.class, new FloatConverter(), throwException, defaultArraySize);
+ registerArrayConverter(Integer.class, new IntegerConverter(), throwException, defaultArraySize);
+ registerArrayConverter(Long.class, new LongConverter(), throwException, defaultArraySize);
+ registerArrayConverter(Short.class, new ShortConverter(), throwException, defaultArraySize);
+ registerArrayConverter(String.class, new StringConverter(), throwException, defaultArraySize);
+
+ // Other
+ registerArrayConverter(Class.class, new ClassConverter(), throwException, defaultArraySize);
+ registerArrayConverter(Enum.class, new EnumConverter(), throwException, defaultArraySize);
+ registerArrayConverter(java.util.Date.class, new DateConverter(), throwException, defaultArraySize);
+ registerArrayConverter(Calendar.class, new CalendarConverter(), throwException, defaultArraySize);
+ registerArrayConverter(File.class, new FileConverter(), throwException, defaultArraySize);
+ registerArrayConverter(Path.class, new PathConverter(), throwException, defaultArraySize);
+ registerArrayConverter(java.sql.Date.class, new SqlDateConverter(), throwException, defaultArraySize);
+ registerArrayConverter(java.sql.Time.class, new SqlTimeConverter(), throwException, defaultArraySize);
+ registerArrayConverter(Timestamp.class, new SqlTimestampConverter(), throwException, defaultArraySize);
+ registerArrayConverter(URL.class, new URLConverter(), throwException, defaultArraySize);
+ registerArrayConverter(URI.class, new URIConverter(), throwException, defaultArraySize);
+ registerArrayConverter(UUID.class, new UUIDConverter(), throwException, defaultArraySize);
+ registerArrayConverter(LocalDate.class, new LocalDateConverter(), throwException, defaultArraySize);
+ registerArrayConverter(LocalDateTime.class, new LocalDateTimeConverter(), throwException, defaultArraySize);
+ registerArrayConverter(LocalTime.class, new LocalTimeConverter(), throwException, defaultArraySize);
+ registerArrayConverter(OffsetDateTime.class, new OffsetDateTimeConverter(),throwException, defaultArraySize);
+ registerArrayConverter(OffsetTime.class, new OffsetTimeConverter(), throwException, defaultArraySize);
+ registerArrayConverter(ZonedDateTime.class, new ZonedDateTimeConverter(), throwException, defaultArraySize);
+ registerArrayConverter(Duration.class, new DurationConverter(), throwException, defaultArraySize);
+ registerArrayConverter(MonthDay.class, new MonthDayConverter(), throwException, defaultArraySize);
+ registerArrayConverter(Period.class, new PeriodConverter(), throwException, defaultArraySize);
+ registerArrayConverter(Year.class, new YearConverter(), throwException, defaultArraySize);
+ registerArrayConverter(YearMonth.class, new YearMonthConverter(), throwException, defaultArraySize);
+ registerArrayConverter(ZoneId.class, new ZoneIdConverter(), throwException, defaultArraySize);
+ registerArrayConverter(ZoneOffset.class, new ZoneOffsetConverter(), throwException, defaultArraySize);
+ // @formatter:on
}
/**
@@ -534,179 +634,79 @@ public class ConvertUtilsBean {
}
/**
- * Register array converters.
- *
+ * Register the converters for primitive types.
+ * </p>
+ * This method registers the following converters:
+ * <ul>
+ * <li>{@code Boolean.TYPE} - {@link BooleanConverter}</li>
+ * <li>{@code Byte.TYPE} - {@link ByteConverter}</li>
+ * <li>{@code Character.TYPE} - {@link CharacterConverter}</li>
+ * <li>{@code Double.TYPE} - {@link DoubleConverter}</li>
+ * <li>{@code Float.TYPE} - {@link FloatConverter}</li>
+ * <li>{@code Integer.TYPE} - {@link IntegerConverter}</li>
+ * <li>{@code Long.TYPE} - {@link LongConverter}</li>
+ * <li>{@code Short.TYPE} - {@link ShortConverter}</li>
+ * </ul>
* @param throwException {@code true} if the converters should
* throw an exception when a conversion error occurs, otherwise <code>
* {@code false} if a default value should be used.
- * @param defaultArraySize The size of the default array value for array converters
- * (N.B. This values is ignored if {@code throwException</code> is <code>true}).
- * Specifying a value less than zero causes a <code>null<code> value to be used for
- * the default.
- */
- private void registerArrays(final boolean throwException, final int defaultArraySize) {
- // @formatter:off
-
- // Primitives
- registerArrayConverter(Boolean.TYPE, new BooleanConverter(), throwException, defaultArraySize);
- registerArrayConverter(Byte.TYPE, new ByteConverter(), throwException, defaultArraySize);
- registerArrayConverter(Character.TYPE, new CharacterConverter(), throwException, defaultArraySize);
- registerArrayConverter(Double.TYPE, new DoubleConverter(), throwException, defaultArraySize);
- registerArrayConverter(Float.TYPE, new FloatConverter(), throwException, defaultArraySize);
- registerArrayConverter(Integer.TYPE, new IntegerConverter(), throwException, defaultArraySize);
- registerArrayConverter(Long.TYPE, new LongConverter(), throwException, defaultArraySize);
- registerArrayConverter(Short.TYPE, new ShortConverter(), throwException, defaultArraySize);
-
- // Standard
- registerArrayConverter(BigDecimal.class, new BigDecimalConverter(), throwException, defaultArraySize);
- registerArrayConverter(BigInteger.class, new BigIntegerConverter(), throwException, defaultArraySize);
- registerArrayConverter(Boolean.class, new BooleanConverter(), throwException, defaultArraySize);
- registerArrayConverter(Byte.class, new ByteConverter(), throwException, defaultArraySize);
- registerArrayConverter(Character.class, new CharacterConverter(), throwException, defaultArraySize);
- registerArrayConverter(Double.class, new DoubleConverter(), throwException, defaultArraySize);
- registerArrayConverter(Float.class, new FloatConverter(), throwException, defaultArraySize);
- registerArrayConverter(Integer.class, new IntegerConverter(), throwException, defaultArraySize);
- registerArrayConverter(Long.class, new LongConverter(), throwException, defaultArraySize);
- registerArrayConverter(Short.class, new ShortConverter(), throwException, defaultArraySize);
- registerArrayConverter(String.class, new StringConverter(), throwException, defaultArraySize);
-
- // Other
- registerArrayConverter(Class.class, new ClassConverter(), throwException, defaultArraySize);
- registerArrayConverter(Enum.class, new EnumConverter(), throwException, defaultArraySize);
- registerArrayConverter(java.util.Date.class, new DateConverter(), throwException, defaultArraySize);
- registerArrayConverter(Calendar.class, new CalendarConverter(), throwException, defaultArraySize);
- registerArrayConverter(File.class, new FileConverter(), throwException, defaultArraySize);
- registerArrayConverter(Path.class, new PathConverter(), throwException, defaultArraySize);
- registerArrayConverter(java.sql.Date.class, new SqlDateConverter(), throwException, defaultArraySize);
- registerArrayConverter(java.sql.Time.class, new SqlTimeConverter(), throwException, defaultArraySize);
- registerArrayConverter(Timestamp.class, new SqlTimestampConverter(), throwException, defaultArraySize);
- registerArrayConverter(URL.class, new URLConverter(), throwException, defaultArraySize);
- registerArrayConverter(URI.class, new URIConverter(), throwException, defaultArraySize);
- registerArrayConverter(UUID.class, new UUIDConverter(), throwException, defaultArraySize);
- registerArrayConverter(LocalDate.class, new LocalDateConverter(), throwException, defaultArraySize);
- registerArrayConverter(LocalDateTime.class, new LocalDateTimeConverter(), throwException, defaultArraySize);
- registerArrayConverter(LocalTime.class, new LocalTimeConverter(), throwException, defaultArraySize);
- registerArrayConverter(OffsetDateTime.class, new OffsetDateTimeConverter(),throwException, defaultArraySize);
- registerArrayConverter(OffsetTime.class, new OffsetTimeConverter(), throwException, defaultArraySize);
- registerArrayConverter(ZonedDateTime.class, new ZonedDateTimeConverter(), throwException, defaultArraySize);
- registerArrayConverter(Duration.class, new DurationConverter(), throwException, defaultArraySize);
- registerArrayConverter(MonthDay.class, new MonthDayConverter(), throwException, defaultArraySize);
- registerArrayConverter(Period.class, new PeriodConverter(), throwException, defaultArraySize);
- registerArrayConverter(Year.class, new YearConverter(), throwException, defaultArraySize);
- registerArrayConverter(YearMonth.class, new YearMonthConverter(), throwException, defaultArraySize);
- registerArrayConverter(ZoneId.class, new ZoneIdConverter(), throwException, defaultArraySize);
- registerArrayConverter(ZoneOffset.class, new ZoneOffsetConverter(), throwException, defaultArraySize);
- // @formatter:on
- }
-
- /**
- * Register a new ArrayConverter with the specified element delegate converter
- * that returns a default array of the specified size in the event of conversion errors.
- *
- * @param componentType The component type of the array
- * @param componentConverter The converter to delegate to for the array elements
- * @param throwException Whether a conversion exception should be thrown or a default
- * value used in the event of a conversion error
- * @param defaultArraySize The size of the default array
- */
- private <T> void registerArrayConverter(final Class<T> componentType, final Converter<T> componentConverter,
- final boolean throwException, final int defaultArraySize) {
- final Class<T[]> arrayType = (Class<T[]>) Array.newInstance(componentType, 0).getClass();
- final Converter<T[]> arrayConverter;
- if (throwException) {
- arrayConverter = new ArrayConverter<>(arrayType, componentConverter);
- } else {
- arrayConverter = new ArrayConverter<>(arrayType, componentConverter, defaultArraySize);
- }
- register(arrayType, arrayConverter);
- }
-
- /** strictly for convenience since it has same parameter order as Map.put */
- private <T> void register(final Class<?> clazz, final Converter<T> converter) {
- register(new ConverterFacade<>(converter), clazz);
- }
-
- /**
- * Remove any registered {@link Converter} for the specified destination
- * {@code Class}.
- *
- * @param clazz Class for which to remove a registered Converter
*/
- public void deregister(final Class<?> clazz) {
- converters.remove(clazz);
- }
-
- /**
- * Look up and return any registered {@link Converter} for the specified
- * destination class; if there is no registered Converter, return
- * {@code null}.
- *
- * @param <T> The converter type.
- * @param clazz Class for which to return a registered Converter
- * @return The registered {@link Converter} or {@code null} if not found
- */
- @SuppressWarnings("unchecked")
- public <T> Converter<T> lookup(final Class<T> clazz) {
- return (Converter<T>) converters.get(clazz);
+ private void registerPrimitives(final boolean throwException) {
+ register(Boolean.TYPE, throwException ? new BooleanConverter() : new BooleanConverter(Boolean.FALSE));
+ register(Byte.TYPE, throwException ? new ByteConverter() : new ByteConverter(ZERO));
+ register(Character.TYPE, throwException ? new CharacterConverter() : new CharacterConverter(SPACE));
+ register(Double.TYPE, throwException ? new DoubleConverter() : new DoubleConverter(ZERO));
+ register(Float.TYPE, throwException ? new FloatConverter() : new FloatConverter(ZERO));
+ register(Integer.TYPE, throwException ? new IntegerConverter() : new IntegerConverter(ZERO));
+ register(Long.TYPE, throwException ? new LongConverter() : new LongConverter(ZERO));
+ register(Short.TYPE, throwException ? new ShortConverter() : new ShortConverter(ZERO));
}
/**
- * Look up and return any registered {@link Converter} for the specified
- * source and destination class; if there is no registered Converter,
- * return {@code null}.
- *
- * @param <T> The converter type.
- * @param sourceType Class of the value being converted
- * @param targetType Class of the value to be converted to
- * @return The registered {@link Converter} or {@code null} if not found
+ * Register the converters for standard types.
+ * </p>
+ * This method registers the following converters:
+ * <ul>
+ * <li>{@code BigDecimal.class} - {@link BigDecimalConverter}</li>
+ * <li>{@code BigInteger.class} - {@link BigIntegerConverter}</li>
+ * <li>{@code Boolean.class} - {@link BooleanConverter}</li>
+ * <li>{@code Byte.class} - {@link ByteConverter}</li>
+ * <li>{@code Character.class} - {@link CharacterConverter}</li>
+ * <li>{@code Double.class} - {@link DoubleConverter}</li>
+ * <li>{@code Float.class} - {@link FloatConverter}</li>
+ * <li>{@code Integer.class} - {@link IntegerConverter}</li>
+ * <li>{@code Long.class} - {@link LongConverter}</li>
+ * <li>{@code Short.class} - {@link ShortConverter}</li>
+ * <li>{@code String.class} - {@link StringConverter}</li>
+ * </ul>
+ * @param throwException {@code true} if the converters should
+ * throw an exception when a conversion error occurs, otherwise <code>
+ * {@code false} if a default value should be used.
+ * @param defaultNull {@code true}if the <i>standard</i> converters
+ * (see {@link ConvertUtilsBean#registerStandard(boolean, boolean)})
+ * should use a default value of {@code null</code>, otherwise <code>false}.
+ * N.B. This values is ignored if {@code throwException</code> is <code>true}
*/
- public <T> Converter<T> lookup(final Class<?> sourceType, final Class<T> targetType) {
-
- if (targetType == null) {
- throw new IllegalArgumentException("Target type is missing");
- }
- if (sourceType == null) {
- return lookup(targetType);
- }
-
- Converter converter = null;
- // Convert --> String
- if (targetType == String.class) {
- converter = lookup(sourceType);
- if (converter == null && (sourceType.isArray() ||
- Collection.class.isAssignableFrom(sourceType))) {
- converter = lookup(String[].class);
- }
- if (converter == null) {
- converter = lookup(String.class);
- }
- return converter;
- }
-
- // Convert --> String array
- if (targetType == String[].class) {
- if (sourceType.isArray() || Collection.class.isAssignableFrom(sourceType)) {
- converter = lookup(sourceType);
- }
- if (converter == null) {
- converter = lookup(String[].class);
- }
- return converter;
- }
+ private void registerStandard(final boolean throwException, final boolean defaultNull) {
- return lookup(targetType);
+ final Number defaultNumber = defaultNull ? null : ZERO;
+ final BigDecimal bigDecDeflt = defaultNull ? null : new BigDecimal("0.0");
+ final BigInteger bigIntDeflt = defaultNull ? null : new BigInteger("0");
+ final Boolean booleanDefault = defaultNull ? null : Boolean.FALSE;
+ final Character charDefault = defaultNull ? null : SPACE;
+ final String stringDefault = defaultNull ? null : "";
- }
+ register(BigDecimal.class, throwException ? new BigDecimalConverter() : new BigDecimalConverter(bigDecDeflt));
+ register(BigInteger.class, throwException ? new BigIntegerConverter() : new BigIntegerConverter(bigIntDeflt));
+ register(Boolean.class, throwException ? new BooleanConverter() : new BooleanConverter(booleanDefault));
+ register(Byte.class, throwException ? new ByteConverter() : new ByteConverter(defaultNumber));
+ register(Character.class, throwException ? new CharacterConverter() : new CharacterConverter(charDefault));
+ register(Double.class, throwException ? new DoubleConverter() : new DoubleConverter(defaultNumber));
+ register(Float.class, throwException ? new FloatConverter() : new FloatConverter(defaultNumber));
+ register(Integer.class, throwException ? new IntegerConverter() : new IntegerConverter(defaultNumber));
+ register(Long.class, throwException ? new LongConverter() : new LongConverter(defaultNumber));
+ register(Short.class, throwException ? new ShortConverter() : new ShortConverter(defaultNumber));
+ register(String.class, throwException ? new StringConverter() : new StringConverter(stringDefault));
- /**
- * Register a custom {@link Converter} for the specified destination
- * {@code Class}, replacing any previously registered Converter.
- *
- * @param converter Converter to be registered
- * @param clazz Destination class for conversions performed by this
- * Converter
- */
- public void register(final Converter converter, final Class<?> clazz) {
- converters.put(clazz, converter);
}
}
diff --git a/src/main/java/org/apache/commons/beanutils2/DefaultBeanIntrospector.java b/src/main/java/org/apache/commons/beanutils2/DefaultBeanIntrospector.java
index 1aa378c9..154c2863 100644
--- a/src/main/java/org/apache/commons/beanutils2/DefaultBeanIntrospector.java
+++ b/src/main/java/org/apache/commons/beanutils2/DefaultBeanIntrospector.java
@@ -62,37 +62,6 @@ public class DefaultBeanIntrospector implements BeanIntrospector {
private DefaultBeanIntrospector() {
}
- /**
- * Performs introspection of a specific Java class. This implementation uses
- * the {@code java.beans.Introspector.getBeanInfo()} method to obtain
- * all property descriptors for the current class and adds them to the
- * passed in introspection context.
- *
- * @param icontext the introspection context
- */
- @Override
- public void introspect(final IntrospectionContext icontext) {
- BeanInfo beanInfo = null;
- try {
- beanInfo = Introspector.getBeanInfo(icontext.getTargetClass());
- } catch (final IntrospectionException e) {
- // no descriptors are added to the context
- log.error(
- "Error when inspecting class " + icontext.getTargetClass(),
- e);
- return;
- }
-
- PropertyDescriptor[] descriptors = beanInfo.getPropertyDescriptors();
- if (descriptors == null) {
- descriptors = PropertyDescriptors.EMPTY_ARRAY;
- }
-
- handleIndexedPropertyDescriptors(icontext.getTargetClass(),
- descriptors);
- icontext.addPropertyDescriptors(descriptors);
- }
-
/**
* This method fixes an issue where IndexedPropertyDescriptor behaves
* differently in different versions of the JDK for 'indexed' properties
@@ -176,4 +145,35 @@ public class DefaultBeanIntrospector implements BeanIntrospector {
}
}
}
+
+ /**
+ * Performs introspection of a specific Java class. This implementation uses
+ * the {@code java.beans.Introspector.getBeanInfo()} method to obtain
+ * all property descriptors for the current class and adds them to the
+ * passed in introspection context.
+ *
+ * @param icontext the introspection context
+ */
+ @Override
+ public void introspect(final IntrospectionContext icontext) {
+ BeanInfo beanInfo = null;
+ try {
+ beanInfo = Introspector.getBeanInfo(icontext.getTargetClass());
+ } catch (final IntrospectionException e) {
+ // no descriptors are added to the context
+ log.error(
+ "Error when inspecting class " + icontext.getTargetClass(),
+ e);
+ return;
+ }
+
+ PropertyDescriptor[] descriptors = beanInfo.getPropertyDescriptors();
+ if (descriptors == null) {
+ descriptors = PropertyDescriptors.EMPTY_ARRAY;
+ }
+
+ handleIndexedPropertyDescriptors(icontext.getTargetClass(),
+ descriptors);
+ icontext.addPropertyDescriptors(descriptors);
+ }
}
diff --git a/src/main/java/org/apache/commons/beanutils2/DefaultIntrospectionContext.java b/src/main/java/org/apache/commons/beanutils2/DefaultIntrospectionContext.java
index 6ffd0396..af814cb5 100644
--- a/src/main/java/org/apache/commons/beanutils2/DefaultIntrospectionContext.java
+++ b/src/main/java/org/apache/commons/beanutils2/DefaultIntrospectionContext.java
@@ -53,11 +53,6 @@ class DefaultIntrospectionContext implements IntrospectionContext {
descriptors = new HashMap<>();
}
- @Override
- public Class<?> getTargetClass() {
- return currentClass;
- }
-
@Override
public void addPropertyDescriptor(final PropertyDescriptor desc) {
if (desc == null) {
@@ -80,18 +75,28 @@ class DefaultIntrospectionContext implements IntrospectionContext {
}
@Override
- public boolean hasProperty(final String name) {
- return descriptors.containsKey(name);
+ public PropertyDescriptor getPropertyDescriptor(final String name) {
+ return descriptors.get(name);
+ }
+
+ /**
+ * Returns an array with all descriptors added to this context. This method
+ * is used to obtain the results of introspection.
+ *
+ * @return an array with all known property descriptors
+ */
+ public PropertyDescriptor[] getPropertyDescriptors() {
+ return descriptors.values().toArray(PropertyDescriptors.EMPTY_ARRAY);
}
@Override
- public PropertyDescriptor getPropertyDescriptor(final String name) {
- return descriptors.get(name);
+ public Class<?> getTargetClass() {
+ return currentClass;
}
@Override
- public void removePropertyDescriptor(final String name) {
- descriptors.remove(name);
+ public boolean hasProperty(final String name) {
+ return descriptors.containsKey(name);
}
@Override
@@ -99,13 +104,8 @@ class DefaultIntrospectionContext implements IntrospectionContext {
return descriptors.keySet();
}
- /**
- * Returns an array with all descriptors added to this context. This method
- * is used to obtain the results of introspection.
- *
- * @return an array with all known property descriptors
- */
- public PropertyDescriptor[] getPropertyDescriptors() {
- return descriptors.values().toArray(PropertyDescriptors.EMPTY_ARRAY);
+ @Override
+ public void removePropertyDescriptor(final String name) {
+ descriptors.remove(name);
}
}
diff --git a/src/main/java/org/apache/commons/beanutils2/DynaBean.java b/src/main/java/org/apache/commons/beanutils2/DynaBean.java
index 25bcf2ad..35a7f66a 100644
--- a/src/main/java/org/apache/commons/beanutils2/DynaBean.java
+++ b/src/main/java/org/apache/commons/beanutils2/DynaBean.java
@@ -106,37 +106,37 @@ public interface DynaBean {
void remove(String name, String key);
/**
- * Sets the value of a simple property with the specified name.
+ * Sets 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 NullPointerException if an attempt is made to set a
- * primitive property to null
+ * @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
*/
- void set(String name, Object value);
+ void set(String name, int index, Object value);
/**
- * Sets the value of an indexed property with the specified name.
+ * Sets the value of a simple 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
+ * @throws NullPointerException if an attempt is made to set a
+ * primitive property to null
*/
- void set(String name, int index, Object value);
+ void set(String name, Object value);
/**
* Sets the value of a mapped property with the specified name.
diff --git a/src/main/java/org/apache/commons/beanutils2/DynaBeanPropertyMapDecorator.java b/src/main/java/org/apache/commons/beanutils2/DynaBeanPropertyMapDecorator.java
index 9b8030e3..17e1e76d 100644
--- a/src/main/java/org/apache/commons/beanutils2/DynaBeanPropertyMapDecorator.java
+++ b/src/main/java/org/apache/commons/beanutils2/DynaBeanPropertyMapDecorator.java
@@ -61,26 +61,26 @@ package org.apache.commons.beanutils2;
public class DynaBeanPropertyMapDecorator extends BaseDynaBeanMapDecorator<String> {
/**
- * Constructs a Map for the specified {@link DynaBean}.
+ * Constructs a read only Map for the specified
+ * {@link DynaBean}.
*
* @param dynaBean The dyna bean being decorated
- * @param readOnly {@code true} if the Map is read only
- * otherwise {@code false}
* @throws IllegalArgumentException if the {@link DynaBean} is null.
*/
- public DynaBeanPropertyMapDecorator(final DynaBean dynaBean, final boolean readOnly) {
- super(dynaBean, readOnly);
+ public DynaBeanPropertyMapDecorator(final DynaBean dynaBean) {
+ super(dynaBean);
}
/**
- * Constructs a read only Map for the specified
- * {@link DynaBean}.
+ * Constructs a Map for the specified {@link DynaBean}.
*
* @param dynaBean The dyna bean being decorated
+ * @param readOnly {@code true} if the Map is read only
+ * otherwise {@code false}
* @throws IllegalArgumentException if the {@link DynaBean} is null.
*/
- public DynaBeanPropertyMapDecorator(final DynaBean dynaBean) {
- super(dynaBean);
+ public DynaBeanPropertyMapDecorator(final DynaBean dynaBean, final boolean readOnly) {
+ super(dynaBean, readOnly);
}
@Override
diff --git a/src/main/java/org/apache/commons/beanutils2/DynaClass.java b/src/main/java/org/apache/commons/beanutils2/DynaClass.java
index 9aa7b6c8..3cb417fb 100644
--- a/src/main/java/org/apache/commons/beanutils2/DynaClass.java
+++ b/src/main/java/org/apache/commons/beanutils2/DynaClass.java
@@ -26,14 +26,17 @@ package org.apache.commons.beanutils2;
public interface DynaClass {
/**
- * Returns the name of this DynaClass (analogous to the
- * {@code getName()} method of {@code java.lang.Class}, which
- * allows the same {@code DynaClass} implementation class to support
- * different dynamic classes, with different sets of properties.
+ * <p>Returns an array of {@code PropertyDescriptor} for the properties
+ * currently defined in this DynaClass. If no properties are defined, a
+ * zero-length array will be returned.</p>
*
- * @return the name of the DynaClass
+ * <p><strong>FIXME</strong> - Should we really be implementing
+ * {@code getBeanInfo()} instead, which returns property descriptors
+ * and a bunch of other stuff?</p>
+ *
+ * @return the set of properties for this DynaClass
*/
- String getName();
+ DynaProperty[] getDynaProperties();
/**
* Returns a property descriptor for the specified property, if it exists;
@@ -48,17 +51,14 @@ public interface DynaClass {
DynaProperty getDynaProperty(String name);
/**
- * <p>Returns an array of {@code PropertyDescriptor} for the properties
- * currently defined in this DynaClass. If no properties are defined, a
- * zero-length array will be returned.</p>
- *
- * <p><strong>FIXME</strong> - Should we really be implementing
- * {@code getBeanInfo()} instead, which returns property descriptors
- * and a bunch of other stuff?</p>
+ * Returns the name of this DynaClass (analogous to the
+ * {@code getName()} method of {@code java.lang.Class}, which
+ * allows the same {@code DynaClass} implementation class to support
+ * different dynamic classes, with different sets of properties.
*
- * @return the set of properties for this DynaClass
+ * @return the name of the DynaClass
*/
- DynaProperty[] getDynaProperties();
+ String getName();
/**
* Instantiates and return a new DynaBean instance, associated
diff --git a/src/main/java/org/apache/commons/beanutils2/DynaProperty.java b/src/main/java/org/apache/commons/beanutils2/DynaProperty.java
index ca2e34cd..a9054e95 100644
--- a/src/main/java/org/apache/commons/beanutils2/DynaProperty.java
+++ b/src/main/java/org/apache/commons/beanutils2/DynaProperty.java
@@ -57,6 +57,19 @@ public class DynaProperty implements Serializable {
private static final int LONG_TYPE = 7;
private static final int SHORT_TYPE = 8;
+ /**
+ * Empty array.
+ */
+ public static final DynaProperty[] EMPTY_ARRAY = {};
+
+ /** Property name */
+ protected String name;
+
+ /** Property type */
+ protected transient Class<?> type;
+
+ /** The <em>(optional)</em> type of content elements for indexed {@code DynaProperty} */
+ protected transient Class<?> contentType;
/**
* Constructs a property that accepts any data type.
*
@@ -94,8 +107,46 @@ public class DynaProperty implements Serializable {
this.contentType = contentType;
}
- /** Property name */
- protected String name;
+ /**
+ * Checks this instance against the specified Object for equality. Overrides the
+ * default reference test for equality provided by {@link java.lang.Object#equals(Object)}
+ * @param obj The object to compare to
+ * @return {@code true} if object is a dyna property with the same name
+ * type and content type, otherwise {@code false}
+ * @since 1.8.0
+ */
+ @Override
+ public boolean equals(final Object obj) {
+ boolean result;
+
+ result = obj == this;
+
+ if (!result && obj instanceof DynaProperty) {
+ final DynaProperty that = (DynaProperty) obj;
+ result =
+ (Objects.equals(this.name, that.name)) &&
+ (Objects.equals(this.type, that.type)) &&
+ (Objects.equals(this.contentType, that.contentType));
+ }
+
+ return result;
+ }
+
+ /**
+ * Gets the <em>(optional)</em> type of the indexed content for {@code DynaProperty}'s
+ * that support this feature.
+ *
+ * <p>There are issues with serializing primitive class types on certain JVM versions
+ * (including java 1.3).
+ * Therefore, this field <strong>must not be serialized using the standard methods</strong>.</p>
+ *
+ * @return the Class for the content type if this is an indexed {@code DynaProperty}
+ * and this feature is supported. Otherwise null.
+ */
+ public Class<?> getContentType() {
+ return contentType;
+ }
+
/**
* Get the name of this property.
* @return the name of the property
@@ -104,9 +155,6 @@ public class DynaProperty implements Serializable {
return this.name;
}
- /** Property type */
- protected transient Class<?> type;
-
/**
* <p>Gets the Java class representing the data type of the underlying property
* values.</p>
@@ -123,27 +171,20 @@ public class DynaProperty implements Serializable {
return this.type;
}
- /** The <em>(optional)</em> type of content elements for indexed {@code DynaProperty} */
- protected transient Class<?> contentType;
-
/**
- * Empty array.
+ * @return the hashcode for this dyna property
+ * @see Object#hashCode
+ * @since 1.8.0
*/
- public static final DynaProperty[] EMPTY_ARRAY = {};
+ @Override
+ public int hashCode() {
+ int result = 1;
- /**
- * Gets the <em>(optional)</em> type of the indexed content for {@code DynaProperty}'s
- * that support this feature.
- *
- * <p>There are issues with serializing primitive class types on certain JVM versions
- * (including java 1.3).
- * Therefore, this field <strong>must not be serialized using the standard methods</strong>.</p>
- *
- * @return the Class for the content type if this is an indexed {@code DynaProperty}
- * and this feature is supported. Otherwise null.
- */
- public Class<?> getContentType() {
- return contentType;
+ result = result * 31 + (name == null ? 0 : name.hashCode());
+ result = result * 31 + (type == null ? 0 : type.hashCode());
+ result = result * 31 + (contentType == null ? 0 : contentType.hashCode());
+
+ return result;
}
/**
@@ -177,44 +218,55 @@ public class DynaProperty implements Serializable {
}
/**
- * Checks this instance against the specified Object for equality. Overrides the
- * default reference test for equality provided by {@link java.lang.Object#equals(Object)}
- * @param obj The object to compare to
- * @return {@code true} if object is a dyna property with the same name
- * type and content type, otherwise {@code false}
- * @since 1.8.0
+ * Reads a class using safe encoding to workaround java 1.3 serialization bug.
*/
- @Override
- public boolean equals(final Object obj) {
- boolean result;
+ private Class<?> readAnyClass(final ObjectInputStream in) throws IOException, ClassNotFoundException {
+ // read back type class safely
+ if (in.readBoolean()) {
+ // it's a type constant
+ switch (in.readInt()) {
- result = obj == this;
+ case BOOLEAN_TYPE: return Boolean.TYPE;
+ case BYTE_TYPE: return Byte.TYPE;
+ case CHAR_TYPE: return Character.TYPE;
+ case DOUBLE_TYPE: return Double.TYPE;
+ case FLOAT_TYPE: return Float.TYPE;
+ case INT_TYPE: return Integer.TYPE;
+ case LONG_TYPE: return Long.TYPE;
+ case SHORT_TYPE: return Short.TYPE;
+ default:
+ // something's gone wrong
+ throw new StreamCorruptedException(
+ "Invalid primitive type. "
+ + "Check version of beanutils used to serialize is compatible.");
- if (!result && obj instanceof DynaProperty) {
- final DynaProperty that = (DynaProperty) obj;
- result =
- (Objects.equals(this.name, that.name)) &&
- (Objects.equals(this.type, that.type)) &&
- (Objects.equals(this.contentType, that.contentType));
- }
+ }
- return result;
+ }
+ // it's another class
+ return (Class<?>) in.readObject();
}
/**
- * @return the hashcode for this dyna property
- * @see Object#hashCode
- * @since 1.8.0
+ * Reads field values for this object safely.
+ * There are issues with serializing primitive class types on certain JVM versions
+ * (including java 1.3).
+ * This method provides a workaround.
+ *
+ * @param in {@link ObjectInputStream} to read object from
+ * @throws StreamCorruptedException when the stream data values are outside expected range
+ * @throws IOException if the input stream can't be read
+ * @throws ClassNotFoundException When trying to read an object of class that is not on the classpath
*/
- @Override
- public int hashCode() {
- int result = 1;
+ private void readObject(final ObjectInputStream in) throws IOException, ClassNotFoundException {
+ this.type = readAnyClass(in);
- result = result * 31 + (name == null ? 0 : name.hashCode());
- result = result * 31 + (type == null ? 0 : type.hashCode());
- result = result * 31 + (contentType == null ? 0 : contentType.hashCode());
+ if (isMapped() || isIndexed()) {
+ this.contentType = readAnyClass(in);
+ }
- return result;
+ // read other values
+ in.defaultReadObject();
}
/**
@@ -234,26 +286,6 @@ public class DynaProperty implements Serializable {
return sb.toString();
}
- /**
- * Writes this object safely.
- * There are issues with serializing primitive class types on certain JVM versions
- * (including java 1.3).
- * This method provides a workaround.
- *
- * @param out {@link ObjectOutputStream} to write object to
- * @throws IOException if the object can't be written
- */
- private void writeObject(final ObjectOutputStream out) throws IOException {
- writeAnyClass(this.type,out);
-
- if (isMapped() || isIndexed()) {
- writeAnyClass(this.contentType,out);
- }
-
- // write out other values
- out.defaultWriteObject();
- }
-
/**
* Write a class using safe encoding to workaround java 1.3 serialization bug.
*/
@@ -290,54 +322,22 @@ public class DynaProperty implements Serializable {
}
/**
- * Reads field values for this object safely.
+ * Writes this object safely.
* There are issues with serializing primitive class types on certain JVM versions
* (including java 1.3).
* This method provides a workaround.
*
- * @param in {@link ObjectInputStream} to read object from
- * @throws StreamCorruptedException when the stream data values are outside expected range
- * @throws IOException if the input stream can't be read
- * @throws ClassNotFoundException When trying to read an object of class that is not on the classpath
+ * @param out {@link ObjectOutputStream} to write object to
+ * @throws IOException if the object can't be written
*/
- private void readObject(final ObjectInputStream in) throws IOException, ClassNotFoundException {
- this.type = readAnyClass(in);
+ private void writeObject(final ObjectOutputStream out) throws IOException {
+ writeAnyClass(this.type,out);
if (isMapped() || isIndexed()) {
- this.contentType = readAnyClass(in);
+ writeAnyClass(this.contentType,out);
}
- // read other values
- in.defaultReadObject();
- }
-
- /**
- * Reads a class using safe encoding to workaround java 1.3 serialization bug.
- */
- private Class<?> readAnyClass(final ObjectInputStream in) throws IOException, ClassNotFoundException {
- // read back type class safely
- if (in.readBoolean()) {
- // it's a type constant
- switch (in.readInt()) {
-
- case BOOLEAN_TYPE: return Boolean.TYPE;
- case BYTE_TYPE: return Byte.TYPE;
- case CHAR_TYPE: return Character.TYPE;
- case DOUBLE_TYPE: return Double.TYPE;
- case FLOAT_TYPE: return Float.TYPE;
- case INT_TYPE: return Integer.TYPE;
- case LONG_TYPE: return Long.TYPE;
- case SHORT_TYPE: return Short.TYPE;
- default:
- // something's gone wrong
- throw new StreamCorruptedException(
- "Invalid primitive type. "
- + "Check version of beanutils used to serialize is compatible.");
-
- }
-
- }
- // it's another class
- return (Class<?>) in.readObject();
+ // write out other values
+ out.defaultWriteObject();
}
}
diff --git a/src/main/java/org/apache/commons/beanutils2/FluentPropertyBeanIntrospector.java b/src/main/java/org/apache/commons/beanutils2/FluentPropertyBeanIntrospector.java
index 14389451..93c15aec 100644
--- a/src/main/java/org/apache/commons/beanutils2/FluentPropertyBeanIntrospector.java
+++ b/src/main/java/org/apache/commons/beanutils2/FluentPropertyBeanIntrospector.java
@@ -85,6 +85,15 @@ public class FluentPropertyBeanIntrospector implements BeanIntrospector {
/** The prefix of write methods to search for. */
private final String writeMethodPrefix;
+ /**
+ *
+ * Creates a new instance of {@code FluentPropertyBeanIntrospector} and
+ * sets the default prefix for write methods.
+ */
+ public FluentPropertyBeanIntrospector() {
+ this(DEFAULT_WRITE_METHOD_PREFIX);
+ }
+
/**
*
* Creates a new instance of {@code FluentPropertyBeanIntrospector} and
@@ -103,12 +112,16 @@ public class FluentPropertyBeanIntrospector implements BeanIntrospector {
}
/**
+ * Creates a property descriptor for a fluent API property.
*
- * Creates a new instance of {@code FluentPropertyBeanIntrospector} and
- * sets the default prefix for write methods.
+ * @param m the set method for the fluent API property
+ * @param propertyName the name of the corresponding property
+ * @return the descriptor
+ * @throws IntrospectionException if an error occurs
*/
- public FluentPropertyBeanIntrospector() {
- this(DEFAULT_WRITE_METHOD_PREFIX);
+ private PropertyDescriptor createFluentPropertyDescritor(final Method m,
+ final String propertyName) throws IntrospectionException {
+ return new PropertyDescriptor(propertyName(m), null, m);
}
/**
@@ -165,17 +178,4 @@ public class FluentPropertyBeanIntrospector implements BeanIntrospector {
return methodName.length() > 1 ? Introspector.decapitalize(methodName) : methodName
.toLowerCase(Locale.ENGLISH);
}
-
- /**
- * Creates a property descriptor for a fluent API property.
- *
- * @param m the set method for the fluent API property
- * @param propertyName the name of the corresponding property
- * @return the descriptor
- * @throws IntrospectionException if an error occurs
- */
- private PropertyDescriptor createFluentPropertyDescritor(final Method m,
- final String propertyName) throws IntrospectionException {
- return new PropertyDescriptor(propertyName(m), null, m);
- }
}
\ No newline at end of file
diff --git a/src/main/java/org/apache/commons/beanutils2/IntrospectionContext.java b/src/main/java/org/apache/commons/beanutils2/IntrospectionContext.java
index bfe2fda0..f83c49a4 100644
--- a/src/main/java/org/apache/commons/beanutils2/IntrospectionContext.java
+++ b/src/main/java/org/apache/commons/beanutils2/IntrospectionContext.java
@@ -37,13 +37,6 @@ import java.util.Set;
*/
public interface IntrospectionContext {
- /**
- * Returns the class that is subject of introspection.
- *
- * @return the current class
- */
- Class<?> getTargetClass();
-
/**
* Adds the given property descriptor to this context. This method is called
* by a {@code BeanIntrospector} during introspection for each detected
@@ -62,6 +55,23 @@ public interface IntrospectionContext {
*/
void addPropertyDescriptors(PropertyDescriptor[] descriptors);
+ /**
+ * Returns the descriptor for the property with the given name or
+ * <b>null</b> if this property is unknown.
+ *
+ * @param name the name of the property in question
+ * @return the descriptor for this property or <b>null</b> if this property
+ * is unknown
+ */
+ PropertyDescriptor getPropertyDescriptor(String name);
+
+ /**
+ * Returns the class that is subject of introspection.
+ *
+ * @return the current class
+ */
+ Class<?> getTargetClass();
+
/**
* Tests whether a descriptor for the property with the given name is
* already contained in this context. This method can be used for instance
@@ -74,14 +84,11 @@ public interface IntrospectionContext {
boolean hasProperty(String name);
/**
- * Returns the descriptor for the property with the given name or
- * <b>null</b> if this property is unknown.
+ * Returns a set with the names of all properties known to this context.
*
- * @param name the name of the property in question
- * @return the descriptor for this property or <b>null</b> if this property
- * is unknown
+ * @return a set with the known property names
*/
- PropertyDescriptor getPropertyDescriptor(String name);
+ Set<String> propertyNames();
/**
* Removes the descriptor for the property with the given name.
@@ -89,11 +96,4 @@ public interface IntrospectionContext {
* @param name the name of the affected property
*/
void removePropertyDescriptor(String name);
-
- /**
- * Returns a set with the names of all properties known to this context.
- *
- * @return a set with the known property names
- */
- Set<String> propertyNames();
}
diff --git a/src/main/java/org/apache/commons/beanutils2/LazyDynaBean.java b/src/main/java/org/apache/commons/beanutils2/LazyDynaBean.java
index 9433d4c7..4425bd6b 100644
--- a/src/main/java/org/apache/commons/beanutils2/LazyDynaBean.java
+++ b/src/main/java/org/apache/commons/beanutils2/LazyDynaBean.java
@@ -148,6 +148,8 @@ public class LazyDynaBean implements DynaBean, Serializable {
/** Double Zero */
protected static final Double Double_ZERO = Double.valueOf((byte)0);
+ static final LazyDynaBean[] EMPTY_ARRAY = {};
+
/**
* The {@code MutableDynaClass} "base class" that this DynaBean
* is associated with.
@@ -163,8 +165,6 @@ public class LazyDynaBean implements DynaBean, Serializable {
*/
protected MutableDynaClass dynaClass;
- static final LazyDynaBean[] EMPTY_ARRAY = {};
-
/**
* Constructs a new {@code LazyDynaBean</code> with a <code>LazyDynaClass} instance.
*/
@@ -172,15 +172,6 @@ public class LazyDynaBean implements DynaBean, Serializable {
this(new LazyDynaClass());
}
- /**
- * Constructs a new {@code LazyDynaBean</code> with a <code>LazyDynaClass} instance.
- *
- * @param name Name of this DynaBean class
- */
- public LazyDynaBean(final String name) {
- this(new LazyDynaClass(name));
- }
-
/**
* Constructs a new {@code DynaBean} associated with the specified
* {@code DynaClass</code> instance - if its not a <code>MutableDynaClass}
@@ -199,57 +190,14 @@ public class LazyDynaBean implements DynaBean, Serializable {
}
/**
- * <p>
- * Gets a Map representation of this DynaBean.
- * </p>
- * This, for example, could be used in JSTL in the following way to access
- * a DynaBean's {@code fooProperty}:
- * <ul><li>{@code ${myDynaBean.<b>map</b>.fooProperty}}</li></ul>
- *
- * @return a Map representation of this DynaBean
- */
- public Map<String, Object> getMap() {
- // cache the Map
- if (mapDecorator == null) {
- mapDecorator = new DynaBeanPropertyMapDecorator(this);
- }
- return mapDecorator;
- }
-
- /**
- * <p>Return the size of an indexed or mapped property.</p>
+ * Constructs a new {@code LazyDynaBean</code> with a <code>LazyDynaClass} instance.
*
- * @param name Name of the property
- * @return The indexed or mapped property size
- * @throws IllegalArgumentException if no property name is specified
+ * @param name Name of this DynaBean class
*/
- 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;
+ public LazyDynaBean(final String name) {
+ this(new LazyDynaClass(name));
}
-
-
/**
* Does the specified mapped property contain a value for the specified
* key value?
@@ -280,233 +228,287 @@ public class LazyDynaBean implements DynaBean, Serializable {
}
/**
- * <p>Return the value of a simple property with the specified name.</p>
- *
- * <p><strong>N.B.</strong> Returns {@code null} if there is no property
- * of the specified name.</p>
- *
- * @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
+ * Create a new Instance of a 'DynaBean' Property.
+ * @param name The name of the property
+ * @param type The class of the property
+ * @return The new value
*/
- @Override
- 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;
+ protected Object createDynaBeanProperty(final String name, final Class<?> type) {
+ try {
+ return type.newInstance();
}
-
- // Property doesn't exist
- if (!isDynaProperty(name)) {
+ catch (final Exception ex) {
+ if (logger().isWarnEnabled()) {
+ logger().warn("Error instantiating DynaBean property of type '" +
+ type.getName() + "' for '" + name + "' ", ex);
+ }
return null;
}
+ }
- // Property doesn't exist
- value = createProperty(name, dynaClass.getDynaProperty(name).getType());
-
- if (value != null) {
- set(name, value);
- }
- return value;
- }
/**
- * <p>Return the value of an indexed property with the specified name.</p>
- *
- * <p><strong>N.B.</strong> Returns {@code null} if there is no 'indexed'
- * property of the specified name.</p>
- *
- * @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
+ * Create a new Instance of an 'Indexed' Property
+ * @param name The name of the property
+ * @param type The class of the property
+ * @return The new value
*/
- @Override
- 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));
- }
+ protected Object createIndexedProperty(final String name, final Class<?> type) {
+ // Create the indexed object
+ Object indexedProperty = null;
- // Get the indexed property
- Object indexedProperty = get(name);
+ if (type == null) {
- // Check that the property is indexed
- if (!dynaClass.getDynaProperty(name).isIndexed()) {
- throw new IllegalArgumentException
- ("Non-indexed property for '" + name + "[" + index + "]' "
- + dynaClass.getDynaProperty(name).getName());
- }
+ indexedProperty = defaultIndexedProperty(name);
- // Grow indexed property to appropriate size
- indexedProperty = growIndexedProperty(name, indexedProperty, index);
+ } else if (type.isArray()) {
- // Return the indexed value
- if (indexedProperty.getClass().isArray()) {
- return Array.get(indexedProperty, index);
- }
- if (indexedProperty instanceof List) {
- return ((List<?>)indexedProperty).get(index);
+ indexedProperty = Array.newInstance(type.getComponentType(), 0);
+
+ } else if (List.class.isAssignableFrom(type)) {
+ if (type.isInterface()) {
+ indexedProperty = defaultIndexedProperty(name);
+ } else {
+ try {
+ indexedProperty = type.newInstance();
+ }
+ catch (final Exception ex) {
+ throw new IllegalArgumentException
+ ("Error instantiating indexed property of type '" +
+ type.getName() + "' for '" + name + "' " + ex);
+ }
+ }
+ } else {
+
+ throw new IllegalArgumentException
+ ("Non-indexed property of type '" + type.getName() + "' for '" + name + "'");
}
- throw new IllegalArgumentException
- ("Non-indexed property for '" + name + "[" + index + "]' "
- + indexedProperty.getClass().getName());
+
+ return indexedProperty;
}
/**
- * <p>Return the value of a mapped property with the specified name.</p>
- *
- * <p><strong>N.B.</strong> Returns {@code null} if there is no 'mapped'
- * property of the specified name.</p>
- *
- * @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
+ * Create a new Instance of a 'Mapped' Property
+ * @param name The name of the property
+ * @param type The class of the property
+ * @return The new value
*/
- @Override
- 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));
- }
+ protected Object createMappedProperty(final String name, final Class<?> type) {
+ // Create the mapped object
+ Object mappedProperty = null;
- // Get the mapped property
- final Object mappedProperty = get(name);
+ if ((type == null) || type.isInterface()) {
+
+ mappedProperty = defaultMappedProperty(name);
+
+ } else if (Map.class.isAssignableFrom(type)) {
+ try {
+ mappedProperty = type.newInstance();
+ }
+ catch (final Exception ex) {
+ throw new IllegalArgumentException
+ ("Error instantiating mapped property of type '" +
+ type.getName() + "' for '" + name + "' " + ex);
+ }
+ } else {
- // 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());
+ ("Non-mapped property of type '" + type.getName() + "' for '" + name + "'");
}
- // Get the value from the Map
- if (mappedProperty instanceof Map) {
- return ((Map<?, ?>) mappedProperty).get(key);
- }
- throw new IllegalArgumentException
- ("Non-mapped property for '" + name + "(" + key + ")'"
- + mappedProperty.getClass().getName());
+ return mappedProperty;
}
/**
- * Gets the {@code DynaClass} instance that describes the set of
- * properties available for this DynaBean.
- *
- * @return The associated DynaClass
+ * Create a new Instance of a {@code java.lang.Number} Property.
+ * @param name The name of the property
+ * @param type The class of the property
+ * @return The new value
*/
- @Override
- public DynaClass getDynaClass() {
- return dynaClass;
+ protected Object createNumberProperty(final String name, final Class<?> type) {
+ return null;
}
/**
- * 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
+ * Create a new Instance of other Property types
+ * @param name The name of the property
+ * @param type The class of the property
+ * @return The new value
*/
- @Override
- public void remove(final String name, final String key) {
- if (name == null) {
- throw new IllegalArgumentException("No property name specified");
+ protected Object createOtherProperty(final String name, final Class<?> type) {
+ if (type == Object.class ||
+ type == String.class ||
+ type == Boolean.class ||
+ type == Character.class ||
+ Date.class.isAssignableFrom(type)) {
+
+ return null;
+
}
- final Object value = values.get(name);
- if (value == null) {
- return;
+ try {
+ return type.newInstance();
}
+ catch (final Exception ex) {
+ if (logger().isWarnEnabled()) {
+ logger().warn("Error instantiating property of type '" + type.getName() + "' for '" + name + "' ", ex);
+ }
+ return null;
+ }
+ }
- if (!(value instanceof Map)) {
- throw new IllegalArgumentException
- ("Non-mapped property for '" + name + "(" + key + ")'"
- + value.getClass().getName());
+ /**
+ * Create a new Instance of a 'Primitive' Property.
+ * @param name The name of the property
+ * @param type The class of the property
+ * @return The new value
+ */
+ protected Object createPrimitiveProperty(final String name, final Class<?> type) {
+ if (type == Boolean.TYPE) {
+ return Boolean.FALSE;
}
- ((Map<?, ?>) value).remove(key);
+ if (type == Integer.TYPE) {
+ return Integer_ZERO;
+ }
+ if (type == Long.TYPE) {
+ return Long_ZERO;
+ }
+ if (type == Double.TYPE) {
+ return Double_ZERO;
+ }
+ if (type == Float.TYPE) {
+ return Float_ZERO;
+ }
+ if (type == Byte.TYPE) {
+ return Byte_ZERO;
+ }
+ if (type == Short.TYPE) {
+ return Short_ZERO;
+ }
+ if (type == Character.TYPE) {
+ return Character_SPACE;
+ }
+ return null;
}
/**
- * Sets the value of a simple property with the specified name.
+ * Create a new Instance of a Property
+ * @param name The name of the property
+ * @param type The class of the property
+ * @return The new value
+ */
+ protected Object createProperty(final String name, final Class<?> type) {
+ if (type == null) {
+ return null;
+ }
+
+ // Create Lists, arrays or DynaBeans
+ if (type.isArray() || List.class.isAssignableFrom(type)) {
+ return createIndexedProperty(name, type);
+ }
+
+ if (Map.class.isAssignableFrom(type)) {
+ return createMappedProperty(name, type);
+ }
+
+ if (DynaBean.class.isAssignableFrom(type)) {
+ return createDynaBeanProperty(name, type);
+ }
+
+ if (type.isPrimitive()) {
+ return createPrimitiveProperty(name, type);
+ }
+
+ if (Number.class.isAssignableFrom(type)) {
+ return createNumberProperty(name, type);
+ }
+
+ return createOtherProperty(name, type);
+ }
+
+ /**
+ * <p>Creates a new {@code ArrayList} for an 'indexed' property
+ * which doesn't exist.</p>
*
- * @param name Name of the property whose value is to be set
- * @param value Value to which this property is to be set
+ * <p>This method should be overridden if an alternative {@code List}
+ * or {@code Array} implementation is required for 'indexed' properties.</p>
*
- * @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
+ * @param name Name of the 'indexed property.
+ * @return The default value for an indexed property (java.util.ArrayList)
+ */
+ protected Object defaultIndexedProperty(final String name) {
+ return new ArrayList<>();
+ }
+
+ /**
+ * <p>Creates a new {@code HashMap} for a 'mapped' property
+ * which doesn't exist.</p>
+ *
+ * <p>This method can be overridden if an alternative {@code Map}
+ * implementation is required for 'mapped' properties.</p>
+ *
+ * @param name Name of the 'mapped property.
+ * @return The default value for a mapped property (java.util.HashMap)
+ */
+ protected Map<String, Object> defaultMappedProperty(final String name) {
+ return new HashMap<>();
+ }
+
+ /**
+ * <p>Return the value of a simple property with the specified name.</p>
+ *
+ * <p><strong>N.B.</strong> Returns {@code null} if there is no property
+ * of the specified name.</p>
+ *
+ * @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
*/
@Override
- public void set(final String name, final Object value) {
- // If the property doesn't exist, then add it
- if (!isDynaProperty(name)) {
+ public Object get(final String name) {
+ if (name == null) {
+ throw new IllegalArgumentException("No property name specified");
+ }
- 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());
- }
+ // Value found
+ Object value = values.get(name);
+ if (value != null) {
+ return value;
+ }
+ // Property doesn't exist
+ if (!isDynaProperty(name)) {
+ return null;
}
- final DynaProperty descriptor = dynaClass.getDynaProperty(name);
+ // Property doesn't exist
+ value = createProperty(name, dynaClass.getDynaProperty(name).getType());
- if (value == null) {
- if (descriptor.getType().isPrimitive()) {
- throw new NullPointerException
- ("Primitive value for '" + name + "'");
- }
- } else if (!isAssignable(descriptor.getType(), value.getClass())) {
- throw ConversionException.format
- ("Cannot assign value of type '%s' to property '%s' of type '%s'", value.getClass().getName(), name, descriptor.getType().getName());
+ if (value != null) {
+ set(name, value);
}
- // Set the property's value
- values.put(name, value);
+ return value;
}
/**
- * Sets the value of an indexed property with the specified name.
+ * <p>Return the value of an indexed property with the specified name.</p>
*
- * @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
+ * <p><strong>N.B.</strong> Returns {@code null} if there is no 'indexed'
+ * property of the specified name.</p>
+ *
+ * @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 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
*/
@Override
- public void set(final String name, final int index, final Object value) {
+ 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));
@@ -518,46 +520,41 @@ public class LazyDynaBean implements DynaBean, Serializable {
// 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());
+ ("Non-indexed property for '" + name + "[" + index + "]' "
+ + dynaClass.getDynaProperty(name).getName());
}
// Grow indexed property to appropriate size
indexedProperty = growIndexedProperty(name, indexedProperty, index);
- // Set the value in an array
+ // Return the indexed value
if (indexedProperty.getClass().isArray()) {
- Array.set(indexedProperty, index, value);
- } else if (indexedProperty instanceof List) {
- @SuppressWarnings("unchecked")
- final
- // Indexed properties are stored in a List<Object>
- List<Object> values = (List<Object>) indexedProperty;
- values.set(index, value);
- } else {
- throw new IllegalArgumentException
- ("Non-indexed property for '" + name + "[" + index + "]' "
- + indexedProperty.getClass().getName());
+ return Array.get(indexedProperty, index);
}
+ if (indexedProperty instanceof List) {
+ return ((List<?>)indexedProperty).get(index);
+ }
+ throw new IllegalArgumentException
+ ("Non-indexed property for '" + name + "[" + index + "]' "
+ + indexedProperty.getClass().getName());
}
/**
- * Sets the value of a mapped property with the specified name.
+ * <p>Return the value of a mapped property with the specified name.</p>
*
- * @param name Name of the property whose value is to be set
- * @param key Key of the property to be set
- * @param value Value to which this property is to be set
+ * <p><strong>N.B.</strong> Returns {@code null} if there is no 'mapped'
+ * property of the specified name.</p>
+ *
+ * @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 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 mapped
*/
@Override
- public void set(final String name, final String key, final Object value) {
- // If the 'mapped' property doesn't exist, then add it
+ 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));
}
@@ -568,23 +565,53 @@ public class LazyDynaBean implements DynaBean, Serializable {
// Check that the property is mapped
if (!dynaClass.getDynaProperty(name).isMapped()) {
throw new IllegalArgumentException
- ("Non-mapped property for '" + name + "(" + key + ")'"
+ ("Non-mapped property for '" + name + "(" + key + ")' "
+ dynaClass.getDynaProperty(name).getType().getName());
}
- // Set the value in the Map
- @SuppressWarnings("unchecked")
- final
- // mapped properties are stored in a Map<String, Object>
- Map<String, Object> valuesMap = (Map<String, Object>) mappedProperty;
- valuesMap.put(key, value);
+ // Get the value from the Map
+ if (mappedProperty instanceof Map) {
+ return ((Map<?, ?>) mappedProperty).get(key);
+ }
+ throw new IllegalArgumentException
+ ("Non-mapped property for '" + name + "(" + key + ")'"
+ + mappedProperty.getClass().getName());
}
/**
- * Grow the size of an indexed property
- * @param name The name of the property
- * @param indexedProperty The current property value
- * @param index The indexed value to grow the property to (i.e. one less than
+ * Gets the {@code DynaClass} instance that describes the set of
+ * properties available for this DynaBean.
+ *
+ * @return The associated DynaClass
+ */
+ @Override
+ public DynaClass getDynaClass() {
+ return dynaClass;
+ }
+
+ /**
+ * <p>
+ * Gets a Map representation of this DynaBean.
+ * </p>
+ * This, for example, could be used in JSTL in the following way to access
+ * a DynaBean's {@code fooProperty}:
+ * <ul><li>{@code ${myDynaBean.<b>map</b>.fooProperty}}</li></ul>
+ *
+ * @return a Map representation of this DynaBean
+ */
+ public Map<String, Object> getMap() {
+ // cache the Map
+ if (mapDecorator == null) {
+ mapDecorator = new DynaBeanPropertyMapDecorator(this);
+ }
+ return mapDecorator;
+ }
+
+ /**
+ * Grow the size of an indexed property
+ * @param name The name of the property
+ * @param indexedProperty The current property value
+ * @param index The indexed value to grow the property to (i.e. one less than
* the required size)
* @return The new property value (grown to the appropriate size)
*/
@@ -628,291 +655,264 @@ public class LazyDynaBean implements DynaBean, Serializable {
}
/**
- * Create a new Instance of a Property
- * @param name The name of the property
- * @param type The class of the property
- * @return The new value
+ * Is an object of the source class assignable to the destination class?
+ *
+ * @param dest Destination class
+ * @param source Source class
+ * @return {@code true} if the source class is assignable to the
+ * destination class, otherwise {@code false}
*/
- protected Object createProperty(final String name, final Class<?> type) {
- if (type == null) {
- return null;
- }
-
- // Create Lists, arrays or DynaBeans
- if (type.isArray() || List.class.isAssignableFrom(type)) {
- return createIndexedProperty(name, type);
- }
-
- if (Map.class.isAssignableFrom(type)) {
- return createMappedProperty(name, type);
- }
-
- if (DynaBean.class.isAssignableFrom(type)) {
- return createDynaBeanProperty(name, type);
- }
-
- if (type.isPrimitive()) {
- return createPrimitiveProperty(name, type);
- }
-
- if (Number.class.isAssignableFrom(type)) {
- return createNumberProperty(name, type);
+ protected boolean isAssignable(final Class<?> dest, final Class<?> source) {
+ if (dest.isAssignableFrom(source) ||
+ dest == Boolean.TYPE && source == Boolean.class ||
+ dest == Byte.TYPE && source == Byte.class ||
+ dest == Character.TYPE && source == Character.class ||
+ dest == Double.TYPE && source == Double.class ||
+ dest == Float.TYPE && source == Float.class ||
+ dest == Integer.TYPE && source == Integer.class ||
+ dest == Long.TYPE && source == Long.class ||
+ dest == Short.TYPE && source == Short.class) {
+ return true;
}
+ return false;
- return createOtherProperty(name, type);
}
/**
- * Create a new Instance of an 'Indexed' Property
- * @param name The name of the property
- * @param type The class of the property
- * @return The new value
+ * Indicates if there is a property with the specified name.
+ * @param name The name of the property to check
+ * @return {@code true} if there is a property of the
+ * specified name, otherwise {@code false}
*/
- protected Object createIndexedProperty(final String name, final Class<?> type) {
- // Create the indexed object
- Object indexedProperty = null;
-
- if (type == null) {
-
- indexedProperty = defaultIndexedProperty(name);
-
- } else if (type.isArray()) {
-
- indexedProperty = Array.newInstance(type.getComponentType(), 0);
-
- } else if (List.class.isAssignableFrom(type)) {
- if (type.isInterface()) {
- indexedProperty = defaultIndexedProperty(name);
- } else {
- try {
- indexedProperty = type.newInstance();
- }
- catch (final Exception ex) {
- throw new IllegalArgumentException
- ("Error instantiating indexed property of type '" +
- type.getName() + "' for '" + name + "' " + ex);
- }
- }
- } else {
+ protected boolean isDynaProperty(final String name) {
+ if (name == null) {
+ throw new IllegalArgumentException("No property name specified");
+ }
- throw new IllegalArgumentException
- ("Non-indexed property of type '" + type.getName() + "' for '" + name + "'");
+ // Handle LazyDynaClasses
+ if (dynaClass instanceof LazyDynaClass) {
+ return ((LazyDynaClass)dynaClass).isDynaProperty(name);
}
- return indexedProperty;
+ // Handle other MutableDynaClass
+ return dynaClass.getDynaProperty(name) != null;
}
/**
- * Create a new Instance of a 'Mapped' Property
- * @param name The name of the property
- * @param type The class of the property
- * @return The new value
+ * <p>Returns the {@code Log}.
*/
- protected Object createMappedProperty(final String name, final Class<?> type) {
- // Create the mapped object
- Object mappedProperty = null;
-
- if ((type == null) || type.isInterface()) {
-
- mappedProperty = defaultMappedProperty(name);
-
- } else if (Map.class.isAssignableFrom(type)) {
- try {
- mappedProperty = type.newInstance();
- }
- catch (final Exception ex) {
- throw new IllegalArgumentException
- ("Error instantiating mapped property of type '" +
- type.getName() + "' for '" + name + "' " + ex);
- }
- } else {
-
- throw new IllegalArgumentException
- ("Non-mapped property of type '" + type.getName() + "' for '" + name + "'");
+ private Log logger() {
+ if (LOG == null) {
+ LOG = LogFactory.getLog(LazyDynaBean.class);
}
-
- return mappedProperty;
+ return LOG;
}
/**
- * Create a new Instance of a 'DynaBean' Property.
- * @param name The name of the property
- * @param type The class of the property
- * @return The new value
+ * <p>Creates a new instance of the {@code Map}.</p>
+ * @return a new Map instance
*/
- protected Object createDynaBeanProperty(final String name, final Class<?> type) {
- try {
- return type.newInstance();
- }
- catch (final Exception ex) {
- if (logger().isWarnEnabled()) {
- logger().warn("Error instantiating DynaBean property of type '" +
- type.getName() + "' for '" + name + "' ", ex);
- }
- return null;
- }
+ protected Map<String, Object> newMap() {
+ return new HashMap<>();
}
/**
- * Create a new Instance of a 'Primitive' Property.
- * @param name The name of the property
- * @param type The class of the property
- * @return The new value
+ * 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
*/
- protected Object createPrimitiveProperty(final String name, final Class<?> type) {
- if (type == Boolean.TYPE) {
- return Boolean.FALSE;
- }
- if (type == Integer.TYPE) {
- return Integer_ZERO;
- }
- if (type == Long.TYPE) {
- return Long_ZERO;
- }
- if (type == Double.TYPE) {
- return Double_ZERO;
- }
- if (type == Float.TYPE) {
- return Float_ZERO;
- }
- if (type == Byte.TYPE) {
- return Byte_ZERO;
- }
- if (type == Short.TYPE) {
- return Short_ZERO;
+ @Override
+ public void remove(final String name, final String key) {
+ if (name == null) {
+ throw new IllegalArgumentException("No property name specified");
}
- if (type == Character.TYPE) {
- return Character_SPACE;
+
+ final Object value = values.get(name);
+ if (value == null) {
+ return;
}
- return null;
- }
- /**
- * Create a new Instance of a {@code java.lang.Number} Property.
- * @param name The name of the property
- * @param type The class of the property
- * @return The new value
- */
- protected Object createNumberProperty(final String name, final Class<?> type) {
- return null;
+ if (!(value instanceof Map)) {
+ throw new IllegalArgumentException
+ ("Non-mapped property for '" + name + "(" + key + ")'"
+ + value.getClass().getName());
+ }
+ ((Map<?, ?>) value).remove(key);
}
/**
- * Create a new Instance of other Property types
- * @param name The name of the property
- * @param type The class of the property
- * @return The new value
+ * Sets 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
*/
- protected Object createOtherProperty(final String name, final Class<?> type) {
- if (type == Object.class ||
- type == String.class ||
- type == Boolean.class ||
- type == Character.class ||
- Date.class.isAssignableFrom(type)) {
+ @Override
+ 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));
+ }
- return null;
+ // 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());
}
- try {
- return type.newInstance();
- }
- catch (final Exception ex) {
- if (logger().isWarnEnabled()) {
- logger().warn("Error instantiating property of type '" + type.getName() + "' for '" + name + "' ", ex);
- }
- return null;
+ // 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<Object>
+ List<Object> values = (List<Object>) indexedProperty;
+ values.set(index, value);
+ } else {
+ throw new IllegalArgumentException
+ ("Non-indexed property for '" + name + "[" + index + "]' "
+ + indexedProperty.getClass().getName());
}
}
/**
- * <p>Creates a new {@code ArrayList} for an 'indexed' property
- * which doesn't exist.</p>
+ * Sets the value of a simple property with the specified name.
*
- * <p>This method should be overridden if an alternative {@code List}
- * or {@code Array} implementation is required for 'indexed' properties.</p>
+ * @param name Name of the property whose value is to be set
+ * @param value Value to which this property is to be set
*
- * @param name Name of the 'indexed property.
- * @return The default value for an indexed property (java.util.ArrayList)
+ * @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
*/
- protected Object defaultIndexedProperty(final String name) {
- return new ArrayList<>();
+ @Override
+ 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 ConversionException.format
+ ("Cannot assign value of type '%s' to property '%s' of type '%s'", value.getClass().getName(), name, descriptor.getType().getName());
+ }
+
+ // Set the property's value
+ values.put(name, value);
}
/**
- * <p>Creates a new {@code HashMap} for a 'mapped' property
- * which doesn't exist.</p>
+ * Sets the value of a mapped property with the specified name.
*
- * <p>This method can be overridden if an alternative {@code Map}
- * implementation is required for 'mapped' properties.</p>
+ * @param name Name of the property whose value is to be set
+ * @param key Key of the property to be set
+ * @param value Value to which this property is to be set
*
- * @param name Name of the 'mapped property.
- * @return The default value for a mapped property (java.util.HashMap)
- */
- protected Map<String, Object> defaultMappedProperty(final String name) {
- return new HashMap<>();
- }
-
- /**
- * Indicates if there is a property with the specified name.
- * @param name The name of the property to check
- * @return {@code true} if there is a property of the
- * specified name, otherwise {@code false}
+ * @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 mapped
*/
- protected boolean isDynaProperty(final String name) {
- if (name == null) {
- throw new IllegalArgumentException("No property name specified");
+ @Override
+ public void set(final String name, final String key, final Object value) {
+ // If the 'mapped' property doesn't exist, then add it
+ if (!isDynaProperty(name)) {
+ set(name, defaultMappedProperty(name));
}
- // Handle LazyDynaClasses
- if (dynaClass instanceof LazyDynaClass) {
- return ((LazyDynaClass)dynaClass).isDynaProperty(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());
}
- // Handle other MutableDynaClass
- return dynaClass.getDynaProperty(name) != null;
+ // Set the value in the Map
+ @SuppressWarnings("unchecked")
+ final
+ // mapped properties are stored in a Map<String, Object>
+ Map<String, Object> valuesMap = (Map<String, Object>) mappedProperty;
+ valuesMap.put(key, value);
}
/**
- * Is an object of the source class assignable to the destination class?
+ * <p>Return the size of an indexed or mapped property.</p>
*
- * @param dest Destination class
- * @param source Source class
- * @return {@code true} if the source class is assignable to the
- * destination class, otherwise {@code false}
+ * @param name Name of the property
+ * @return The indexed or mapped property size
+ * @throws IllegalArgumentException if no property name is specified
*/
- protected boolean isAssignable(final Class<?> dest, final Class<?> source) {
- if (dest.isAssignableFrom(source) ||
- dest == Boolean.TYPE && source == Boolean.class ||
- dest == Byte.TYPE && source == Byte.class ||
- dest == Character.TYPE && source == Character.class ||
- dest == Double.TYPE && source == Double.class ||
- dest == Float.TYPE && source == Float.class ||
- dest == Integer.TYPE && source == Integer.class ||
- dest == Long.TYPE && source == Long.class ||
- dest == Short.TYPE && source == Short.class) {
- return true;
+ public int size(final String name) {
+ if (name == null) {
+ throw new IllegalArgumentException("No property name specified");
}
- return false;
- }
+ final Object value = values.get(name);
+ if (value == null) {
+ return 0;
+ }
- /**
- * <p>Creates a new instance of the {@code Map}.</p>
- * @return a new Map instance
- */
- protected Map<String, Object> newMap() {
- return new HashMap<>();
- }
+ if (value instanceof Map) {
+ return ((Map<?, ?>)value).size();
+ }
- /**
- * <p>Returns the {@code Log}.
- */
- private Log logger() {
- if (LOG == null) {
- LOG = LogFactory.getLog(LazyDynaBean.class);
+ if (value instanceof List) {
+ return ((List<?>)value).size();
}
- return LOG;
+
+ if (value.getClass().isArray()) {
+ return Array.getLength(value);
+ }
+
+ return 0;
}
}
diff --git a/src/main/java/org/apache/commons/beanutils2/LazyDynaClass.java b/src/main/java/org/apache/commons/beanutils2/LazyDynaClass.java
index 2635e25f..bba98f85 100644
--- a/src/main/java/org/apache/commons/beanutils2/LazyDynaClass.java
+++ b/src/main/java/org/apache/commons/beanutils2/LazyDynaClass.java
@@ -86,16 +86,6 @@ public class LazyDynaClass extends BasicDynaClass implements MutableDynaClass {
this(name, dynaBeanClass, null);
}
- /**
- * Constructs a new LazyDynaClass with the specified name and properties.
- *
- * @param name Name of this DynaBean class
- * @param properties Property descriptors for the supported properties
- */
- public LazyDynaClass(final String name, final DynaProperty[] properties) {
- this(name, LazyDynaBean.class, properties);
- }
-
/**
* Constructs a new LazyDynaClass with the specified name, DynaBean class and properties.
*
@@ -108,52 +98,45 @@ public class LazyDynaClass extends BasicDynaClass implements MutableDynaClass {
}
/**
- * <p>Is this DynaClass currently restricted.</p>
- * <p>If restricted, no changes to the existing registration of
- * property names, data types, readability, or writeability are allowed.</p>
- * @return {@code true} if this {@link MutableDynaClass} cannot be changed
- * otherwise {@code false}
- */
- @Override
- public boolean isRestricted() {
- return restricted;
- }
-
- /**
- * <p>Set whether this DynaClass is currently restricted.</p>
- * <p>If restricted, no changes to the existing registration of
- * property names, data types, readability, or writeability are allowed.</p>
- * @param restricted {@code true} if this {@link MutableDynaClass} cannot
- * be changed otherwise {@code false}
+ * Constructs a new LazyDynaClass with the specified name and properties.
+ *
+ * @param name Name of this DynaBean class
+ * @param properties Property descriptors for the supported properties
*/
- @Override
- public void setRestricted(final boolean restricted) {
- this.restricted = restricted;
+ public LazyDynaClass(final String name, final DynaProperty[] properties) {
+ this(name, LazyDynaBean.class, properties);
}
/**
- * Should this DynaClass return a {@code null} from
- * the {@code getDynaProperty(name)} method if the property
- * doesn't exist.
+ * Add a new dynamic property.
*
- * @return {@code true</code> if a <code>null} {@link DynaProperty}
- * should be returned if the property doesn't exist, otherwise
- * {@code false} if a new {@link DynaProperty} should be created.
+ * @param property Property the new dynamic property to add.
+ *
+ * @throws IllegalArgumentException if name is null
+ * @throws IllegalStateException if this DynaClass is currently
+ * restricted, so no new properties can be added
*/
- public boolean isReturnNull() {
- return returnNull;
- }
+ protected void add(final DynaProperty property) {
+ if (property.getName() == null) {
+ throw new IllegalArgumentException("Property name is missing.");
+ }
- /**
- * Sets whether this DynaClass should return a {@code null} from
- * the {@code getDynaProperty(name)} method if the property
- * doesn't exist.
- * @param returnNull {@code true</code> if a <code>null} {@link DynaProperty}
- * should be returned if the property doesn't exist, otherwise
- * {@code false} if a new {@link DynaProperty} should be created.
- */
- public void setReturnNull(final boolean returnNull) {
- this.returnNull = returnNull;
+ if (isRestricted()) {
+ throw new IllegalStateException("DynaClass is currently restricted. No new properties can be added.");
+ }
+
+ // Check if property already exists
+ if (propertiesMap.get(property.getName()) != null) {
+ return;
+ }
+
+ // Create a new property array with the specified property
+ final DynaProperty[] oldProperties = getDynaProperties();
+ final DynaProperty[] newProperties = Arrays.copyOf(oldProperties, oldProperties.length + 1);
+ newProperties[oldProperties.length] = property;
+
+ // Update the properties
+ setProperties(newProperties);
}
/**
@@ -219,35 +202,89 @@ public class LazyDynaClass extends BasicDynaClass implements MutableDynaClass {
}
/**
- * Add a new dynamic property.
+ * <p>Return a property descriptor for the specified property.</p>
*
- * @param property Property the new dynamic property to add.
+ * <p>If the property is not found and the {@code returnNull} indicator is
+ * {@code true</code>, this method always returns <code>null}.</p>
*
- * @throws IllegalArgumentException if name is null
- * @throws IllegalStateException if this DynaClass is currently
- * restricted, so no new properties can be added
+ * <p>If the property is not found and the {@code returnNull} indicator is
+ * {@code false} a new property descriptor is created and returned (although
+ * its not actually added to the DynaClass's properties). This is the default
+ * behavior.</p>
+ *
+ * <p>The reason for not returning a {@code null} property descriptor is that
+ * {@code BeanUtils} uses this method to check if a property exists
+ * before trying to set it - since these <i>Lazy</i> implementations automatically
+ * add any new properties when they are set, returning {@code null} from
+ * this method would defeat their purpose.</p>
+ *
+ * @param name Name of the dynamic property for which a descriptor
+ * is requested
+ * @return The dyna property for the specified name
+ *
+ * @throws IllegalArgumentException if no property name is specified
*/
- protected void add(final DynaProperty property) {
- if (property.getName() == null) {
+ @Override
+ public DynaProperty getDynaProperty(final String name) {
+ if (name == null) {
throw new IllegalArgumentException("Property name is missing.");
}
- if (isRestricted()) {
- throw new IllegalStateException("DynaClass is currently restricted. No new properties can be added.");
+ DynaProperty dynaProperty = propertiesMap.get(name);
+
+ // If it doesn't exist and returnNull is false
+ // create a new DynaProperty
+ if (dynaProperty == null && !isReturnNull() && !isRestricted()) {
+ dynaProperty = new DynaProperty(name);
}
- // Check if property already exists
- if (propertiesMap.get(property.getName()) != null) {
- return;
+ return dynaProperty;
+ }
+
+ /**
+ * <p>Indicate whether a property actually exists.</p>
+ *
+ * <p><strong>N.B.</strong> Using {@code getDynaProperty(name) == null}
+ * doesn't work in this implementation because that method might
+ * return a DynaProperty if it doesn't exist (depending on the
+ * {@code returnNull} indicator).</p>
+ *
+ * @param name The name of the property to check
+ * @return {@code true} if there is a property of the
+ * specified name, otherwise {@code false}
+ * @throws IllegalArgumentException if no property name is specified
+ */
+ public boolean isDynaProperty(final String name) {
+ if (name == null) {
+ throw new IllegalArgumentException("Property name is missing.");
}
- // Create a new property array with the specified property
- final DynaProperty[] oldProperties = getDynaProperties();
- final DynaProperty[] newProperties = Arrays.copyOf(oldProperties, oldProperties.length + 1);
- newProperties[oldProperties.length] = property;
+ return propertiesMap.get(name) != null;
+ }
- // Update the properties
- setProperties(newProperties);
+ /**
+ * <p>Is this DynaClass currently restricted.</p>
+ * <p>If restricted, no changes to the existing registration of
+ * property names, data types, readability, or writeability are allowed.</p>
+ * @return {@code true} if this {@link MutableDynaClass} cannot be changed
+ * otherwise {@code false}
+ */
+ @Override
+ public boolean isRestricted() {
+ return restricted;
+ }
+
+ /**
+ * Should this DynaClass return a {@code null} from
+ * the {@code getDynaProperty(name)} method if the property
+ * doesn't exist.
+ *
+ * @return {@code true</code> if a <code>null} {@link DynaProperty}
+ * should be returned if the property doesn't exist, otherwise
+ * {@code false} if a new {@link DynaProperty} should be created.
+ */
+ public boolean isReturnNull() {
+ return returnNull;
}
/**
@@ -294,64 +331,27 @@ public class LazyDynaClass extends BasicDynaClass implements MutableDynaClass {
}
/**
- * <p>Return a property descriptor for the specified property.</p>
- *
- * <p>If the property is not found and the {@code returnNull} indicator is
- * {@code true</code>, this method always returns <code>null}.</p>
- *
- * <p>If the property is not found and the {@code returnNull} indicator is
- * {@code false} a new property descriptor is created and returned (although
- * its not actually added to the DynaClass's properties). This is the default
- * behavior.</p>
- *
- * <p>The reason for not returning a {@code null} property descriptor is that
- * {@code BeanUtils} uses this method to check if a property exists
- * before trying to set it - since these <i>Lazy</i> implementations automatically
- * add any new properties when they are set, returning {@code null} from
- * this method would defeat their purpose.</p>
- *
- * @param name Name of the dynamic property for which a descriptor
- * is requested
- * @return The dyna property for the specified name
- *
- * @throws IllegalArgumentException if no property name is specified
+ * <p>Set whether this DynaClass is currently restricted.</p>
+ * <p>If restricted, no changes to the existing registration of
+ * property names, data types, readability, or writeability are allowed.</p>
+ * @param restricted {@code true} if this {@link MutableDynaClass} cannot
+ * be changed otherwise {@code false}
*/
@Override
- public DynaProperty getDynaProperty(final String name) {
- if (name == null) {
- throw new IllegalArgumentException("Property name is missing.");
- }
-
- DynaProperty dynaProperty = propertiesMap.get(name);
-
- // If it doesn't exist and returnNull is false
- // create a new DynaProperty
- if (dynaProperty == null && !isReturnNull() && !isRestricted()) {
- dynaProperty = new DynaProperty(name);
- }
-
- return dynaProperty;
+ public void setRestricted(final boolean restricted) {
+ this.restricted = restricted;
}
/**
- * <p>Indicate whether a property actually exists.</p>
- *
- * <p><strong>N.B.</strong> Using {@code getDynaProperty(name) == null}
- * doesn't work in this implementation because that method might
- * return a DynaProperty if it doesn't exist (depending on the
- * {@code returnNull} indicator).</p>
- *
- * @param name The name of the property to check
- * @return {@code true} if there is a property of the
- * specified name, otherwise {@code false}
- * @throws IllegalArgumentException if no property name is specified
+ * Sets whether this DynaClass should return a {@code null} from
+ * the {@code getDynaProperty(name)} method if the property
+ * doesn't exist.
+ * @param returnNull {@code true</code> if a <code>null} {@link DynaProperty}
+ * should be returned if the property doesn't exist, otherwise
+ * {@code false} if a new {@link DynaProperty} should be created.
*/
- public boolean isDynaProperty(final String name) {
- if (name == null) {
- throw new IllegalArgumentException("Property name is missing.");
- }
-
- return propertiesMap.get(name) != null;
+ public void setReturnNull(final boolean returnNull) {
+ this.returnNull = returnNull;
}
}
\ No newline at end of file
diff --git a/src/main/java/org/apache/commons/beanutils2/LazyDynaList.java b/src/main/java/org/apache/commons/beanutils2/LazyDynaList.java
index 3b67bef3..cdc2f754 100644
--- a/src/main/java/org/apache/commons/beanutils2/LazyDynaList.java
+++ b/src/main/java/org/apache/commons/beanutils2/LazyDynaList.java
@@ -194,14 +194,24 @@ public class LazyDynaList extends ArrayList<Object> {
}
/**
- * Constructs a LazyDynaList with the
- * specified capacity.
+ * Constructs a LazyDynaList with a
+ * specified type for its elements.
*
- * @param capacity The initial capacity of the list.
+ * @param elementType The Type of the List's elements.
*/
- public LazyDynaList(final int capacity) {
- super(capacity);
+ public LazyDynaList(final Class<?> elementType) {
+ setElementType(elementType);
+ }
+ /**
+ * Constructs a LazyDynaList populated with the
+ * elements of a Collection.
+ *
+ * @param collection The Collection to populate the List from.
+ */
+ public LazyDynaList(final Collection<?> collection) {
+ super(collection.size());
+ addAll(collection);
}
/**
@@ -215,24 +225,14 @@ public class LazyDynaList extends ArrayList<Object> {
}
/**
- * Constructs a LazyDynaList with a
- * specified type for its elements.
+ * Constructs a LazyDynaList with the
+ * specified capacity.
*
- * @param elementType The Type of the List's elements.
+ * @param capacity The initial capacity of the list.
*/
- public LazyDynaList(final Class<?> elementType) {
- setElementType(elementType);
- }
+ public LazyDynaList(final int capacity) {
+ super(capacity);
- /**
- * Constructs a LazyDynaList populated with the
- * elements of a Collection.
- *
- * @param collection The Collection to populate the List from.
- */
- public LazyDynaList(final Collection<?> collection) {
- super(collection.size());
- addAll(collection);
}
/**
@@ -334,6 +334,20 @@ public class LazyDynaList extends ArrayList<Object> {
return true;
}
+ /**
+ * Creates a new {@code LazyDynaMap} object for the given property value.
+ *
+ * @param value the property value
+ * @return the newly created {@code LazyDynaMap}
+ */
+ private LazyDynaMap createDynaBeanForMapProperty(final Object value) {
+ @SuppressWarnings("unchecked")
+ final
+ // map properties are always stored as Map<String, Object>
+ Map<String, Object> valueMap = (Map<String, Object>) value;
+ return new LazyDynaMap(valueMap);
+ }
+
/**
* <p>Return the element at the specified position.</p>
*
@@ -351,6 +365,35 @@ public class LazyDynaList extends ArrayList<Object> {
return super.get(index);
}
+ /**
+ * Gets the DynaClass.
+ */
+ private DynaClass getDynaClass() {
+ return elementDynaClass == null ? wrapDynaClass : elementDynaClass;
+ }
+
+ /**
+ * <p>Automatically <i>grown</i> the List
+ * to the appropriate size, populating with
+ * DynaBeans.</p>
+ *
+ * @param requiredSize the required size of the List.
+ */
+ private void growList(final int requiredSize) {
+ if (requiredSize < size()) {
+ return;
+ }
+
+ ensureCapacity(requiredSize + 1);
+
+ for (int i = size(); i < requiredSize; i++) {
+ final DynaBean dynaBean = transform(null);
+ super.add(dynaBean);
+ }
+ }
+
+
+
/**
* <p>Set the element at the specified position.</p>
*
@@ -371,6 +414,96 @@ public class LazyDynaList extends ArrayList<Object> {
return super.set(index, dynaBean);
}
+ /**
+ * <p>Set the element Type and DynaClass.</p>
+ *
+ * @param elementDynaClass The DynaClass of the elements.
+ * @throws IllegalArgumentException if the List already
+ * contains elements or the DynaClass is null.
+ */
+ public void setElementDynaClass(final DynaClass elementDynaClass) {
+ if (elementDynaClass == null) {
+ throw new IllegalArgumentException("Element DynaClass is missing");
+ }
+
+ if (!isEmpty()) {
+ throw new IllegalStateException("Element DynaClass cannot be reset");
+ }
+
+ // Try to create a new instance of the DynaBean
+ try {
+ final DynaBean dynaBean = elementDynaClass.newInstance();
+ this.elementDynaBeanType = dynaBean.getClass();
+ if (WrapDynaBean.class.isAssignableFrom(elementDynaBeanType)) {
+ this.elementType = ((WrapDynaBean)dynaBean).getInstance().getClass();
+ this.wrapDynaClass = (WrapDynaClass)elementDynaClass;
+ } else if (LazyDynaMap.class.isAssignableFrom(elementDynaBeanType)) {
+ this.elementType = ((LazyDynaMap)dynaBean).getMap().getClass();
+ this.elementDynaClass = elementDynaClass;
+ } else {
+ this.elementType = dynaBean.getClass();
+ this.elementDynaClass = elementDynaClass;
+ }
+ } catch (final Exception e) {
+ throw new IllegalArgumentException(
+ "Error creating DynaBean from " +
+ elementDynaClass.getClass().getName() + " - " + e);
+ }
+ }
+
+ /**
+ * <p>Set the element Type and DynaClass.</p>
+ *
+ * @param elementType The type of the elements.
+ * @throws IllegalArgumentException if the List already
+ * contains elements or the DynaClass is null.
+ */
+ public void setElementType(final Class<?> elementType) {
+ if (elementType == null) {
+ throw new IllegalArgumentException("Element Type is missing");
+ }
+
+ final boolean changeType = this.elementType != null && !this.elementType.equals(elementType);
+ if (changeType && !isEmpty()) {
+ throw new IllegalStateException("Element Type cannot be reset");
+ }
+
+ this.elementType = elementType;
+
+ // Create a new object of the specified type
+ Object object = null;
+ try {
+ object = elementType.newInstance();
+ } catch (final Exception e) {
+ throw new IllegalArgumentException("Error creating type: "
+ + elementType.getName() + " - " + e);
+ }
+
+ // Create a DynaBean
+ DynaBean dynaBean = null;
+ if (Map.class.isAssignableFrom(elementType)) {
+ dynaBean = createDynaBeanForMapProperty(object);
+ this.elementDynaClass = dynaBean.getDynaClass();
+ } else if (DynaBean.class.isAssignableFrom(elementType)) {
+ dynaBean = (DynaBean)object;
+ this.elementDynaClass = dynaBean.getDynaClass();
+ } else {
+ dynaBean = new WrapDynaBean(object);
+ this.wrapDynaClass = (WrapDynaClass)dynaBean.getDynaClass();
+ }
+
+ this.elementDynaBeanType = dynaBean.getClass();
+
+ // Re-calculate the type
+ if (WrapDynaBean.class.isAssignableFrom(elementDynaBeanType )) {
+ this.elementType = ((WrapDynaBean)dynaBean).getInstance().getClass();
+ } else if (LazyDynaMap.class.isAssignableFrom(elementDynaBeanType )) {
+ this.elementType = ((LazyDynaMap)dynaBean).getMap().getClass();
+ }
+ }
+
+
+
/**
* <p>Converts the List to an Array.</p>
*
@@ -452,8 +585,6 @@ public class LazyDynaList extends ArrayList<Object> {
+ elementType.getName());
}
-
-
/**
* <p>Converts the List to an DynaBean Array.</p>
*
@@ -471,116 +602,6 @@ public class LazyDynaList extends ArrayList<Object> {
return array;
}
- /**
- * <p>Set the element Type and DynaClass.</p>
- *
- * @param elementType The type of the elements.
- * @throws IllegalArgumentException if the List already
- * contains elements or the DynaClass is null.
- */
- public void setElementType(final Class<?> elementType) {
- if (elementType == null) {
- throw new IllegalArgumentException("Element Type is missing");
- }
-
- final boolean changeType = this.elementType != null && !this.elementType.equals(elementType);
- if (changeType && !isEmpty()) {
- throw new IllegalStateException("Element Type cannot be reset");
- }
-
- this.elementType = elementType;
-
- // Create a new object of the specified type
- Object object = null;
- try {
- object = elementType.newInstance();
- } catch (final Exception e) {
- throw new IllegalArgumentException("Error creating type: "
- + elementType.getName() + " - " + e);
- }
-
- // Create a DynaBean
- DynaBean dynaBean = null;
- if (Map.class.isAssignableFrom(elementType)) {
- dynaBean = createDynaBeanForMapProperty(object);
- this.elementDynaClass = dynaBean.getDynaClass();
- } else if (DynaBean.class.isAssignableFrom(elementType)) {
- dynaBean = (DynaBean)object;
- this.elementDynaClass = dynaBean.getDynaClass();
- } else {
- dynaBean = new WrapDynaBean(object);
- this.wrapDynaClass = (WrapDynaClass)dynaBean.getDynaClass();
- }
-
- this.elementDynaBeanType = dynaBean.getClass();
-
- // Re-calculate the type
- if (WrapDynaBean.class.isAssignableFrom(elementDynaBeanType )) {
- this.elementType = ((WrapDynaBean)dynaBean).getInstance().getClass();
- } else if (LazyDynaMap.class.isAssignableFrom(elementDynaBeanType )) {
- this.elementType = ((LazyDynaMap)dynaBean).getMap().getClass();
- }
- }
-
- /**
- * <p>Set the element Type and DynaClass.</p>
- *
- * @param elementDynaClass The DynaClass of the elements.
- * @throws IllegalArgumentException if the List already
- * contains elements or the DynaClass is null.
- */
- public void setElementDynaClass(final DynaClass elementDynaClass) {
- if (elementDynaClass == null) {
- throw new IllegalArgumentException("Element DynaClass is missing");
- }
-
- if (!isEmpty()) {
- throw new IllegalStateException("Element DynaClass cannot be reset");
- }
-
- // Try to create a new instance of the DynaBean
- try {
- final DynaBean dynaBean = elementDynaClass.newInstance();
- this.elementDynaBeanType = dynaBean.getClass();
- if (WrapDynaBean.class.isAssignableFrom(elementDynaBeanType)) {
- this.elementType = ((WrapDynaBean)dynaBean).getInstance().getClass();
- this.wrapDynaClass = (WrapDynaClass)elementDynaClass;
- } else if (LazyDynaMap.class.isAssignableFrom(elementDynaBeanType)) {
- this.elementType = ((LazyDynaMap)dynaBean).getMap().getClass();
- this.elementDynaClass = elementDynaClass;
- } else {
- this.elementType = dynaBean.getClass();
- this.elementDynaClass = elementDynaClass;
- }
- } catch (final Exception e) {
- throw new IllegalArgumentException(
- "Error creating DynaBean from " +
- elementDynaClass.getClass().getName() + " - " + e);
- }
- }
-
-
-
- /**
- * <p>Automatically <i>grown</i> the List
- * to the appropriate size, populating with
- * DynaBeans.</p>
- *
- * @param requiredSize the required size of the List.
- */
- private void growList(final int requiredSize) {
- if (requiredSize < size()) {
- return;
- }
-
- ensureCapacity(requiredSize + 1);
-
- for (int i = size(); i < requiredSize; i++) {
- final DynaBean dynaBean = transform(null);
- super.add(dynaBean);
- }
- }
-
/**
* <p>Transform the element into a DynaBean:</p>
*
@@ -655,25 +676,4 @@ public class LazyDynaList extends ArrayList<Object> {
return dynaBean;
}
-
- /**
- * Creates a new {@code LazyDynaMap} object for the given property value.
- *
- * @param value the property value
- * @return the newly created {@code LazyDynaMap}
- */
- private LazyDynaMap createDynaBeanForMapProperty(final Object value) {
- @SuppressWarnings("unchecked")
- final
- // map properties are always stored as Map<String, Object>
- Map<String, Object> valueMap = (Map<String, Object>) value;
- return new LazyDynaMap(valueMap);
- }
-
- /**
- * Gets the DynaClass.
- */
- private DynaClass getDynaClass() {
- return elementDynaClass == null ? wrapDynaClass : elementDynaClass;
- }
}
diff --git a/src/main/java/org/apache/commons/beanutils2/LazyDynaMap.java b/src/main/java/org/apache/commons/beanutils2/LazyDynaMap.java
index b211b12f..3cf95956 100644
--- a/src/main/java/org/apache/commons/beanutils2/LazyDynaMap.java
+++ b/src/main/java/org/apache/commons/beanutils2/LazyDynaMap.java
@@ -75,42 +75,39 @@ public class LazyDynaMap extends LazyDynaBean implements MutableDynaClass {
}
/**
- * Constructs a new {@code LazyDynaMap} with the specified name.
+ * Constructs a new {@code LazyDynaMap} based on an exisiting DynaClass
*
- * @param name Name of this DynaBean class
+ * @param dynaClass DynaClass to copy the name and properties from
*/
- public LazyDynaMap(final String name) {
- this(name, (Map<String, Object>)null);
+ public LazyDynaMap(final DynaClass dynaClass) {
+ this(dynaClass.getName(), dynaClass.getDynaProperties());
}
/**
- * Constructs a new {@code LazyDynaMap</code> with the specified <code>Map}.
+ * Constructs a new {@code LazyDynaMap} with the specified properties.
*
- * @param values The Map backing this {@code LazyDynaMap}
+ * @param properties Property descriptors for the supported properties
*/
- public LazyDynaMap(final Map<String, Object> values) {
- this(null, values);
+ public LazyDynaMap(final DynaProperty[] properties) {
+ this(null, properties);
}
/**
- * Constructs a new {@code LazyDynaMap</code> with the specified name and <code>Map}.
+ * Constructs a new {@code LazyDynaMap</code> with the specified <code>Map}.
*
- * @param name Name of this DynaBean class
* @param values The Map backing this {@code LazyDynaMap}
*/
- public LazyDynaMap(final String name, final Map<String, Object> values) {
- this.name = name == null ? "LazyDynaMap" : name;
- this.values = values == null ? newMap() : values;
- this.dynaClass = this;
+ public LazyDynaMap(final Map<String, Object> values) {
+ this(null, values);
}
/**
- * Constructs a new {@code LazyDynaMap} with the specified properties.
+ * Constructs a new {@code LazyDynaMap} with the specified name.
*
- * @param properties Property descriptors for the supported properties
+ * @param name Name of this DynaBean class
*/
- public LazyDynaMap(final DynaProperty[] properties) {
- this(null, properties);
+ public LazyDynaMap(final String name) {
+ this(name, (Map<String, Object>)null);
}
/**
@@ -129,64 +126,125 @@ public class LazyDynaMap extends LazyDynaBean implements MutableDynaClass {
}
/**
- * Constructs a new {@code LazyDynaMap} based on an exisiting DynaClass
+ * Constructs a new {@code LazyDynaMap</code> with the specified name and <code>Map}.
*
- * @param dynaClass DynaClass to copy the name and properties from
+ * @param name Name of this DynaBean class
+ * @param values The Map backing this {@code LazyDynaMap}
*/
- public LazyDynaMap(final DynaClass dynaClass) {
- this(dynaClass.getName(), dynaClass.getDynaProperties());
+ public LazyDynaMap(final String name, final Map<String, Object> values) {
+ this.name = name == null ? "LazyDynaMap" : name;
+ this.values = values == null ? newMap() : values;
+ this.dynaClass = this;
}
/**
- * Sets the Map backing this {@code DynaBean}
+ * Add a new dynamic property.
*
- * @param values The new Map of values
+ * @param property Property the new dynamic property to add.
+ *
+ * @throws IllegalArgumentException if name is null
*/
- public void setMap(final Map<String, Object> values) {
- this.values = values;
+ protected void add(final DynaProperty property) {
+ add(property.getName(), property.getType());
}
/**
- * Gets the underlying Map backing this {@code DynaBean}
- * @return the underlying Map
- * @since 1.8.0
+ * Add a new dynamic property with no restrictions on data type,
+ * readability, or writeability.
+ *
+ * @param name Name of the new dynamic property
+ *
+ * @throws IllegalArgumentException if name is null
*/
@Override
- public Map<String, Object> getMap() {
- return values;
+ public void add(final String name) {
+ add(name, null);
}
/**
- * Sets the value of a simple property with the specified name.
+ * Add a new dynamic property with the specified data type, but with
+ * no restrictions on readability or writeability.
*
- * @param name Name of the property whose value is to be set
- * @param value Value to which this property is to be set
+ * @param name Name of the new dynamic property
+ * @param type Data type of the new dynamic property (null for no
+ * restrictions)
+ *
+ * @throws IllegalArgumentException if name is null
+ * @throws IllegalStateException if this DynaClass is currently
+ * restricted, so no new properties can be added
*/
@Override
- public void set(final String name, final Object value) {
- if (isRestricted() && !values.containsKey(name)) {
- throw new IllegalArgumentException
- ("Invalid property name '" + name + "' (DynaClass is restricted)");
+ public void add(final String name, final Class<?> type) {
+ if (name == null) {
+ throw new IllegalArgumentException("Property name is missing.");
}
- values.put(name, value);
+ if (isRestricted()) {
+ throw new IllegalStateException("DynaClass is currently restricted. No new properties can be added.");
+ }
+
+ final Object value = values.get(name);
+
+ // Check if the property already exists
+ if (value == null) {
+ values.put(name, type == null ? null : createProperty(name, type));
+ }
}
/**
- * Gets the name of this DynaClass (analogous to the
- * {@code getName()</code> method of <code>java.lang.Class})
+ * <p>Add a new dynamic property with the specified data type, readability,
+ * and writeability.</p>
*
- * @return the name of the DynaClass
+ * <p><strong>N.B.</strong>Support for readable/writable properties has not been implemented
+ * and this method always throws a {@code UnsupportedOperationException}.</p>
+ *
+ * <p>I'm not sure the intention of the original authors for this method, but it seems to
+ * me that readable/writable should be attributes of the {@code DynaProperty} class
+ * (which they are not) and is the reason this method has not been implemented.</p>
+ *
+ * @param name Name of the new dynamic property
+ * @param type Data type of the new dynamic property (null for no
+ * restrictions)
+ * @param readable Set to {@code true} if this property value
+ * should be readable
+ * @param writable Set to {@code true} if this property value
+ * should be writable
+ *
+ * @throws UnsupportedOperationException anytime this method is called
*/
@Override
- public String getName() {
- return this.name;
+ public void add(final String name, final Class<?> type, final boolean readable, final boolean writable) {
+ throw new java.lang.UnsupportedOperationException("readable/writable properties not supported");
+ }
+
+ /**
+ * <p>Return an array of {@code PropertyDescriptor} for the properties
+ * currently defined in this DynaClass. If no properties are defined, a
+ * zero-length array will be returned.</p>
+ *
+ * <p><strong>FIXME</strong> - Should we really be implementing
+ * {@code getBeanInfo()} instead, which returns property descriptors
+ * and a bunch of other stuff?</p>
+ * @return the set of properties for this DynaClass
+ */
+ @Override
+ public DynaProperty[] getDynaProperties() {
+ int i = 0;
+ final DynaProperty[] properties = new DynaProperty[values.size()];
+ for (final Map.Entry<String, Object> e : values.entrySet()) {
+ final String name = e.getKey();
+ final Object value = values.get(name);
+ properties[i++] = new DynaProperty(name, value == null ? null
+ : value.getClass());
+ }
+
+ return properties;
}
/**
@@ -233,57 +291,47 @@ public class LazyDynaMap extends LazyDynaBean implements MutableDynaClass {
}
/**
- * <p>Return an array of {@code PropertyDescriptor} for the properties
- * currently defined in this DynaClass. If no properties are defined, a
- * zero-length array will be returned.</p>
- *
- * <p><strong>FIXME</strong> - Should we really be implementing
- * {@code getBeanInfo()} instead, which returns property descriptors
- * and a bunch of other stuff?</p>
- * @return the set of properties for this DynaClass
+ * Gets the underlying Map backing this {@code DynaBean}
+ * @return the underlying Map
+ * @since 1.8.0
*/
@Override
- public DynaProperty[] getDynaProperties() {
- int i = 0;
- final DynaProperty[] properties = new DynaProperty[values.size()];
- for (final Map.Entry<String, Object> e : values.entrySet()) {
- final String name = e.getKey();
- final Object value = values.get(name);
- properties[i++] = new DynaProperty(name, value == null ? null
- : value.getClass());
- }
-
- return properties;
+ public Map<String, Object> getMap() {
+ return values;
}
/**
- * Instantiate and return a new DynaBean instance, associated
- * with this DynaClass.
- * @return A new {@code DynaBean} instance
+ * Gets the name of this DynaClass (analogous to the
+ * {@code getName()</code> method of <code>java.lang.Class})
+ *
+ * @return the name of the DynaClass
*/
@Override
- public DynaBean newInstance() {
- // Create a new instance of the Map
- Map<String, Object> newMap = null;
- try {
- final
- // The new map is used as properties map
- Map<String, Object> temp = getMap().getClass().newInstance();
- newMap = temp;
- } catch(final Exception ex) {
- newMap = newMap();
- }
+ public String getName() {
+ return this.name;
+ }
- // Crate new LazyDynaMap and initialize properties
- final LazyDynaMap lazyMap = new LazyDynaMap(newMap);
- final DynaProperty[] properties = this.getDynaProperties();
- if (properties != null) {
- for (final DynaProperty property : properties) {
- lazyMap.add(property);
+ /**
+ * <p>Indicate whether a property actually exists.</p>
+ *
+ * <p><strong>N.B.</strong> Using {@code getDynaProperty(name) == null}
+ * doesn't work in this implementation because that method might
+ * return a DynaProperty if it doesn't exist (depending on the
+ * {@code returnNull} indicator).</p>
+ *
+ * @param name Name of the dynamic property
+ * @return {@code true} if the property exists,
+ * otherwise {@code false}
+ * @throws IllegalArgumentException if no property name is specified
+ */
+ @Override
+ protected boolean isDynaProperty(final String name) {
+ if (name == null) {
+ throw new IllegalArgumentException("Property name is missing.");
}
+
+ return values.containsKey(name);
}
- return lazyMap;
- }
/**
* <p>Is this DynaClass currently restricted.</p>
@@ -299,95 +347,45 @@ public class LazyDynaMap extends LazyDynaBean implements MutableDynaClass {
}
/**
- * <p>Set whether this DynaClass is currently restricted.</p>
- * <p>If restricted, no changes to the existing registration of
- * property names, data types, readability, or writeability are allowed.</p>
- *
- * @param restricted The new restricted state
- */
- @Override
- public void setRestricted(final boolean restricted) {
- this.restricted = restricted;
- }
-
- /**
- * Add a new dynamic property with no restrictions on data type,
- * readability, or writeability.
- *
- * @param name Name of the new dynamic property
+ * Should this DynaClass return a {@code null} from
+ * the {@code getDynaProperty(name)} method if the property
+ * doesn't exist.
*
- * @throws IllegalArgumentException if name is null
+ * @return {@code true</code> if a <code>null} {@link DynaProperty}
+ * should be returned if the property doesn't exist, otherwise
+ * {@code false} if a new {@link DynaProperty} should be created.
*/
- @Override
- public void add(final String name) {
- add(name, null);
+ public boolean isReturnNull() {
+ return returnNull;
}
/**
- * Add a new dynamic property with the specified data type, but with
- * no restrictions on readability or writeability.
- *
- * @param name Name of the new dynamic property
- * @param type Data type of the new dynamic property (null for no
- * restrictions)
- *
- * @throws IllegalArgumentException if name is null
- * @throws IllegalStateException if this DynaClass is currently
- * restricted, so no new properties can be added
+ * Instantiate and return a new DynaBean instance, associated
+ * with this DynaClass.
+ * @return A new {@code DynaBean} instance
*/
@Override
- public void add(final String name, final Class<?> type) {
- if (name == null) {
- throw new IllegalArgumentException("Property name is missing.");
- }
-
- if (isRestricted()) {
- throw new IllegalStateException("DynaClass is currently restricted. No new properties can be added.");
+ public DynaBean newInstance() {
+ // Create a new instance of the Map
+ Map<String, Object> newMap = null;
+ try {
+ final
+ // The new map is used as properties map
+ Map<String, Object> temp = getMap().getClass().newInstance();
+ newMap = temp;
+ } catch(final Exception ex) {
+ newMap = newMap();
}
- final Object value = values.get(name);
-
- // Check if the property already exists
- if (value == null) {
- values.put(name, type == null ? null : createProperty(name, type));
+ // Crate new LazyDynaMap and initialize properties
+ final LazyDynaMap lazyMap = new LazyDynaMap(newMap);
+ final DynaProperty[] properties = this.getDynaProperties();
+ if (properties != null) {
+ for (final DynaProperty property : properties) {
+ lazyMap.add(property);
+ }
}
- }
-
- /**
- * <p>Add a new dynamic property with the specified data type, readability,
- * and writeability.</p>
- *
- * <p><strong>N.B.</strong>Support for readable/writable properties has not been implemented
- * and this method always throws a {@code UnsupportedOperationException}.</p>
- *
- * <p>I'm not sure the intention of the original authors for this method, but it seems to
- * me that readable/writable should be attributes of the {@code DynaProperty} class
- * (which they are not) and is the reason this method has not been implemented.</p>
- *
- * @param name Name of the new dynamic property
- * @param type Data type of the new dynamic property (null for no
- * restrictions)
- * @param readable Set to {@code true} if this property value
- * should be readable
- * @param writable Set to {@code true} if this property value
- * should be writable
- *
- * @throws UnsupportedOperationException anytime this method is called
- */
- @Override
- public void add(final String name, final Class<?> type, final boolean readable, final boolean writable) {
- throw new java.lang.UnsupportedOperationException("readable/writable properties not supported");
- }
-
- /**
- * Add a new dynamic property.
- *
- * @param property Property the new dynamic property to add.
- *
- * @throws IllegalArgumentException if name is null
- */
- protected void add(final DynaProperty property) {
- add(property.getName(), property.getType());
+ return lazyMap;
}
/**
@@ -417,51 +415,53 @@ public class LazyDynaMap extends LazyDynaBean implements MutableDynaClass {
}
/**
- * Should this DynaClass return a {@code null} from
- * the {@code getDynaProperty(name)} method if the property
- * doesn't exist.
+ * Sets the value of a simple property with the specified name.
*
- * @return {@code true</code> if a <code>null} {@link DynaProperty}
- * should be returned if the property doesn't exist, otherwise
- * {@code false} if a new {@link DynaProperty} should be created.
+ * @param name Name of the property whose value is to be set
+ * @param value Value to which this property is to be set
*/
- public boolean isReturnNull() {
- return returnNull;
+ @Override
+ public void set(final String name, final Object value) {
+ if (isRestricted() && !values.containsKey(name)) {
+ throw new IllegalArgumentException
+ ("Invalid property name '" + name + "' (DynaClass is restricted)");
+ }
+
+ values.put(name, value);
}
/**
- * Sets whether this DynaClass should return a {@code null} from
- * the {@code getDynaProperty(name)} method if the property
- * doesn't exist.
+ * Sets the Map backing this {@code DynaBean}
*
- * @param returnNull {@code true</code> if a <code>null} {@link DynaProperty}
- * should be returned if the property doesn't exist, otherwise
- * {@code false} if a new {@link DynaProperty} should be created.
+ * @param values The new Map of values
*/
- public void setReturnNull(final boolean returnNull) {
- this.returnNull = returnNull;
+ public void setMap(final Map<String, Object> values) {
+ this.values = values;
}
- /**
- * <p>Indicate whether a property actually exists.</p>
- *
- * <p><strong>N.B.</strong> Using {@code getDynaProperty(name) == null}
- * doesn't work in this implementation because that method might
- * return a DynaProperty if it doesn't exist (depending on the
- * {@code returnNull} indicator).</p>
+ /**
+ * <p>Set whether this DynaClass is currently restricted.</p>
+ * <p>If restricted, no changes to the existing registration of
+ * property names, data types, readability, or writeability are allowed.</p>
*
- * @param name Name of the dynamic property
- * @return {@code true} if the property exists,
- * otherwise {@code false}
- * @throws IllegalArgumentException if no property name is specified
+ * @param restricted The new restricted state
*/
@Override
- protected boolean isDynaProperty(final String name) {
- if (name == null) {
- throw new IllegalArgumentException("Property name is missing.");
- }
-
- return values.containsKey(name);
+ public void setRestricted(final boolean restricted) {
+ this.restricted = restricted;
}
+ /**
+ * Sets whether this DynaClass should return a {@code null} from
+ * the {@code getDynaProperty(name)} method if the property
+ * doesn't exist.
+ *
+ * @param returnNull {@code true</code> if a <code>null} {@link DynaProperty}
+ * should be returned if the property doesn't exist, otherwise
+ * {@code false} if a new {@link DynaProperty} should be created.
+ */
+public void setReturnNull(final boolean returnNull) {
+ this.returnNull = returnNull;
+}
+
}
\ No newline at end of file
diff --git a/src/main/java/org/apache/commons/beanutils2/MappedPropertyDescriptor.java b/src/main/java/org/apache/commons/beanutils2/MappedPropertyDescriptor.java
index 084c679b..8da407ad 100644
--- a/src/main/java/org/apache/commons/beanutils2/MappedPropertyDescriptor.java
+++ b/src/main/java/org/apache/commons/beanutils2/MappedPropertyDescriptor.java
@@ -43,6 +43,221 @@ import java.lang.reflect.Modifier;
*/
public class MappedPropertyDescriptor extends PropertyDescriptor {
+ /**
+ * Holds a {@link Method} in a {@link SoftReference} so that it
+ * it doesn't prevent any ClassLoader being garbage collected, but
+ * tries to re-create the method if the method reference has been
+ * released.
+ *
+ * See https://issues.apache.org/jira/browse/BEANUTILS-291
+ */
+ private static class MappedMethodReference {
+ private String className;
+ private String methodName;
+ private Reference<Method> methodRef;
+ private Reference<Class<?>> classRef;
+ private Reference<Class<?>> writeParamTypeRef0;
+ private Reference<Class<?>> writeParamTypeRef1;
+ private String[] writeParamClassNames;
+ MappedMethodReference(final Method m) {
+ if (m != null) {
+ className = m.getDeclaringClass().getName();
+ methodName = m.getName();
+ methodRef = new SoftReference<>(m);
+ classRef = new WeakReference<>(m.getDeclaringClass());
+ final Class<?>[] types = m.getParameterTypes();
+ if (types.length == 2) {
+ writeParamTypeRef0 = new WeakReference<>(types[0]);
+ writeParamTypeRef1 = new WeakReference<>(types[1]);
+ writeParamClassNames = new String[2];
+ writeParamClassNames[0] = types[0].getName();
+ writeParamClassNames[1] = types[1].getName();
+ }
+ }
+ }
+ private Method get() {
+ if (methodRef == null) {
+ return null;
+ }
+ Method m = methodRef.get();
+ if (m == null) {
+ Class<?> clazz = classRef.get();
+ if (clazz == null) {
+ clazz = reLoadClass();
+ if (clazz != null) {
+ classRef = new WeakReference<>(clazz);
+ }
+ }
+ if (clazz == null) {
+ throw new RuntimeException("Method " + methodName + " for " +
+ className + " could not be reconstructed - class reference has gone");
+ }
+ Class<?>[] paramTypes = null;
+ if (writeParamClassNames != null) {
+ paramTypes = new Class[2];
+ paramTypes[0] = writeParamTypeRef0.get();
+ if (paramTypes[0] == null) {
+ paramTypes[0] = reLoadClass(writeParamClassNames[0]);
+ if (paramTypes[0] != null) {
+ writeParamTypeRef0 = new WeakReference<>(paramTypes[0]);
+ }
+ }
+ paramTypes[1] = writeParamTypeRef1.get();
+ if (paramTypes[1] == null) {
+ paramTypes[1] = reLoadClass(writeParamClassNames[1]);
+ if (paramTypes[1] != null) {
+ writeParamTypeRef1 = new WeakReference<>(paramTypes[1]);
+ }
+ }
+ } else {
+ paramTypes = STRING_CLASS_PARAMETER;
+ }
+ try {
+ m = clazz.getMethod(methodName, paramTypes);
+ // Un-comment following line for testing
+ // System.out.println("Recreated Method " + methodName + " for " + className);
+ } catch (final NoSuchMethodException e) {
+ throw new RuntimeException("Method " + methodName + " for " +
+ className + " could not be reconstructed - method not found");
+ }
+ methodRef = new SoftReference<>(m);
+ }
+ return m;
+ }
+
+ /**
+ * Try to re-load the class
+ */
+ private Class<?> reLoadClass() {
+ return reLoadClass(className);
+ }
+
+ /**
+ * Try to re-load the class
+ */
+ private Class<?> reLoadClass(final String name) {
+
+ ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
+
+ // Try the context class loader
+ if (classLoader != null) {
+ try {
+ return classLoader.loadClass(name);
+ } catch (final ClassNotFoundException e) {
+ // ignore
+ }
+ }
+
+ // Try this class's class loader
+ classLoader = MappedPropertyDescriptor.class.getClassLoader();
+ try {
+ return classLoader.loadClass(name);
+ } catch (final ClassNotFoundException e) {
+ return null;
+ }
+ }
+ }
+
+ /**
+ * The parameter types array for the reader method signature.
+ */
+ private static final Class<?>[] STRING_CLASS_PARAMETER = new Class[]{String.class};
+
+ /**
+ * Gets a capitalized version of the specified property name.
+ *
+ * @param s The property name
+ */
+ private static String capitalizePropertyName(final String s) {
+ if (s.isEmpty()) {
+ return s;
+ }
+
+ final char[] chars = s.toCharArray();
+ chars[0] = Character.toUpperCase(chars[0]);
+ return new String(chars);
+ }
+
+ /**
+ * Find a method on a class with a specified parameter list.
+ */
+ private static Method getMethod(final Class<?> clazz, final String methodName, final Class<?>[] parameterTypes)
+ throws IntrospectionException {
+ if (methodName == null) {
+ return null;
+ }
+
+ final Method method = MethodUtils.getMatchingAccessibleMethod(clazz, methodName, parameterTypes);
+ if (method != null) {
+ return method;
+ }
+
+ final int parameterCount = parameterTypes == null ? 0 : parameterTypes.length;
+
+ // No Method found
+ throw new IntrospectionException("No method \"" + methodName +
+ "\" with " + parameterCount + " parameter(s) of matching types.");
+ }
+
+ /**
+ * Find a method on a class with a specified number of parameters.
+ */
+ private static Method getMethod(final Class<?> clazz, final String methodName, final int parameterCount)
+ throws IntrospectionException {
+ if (methodName == null) {
+ return null;
+ }
+
+ final Method method = internalGetMethod(clazz, methodName, parameterCount);
+ if (method != null) {
+ return method;
+ }
+
+ // No Method found
+ throw new IntrospectionException("No method \"" + methodName +
+ "\" with " + parameterCount + " parameter(s)");
+ }
+
+ /**
+ * Find a method on a class with a specified number of parameters.
+ */
+ private static Method internalGetMethod(final Class<?> initial, final String methodName,
+ final int parameterCount) {
+ // For overridden methods we need to find the most derived version.
+ // So we start with the given class and walk up the superclass chain.
+ for (Class<?> clazz = initial; clazz != null; clazz = clazz.getSuperclass()) {
+ final Method[] methods = clazz.getDeclaredMethods();
+ for (final Method method : methods) {
+ if (method == null) {
+ continue;
+ }
+ // skip static methods.
+ final int mods = method.getModifiers();
+ if (!Modifier.isPublic(mods) ||
+ Modifier.isStatic(mods)) {
+ continue;
+ }
+ if (method.getName().equals(methodName) &&
+ method.getParameterTypes().length == parameterCount) {
+ return method;
+ }
+ }
+ }
+
+ // Now check any inherited interfaces. This is necessary both when
+ // the argument class is itself an interface, and when the argument
+ // class is an abstract class.
+ final Class<?>[] interfaces = initial.getInterfaces();
+ for (final Class<?> interface1 : interfaces) {
+ final Method method = internalGetMethod(interface1, methodName, parameterCount);
+ if (method != null) {
+ return method;
+ }
+ }
+
+ return null;
+ }
+
/**
* The underlying data type of the property we are describing.
*/
@@ -58,11 +273,6 @@ public class MappedPropertyDescriptor extends PropertyDescriptor {
*/
private MappedMethodReference mappedWriteMethodRef;
- /**
- * The parameter types array for the reader method signature.
- */
- private static final Class<?>[] STRING_CLASS_PARAMETER = new Class[]{String.class};
-
/**
* Constructs a MappedPropertyDescriptor for a property that follows
* the standard Java convention by having getFoo and setFoo
@@ -204,66 +414,6 @@ public class MappedPropertyDescriptor extends PropertyDescriptor {
findMappedPropertyType();
}
- /**
- * Gets the Class object for the property values.
- *
- * @return The Java type info for the property values. Note that
- * the "Class" object may describe a built-in Java type such as "int".
- * The result may be "null" if this is a mapped property that
- * does not support non-keyed access.
- * <p>
- * This is the type that will be returned by the mappedReadMethod.
- */
- public Class<?> getMappedPropertyType() {
- return mappedPropertyTypeRef.get();
- }
-
- /**
- * Gets the method that should be used to read one of the property value.
- *
- * @return The method that should be used to read the property value.
- * May return null if the property can't be read.
- */
- public Method getMappedReadMethod() {
- return mappedReadMethodRef.get();
- }
-
- /**
- * Sets the method that should be used to read one of the property value.
- *
- * @param mappedGetter The mapped getter method.
- * @throws IntrospectionException If an error occurs finding the
- * mapped property
- */
- public void setMappedReadMethod(final Method mappedGetter)
- throws IntrospectionException {
- mappedReadMethodRef = new MappedMethodReference(mappedGetter);
- findMappedPropertyType();
- }
-
- /**
- * Gets the method that should be used to write one of the property value.
- *
- * @return The method that should be used to write one of the property value.
- * May return null if the property can't be written.
- */
- public Method getMappedWriteMethod() {
- return mappedWriteMethodRef.get();
- }
-
- /**
- * Sets the method that should be used to write the property value.
- *
- * @param mappedSetter The mapped setter method.
- * @throws IntrospectionException If an error occurs finding the
- * mapped property
- */
- public void setMappedWriteMethod(final Method mappedSetter)
- throws IntrospectionException {
- mappedWriteMethodRef = new MappedMethodReference(mappedSetter);
- findMappedPropertyType();
- }
-
/**
* Introspect our bean class to identify the corresponding getter
* and setter methods.
@@ -302,212 +452,62 @@ public class MappedPropertyDescriptor extends PropertyDescriptor {
}
/**
- * Gets a capitalized version of the specified property name.
+ * Gets the Class object for the property values.
*
- * @param s The property name
+ * @return The Java type info for the property values. Note that
+ * the "Class" object may describe a built-in Java type such as "int".
+ * The result may be "null" if this is a mapped property that
+ * does not support non-keyed access.
+ * <p>
+ * This is the type that will be returned by the mappedReadMethod.
*/
- private static String capitalizePropertyName(final String s) {
- if (s.isEmpty()) {
- return s;
- }
-
- final char[] chars = s.toCharArray();
- chars[0] = Character.toUpperCase(chars[0]);
- return new String(chars);
+ public Class<?> getMappedPropertyType() {
+ return mappedPropertyTypeRef.get();
}
/**
- * Find a method on a class with a specified number of parameters.
+ * Gets the method that should be used to read one of the property value.
+ *
+ * @return The method that should be used to read the property value.
+ * May return null if the property can't be read.
*/
- private static Method internalGetMethod(final Class<?> initial, final String methodName,
- final int parameterCount) {
- // For overridden methods we need to find the most derived version.
- // So we start with the given class and walk up the superclass chain.
- for (Class<?> clazz = initial; clazz != null; clazz = clazz.getSuperclass()) {
- final Method[] methods = clazz.getDeclaredMethods();
- for (final Method method : methods) {
- if (method == null) {
- continue;
- }
- // skip static methods.
- final int mods = method.getModifiers();
- if (!Modifier.isPublic(mods) ||
- Modifier.isStatic(mods)) {
- continue;
- }
- if (method.getName().equals(methodName) &&
- method.getParameterTypes().length == parameterCount) {
- return method;
- }
- }
- }
-
- // Now check any inherited interfaces. This is necessary both when
- // the argument class is itself an interface, and when the argument
- // class is an abstract class.
- final Class<?>[] interfaces = initial.getInterfaces();
- for (final Class<?> interface1 : interfaces) {
- final Method method = internalGetMethod(interface1, methodName, parameterCount);
- if (method != null) {
- return method;
- }
- }
-
- return null;
+ public Method getMappedReadMethod() {
+ return mappedReadMethodRef.get();
}
/**
- * Find a method on a class with a specified number of parameters.
+ * Gets the method that should be used to write one of the property value.
+ *
+ * @return The method that should be used to write one of the property value.
+ * May return null if the property can't be written.
*/
- private static Method getMethod(final Class<?> clazz, final String methodName, final int parameterCount)
- throws IntrospectionException {
- if (methodName == null) {
- return null;
- }
-
- final Method method = internalGetMethod(clazz, methodName, parameterCount);
- if (method != null) {
- return method;
- }
-
- // No Method found
- throw new IntrospectionException("No method \"" + methodName +
- "\" with " + parameterCount + " parameter(s)");
+ public Method getMappedWriteMethod() {
+ return mappedWriteMethodRef.get();
}
/**
- * Find a method on a class with a specified parameter list.
+ * Sets the method that should be used to read one of the property value.
+ *
+ * @param mappedGetter The mapped getter method.
+ * @throws IntrospectionException If an error occurs finding the
+ * mapped property
*/
- private static Method getMethod(final Class<?> clazz, final String methodName, final Class<?>[] parameterTypes)
- throws IntrospectionException {
- if (methodName == null) {
- return null;
- }
-
- final Method method = MethodUtils.getMatchingAccessibleMethod(clazz, methodName, parameterTypes);
- if (method != null) {
- return method;
- }
-
- final int parameterCount = parameterTypes == null ? 0 : parameterTypes.length;
-
- // No Method found
- throw new IntrospectionException("No method \"" + methodName +
- "\" with " + parameterCount + " parameter(s) of matching types.");
+ public void setMappedReadMethod(final Method mappedGetter)
+ throws IntrospectionException {
+ mappedReadMethodRef = new MappedMethodReference(mappedGetter);
+ findMappedPropertyType();
}
/**
- * Holds a {@link Method} in a {@link SoftReference} so that it
- * it doesn't prevent any ClassLoader being garbage collected, but
- * tries to re-create the method if the method reference has been
- * released.
+ * Sets the method that should be used to write the property value.
*
- * See https://issues.apache.org/jira/browse/BEANUTILS-291
+ * @param mappedSetter The mapped setter method.
+ * @throws IntrospectionException If an error occurs finding the
+ * mapped property
*/
- private static class MappedMethodReference {
- private String className;
- private String methodName;
- private Reference<Method> methodRef;
- private Reference<Class<?>> classRef;
- private Reference<Class<?>> writeParamTypeRef0;
- private Reference<Class<?>> writeParamTypeRef1;
- private String[] writeParamClassNames;
- MappedMethodReference(final Method m) {
- if (m != null) {
- className = m.getDeclaringClass().getName();
- methodName = m.getName();
- methodRef = new SoftReference<>(m);
- classRef = new WeakReference<>(m.getDeclaringClass());
- final Class<?>[] types = m.getParameterTypes();
- if (types.length == 2) {
- writeParamTypeRef0 = new WeakReference<>(types[0]);
- writeParamTypeRef1 = new WeakReference<>(types[1]);
- writeParamClassNames = new String[2];
- writeParamClassNames[0] = types[0].getName();
- writeParamClassNames[1] = types[1].getName();
- }
- }
- }
- private Method get() {
- if (methodRef == null) {
- return null;
- }
- Method m = methodRef.get();
- if (m == null) {
- Class<?> clazz = classRef.get();
- if (clazz == null) {
- clazz = reLoadClass();
- if (clazz != null) {
- classRef = new WeakReference<>(clazz);
- }
- }
- if (clazz == null) {
- throw new RuntimeException("Method " + methodName + " for " +
- className + " could not be reconstructed - class reference has gone");
- }
- Class<?>[] paramTypes = null;
- if (writeParamClassNames != null) {
- paramTypes = new Class[2];
- paramTypes[0] = writeParamTypeRef0.get();
- if (paramTypes[0] == null) {
- paramTypes[0] = reLoadClass(writeParamClassNames[0]);
- if (paramTypes[0] != null) {
- writeParamTypeRef0 = new WeakReference<>(paramTypes[0]);
- }
- }
- paramTypes[1] = writeParamTypeRef1.get();
- if (paramTypes[1] == null) {
- paramTypes[1] = reLoadClass(writeParamClassNames[1]);
- if (paramTypes[1] != null) {
- writeParamTypeRef1 = new WeakReference<>(paramTypes[1]);
- }
- }
- } else {
- paramTypes = STRING_CLASS_PARAMETER;
- }
- try {
- m = clazz.getMethod(methodName, paramTypes);
- // Un-comment following line for testing
- // System.out.println("Recreated Method " + methodName + " for " + className);
- } catch (final NoSuchMethodException e) {
- throw new RuntimeException("Method " + methodName + " for " +
- className + " could not be reconstructed - method not found");
- }
- methodRef = new SoftReference<>(m);
- }
- return m;
- }
-
- /**
- * Try to re-load the class
- */
- private Class<?> reLoadClass() {
- return reLoadClass(className);
- }
-
- /**
- * Try to re-load the class
- */
- private Class<?> reLoadClass(final String name) {
-
- ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
-
- // Try the context class loader
- if (classLoader != null) {
- try {
- return classLoader.loadClass(name);
- } catch (final ClassNotFoundException e) {
- // ignore
- }
- }
-
- // Try this class's class loader
- classLoader = MappedPropertyDescriptor.class.getClassLoader();
- try {
- return classLoader.loadClass(name);
- } catch (final ClassNotFoundException e) {
- return null;
- }
- }
+ public void setMappedWriteMethod(final Method mappedSetter)
+ throws IntrospectionException {
+ mappedWriteMethodRef = new MappedMethodReference(mappedSetter);
+ findMappedPropertyType();
}
}
diff --git a/src/main/java/org/apache/commons/beanutils2/MethodUtils.java b/src/main/java/org/apache/commons/beanutils2/MethodUtils.java
index 5f0553e2..406b6745 100644
--- a/src/main/java/org/apache/commons/beanutils2/MethodUtils.java
+++ b/src/main/java/org/apache/commons/beanutils2/MethodUtils.java
@@ -46,6 +46,73 @@ import org.apache.commons.logging.LogFactory;
*/
public class MethodUtils {
+ /**
+ * Represents the key to looking up a Method by reflection.
+ */
+ private static class MethodDescriptor {
+ private final Class<?> cls;
+ private final String methodName;
+ private final Class<?>[] paramTypes;
+ private final boolean exact;
+ private final int hashCode;
+
+ /**
+ * The sole constructor.
+ *
+ * @param cls the class to reflect, must not be null
+ * @param methodName the method name to obtain
+ * @param paramTypes the array of classes representing the parameter types
+ * @param exact whether the match has to be exact.
+ */
+ public MethodDescriptor(final Class<?> cls, final String methodName, Class<?>[] paramTypes,
+ final boolean exact) {
+ if (cls == null) {
+ throw new IllegalArgumentException("Class cannot be null");
+ }
+ if (methodName == null) {
+ throw new IllegalArgumentException("Method Name cannot be null");
+ }
+ if (paramTypes == null) {
+ paramTypes = BeanUtils.EMPTY_CLASS_ARRAY;
+ }
+
+ this.cls = cls;
+ this.methodName = methodName;
+ this.paramTypes = paramTypes;
+ this.exact= exact;
+
+ this.hashCode = methodName.length();
+ }
+ /**
+ * Checks for equality.
+ * @param obj object to be tested for equality
+ * @return true, if the object describes the same Method.
+ */
+ @Override
+ public boolean equals(final Object obj) {
+ if (!(obj instanceof MethodDescriptor)) {
+ return false;
+ }
+ final MethodDescriptor md = (MethodDescriptor)obj;
+
+ return exact == md.exact &&
+ methodName.equals(md.methodName) &&
+ cls.equals(md.cls) &&
+ java.util.Arrays.equals(paramTypes, md.paramTypes);
+ }
+ /**
+ * Returns the string length of method name. I.e. if the
+ * hashcodes are different, the objects are different. If the
+ * hashcodes are the same, need to use the equals method to
+ * determine equality.
+ * @return the string length of method name.
+ */
+ @Override
+ public int hashCode() {
+ return hashCode;
+ }
+ }
+
private static final Log LOG = LogFactory.getLog(MethodUtils.class);
/**
@@ -95,17 +162,14 @@ public class MethodUtils {
.synchronizedMap(new WeakHashMap<MethodDescriptor, Reference<Method>>());
/**
- * Sets whether methods should be cached for greater performance or not,
- * default is {@code true}.
+ * Add a method to the cache.
*
- * @param cacheMethods {@code true} if methods should be
- * cached for greater performance, otherwise {@code false}
- * @since 1.8.0
+ * @param md The method descriptor
+ * @param method The method to cache
*/
- public static synchronized void setCacheMethods(final boolean cacheMethods) {
- CACHE_METHODS = cacheMethods;
- if (!CACHE_METHODS) {
- clearCache();
+ private static void cacheMethod(final MethodDescriptor md, final Method method) {
+ if (CACHE_METHODS && method != null) {
+ cache.put(md, new WeakReference<>(method));
}
}
@@ -121,330 +185,499 @@ public class MethodUtils {
}
/**
- * <p>Invoke a named method whose parameter type matches the object type.</p>
- *
- * <p>The behavior of this method is less deterministic
- * than {@code invokeExactMethod()}.
- * It loops through all methods with names that match
- * and then executes the first it finds with compatible parameters.</p>
- *
- * <p>This method supports calls to methods taking primitive parameters
- * via passing in wrapping classes. So, for example, a {@code Boolean} class
- * would match a {@code boolean} primitive.</p>
+ * <p>Return an accessible method (that is, one that can be invoked via
+ * reflection) that implements the specified Method. If no such method
+ * can be found, return {@code null}.</p>
*
- * <p> This is a convenient wrapper for
- * {@link #invokeMethod(Object object,String methodName,Object [] args)}.
- * </p>
+ * @param clazz The class of the object
+ * @param method The method that we wish to call
+ * @return The accessible method
+ * @since 1.8.0
+ */
+ public static Method getAccessibleMethod(Class<?> clazz, Method method) {
+ // Make sure we have a method to check
+ if (method == null) {
+ return null;
+ }
+
+ // If the requested method is not public we cannot call it
+ if (!Modifier.isPublic(method.getModifiers())) {
+ return null;
+ }
+
+ boolean sameClass = true;
+ if (clazz == null) {
+ clazz = method.getDeclaringClass();
+ } else {
+ if (!method.getDeclaringClass().isAssignableFrom(clazz)) {
+ throw new IllegalArgumentException(clazz.getName() +
+ " is not assignable from " + method.getDeclaringClass().getName());
+ }
+ sameClass = clazz.equals(method.getDeclaringClass());
+ }
+
+ // If the class is public, we are done
+ if (Modifier.isPublic(clazz.getModifiers())) {
+ if (!sameClass && !Modifier.isPublic(method.getDeclaringClass().getModifiers())) {
+ setMethodAccessible(method); // Default access superclass workaround
+ }
+ return method;
+ }
+
+ final String methodName = method.getName();
+ final Class<?>[] parameterTypes = method.getParameterTypes();
+
+ // Check the implemented interfaces and subinterfaces
+ method =
+ getAccessibleMethodFromInterfaceNest(clazz,
+ methodName,
+ parameterTypes);
+
+ // Check the superclass chain
+ if (method == null) {
+ method = getAccessibleMethodFromSuperclass(clazz,
+ methodName,
+ parameterTypes);
+ }
+
+ return method;
+ }
+
+ /**
+ * <p>Return an accessible method (that is, one that can be invoked via
+ * reflection) with given name and a single parameter. If no such method
+ * can be found, return {@code null}.
+ * Basically, a convenience wrapper that constructs a {@code Class}
+ * array for you.</p>
*
- * @param object invoke method on this object
+ * @param clazz get method from this class
* @param methodName get method with this name
- * @param arg use this argument. May be null (this will result in calling the
- * parameterless method with name {@code methodName}).
- * @return The value returned by the invoked method
- *
- * @throws NoSuchMethodException if there is no such accessible method
- * @throws InvocationTargetException wraps an exception thrown by the
- * method invoked
- * @throws IllegalAccessException if the requested method is not accessible
- * via reflection
+ * @param parameterType taking this type of parameter
+ * @return The accessible method
*/
- public static Object invokeMethod(
- final Object object,
+ public static Method getAccessibleMethod(
+ final Class<?> clazz,
final String methodName,
- final Object arg)
- throws
- NoSuchMethodException,
- IllegalAccessException,
- InvocationTargetException {
- final Object[] args = toArray(arg);
- return invokeMethod(object, methodName, args);
+ final Class<?> parameterType) {
+ final Class<?>[] parameterTypes = {parameterType};
+ return getAccessibleMethod(clazz, methodName, parameterTypes);
}
/**
- * <p>Invoke a named method whose parameter type matches the object type.</p>
- *
- * <p>The behavior of this method is less deterministic
- * than {@link #invokeExactMethod(Object object,String methodName,Object [] args)}.
- * It loops through all methods with names that match
- * and then executes the first it finds with compatible parameters.</p>
- *
- * <p>This method supports calls to methods taking primitive parameters
- * via passing in wrapping classes. So, for example, a {@code Boolean} class
- * would match a {@code boolean} primitive.</p>
- *
- * <p> This is a convenient wrapper for
- * {@link #invokeMethod(Object object, String methodName, Object[] args, Class[] parameterTypes)}.
- * </p>
+ * <p>Return an accessible method (that is, one that can be invoked via
+ * reflection) with given name and parameters. If no such method
+ * can be found, return {@code null}.
+ * This is just a convenient wrapper for
+ * {@link #getAccessibleMethod(Method method)}.</p>
*
- * @param object invoke method on this object
+ * @param clazz get method from this class
* @param methodName get method with this name
- * @param args use these arguments - treat null as empty array (passing null will
- * result in calling the parameterless method with name {@code methodName}).
- * @return The value returned by the invoked method
- *
- * @throws NoSuchMethodException if there is no such accessible method
- * @throws InvocationTargetException wraps an exception thrown by the
- * method invoked
- * @throws IllegalAccessException if the requested method is not accessible
- * via reflection
+ * @param parameterTypes with these parameters types
+ * @return The accessible method
*/
- public static Object invokeMethod(
- final Object object,
+ public static Method getAccessibleMethod(
+ final Class<?> clazz,
final String methodName,
- Object[] args)
- throws
- NoSuchMethodException,
- IllegalAccessException,
- InvocationTargetException {
- if (args == null) {
- args = BeanUtils.EMPTY_OBJECT_ARRAY;
- }
- final int arguments = args.length;
- final Class<?>[] parameterTypes = new Class[arguments];
- for (int i = 0; i < arguments; i++) {
- parameterTypes[i] = args[i].getClass();
+ final Class<?>[] parameterTypes) {
+ try {
+ final MethodDescriptor md = new MethodDescriptor(clazz, methodName, parameterTypes, true);
+ // Check the cache first
+ Method method = getCachedMethod(md);
+ if (method != null) {
+ return method;
+ }
+
+ method = getAccessibleMethod
+ (clazz, clazz.getMethod(methodName, parameterTypes));
+ cacheMethod(md, method);
+ return method;
+ } catch (final NoSuchMethodException e) {
+ return null;
}
- return invokeMethod(object, methodName, args, parameterTypes);
}
/**
- * <p>Invoke a named method whose parameter type matches the object type.</p>
+ * <p>Return an accessible method (that is, one that can be invoked via
+ * reflection) that implements the specified Method. If no such method
+ * can be found, return {@code null}.</p>
*
- * <p>The behavior of this method is less deterministic
- * than {@link
- * #invokeExactMethod(Object object, String methodName, Object[] args, Class[] parameterTypes)}.
- * It loops through all methods with names that match
- * and then executes the first it finds with compatible parameters.</p>
- *
- * <p>This method supports calls to methods taking primitive parameters
- * via passing in wrapping classes. So, for example, a {@code Boolean} class
- * would match a {@code boolean} primitive.</p>
- *
- *
- * @param object invoke method on this object
- * @param methodName get method with this name
- * @param args use these arguments - treat null as empty array (passing null will
- * result in calling the parameterless method with name {@code methodName}).
- * @param parameterTypes match these parameters - treat null as empty array
- * @return The value returned by the invoked method
- *
- * @throws NoSuchMethodException if there is no such accessible method
- * @throws InvocationTargetException wraps an exception thrown by the
- * method invoked
- * @throws IllegalAccessException if the requested method is not accessible
- * via reflection
+ * @param method The method that we wish to call
+ * @return The accessible method
*/
- public static Object invokeMethod(
- final Object object,
- final String methodName,
- Object[] args,
- Class<?>[] parameterTypes)
- throws
- NoSuchMethodException,
- IllegalAccessException,
- InvocationTargetException {
- if (parameterTypes == null) {
- parameterTypes = BeanUtils.EMPTY_CLASS_ARRAY;
- }
- if (args == null) {
- args = BeanUtils.EMPTY_OBJECT_ARRAY;
- }
-
- final Method method = getMatchingAccessibleMethod(
- object.getClass(),
- methodName,
- parameterTypes);
+ public static Method getAccessibleMethod(final Method method) {
+ // Make sure we have a method to check
if (method == null) {
- throw new NoSuchMethodException("No such accessible method: " +
- methodName + "() on object: " + object.getClass().getName());
+ return null;
}
- return method.invoke(object, args);
+
+ return getAccessibleMethod(method.getDeclaringClass(), method);
}
/**
- * <p>Invoke a method whose parameter type matches exactly the object
- * type.</p>
- *
- * <p> This is a convenient wrapper for
- * {@link #invokeExactMethod(Object object,String methodName,Object [] args)}.
- * </p>
+ * <p>Return an accessible method (that is, one that can be invoked via
+ * reflection) that implements the specified method, by scanning through
+ * all implemented interfaces and subinterfaces. If no such method
+ * can be found, return {@code null}.</p>
*
- * @param object invoke method on this object
- * @param methodName get method with this name
- * @param arg use this argument. May be null (this will result in calling the
- * parameterless method with name {@code methodName}).
- * @return The value returned by the invoked method
+ * <p> There isn't any good reason why this method must be private.
+ * It is because there doesn't seem any reason why other classes should
+ * call this rather than the higher level methods.</p>
*
- * @throws NoSuchMethodException if there is no such accessible method
- * @throws InvocationTargetException wraps an exception thrown by the
- * method invoked
- * @throws IllegalAccessException if the requested method is not accessible
- * via reflection
+ * @param clazz Parent class for the interfaces to be checked
+ * @param methodName Method name of the method we wish to call
+ * @param parameterTypes The parameter type signatures
*/
- public static Object invokeExactMethod(
- final Object object,
- final String methodName,
- final Object arg)
- throws
- NoSuchMethodException,
- IllegalAccessException,
- InvocationTargetException {
- final Object[] args = toArray(arg);
- return invokeExactMethod(object, methodName, args);
+ private static Method getAccessibleMethodFromInterfaceNest
+ (Class<?> clazz, final String methodName, final Class<?>[] parameterTypes) {
+ Method method = null;
+
+ // Search up the superclass chain
+ for (; clazz != null; clazz = clazz.getSuperclass()) {
+
+ // Check the implemented interfaces of the parent class
+ final Class<?>[] interfaces = clazz.getInterfaces();
+ for (final Class<?> anInterface : interfaces) {
+
+ // Is this interface public?
+ if (!Modifier.isPublic(anInterface.getModifiers())) {
+ continue;
+ }
+
+ // Does the method exist on this interface?
+ try {
+ method = anInterface.getDeclaredMethod(methodName,
+ parameterTypes);
+ } catch (final NoSuchMethodException e) {
+ /* Swallow, if no method is found after the loop then this
+ * method returns null.
+ */
+ }
+ if (method != null) {
+ return method;
+ }
+
+ // Recursively check our parent interfaces
+ method =
+ getAccessibleMethodFromInterfaceNest(anInterface,
+ methodName,
+ parameterTypes);
+ if (method != null) {
+ return method;
+ }
+
+ }
+
+ }
+
+ // We did not find anything
+ return null;
}
/**
- * <p>Invoke a method whose parameter types match exactly the object
- * types.</p>
- *
- * <p> This uses reflection to invoke the method obtained from a call to
- * {@code getAccessibleMethod()}.</p>
- *
- * @param object invoke method on this object
- * @param methodName get method with this name
- * @param args use these arguments - treat null as empty array (passing null will
- * result in calling the parameterless method with name {@code methodName}).
- * @return The value returned by the invoked method
+ * <p>Return an accessible method (that is, one that can be invoked via
+ * reflection) by scanning through the superclasses. If no such method
+ * can be found, return {@code null}.</p>
*
- * @throws NoSuchMethodException if there is no such accessible method
- * @throws InvocationTargetException wraps an exception thrown by the
- * method invoked
- * @throws IllegalAccessException if the requested method is not accessible
- * via reflection
+ * @param clazz Class to be checked
+ * @param methodName Method name of the method we wish to call
+ * @param parameterTypes The parameter type signatures
*/
- public static Object invokeExactMethod(
- final Object object,
- final String methodName,
- Object[] args)
- throws
- NoSuchMethodException,
- IllegalAccessException,
- InvocationTargetException {
- if (args == null) {
- args = BeanUtils.EMPTY_OBJECT_ARRAY;
- }
- final int arguments = args.length;
- final Class<?>[] parameterTypes = new Class[arguments];
- for (int i = 0; i < arguments; i++) {
- parameterTypes[i] = args[i].getClass();
+ private static Method getAccessibleMethodFromSuperclass
+ (final Class<?> clazz, final String methodName, final Class<?>[] parameterTypes) {
+ Class<?> parentClazz = clazz.getSuperclass();
+ while (parentClazz != null) {
+ if (Modifier.isPublic(parentClazz.getModifiers())) {
+ try {
+ return parentClazz.getMethod(methodName, parameterTypes);
+ } catch (final NoSuchMethodException e) {
+ return null;
+ }
+ }
+ parentClazz = parentClazz.getSuperclass();
}
- return invokeExactMethod(object, methodName, args, parameterTypes);
+ return null;
}
/**
- * <p>Invoke a method whose parameter types match exactly the parameter
- * types given.</p>
- *
- * <p>This uses reflection to invoke the method obtained from a call to
- * {@code getAccessibleMethod()}.</p>
- *
- * @param object invoke method on this object
- * @param methodName get method with this name
- * @param args use these arguments - treat null as empty array (passing null will
- * result in calling the parameterless method with name {@code methodName}).
- * @param parameterTypes match these parameters - treat null as empty array
- * @return The value returned by the invoked method
+ * Gets the method from the cache, if present.
*
- * @throws NoSuchMethodException if there is no such accessible method
- * @throws InvocationTargetException wraps an exception thrown by the
- * method invoked
- * @throws IllegalAccessException if the requested method is not accessible
- * via reflection
+ * @param md The method descriptor
+ * @return The cached method
*/
- public static Object invokeExactMethod(
- final Object object,
- final String methodName,
- Object[] args,
- Class<?>[] parameterTypes)
- throws
- NoSuchMethodException,
- IllegalAccessException,
- InvocationTargetException {
- if (args == null) {
- args = BeanUtils.EMPTY_OBJECT_ARRAY;
- }
-
- if (parameterTypes == null) {
- parameterTypes = BeanUtils.EMPTY_CLASS_ARRAY;
- }
-
- final Method method = getAccessibleMethod(
- object.getClass(),
- methodName,
- parameterTypes);
- if (method == null) {
- throw new NoSuchMethodException("No such accessible method: " +
- methodName + "() on object: " + object.getClass().getName());
+ private static Method getCachedMethod(final MethodDescriptor md) {
+ if (CACHE_METHODS) {
+ final Reference<Method> methodRef = cache.get(md);
+ if (methodRef != null) {
+ return methodRef.get();
+ }
}
- return method.invoke(object, args);
+ return null;
}
/**
- * <p>Invoke a static method whose parameter types match exactly the parameter
- * types given.</p>
+ * <p>Find an accessible method that matches the given name and has compatible parameters.
+ * Compatible parameters mean that every method parameter is assignable from
+ * the given parameters.
+ * In other words, it finds a method with the given name
+ * that will take the parameters given.</p>
*
- * <p>This uses reflection to invoke the method obtained from a call to
- * {@link #getAccessibleMethod(Class, String, Class[])}.</p>
+ * <p>This method is slightly indeterministic since it loops
+ * through methods names and return the first matching method.</p>
*
- * @param objectClass invoke static method on this class
- * @param methodName get method with this name
- * @param args use these arguments - treat null as empty array (passing null will
- * result in calling the parameterless method with name {@code methodName}).
- * @param parameterTypes match these parameters - treat null as empty array
- * @return The value returned by the invoked method
+ * <p>This method is used by
+ * {@link
+ * #invokeMethod(Object object, String methodName, Object[] args, Class[] parameterTypes)}.
*
- * @throws NoSuchMethodException if there is no such accessible method
- * @throws InvocationTargetException wraps an exception thrown by the
- * method invoked
- * @throws IllegalAccessException if the requested method is not accessible
- * via reflection
- * @since 1.8.0
+ * <p>This method can match primitive parameter by passing in wrapper classes.
+ * For example, a {@code Boolean</code> will match a primitive <code>boolean}
+ * parameter.
+ *
+ * @param clazz find method in this class
+ * @param methodName find method with this name
+ * @param parameterTypes find method with compatible parameters
+ * @return The accessible method
*/
- public static Object invokeExactStaticMethod(
- final Class<?> objectClass,
- final String methodName,
- Object[] args,
- Class<?>[] parameterTypes)
- throws
- NoSuchMethodException,
- IllegalAccessException,
- InvocationTargetException {
- if (args == null) {
- args = BeanUtils.EMPTY_OBJECT_ARRAY;
+ public static Method getMatchingAccessibleMethod(
+ final Class<?> clazz,
+ final String methodName,
+ final Class<?>[] parameterTypes) {
+ // trace logging
+ if (LOG.isTraceEnabled()) {
+ LOG.trace("Matching name=" + methodName + " on " + clazz);
}
+ final MethodDescriptor md = new MethodDescriptor(clazz, methodName, parameterTypes, false);
- if (parameterTypes == null) {
- parameterTypes = BeanUtils.EMPTY_CLASS_ARRAY;
+ // see if we can find the method directly
+ // most of the time this works and it's much faster
+ try {
+ // Check the cache first
+ Method method = getCachedMethod(md);
+ if (method != null) {
+ return method;
+ }
+
+ method = clazz.getMethod(methodName, parameterTypes);
+ if (LOG.isTraceEnabled()) {
+ LOG.trace("Found straight match: " + method);
+ LOG.trace("isPublic:" + Modifier.isPublic(method.getModifiers()));
+ }
+
+ setMethodAccessible(method); // Default access superclass workaround
+
+ cacheMethod(md, method);
+ return method;
+
+ } catch (final NoSuchMethodException e) { /* SWALLOW */ }
+
+ // search through all methods
+ final int paramSize = parameterTypes.length;
+ Method bestMatch = null;
+ final Method[] methods = clazz.getMethods();
+ float bestMatchCost = Float.MAX_VALUE;
+ float myCost = Float.MAX_VALUE;
+ for (final Method method2 : methods) {
+ if (method2.getName().equals(methodName)) {
+ // log some trace information
+ if (LOG.isTraceEnabled()) {
+ LOG.trace("Found matching name:");
+ LOG.trace(method2);
+ }
+
+ // compare parameters
+ final Class<?>[] methodsParams = method2.getParameterTypes();
+ final int methodParamSize = methodsParams.length;
+ if (methodParamSize == paramSize) {
+ boolean match = true;
+ for (int n = 0 ; n < methodParamSize; n++) {
+ if (LOG.isTraceEnabled()) {
+ LOG.trace("Param=" + parameterTypes[n].getName());
+ LOG.trace("Method=" + methodsParams[n].getName());
+ }
+ if (!isAssignmentCompatible(methodsParams[n], parameterTypes[n])) {
+ if (LOG.isTraceEnabled()) {
+ LOG.trace(methodsParams[n] + " is not assignable from "
+ + parameterTypes[n]);
+ }
+ match = false;
+ break;
+ }
+ }
+
+ if (match) {
+ // get accessible version of method
+ final Method method = getAccessibleMethod(clazz, method2);
+ if (method != null) {
+ if (LOG.isTraceEnabled()) {
+ LOG.trace(method + " accessible version of "
+ + method2);
+ }
+ setMethodAccessible(method); // Default access superclass workaround
+ myCost = getTotalTransformationCost(parameterTypes,method.getParameterTypes());
+ if ( myCost < bestMatchCost ) {
+ bestMatch = method;
+ bestMatchCost = myCost;
+ }
+ }
+
+ LOG.trace("Couldn't find accessible method.");
+ }
+ }
+ }
+ }
+ if (bestMatch != null) {
+ cacheMethod(md, bestMatch);
+ } else {
+ // didn't find a match
+ LOG.trace("No match found.");
}
- final Method method = getAccessibleMethod(
- objectClass,
- methodName,
- parameterTypes);
- if (method == null) {
- throw new NoSuchMethodException("No such accessible method: " +
- methodName + "() on class: " + objectClass.getName());
+ return bestMatch;
+ }
+
+ /**
+ * Gets the number of steps required needed to turn the source class into the
+ * destination class. This represents the number of steps in the object hierarchy
+ * graph.
+ * @param srcClass The source class
+ * @param destClass The destination class
+ * @return The cost of transforming an object
+ */
+ private static float getObjectTransformationCost(Class<?> srcClass, final Class<?> destClass) {
+ float cost = 0.0f;
+ while (srcClass != null && !destClass.equals(srcClass)) {
+ if (destClass.isPrimitive()) {
+ final Class<?> destClassWrapperClazz = getPrimitiveWrapper(destClass);
+ if (destClassWrapperClazz != null && destClassWrapperClazz.equals(srcClass)) {
+ cost += 0.25f;
+ break;
+ }
+ }
+ if (destClass.isInterface() && isAssignmentCompatible(destClass,srcClass)) {
+ // slight penalty for interface match.
+ // we still want an exact match to override an interface match, but
+ // an interface match should override anything where we have to get a
+ // superclass.
+ cost += 0.25f;
+ break;
+ }
+ cost++;
+ srcClass = srcClass.getSuperclass();
}
- return method.invoke(null, args);
+
+ /*
+ * If the destination class is null, we've traveled all the way up to
+ * an Object match. We'll penalize this by adding 1.5 to the cost.
+ */
+ if (srcClass == null) {
+ cost += 1.5f;
+ }
+
+ return cost;
}
/**
- * <p>Invoke a named static method whose parameter type matches the object type.</p>
- *
- * <p>The behavior of this method is less deterministic
- * than {@link #invokeExactMethod(Object, String, Object[], Class[])}.
- * It loops through all methods with names that match
- * and then executes the first it finds with compatible parameters.</p>
- *
- * <p>This method supports calls to methods taking primitive parameters
- * via passing in wrapping classes. So, for example, a {@code Boolean} class
- * would match a {@code boolean} primitive.</p>
+ * Gets the class for the primitive type corresponding to the primitive wrapper class given.
+ * For example, an instance of {@code Boolean.class</code> returns a <code>boolean.class}.
+ * @param wrapperType the
+ * @return the primitive type class corresponding to the given wrapper class,
+ * null if no match is found
+ */
+ public static Class<?> getPrimitiveType(final Class<?> wrapperType) {
+ // does anyone know a better strategy than comparing names?
+ if (Boolean.class.equals(wrapperType)) {
+ return boolean.class;
+ }
+ if (Float.class.equals(wrapperType)) {
+ return float.class;
+ }
+ if (Long.class.equals(wrapperType)) {
+ return long.class;
+ }
+ if (Integer.class.equals(wrapperType)) {
+ return int.class;
+ }
+ if (Short.class.equals(wrapperType)) {
+ return short.class;
+ }
+ if (Byte.class.equals(wrapperType)) {
+ return byte.class;
+ }
+ if (Double.class.equals(wrapperType)) {
+ return double.class;
+ }
+ if (Character.class.equals(wrapperType)) {
+ return char.class;
+ }
+ if (LOG.isDebugEnabled()) {
+ LOG.debug("Not a known primitive wrapper class: " + wrapperType);
+ }
+ return null;
+ }
+
+ /**
+ * Gets the wrapper object class for the given primitive type class.
+ * For example, passing {@code boolean.class</code> returns <code>Boolean.class}
+ * @param primitiveType the primitive type class for which a match is to be found
+ * @return the wrapper type associated with the given primitive
+ * or null if no match is found
+ */
+ public static Class<?> getPrimitiveWrapper(final Class<?> primitiveType) {
+ // does anyone know a better strategy than comparing names?
+ if (boolean.class.equals(primitiveType)) {
+ return Boolean.class;
+ }
+ if (float.class.equals(primitiveType)) {
+ return Float.class;
+ }
+ if (long.class.equals(primitiveType)) {
+ return Long.class;
+ }
+ if (int.class.equals(primitiveType)) {
+ return Integer.class;
+ }
+ if (short.class.equals(primitiveType)) {
+ return Short.class;
+ }
+ if (byte.class.equals(primitiveType)) {
+ return Byte.class;
+ }
+ if (double.class.equals(primitiveType)) {
+ return Double.class;
+ }
+ if (char.class.equals(primitiveType)) {
+ return Character.class;
+ }
+ return null;
+ }
+
+ /**
+ * Returns the sum of the object transformation cost for each class in the source
+ * argument list.
+ * @param srcArgs The source arguments
+ * @param destArgs The destination arguments
+ * @return The total transformation cost
+ */
+ private static float getTotalTransformationCost(final Class<?>[] srcArgs, final Class<?>[] destArgs) {
+ float totalCost = 0.0f;
+ for (int i = 0; i < srcArgs.length; i++) {
+ Class<?> srcClass, destClass;
+ srcClass = srcArgs[i];
+ destClass = destArgs[i];
+ totalCost += getObjectTransformationCost(srcClass, destClass);
+ }
+
+ return totalCost;
+ }
+
+ /**
+ * <p>Invoke a method whose parameter type matches exactly the object
+ * type.</p>
*
* <p> This is a convenient wrapper for
- * {@link #invokeStaticMethod(Class objectClass,String methodName,Object [] args)}.
+ * {@link #invokeExactMethod(Object object,String methodName,Object [] args)}.
* </p>
*
- * @param objectClass invoke static method on this class
+ * @param object invoke method on this object
* @param methodName get method with this name
* @param arg use this argument. May be null (this will result in calling the
* parameterless method with name {@code methodName}).
@@ -455,10 +688,9 @@ public class MethodUtils {
* method invoked
* @throws IllegalAccessException if the requested method is not accessible
* via reflection
- * @since 1.8.0
*/
- public static Object invokeStaticMethod(
- final Class<?> objectClass,
+ public static Object invokeExactMethod(
+ final Object object,
final String methodName,
final Object arg)
throws
@@ -466,26 +698,17 @@ public class MethodUtils {
IllegalAccessException,
InvocationTargetException {
final Object[] args = toArray(arg);
- return invokeStaticMethod (objectClass, methodName, args);
+ return invokeExactMethod(object, methodName, args);
}
/**
- * <p>Invoke a named static method whose parameter type matches the object type.</p>
- *
- * <p>The behavior of this method is less deterministic
- * than {@link #invokeExactMethod(Object object,String methodName,Object [] args)}.
- * It loops through all methods with names that match
- * and then executes the first it finds with compatible parameters.</p>
- *
- * <p>This method supports calls to methods taking primitive parameters
- * via passing in wrapping classes. So, for example, a {@code Boolean} class
- * would match a {@code boolean} primitive.</p>
+ * <p>Invoke a method whose parameter types match exactly the object
+ * types.</p>
*
- * <p> This is a convenient wrapper for
- * {@link #invokeStaticMethod(Class objectClass, String methodName, Object[] args, Class[] parameterTypes)}.
- * </p>
+ * <p> This uses reflection to invoke the method obtained from a call to
+ * {@code getAccessibleMethod()}.</p>
*
- * @param objectClass invoke static method on this class
+ * @param object invoke method on this object
* @param methodName get method with this name
* @param args use these arguments - treat null as empty array (passing null will
* result in calling the parameterless method with name {@code methodName}).
@@ -496,10 +719,9 @@ public class MethodUtils {
* method invoked
* @throws IllegalAccessException if the requested method is not accessible
* via reflection
- * @since 1.8.0
*/
- public static Object invokeStaticMethod(
- final Class<?> objectClass,
+ public static Object invokeExactMethod(
+ final Object object,
final String methodName,
Object[] args)
throws
@@ -514,24 +736,17 @@ public class MethodUtils {
for (int i = 0; i < arguments; i++) {
parameterTypes[i] = args[i].getClass();
}
- return invokeStaticMethod (objectClass, methodName, args, parameterTypes);
+ return invokeExactMethod(object, methodName, args, parameterTypes);
}
/**
- * <p>Invoke a named static method whose parameter type matches the object type.</p>
- *
- * <p>The behavior of this method is less deterministic
- * than {@link
- * #invokeExactStaticMethod(Class objectClass, String methodName, Object[] args, Class[] parameterTypes)}.
- * It loops through all methods with names that match
- * and then executes the first it finds with compatible parameters.</p>
- *
- * <p>This method supports calls to methods taking primitive parameters
- * via passing in wrapping classes. So, for example, a {@code Boolean} class
- * would match a {@code boolean} primitive.</p>
+ * <p>Invoke a method whose parameter types match exactly the parameter
+ * types given.</p>
*
+ * <p>This uses reflection to invoke the method obtained from a call to
+ * {@code getAccessibleMethod()}.</p>
*
- * @param objectClass invoke static method on this class
+ * @param object invoke method on this object
* @param methodName get method with this name
* @param args use these arguments - treat null as empty array (passing null will
* result in calling the parameterless method with name {@code methodName}).
@@ -543,34 +758,33 @@ public class MethodUtils {
* method invoked
* @throws IllegalAccessException if the requested method is not accessible
* via reflection
- * @since 1.8.0
*/
- public static Object invokeStaticMethod(
- final Class<?> objectClass,
+ public static Object invokeExactMethod(
+ final Object object,
final String methodName,
Object[] args,
Class<?>[] parameterTypes)
- throws
- NoSuchMethodException,
- IllegalAccessException,
- InvocationTargetException {
+ throws
+ NoSuchMethodException,
+ IllegalAccessException,
+ InvocationTargetException {
+ if (args == null) {
+ args = BeanUtils.EMPTY_OBJECT_ARRAY;
+ }
if (parameterTypes == null) {
parameterTypes = BeanUtils.EMPTY_CLASS_ARRAY;
}
- if (args == null) {
- args = BeanUtils.EMPTY_OBJECT_ARRAY;
- }
- final Method method = getMatchingAccessibleMethod(
- objectClass,
+ final Method method = getAccessibleMethod(
+ object.getClass(),
methodName,
parameterTypes);
if (method == null) {
throw new NoSuchMethodException("No such accessible method: " +
- methodName + "() on class: " + objectClass.getName());
+ methodName + "() on object: " + object.getClass().getName());
}
- return method.invoke(null, args);
+ return method.invoke(object, args);
}
/**
@@ -606,6 +820,8 @@ public class MethodUtils {
return invokeExactStaticMethod (objectClass, methodName, args);
}
+
+
/**
* <p>Invoke a static method whose parameter types match exactly the object
* types.</p>
@@ -645,463 +861,339 @@ public class MethodUtils {
return invokeExactStaticMethod(objectClass, methodName, args, parameterTypes);
}
- private static Object[] toArray(final Object arg) {
- Object[] args = null;
- if (arg != null) {
- args = new Object[] { arg };
- }
- return args;
- }
-
/**
- * <p>Return an accessible method (that is, one that can be invoked via
- * reflection) with given name and a single parameter. If no such method
- * can be found, return {@code null}.
- * Basically, a convenience wrapper that constructs a {@code Class}
- * array for you.</p>
+ * <p>Invoke a static method whose parameter types match exactly the parameter
+ * types given.</p>
*
- * @param clazz get method from this class
- * @param methodName get method with this name
- * @param parameterType taking this type of parameter
- * @return The accessible method
- */
- public static Method getAccessibleMethod(
- final Class<?> clazz,
- final String methodName,
- final Class<?> parameterType) {
- final Class<?>[] parameterTypes = {parameterType};
- return getAccessibleMethod(clazz, methodName, parameterTypes);
- }
-
- /**
- * <p>Return an accessible method (that is, one that can be invoked via
- * reflection) with given name and parameters. If no such method
- * can be found, return {@code null}.
- * This is just a convenient wrapper for
- * {@link #getAccessibleMethod(Method method)}.</p>
+ * <p>This uses reflection to invoke the method obtained from a call to
+ * {@link #getAccessibleMethod(Class, String, Class[])}.</p>
*
- * @param clazz get method from this class
+ * @param objectClass invoke static method on this class
* @param methodName get method with this name
- * @param parameterTypes with these parameters types
- * @return The accessible method
- */
- public static Method getAccessibleMethod(
- final Class<?> clazz,
- final String methodName,
- final Class<?>[] parameterTypes) {
- try {
- final MethodDescriptor md = new MethodDescriptor(clazz, methodName, parameterTypes, true);
- // Check the cache first
- Method method = getCachedMethod(md);
- if (method != null) {
- return method;
- }
-
- method = getAccessibleMethod
- (clazz, clazz.getMethod(methodName, parameterTypes));
- cacheMethod(md, method);
- return method;
- } catch (final NoSuchMethodException e) {
- return null;
- }
- }
-
- /**
- * <p>Return an accessible method (that is, one that can be invoked via
- * reflection) that implements the specified Method. If no such method
- * can be found, return {@code null}.</p>
- *
- * @param method The method that we wish to call
- * @return The accessible method
- */
- public static Method getAccessibleMethod(final Method method) {
- // Make sure we have a method to check
- if (method == null) {
- return null;
- }
-
- return getAccessibleMethod(method.getDeclaringClass(), method);
- }
-
- /**
- * <p>Return an accessible method (that is, one that can be invoked via
- * reflection) that implements the specified Method. If no such method
- * can be found, return {@code null}.</p>
+ * @param args use these arguments - treat null as empty array (passing null will
+ * result in calling the parameterless method with name {@code methodName}).
+ * @param parameterTypes match these parameters - treat null as empty array
+ * @return The value returned by the invoked method
*
- * @param clazz The class of the object
- * @param method The method that we wish to call
- * @return The accessible method
+ * @throws NoSuchMethodException if there is no such accessible method
+ * @throws InvocationTargetException wraps an exception thrown by the
+ * method invoked
+ * @throws IllegalAccessException if the requested method is not accessible
+ * via reflection
* @since 1.8.0
- */
- public static Method getAccessibleMethod(Class<?> clazz, Method method) {
- // Make sure we have a method to check
- if (method == null) {
- return null;
- }
-
- // If the requested method is not public we cannot call it
- if (!Modifier.isPublic(method.getModifiers())) {
- return null;
- }
-
- boolean sameClass = true;
- if (clazz == null) {
- clazz = method.getDeclaringClass();
- } else {
- if (!method.getDeclaringClass().isAssignableFrom(clazz)) {
- throw new IllegalArgumentException(clazz.getName() +
- " is not assignable from " + method.getDeclaringClass().getName());
- }
- sameClass = clazz.equals(method.getDeclaringClass());
- }
-
- // If the class is public, we are done
- if (Modifier.isPublic(clazz.getModifiers())) {
- if (!sameClass && !Modifier.isPublic(method.getDeclaringClass().getModifiers())) {
- setMethodAccessible(method); // Default access superclass workaround
- }
- return method;
- }
-
- final String methodName = method.getName();
- final Class<?>[] parameterTypes = method.getParameterTypes();
-
- // Check the implemented interfaces and subinterfaces
- method =
- getAccessibleMethodFromInterfaceNest(clazz,
- methodName,
- parameterTypes);
-
- // Check the superclass chain
- if (method == null) {
- method = getAccessibleMethodFromSuperclass(clazz,
- methodName,
- parameterTypes);
- }
-
- return method;
- }
-
-
-
- /**
- * <p>Return an accessible method (that is, one that can be invoked via
- * reflection) by scanning through the superclasses. If no such method
- * can be found, return {@code null}.</p>
- *
- * @param clazz Class to be checked
- * @param methodName Method name of the method we wish to call
- * @param parameterTypes The parameter type signatures
- */
- private static Method getAccessibleMethodFromSuperclass
- (final Class<?> clazz, final String methodName, final Class<?>[] parameterTypes) {
- Class<?> parentClazz = clazz.getSuperclass();
- while (parentClazz != null) {
- if (Modifier.isPublic(parentClazz.getModifiers())) {
- try {
- return parentClazz.getMethod(methodName, parameterTypes);
- } catch (final NoSuchMethodException e) {
- return null;
- }
- }
- parentClazz = parentClazz.getSuperclass();
+ */
+ public static Object invokeExactStaticMethod(
+ final Class<?> objectClass,
+ final String methodName,
+ Object[] args,
+ Class<?>[] parameterTypes)
+ throws
+ NoSuchMethodException,
+ IllegalAccessException,
+ InvocationTargetException {
+ if (args == null) {
+ args = BeanUtils.EMPTY_OBJECT_ARRAY;
}
- return null;
+
+ if (parameterTypes == null) {
+ parameterTypes = BeanUtils.EMPTY_CLASS_ARRAY;
+ }
+
+ final Method method = getAccessibleMethod(
+ objectClass,
+ methodName,
+ parameterTypes);
+ if (method == null) {
+ throw new NoSuchMethodException("No such accessible method: " +
+ methodName + "() on class: " + objectClass.getName());
+ }
+ return method.invoke(null, args);
}
/**
- * <p>Return an accessible method (that is, one that can be invoked via
- * reflection) that implements the specified method, by scanning through
- * all implemented interfaces and subinterfaces. If no such method
- * can be found, return {@code null}.</p>
+ * <p>Invoke a named method whose parameter type matches the object type.</p>
*
- * <p> There isn't any good reason why this method must be private.
- * It is because there doesn't seem any reason why other classes should
- * call this rather than the higher level methods.</p>
+ * <p>The behavior of this method is less deterministic
+ * than {@code invokeExactMethod()}.
+ * It loops through all methods with names that match
+ * and then executes the first it finds with compatible parameters.</p>
*
- * @param clazz Parent class for the interfaces to be checked
- * @param methodName Method name of the method we wish to call
- * @param parameterTypes The parameter type signatures
+ * <p>This method supports calls to methods taking primitive parameters
+ * via passing in wrapping classes. So, for example, a {@code Boolean} class
+ * would match a {@code boolean} primitive.</p>
+ *
+ * <p> This is a convenient wrapper for
+ * {@link #invokeMethod(Object object,String methodName,Object [] args)}.
+ * </p>
+ *
+ * @param object invoke method on this object
+ * @param methodName get method with this name
+ * @param arg use this argument. May be null (this will result in calling the
+ * parameterless method with name {@code methodName}).
+ * @return The value returned by the invoked method
+ *
+ * @throws NoSuchMethodException if there is no such accessible method
+ * @throws InvocationTargetException wraps an exception thrown by the
+ * method invoked
+ * @throws IllegalAccessException if the requested method is not accessible
+ * via reflection
*/
- private static Method getAccessibleMethodFromInterfaceNest
- (Class<?> clazz, final String methodName, final Class<?>[] parameterTypes) {
- Method method = null;
-
- // Search up the superclass chain
- for (; clazz != null; clazz = clazz.getSuperclass()) {
-
- // Check the implemented interfaces of the parent class
- final Class<?>[] interfaces = clazz.getInterfaces();
- for (final Class<?> anInterface : interfaces) {
-
- // Is this interface public?
- if (!Modifier.isPublic(anInterface.getModifiers())) {
- continue;
- }
-
- // Does the method exist on this interface?
- try {
- method = anInterface.getDeclaredMethod(methodName,
- parameterTypes);
- } catch (final NoSuchMethodException e) {
- /* Swallow, if no method is found after the loop then this
- * method returns null.
- */
- }
- if (method != null) {
- return method;
- }
-
- // Recursively check our parent interfaces
- method =
- getAccessibleMethodFromInterfaceNest(anInterface,
- methodName,
- parameterTypes);
- if (method != null) {
- return method;
- }
-
- }
-
- }
-
- // We did not find anything
- return null;
+ public static Object invokeMethod(
+ final Object object,
+ final String methodName,
+ final Object arg)
+ throws
+ NoSuchMethodException,
+ IllegalAccessException,
+ InvocationTargetException {
+ final Object[] args = toArray(arg);
+ return invokeMethod(object, methodName, args);
}
/**
- * <p>Find an accessible method that matches the given name and has compatible parameters.
- * Compatible parameters mean that every method parameter is assignable from
- * the given parameters.
- * In other words, it finds a method with the given name
- * that will take the parameters given.</p>
+ * <p>Invoke a named method whose parameter type matches the object type.</p>
*
- * <p>This method is slightly indeterministic since it loops
- * through methods names and return the first matching method.</p>
+ * <p>The behavior of this method is less deterministic
+ * than {@link #invokeExactMethod(Object object,String methodName,Object [] args)}.
+ * It loops through all methods with names that match
+ * and then executes the first it finds with compatible parameters.</p>
*
- * <p>This method is used by
- * {@link
- * #invokeMethod(Object object, String methodName, Object[] args, Class[] parameterTypes)}.
+ * <p>This method supports calls to methods taking primitive parameters
+ * via passing in wrapping classes. So, for example, a {@code Boolean} class
+ * would match a {@code boolean} primitive.</p>
*
- * <p>This method can match primitive parameter by passing in wrapper classes.
- * For example, a {@code Boolean</code> will match a primitive <code>boolean}
- * parameter.
+ * <p> This is a convenient wrapper for
+ * {@link #invokeMethod(Object object, String methodName, Object[] args, Class[] parameterTypes)}.
+ * </p>
*
- * @param clazz find method in this class
- * @param methodName find method with this name
- * @param parameterTypes find method with compatible parameters
- * @return The accessible method
+ * @param object invoke method on this object
+ * @param methodName get method with this name
+ * @param args use these arguments - treat null as empty array (passing null will
+ * result in calling the parameterless method with name {@code methodName}).
+ * @return The value returned by the invoked method
+ *
+ * @throws NoSuchMethodException if there is no such accessible method
+ * @throws InvocationTargetException wraps an exception thrown by the
+ * method invoked
+ * @throws IllegalAccessException if the requested method is not accessible
+ * via reflection
*/
- public static Method getMatchingAccessibleMethod(
- final Class<?> clazz,
- final String methodName,
- final Class<?>[] parameterTypes) {
- // trace logging
- if (LOG.isTraceEnabled()) {
- LOG.trace("Matching name=" + methodName + " on " + clazz);
- }
- final MethodDescriptor md = new MethodDescriptor(clazz, methodName, parameterTypes, false);
-
- // see if we can find the method directly
- // most of the time this works and it's much faster
- try {
- // Check the cache first
- Method method = getCachedMethod(md);
- if (method != null) {
- return method;
- }
-
- method = clazz.getMethod(methodName, parameterTypes);
- if (LOG.isTraceEnabled()) {
- LOG.trace("Found straight match: " + method);
- LOG.trace("isPublic:" + Modifier.isPublic(method.getModifiers()));
- }
-
- setMethodAccessible(method); // Default access superclass workaround
-
- cacheMethod(md, method);
- return method;
-
- } catch (final NoSuchMethodException e) { /* SWALLOW */ }
-
- // search through all methods
- final int paramSize = parameterTypes.length;
- Method bestMatch = null;
- final Method[] methods = clazz.getMethods();
- float bestMatchCost = Float.MAX_VALUE;
- float myCost = Float.MAX_VALUE;
- for (final Method method2 : methods) {
- if (method2.getName().equals(methodName)) {
- // log some trace information
- if (LOG.isTraceEnabled()) {
- LOG.trace("Found matching name:");
- LOG.trace(method2);
- }
-
- // compare parameters
- final Class<?>[] methodsParams = method2.getParameterTypes();
- final int methodParamSize = methodsParams.length;
- if (methodParamSize == paramSize) {
- boolean match = true;
- for (int n = 0 ; n < methodParamSize; n++) {
- if (LOG.isTraceEnabled()) {
- LOG.trace("Param=" + parameterTypes[n].getName());
- LOG.trace("Method=" + methodsParams[n].getName());
- }
- if (!isAssignmentCompatible(methodsParams[n], parameterTypes[n])) {
- if (LOG.isTraceEnabled()) {
- LOG.trace(methodsParams[n] + " is not assignable from "
- + parameterTypes[n]);
- }
- match = false;
- break;
- }
- }
-
- if (match) {
- // get accessible version of method
- final Method method = getAccessibleMethod(clazz, method2);
- if (method != null) {
- if (LOG.isTraceEnabled()) {
- LOG.trace(method + " accessible version of "
- + method2);
- }
- setMethodAccessible(method); // Default access superclass workaround
- myCost = getTotalTransformationCost(parameterTypes,method.getParameterTypes());
- if ( myCost < bestMatchCost ) {
- bestMatch = method;
- bestMatchCost = myCost;
- }
- }
-
- LOG.trace("Couldn't find accessible method.");
- }
- }
- }
- }
- if (bestMatch != null) {
- cacheMethod(md, bestMatch);
- } else {
- // didn't find a match
- LOG.trace("No match found.");
+ public static Object invokeMethod(
+ final Object object,
+ final String methodName,
+ Object[] args)
+ throws
+ NoSuchMethodException,
+ IllegalAccessException,
+ InvocationTargetException {
+ if (args == null) {
+ args = BeanUtils.EMPTY_OBJECT_ARRAY;
}
-
- return bestMatch;
+ final int arguments = args.length;
+ final Class<?>[] parameterTypes = new Class[arguments];
+ for (int i = 0; i < arguments; i++) {
+ parameterTypes[i] = args[i].getClass();
+ }
+ return invokeMethod(object, methodName, args, parameterTypes);
}
/**
- * Try to make the method accessible
- * @param method The source arguments
+ * <p>Invoke a named method whose parameter type matches the object type.</p>
+ *
+ * <p>The behavior of this method is less deterministic
+ * than {@link
+ * #invokeExactMethod(Object object, String methodName, Object[] args, Class[] parameterTypes)}.
+ * It loops through all methods with names that match
+ * and then executes the first it finds with compatible parameters.</p>
+ *
+ * <p>This method supports calls to methods taking primitive parameters
+ * via passing in wrapping classes. So, for example, a {@code Boolean} class
+ * would match a {@code boolean} primitive.</p>
+ *
+ *
+ * @param object invoke method on this object
+ * @param methodName get method with this name
+ * @param args use these arguments - treat null as empty array (passing null will
+ * result in calling the parameterless method with name {@code methodName}).
+ * @param parameterTypes match these parameters - treat null as empty array
+ * @return The value returned by the invoked method
+ *
+ * @throws NoSuchMethodException if there is no such accessible method
+ * @throws InvocationTargetException wraps an exception thrown by the
+ * method invoked
+ * @throws IllegalAccessException if the requested method is not accessible
+ * via reflection
*/
- private static void setMethodAccessible(final Method method) {
- try {
- //
- // XXX Default access superclass workaround
- //
- // When a public class has a default access superclass
- // with public methods, these methods are accessible.
- // Calling them from compiled code works fine.
- //
- // Unfortunately, using reflection to invoke these methods
- // seems to (wrongly) to prevent access even when the method
- // modifier is public.
- //
- // The following workaround solves the problem but will only
- // work from sufficiently privileges code.
- //
- // Better workarounds would be gratefully accepted.
- //
- if (!method.isAccessible()) {
- method.setAccessible(true);
- }
-
- } catch (final SecurityException se) {
- // log but continue just in case the method.invoke works anyway
- if (!loggedAccessibleWarning) {
- boolean vulnerableJVM = false;
- try {
- final String specVersion = System.getProperty("java.specification.version");
- if (specVersion.charAt(0) == '1' &&
- (specVersion.charAt(2) == '0' ||
- specVersion.charAt(2) == '1' ||
- specVersion.charAt(2) == '2' ||
- specVersion.charAt(2) == '3')) {
+ public static Object invokeMethod(
+ final Object object,
+ final String methodName,
+ Object[] args,
+ Class<?>[] parameterTypes)
+ throws
+ NoSuchMethodException,
+ IllegalAccessException,
+ InvocationTargetException {
+ if (parameterTypes == null) {
+ parameterTypes = BeanUtils.EMPTY_CLASS_ARRAY;
+ }
+ if (args == null) {
+ args = BeanUtils.EMPTY_OBJECT_ARRAY;
+ }
- vulnerableJVM = true;
- }
- } catch (final SecurityException e) {
- // don't know - so display warning
- vulnerableJVM = true;
- }
- if (vulnerableJVM) {
- LOG.warn(
- "Current Security Manager restricts use of workarounds for reflection bugs "
- + " in pre-1.4 JVMs.");
- }
- loggedAccessibleWarning = true;
- }
- LOG.debug("Cannot setAccessible on method. Therefore cannot use jvm access bug workaround.", se);
+ final Method method = getMatchingAccessibleMethod(
+ object.getClass(),
+ methodName,
+ parameterTypes);
+ if (method == null) {
+ throw new NoSuchMethodException("No such accessible method: " +
+ methodName + "() on object: " + object.getClass().getName());
}
+ return method.invoke(object, args);
}
/**
- * Returns the sum of the object transformation cost for each class in the source
- * argument list.
- * @param srcArgs The source arguments
- * @param destArgs The destination arguments
- * @return The total transformation cost
+ * <p>Invoke a named static method whose parameter type matches the object type.</p>
+ *
+ * <p>The behavior of this method is less deterministic
+ * than {@link #invokeExactMethod(Object, String, Object[], Class[])}.
+ * It loops through all methods with names that match
+ * and then executes the first it finds with compatible parameters.</p>
+ *
+ * <p>This method supports calls to methods taking primitive parameters
+ * via passing in wrapping classes. So, for example, a {@code Boolean} class
+ * would match a {@code boolean} primitive.</p>
+ *
+ * <p> This is a convenient wrapper for
+ * {@link #invokeStaticMethod(Class objectClass,String methodName,Object [] args)}.
+ * </p>
+ *
+ * @param objectClass invoke static method on this class
+ * @param methodName get method with this name
+ * @param arg use this argument. May be null (this will result in calling the
+ * parameterless method with name {@code methodName}).
+ * @return The value returned by the invoked method
+ *
+ * @throws NoSuchMethodException if there is no such accessible method
+ * @throws InvocationTargetException wraps an exception thrown by the
+ * method invoked
+ * @throws IllegalAccessException if the requested method is not accessible
+ * via reflection
+ * @since 1.8.0
*/
- private static float getTotalTransformationCost(final Class<?>[] srcArgs, final Class<?>[] destArgs) {
- float totalCost = 0.0f;
- for (int i = 0; i < srcArgs.length; i++) {
- Class<?> srcClass, destClass;
- srcClass = srcArgs[i];
- destClass = destArgs[i];
- totalCost += getObjectTransformationCost(srcClass, destClass);
- }
-
- return totalCost;
+ public static Object invokeStaticMethod(
+ final Class<?> objectClass,
+ final String methodName,
+ final Object arg)
+ throws
+ NoSuchMethodException,
+ IllegalAccessException,
+ InvocationTargetException {
+ final Object[] args = toArray(arg);
+ return invokeStaticMethod (objectClass, methodName, args);
}
/**
- * Gets the number of steps required needed to turn the source class into the
- * destination class. This represents the number of steps in the object hierarchy
- * graph.
- * @param srcClass The source class
- * @param destClass The destination class
- * @return The cost of transforming an object
+ * <p>Invoke a named static method whose parameter type matches the object type.</p>
+ *
+ * <p>The behavior of this method is less deterministic
+ * than {@link #invokeExactMethod(Object object,String methodName,Object [] args)}.
+ * It loops through all methods with names that match
+ * and then executes the first it finds with compatible parameters.</p>
+ *
+ * <p>This method supports calls to methods taking primitive parameters
+ * via passing in wrapping classes. So, for example, a {@code Boolean} class
+ * would match a {@code boolean} primitive.</p>
+ *
+ * <p> This is a convenient wrapper for
+ * {@link #invokeStaticMethod(Class objectClass, String methodName, Object[] args, Class[] parameterTypes)}.
+ * </p>
+ *
+ * @param objectClass invoke static method on this class
+ * @param methodName get method with this name
+ * @param args use these arguments - treat null as empty array (passing null will
+ * result in calling the parameterless method with name {@code methodName}).
+ * @return The value returned by the invoked method
+ *
+ * @throws NoSuchMethodException if there is no such accessible method
+ * @throws InvocationTargetException wraps an exception thrown by the
+ * method invoked
+ * @throws IllegalAccessException if the requested method is not accessible
+ * via reflection
+ * @since 1.8.0
*/
- private static float getObjectTransformationCost(Class<?> srcClass, final Class<?> destClass) {
- float cost = 0.0f;
- while (srcClass != null && !destClass.equals(srcClass)) {
- if (destClass.isPrimitive()) {
- final Class<?> destClassWrapperClazz = getPrimitiveWrapper(destClass);
- if (destClassWrapperClazz != null && destClassWrapperClazz.equals(srcClass)) {
- cost += 0.25f;
- break;
- }
- }
- if (destClass.isInterface() && isAssignmentCompatible(destClass,srcClass)) {
- // slight penalty for interface match.
- // we still want an exact match to override an interface match, but
- // an interface match should override anything where we have to get a
- // superclass.
- cost += 0.25f;
- break;
- }
- cost++;
- srcClass = srcClass.getSuperclass();
+ public static Object invokeStaticMethod(
+ final Class<?> objectClass,
+ final String methodName,
+ Object[] args)
+ throws
+ NoSuchMethodException,
+ IllegalAccessException,
+ InvocationTargetException {
+ if (args == null) {
+ args = BeanUtils.EMPTY_OBJECT_ARRAY;
+ }
+ final int arguments = args.length;
+ final Class<?>[] parameterTypes = new Class[arguments];
+ for (int i = 0; i < arguments; i++) {
+ parameterTypes[i] = args[i].getClass();
}
+ return invokeStaticMethod (objectClass, methodName, args, parameterTypes);
+ }
+
+ /**
+ * <p>Invoke a named static method whose parameter type matches the object type.</p>
+ *
+ * <p>The behavior of this method is less deterministic
+ * than {@link
+ * #invokeExactStaticMethod(Class objectClass, String methodName, Object[] args, Class[] parameterTypes)}.
+ * It loops through all methods with names that match
+ * and then executes the first it finds with compatible parameters.</p>
+ *
+ * <p>This method supports calls to methods taking primitive parameters
+ * via passing in wrapping classes. So, for example, a {@code Boolean} class
+ * would match a {@code boolean} primitive.</p>
+ *
+ *
+ * @param objectClass invoke static method on this class
+ * @param methodName get method with this name
+ * @param args use these arguments - treat null as empty array (passing null will
+ * result in calling the parameterless method with name {@code methodName}).
+ * @param parameterTypes match these parameters - treat null as empty array
+ * @return The value returned by the invoked method
+ *
+ * @throws NoSuchMethodException if there is no such accessible method
+ * @throws InvocationTargetException wraps an exception thrown by the
+ * method invoked
+ * @throws IllegalAccessException if the requested method is not accessible
+ * via reflection
+ * @since 1.8.0
+ */
+ public static Object invokeStaticMethod(
+ final Class<?> objectClass,
+ final String methodName,
+ Object[] args,
+ Class<?>[] parameterTypes)
+ throws
+ NoSuchMethodException,
+ IllegalAccessException,
+ InvocationTargetException {
- /*
- * If the destination class is null, we've traveled all the way up to
- * an Object match. We'll penalize this by adding 1.5 to the cost.
- */
- if (srcClass == null) {
- cost += 1.5f;
+ if (parameterTypes == null) {
+ parameterTypes = BeanUtils.EMPTY_CLASS_ARRAY;
+ }
+ if (args == null) {
+ args = BeanUtils.EMPTY_OBJECT_ARRAY;
}
- return cost;
+ final Method method = getMatchingAccessibleMethod(
+ objectClass,
+ methodName,
+ parameterTypes);
+ if (method == null) {
+ throw new NoSuchMethodException("No such accessible method: " +
+ methodName + "() on class: " + objectClass.getName());
+ }
+ return method.invoke(null, args);
}
/**
@@ -1140,78 +1232,81 @@ public class MethodUtils {
}
/**
- * Gets the wrapper object class for the given primitive type class.
- * For example, passing {@code boolean.class</code> returns <code>Boolean.class}
- * @param primitiveType the primitive type class for which a match is to be found
- * @return the wrapper type associated with the given primitive
- * or null if no match is found
+ * Sets whether methods should be cached for greater performance or not,
+ * default is {@code true}.
+ *
+ * @param cacheMethods {@code true} if methods should be
+ * cached for greater performance, otherwise {@code false}
+ * @since 1.8.0
*/
- public static Class<?> getPrimitiveWrapper(final Class<?> primitiveType) {
- // does anyone know a better strategy than comparing names?
- if (boolean.class.equals(primitiveType)) {
- return Boolean.class;
- }
- if (float.class.equals(primitiveType)) {
- return Float.class;
- }
- if (long.class.equals(primitiveType)) {
- return Long.class;
- }
- if (int.class.equals(primitiveType)) {
- return Integer.class;
- }
- if (short.class.equals(primitiveType)) {
- return Short.class;
- }
- if (byte.class.equals(primitiveType)) {
- return Byte.class;
- }
- if (double.class.equals(primitiveType)) {
- return Double.class;
- }
- if (char.class.equals(primitiveType)) {
- return Character.class;
+ public static synchronized void setCacheMethods(final boolean cacheMethods) {
+ CACHE_METHODS = cacheMethods;
+ if (!CACHE_METHODS) {
+ clearCache();
}
- return null;
}
/**
- * Gets the class for the primitive type corresponding to the primitive wrapper class given.
- * For example, an instance of {@code Boolean.class</code> returns a <code>boolean.class}.
- * @param wrapperType the
- * @return the primitive type class corresponding to the given wrapper class,
- * null if no match is found
+ * Try to make the method accessible
+ * @param method The source arguments
*/
- public static Class<?> getPrimitiveType(final Class<?> wrapperType) {
- // does anyone know a better strategy than comparing names?
- if (Boolean.class.equals(wrapperType)) {
- return boolean.class;
- }
- if (Float.class.equals(wrapperType)) {
- return float.class;
- }
- if (Long.class.equals(wrapperType)) {
- return long.class;
- }
- if (Integer.class.equals(wrapperType)) {
- return int.class;
- }
- if (Short.class.equals(wrapperType)) {
- return short.class;
- }
- if (Byte.class.equals(wrapperType)) {
- return byte.class;
- }
- if (Double.class.equals(wrapperType)) {
- return double.class;
- }
- if (Character.class.equals(wrapperType)) {
- return char.class;
+ private static void setMethodAccessible(final Method method) {
+ try {
+ //
+ // XXX Default access superclass workaround
+ //
+ // When a public class has a default access superclass
+ // with public methods, these methods are accessible.
+ // Calling them from compiled code works fine.
+ //
+ // Unfortunately, using reflection to invoke these methods
+ // seems to (wrongly) to prevent access even when the method
+ // modifier is public.
+ //
+ // The following workaround solves the problem but will only
+ // work from sufficiently privileges code.
+ //
+ // Better workarounds would be gratefully accepted.
+ //
+ if (!method.isAccessible()) {
+ method.setAccessible(true);
+ }
+
+ } catch (final SecurityException se) {
+ // log but continue just in case the method.invoke works anyway
+ if (!loggedAccessibleWarning) {
+ boolean vulnerableJVM = false;
+ try {
+ final String specVersion = System.getProperty("java.specification.version");
+ if (specVersion.charAt(0) == '1' &&
+ (specVersion.charAt(2) == '0' ||
+ specVersion.charAt(2) == '1' ||
+ specVersion.charAt(2) == '2' ||
+ specVersion.charAt(2) == '3')) {
+
+ vulnerableJVM = true;
+ }
+ } catch (final SecurityException e) {
+ // don't know - so display warning
+ vulnerableJVM = true;
+ }
+ if (vulnerableJVM) {
+ LOG.warn(
+ "Current Security Manager restricts use of workarounds for reflection bugs "
+ + " in pre-1.4 JVMs.");
+ }
+ loggedAccessibleWarning = true;
+ }
+ LOG.debug("Cannot setAccessible on method. Therefore cannot use jvm access bug workaround.", se);
}
- if (LOG.isDebugEnabled()) {
- LOG.debug("Not a known primitive wrapper class: " + wrapperType);
+ }
+
+ private static Object[] toArray(final Object arg) {
+ Object[] args = null;
+ if (arg != null) {
+ args = new Object[] { arg };
}
- return null;
+ return args;
}
/**
@@ -1230,99 +1325,4 @@ public class MethodUtils {
}
return clazz;
}
-
- /**
- * Gets the method from the cache, if present.
- *
- * @param md The method descriptor
- * @return The cached method
- */
- private static Method getCachedMethod(final MethodDescriptor md) {
- if (CACHE_METHODS) {
- final Reference<Method> methodRef = cache.get(md);
- if (methodRef != null) {
- return methodRef.get();
- }
- }
- return null;
- }
-
- /**
- * Add a method to the cache.
- *
- * @param md The method descriptor
- * @param method The method to cache
- */
- private static void cacheMethod(final MethodDescriptor md, final Method method) {
- if (CACHE_METHODS && method != null) {
- cache.put(md, new WeakReference<>(method));
- }
- }
-
- /**
- * Represents the key to looking up a Method by reflection.
- */
- private static class MethodDescriptor {
- private final Class<?> cls;
- private final String methodName;
- private final Class<?>[] paramTypes;
- private final boolean exact;
- private final int hashCode;
-
- /**
- * The sole constructor.
- *
- * @param cls the class to reflect, must not be null
- * @param methodName the method name to obtain
- * @param paramTypes the array of classes representing the parameter types
- * @param exact whether the match has to be exact.
- */
- public MethodDescriptor(final Class<?> cls, final String methodName, Class<?>[] paramTypes,
- final boolean exact) {
- if (cls == null) {
- throw new IllegalArgumentException("Class cannot be null");
- }
- if (methodName == null) {
- throw new IllegalArgumentException("Method Name cannot be null");
- }
- if (paramTypes == null) {
- paramTypes = BeanUtils.EMPTY_CLASS_ARRAY;
- }
-
- this.cls = cls;
- this.methodName = methodName;
- this.paramTypes = paramTypes;
- this.exact= exact;
-
- this.hashCode = methodName.length();
- }
- /**
- * Checks for equality.
- * @param obj object to be tested for equality
- * @return true, if the object describes the same Method.
- */
- @Override
- public boolean equals(final Object obj) {
- if (!(obj instanceof MethodDescriptor)) {
- return false;
- }
- final MethodDescriptor md = (MethodDescriptor)obj;
-
- return exact == md.exact &&
- methodName.equals(md.methodName) &&
- cls.equals(md.cls) &&
- java.util.Arrays.equals(paramTypes, md.paramTypes);
- }
- /**
- * Returns the string length of method name. I.e. if the
- * hashcodes are different, the objects are different. If the
- * hashcodes are the same, need to use the equals method to
- * determine equality.
- * @return the string length of method name.
- */
- @Override
- public int hashCode() {
- return hashCode;
- }
- }
}
diff --git a/src/main/java/org/apache/commons/beanutils2/PropertyUtils.java b/src/main/java/org/apache/commons/beanutils2/PropertyUtils.java
index 081c532f..cb50ec3e 100644
--- a/src/main/java/org/apache/commons/beanutils2/PropertyUtils.java
+++ b/src/main/java/org/apache/commons/beanutils2/PropertyUtils.java
@@ -37,29 +37,6 @@ import java.util.Map;
public class PropertyUtils {
- /**
- * Clear any cached property descriptors information for all classes
- * loaded by any class loaders. This is useful in cases where class
- * loaders are thrown away to implement class reloading.
- *
- * <p>For more details see {@code PropertyUtilsBean}.</p>
- *
- * @see PropertyUtilsBean#clearDescriptors
- */
- public static void clearDescriptors() {
- PropertyUtilsBean.getInstance().clearDescriptors();
- }
-
- /**
- * Resets the registered {@link BeanIntrospector} objects to the initial default
- * state.
- *
- * @since 1.9
- */
- public static void resetBeanIntrospectors() {
- PropertyUtilsBean.getInstance().resetBeanIntrospectors();
- }
-
/**
* Adds a {@code BeanIntrospector}. This object is invoked when the
* property descriptors of a class need to be obtained.
@@ -74,16 +51,16 @@ public class PropertyUtils {
}
/**
- * Removes the specified {@code BeanIntrospector}.
+ * Clear any cached property descriptors information for all classes
+ * loaded by any class loaders. This is useful in cases where class
+ * loaders are thrown away to implement class reloading.
*
- * @param introspector the {@code BeanIntrospector} to be removed
- * @return <b>true</b> if the {@code BeanIntrospector} existed and
- * could be removed, <b>false</b> otherwise
- * @since 1.9
+ * <p>For more details see {@code PropertyUtilsBean}.</p>
+ *
+ * @see PropertyUtilsBean#clearDescriptors
*/
- public static boolean removeBeanIntrospector(final BeanIntrospector introspector) {
- return PropertyUtilsBean.getInstance().removeBeanIntrospector(
- introspector);
+ public static void clearDescriptors() {
+ PropertyUtilsBean.getInstance().clearDescriptors();
}
/**
@@ -535,17 +512,39 @@ public class PropertyUtils {
return PropertyUtilsBean.getInstance().isWriteable(bean, name);
}
+ /**
+ * Removes the specified {@code BeanIntrospector}.
+ *
+ * @param introspector the {@code BeanIntrospector} to be removed
+ * @return <b>true</b> if the {@code BeanIntrospector} existed and
+ * could be removed, <b>false</b> otherwise
+ * @since 1.9
+ */
+ public static boolean removeBeanIntrospector(final BeanIntrospector introspector) {
+ return PropertyUtilsBean.getInstance().removeBeanIntrospector(
+ introspector);
+ }
+
+ /**
+ * Resets the registered {@link BeanIntrospector} objects to the initial default
+ * state.
+ *
+ * @since 1.9
+ */
+ public static void resetBeanIntrospectors() {
+ PropertyUtilsBean.getInstance().resetBeanIntrospectors();
+ }
+
/**
* <p>Sets the value of the specified indexed property of the specified
* bean, with no type conversions.</p>
*
* <p>For more details see {@code PropertyUtilsBean}.</p>
*
- * @param bean Bean whose property is to be modified
- * @param name {@code propertyname[index]} of the property value
- * to be modified
- * @param value Value to which the specified property element
- * should be set
+ * @param bean Bean whose property is to be set
+ * @param name Simple property name of the property value to be set
+ * @param index Index of the property value to be set
+ * @param value Value to which the indexed property element is to be set
*
* @throws IndexOutOfBoundsException if the specified index
* is outside the valid range for the underlying property
@@ -560,10 +559,10 @@ public class PropertyUtils {
* @see PropertyUtilsBean#setIndexedProperty(Object, String, Object)
*/
public static void setIndexedProperty(final Object bean, final String name,
- final Object value)
+ final int index, final Object value)
throws IllegalAccessException, InvocationTargetException,
NoSuchMethodException {
- PropertyUtilsBean.getInstance().setIndexedProperty(bean, name, value);
+ PropertyUtilsBean.getInstance().setIndexedProperty(bean, name, index, value);
}
/**
@@ -572,10 +571,11 @@ public class PropertyUtils {
*
* <p>For more details see {@code PropertyUtilsBean}.</p>
*
- * @param bean Bean whose property is to be set
- * @param name Simple property name of the property value to be set
- * @param index Index of the property value to be set
- * @param value Value to which the indexed property element is to be set
+ * @param bean Bean whose property is to be modified
+ * @param name {@code propertyname[index]} of the property value
+ * to be modified
+ * @param value Value to which the specified property element
+ * should be set
*
* @throws IndexOutOfBoundsException if the specified index
* is outside the valid range for the underlying property
@@ -590,10 +590,10 @@ public class PropertyUtils {
* @see PropertyUtilsBean#setIndexedProperty(Object, String, Object)
*/
public static void setIndexedProperty(final Object bean, final String name,
- final int index, final Object value)
+ final Object value)
throws IllegalAccessException, InvocationTargetException,
NoSuchMethodException {
- PropertyUtilsBean.getInstance().setIndexedProperty(bean, name, index, value);
+ PropertyUtilsBean.getInstance().setIndexedProperty(bean, name, value);
}
/**
diff --git a/src/main/java/org/apache/commons/beanutils2/PropertyUtilsBean.java b/src/main/java/org/apache/commons/beanutils2/PropertyUtilsBean.java
index 89159c19..076260af 100644
--- a/src/main/java/org/apache/commons/beanutils2/PropertyUtilsBean.java
+++ b/src/main/java/org/apache/commons/beanutils2/PropertyUtilsBean.java
@@ -89,7 +89,8 @@ import org.apache.commons.logging.LogFactory;
*/
public class PropertyUtilsBean {
- private Resolver resolver = new DefaultResolver();
+ /** Log instance */
+ private static final Log LOG = LogFactory.getLog(PropertyUtilsBean.class);
/**
* Gets the PropertyUtils bean instance.
@@ -99,15 +100,46 @@ public class PropertyUtilsBean {
return BeanUtilsBean.getInstance().getPropertyUtils();
}
+ /**
+ * Converts an object to a list of objects. This method is used when dealing
+ * with indexed properties. It assumes that indexed properties are stored as
+ * lists of objects.
+ *
+ * @param obj the object to be converted
+ * @return the resulting list of objects
+ */
+ private static List<Object> toObjectList(final Object obj) {
+ @SuppressWarnings("unchecked")
+ final
+ // indexed properties are stored in lists of objects
+ List<Object> list = (List<Object>) obj;
+ return list;
+ }
+ /**
+ * Converts an object to a map with property values. This method is used
+ * when dealing with mapped properties. It assumes that mapped properties
+ * are stored in a Map<String, Object>.
+ *
+ * @param obj the object to be converted
+ * @return the resulting properties map
+ */
+ private static Map<String, Object> toPropertyMap(final Object obj) {
+ @SuppressWarnings("unchecked")
+ final
+ // mapped properties are stores in maps of type <String, Object>
+ Map<String, Object> map = (Map<String, Object>) obj;
+ return map;
+ }
+
+ private Resolver resolver = new DefaultResolver();
+
/**
* The cache of PropertyDescriptor arrays for beans we have already
* introspected, keyed by the java.lang.Class of this object.
*/
private final WeakFastHashMap<Class<?>, BeanIntrospectionData> descriptorsCache;
- private final WeakFastHashMap<Class<?>, Map> mappedDescriptorsCache;
- /** Log instance */
- private static final Log LOG = LogFactory.getLog(PropertyUtilsBean.class);
+ private final WeakFastHashMap<Class<?>, Map> mappedDescriptorsCache;
/** The list with BeanIntrospector objects. */
private final List<BeanIntrospector> introspectors;
@@ -122,56 +154,6 @@ public class PropertyUtilsBean {
resetBeanIntrospectors();
}
- /**
- * Gets the configured {@link Resolver} implementation used by BeanUtils.
- * <p>
- * The {@link Resolver} handles the <i>property name</i>
- * expressions and the implementation in use effectively
- * controls the dialect of the <i>expression language</i>
- * that BeanUtils recognizes.
- * <p>
- * {@link DefaultResolver} is the default implementation used.
- *
- * @return resolver The property expression resolver.
- * @since 1.8.0
- */
- public Resolver getResolver() {
- return resolver;
- }
-
- /**
- * Configure the {@link Resolver} implementation used by BeanUtils.
- * <p>
- * The {@link Resolver} handles the <i>property name</i>
- * expressions and the implementation in use effectively
- * controls the dialect of the <i>expression language</i>
- * that BeanUtils recognizes.
- * <p>
- * {@link DefaultResolver} is the default implementation used.
- *
- * @param resolver The property expression resolver.
- * @since 1.8.0
- */
- public void setResolver(final Resolver resolver) {
- if (resolver == null) {
- this.resolver = new DefaultResolver();
- } else {
- this.resolver = resolver;
- }
- }
-
- /**
- * Resets the {@link BeanIntrospector} objects registered at this instance. After this
- * method was called, only the default {@code BeanIntrospector} is registered.
- *
- * @since 1.9
- */
- public final void resetBeanIntrospectors() {
- introspectors.clear();
- introspectors.add(DefaultBeanIntrospector.INSTANCE);
- introspectors.add(SuppressPropertiesBeanIntrospector.SUPPRESS_CLASS);
- }
-
/**
* Adds a {@code BeanIntrospector}. This object is invoked when the
* property descriptors of a class need to be obtained.
@@ -189,18 +171,6 @@ public class PropertyUtilsBean {
introspectors.add(introspector);
}
- /**
- * Removes the specified {@code BeanIntrospector}.
- *
- * @param introspector the {@code BeanIntrospector} to be removed
- * @return <b>true</b> if the {@code BeanIntrospector} existed and
- * could be removed, <b>false</b> otherwise
- * @since 1.9
- */
- public boolean removeBeanIntrospector(final BeanIntrospector introspector) {
- return introspectors.remove(introspector);
- }
-
/**
* Clear any cached property descriptors information for all classes
* loaded by any class loaders. This is useful in cases where class
@@ -361,6 +331,27 @@ public class PropertyUtilsBean {
return description;
}
+ /**
+ * Performs introspection on the specified class. This method invokes all {@code BeanIntrospector} objects that were
+ * added to this instance.
+ *
+ * @param beanClass the class to be inspected
+ * @return a data object with the results of introspection
+ */
+ private BeanIntrospectionData fetchIntrospectionData(final Class<?> beanClass) {
+ final DefaultIntrospectionContext ictx = new DefaultIntrospectionContext(beanClass);
+
+ for (final BeanIntrospector bi : introspectors) {
+ try {
+ bi.introspect(ictx);
+ } catch (final IntrospectionException iex) {
+ LOG.error("Exception during introspection", iex);
+ }
+ }
+
+ return new BeanIntrospectionData(ictx.getPropertyDescriptors());
+ }
+
/**
* Gets the value of the specified indexed property of the specified
* bean, with no type conversions. The zero-relative index of the
@@ -526,6 +517,30 @@ public class PropertyUtilsBean {
}
}
+ /**
+ * Obtains the {@code BeanIntrospectionData} object describing the specified bean
+ * class. This object is looked up in the internal cache. If necessary, introspection
+ * is performed now on the affected bean class, and the results object is created.
+ *
+ * @param beanClass the bean class in question
+ * @return the {@code BeanIntrospectionData} object for this class
+ * @throws IllegalArgumentException if the bean class is <b>null</b>
+ */
+ private BeanIntrospectionData getIntrospectionData(final Class<?> beanClass) {
+ if (beanClass == null) {
+ throw new IllegalArgumentException("No bean class specified");
+ }
+
+ // Look up any cached information for this bean class
+ BeanIntrospectionData data = descriptorsCache.get(beanClass);
+ if (data == null) {
+ data = fetchIntrospectionData(beanClass);
+ descriptorsCache.put(beanClass, data);
+ }
+
+ return data;
+ }
+
/**
* Gets the value of the specified mapped property of the
* specified bean, with no type conversions. The key of the
@@ -755,51 +770,6 @@ public class PropertyUtilsBean {
return bean;
}
- /**
- * This method is called by getNestedProperty and setNestedProperty to
- * define what it means to get a property from an object which implements
- * Map. See setPropertyOfMapBean for more information.
- *
- * @param bean Map bean
- * @param propertyName The property name
- * @return the property value
- *
- * @throws IllegalArgumentException when the propertyName is regarded as
- * being invalid.
- *
- * @throws IllegalAccessException just in case subclasses override this
- * method to try to access real getter methods and find permission is denied.
- *
- * @throws InvocationTargetException just in case subclasses override this
- * method to try to access real getter methods, and find it throws an
- * exception when invoked.
- *
- * @throws NoSuchMethodException just in case subclasses override this
- * method to try to access real getter methods, and want to fail if
- * no simple method is available.
- * @since 1.8.0
- */
- protected Object getPropertyOfMapBean(final Map<?, ?> bean, String propertyName)
- throws IllegalArgumentException, IllegalAccessException,
- InvocationTargetException, NoSuchMethodException {
-
- if (resolver.isMapped(propertyName)) {
- final String name = resolver.getProperty(propertyName);
- if (name == null || name.isEmpty()) {
- propertyName = resolver.getKey(propertyName);
- }
- }
-
- if (resolver.isIndexed(propertyName) ||
- resolver.isMapped(propertyName)) {
- throw new IllegalArgumentException(
- "Indexed or mapped properties are not supported on"
- + " objects of type Map: " + propertyName);
- }
-
- return bean.get(propertyName);
- }
-
/**
* Gets the value of the specified property of the specified bean,
* no matter which property reference format is used, with no
@@ -1003,6 +973,51 @@ public class PropertyUtilsBean {
return null;
}
+ /**
+ * This method is called by getNestedProperty and setNestedProperty to
+ * define what it means to get a property from an object which implements
+ * Map. See setPropertyOfMapBean for more information.
+ *
+ * @param bean Map bean
+ * @param propertyName The property name
+ * @return the property value
+ *
+ * @throws IllegalArgumentException when the propertyName is regarded as
+ * being invalid.
+ *
+ * @throws IllegalAccessException just in case subclasses override this
+ * method to try to access real getter methods and find permission is denied.
+ *
+ * @throws InvocationTargetException just in case subclasses override this
+ * method to try to access real getter methods, and find it throws an
+ * exception when invoked.
+ *
+ * @throws NoSuchMethodException just in case subclasses override this
+ * method to try to access real getter methods, and want to fail if
+ * no simple method is available.
+ * @since 1.8.0
+ */
+ protected Object getPropertyOfMapBean(final Map<?, ?> bean, String propertyName)
+ throws IllegalArgumentException, IllegalAccessException,
+ InvocationTargetException, NoSuchMethodException {
+
+ if (resolver.isMapped(propertyName)) {
+ final String name = resolver.getProperty(propertyName);
+ if (name == null || name.isEmpty()) {
+ propertyName = resolver.getKey(propertyName);
+ }
+ }
+
+ if (resolver.isIndexed(propertyName) ||
+ resolver.isMapped(propertyName)) {
+ throw new IllegalArgumentException(
+ "Indexed or mapped properties are not supported on"
+ + " objects of type Map: " + propertyName);
+ }
+
+ return bean.get(propertyName);
+ }
+
/**
* Gets the Java Class representing the property type of the specified
* property, or {@code null} if there is no such property for the
@@ -1101,11 +1116,12 @@ public class PropertyUtilsBean {
*
* <p><strong>FIXME</strong> - Does not work with DynaBeans.</p>
*
+ * @param clazz The class of the read method will be invoked on
* @param descriptor Property descriptor to return a getter for
* @return The read method
*/
- public Method getReadMethod(final PropertyDescriptor descriptor) {
- return MethodUtils.getAccessibleMethod(descriptor.getReadMethod());
+ Method getReadMethod(final Class<?> clazz, final PropertyDescriptor descriptor) {
+ return MethodUtils.getAccessibleMethod(clazz, descriptor.getReadMethod());
}
/**
@@ -1114,24 +1130,40 @@ public class PropertyUtilsBean {
*
* <p><strong>FIXME</strong> - Does not work with DynaBeans.</p>
*
- * @param clazz The class of the read method will be invoked on
* @param descriptor Property descriptor to return a getter for
* @return The read method
*/
- Method getReadMethod(final Class<?> clazz, final PropertyDescriptor descriptor) {
- return MethodUtils.getAccessibleMethod(clazz, descriptor.getReadMethod());
+ public Method getReadMethod(final PropertyDescriptor descriptor) {
+ return MethodUtils.getAccessibleMethod(descriptor.getReadMethod());
}
/**
- * Gets the value of the specified simple property of the specified
- * bean, with no type conversions.
- *
- * @param bean Bean whose property is to be extracted
- * @param name Name of the property to be extracted
- * @return The property value
- *
- * @throws IllegalAccessException if the caller does not have
- * access to the property accessor method
+ * Gets the configured {@link Resolver} implementation used by BeanUtils.
+ * <p>
+ * The {@link Resolver} handles the <i>property name</i>
+ * expressions and the implementation in use effectively
+ * controls the dialect of the <i>expression language</i>
+ * that BeanUtils recognizes.
+ * <p>
+ * {@link DefaultResolver} is the default implementation used.
+ *
+ * @return resolver The property expression resolver.
+ * @since 1.8.0
+ */
+ public Resolver getResolver() {
+ return resolver;
+ }
+
+ /**
+ * Gets the value of the specified simple property of the specified
+ * bean, with no type conversions.
+ *
+ * @param bean Bean whose property is to be extracted
+ * @param name Name of the property to be extracted
+ * @return The property value
+ *
+ * @throws IllegalAccessException if the caller does not have
+ * access to the property accessor method
* @throws IllegalArgumentException if {@code bean} or
* {@code name} is null
* @throws IllegalArgumentException if the property name
@@ -1202,35 +1234,94 @@ public class PropertyUtilsBean {
* <p>Return an accessible property setter method for this property,
* if there is one; otherwise return {@code null}.</p>
*
- * <p><em>Note:</em> This method does not work correctly with custom bean
- * introspection under certain circumstances. It may return {@code null}
- * even if a write method is defined for the property in question. Use
- * {@link #getWriteMethod(Class, PropertyDescriptor)} to be sure that the
- * correct result is returned.</p>
* <p><strong>FIXME</strong> - Does not work with DynaBeans.</p>
*
+ * @param clazz The class of the read method will be invoked on
* @param descriptor Property descriptor to return a setter for
* @return The write method
+ * @since 1.9.1
*/
- public Method getWriteMethod(final PropertyDescriptor descriptor) {
- return MethodUtils.getAccessibleMethod(descriptor.getWriteMethod());
+ public Method getWriteMethod(final Class<?> clazz, final PropertyDescriptor descriptor) {
+ final BeanIntrospectionData data = getIntrospectionData(clazz);
+ return MethodUtils.getAccessibleMethod(clazz,
+ data.getWriteMethod(clazz, descriptor));
}
/**
* <p>Return an accessible property setter method for this property,
* if there is one; otherwise return {@code null}.</p>
*
+ * <p><em>Note:</em> This method does not work correctly with custom bean
+ * introspection under certain circumstances. It may return {@code null}
+ * even if a write method is defined for the property in question. Use
+ * {@link #getWriteMethod(Class, PropertyDescriptor)} to be sure that the
+ * correct result is returned.</p>
* <p><strong>FIXME</strong> - Does not work with DynaBeans.</p>
*
- * @param clazz The class of the read method will be invoked on
* @param descriptor Property descriptor to return a setter for
* @return The write method
- * @since 1.9.1
*/
- public Method getWriteMethod(final Class<?> clazz, final PropertyDescriptor descriptor) {
- final BeanIntrospectionData data = getIntrospectionData(clazz);
- return MethodUtils.getAccessibleMethod(clazz,
- data.getWriteMethod(clazz, descriptor));
+ public Method getWriteMethod(final PropertyDescriptor descriptor) {
+ return MethodUtils.getAccessibleMethod(descriptor.getWriteMethod());
+ }
+
+ /** This just catches and wraps IllegalArgumentException. */
+ private Object invokeMethod(
+ final Method method,
+ final Object bean,
+ final Object[] values)
+ throws
+ IllegalAccessException,
+ InvocationTargetException {
+ if (bean == null) {
+ throw new IllegalArgumentException("No bean specified " +
+ "- this should have been checked before reaching this method");
+ }
+
+ try {
+
+ return method.invoke(bean, values);
+
+ } catch (final NullPointerException | IllegalArgumentException cause) {
+ // JDK 1.3 and JDK 1.4 throw NullPointerException if an argument is
+ // null for a primitive value (JDK 1.5+ throw IllegalArgumentException)
+ final StringBuilder valueString = new StringBuilder();
+ if (values != null) {
+ for (int i = 0; i < values.length; i++) {
+ if (i>0) {
+ valueString.append(", ");
+ }
+ if (values[i] == null) {
+ valueString.append("<null>");
+ } else {
+ valueString.append(values[i].getClass().getName());
+ }
+ }
+ }
+ final StringBuilder expectedString = new StringBuilder();
+ final Class<?>[] parTypes = method.getParameterTypes();
+ if (parTypes != null) {
+ for (int i = 0; i < parTypes.length; i++) {
+ if (i > 0) {
+ expectedString.append(", ");
+ }
+ expectedString.append(parTypes[i].getName());
+ }
+ }
+ final IllegalArgumentException e = new IllegalArgumentException(
+ "Cannot invoke " + method.getDeclaringClass().getName() + "."
+ + method.getName() + " on bean class '" + bean.getClass() +
+ "' - " + cause.getMessage()
+ // as per https://issues.apache.org/jira/browse/BEANUTILS-224
+ + " - had objects of type \"" + valueString
+ + "\" but expected signature \""
+ + expectedString + "\""
+ );
+ if (!BeanUtils.initCause(e, cause)) {
+ LOG.error("Method invocation failed", cause);
+ }
+ throw e;
+ }
}
/**
@@ -1390,60 +1481,27 @@ public class PropertyUtilsBean {
}
/**
- * Sets the value of the specified indexed property of the specified
- * bean, with no type conversions. The zero-relative index of the
- * required value must be included (in square brackets) as a suffix to
- * the property name, or {@code IllegalArgumentException} will be
- * thrown. In addition to supporting the JavaBeans specification, this
- * method has been extended to support {@code List} objects as well.
- *
- * @param bean Bean whose property is to be modified
- * @param name {@code propertyname[index]} of the property value
- * to be modified
- * @param value Value to which the specified property element
- * should be set
+ * Removes the specified {@code BeanIntrospector}.
*
- * @throws IndexOutOfBoundsException if the specified index
- * is outside the valid range for the underlying property
- * @throws IllegalAccessException if the caller does not have
- * access to the property accessor method
- * @throws IllegalArgumentException if {@code bean} or
- * {@code name} is null
- * @throws InvocationTargetException if the property accessor method
- * throws an exception
- * @throws NoSuchMethodException if an accessor method for this
- * property cannot be found
+ * @param introspector the {@code BeanIntrospector} to be removed
+ * @return <b>true</b> if the {@code BeanIntrospector} existed and
+ * could be removed, <b>false</b> otherwise
+ * @since 1.9
*/
- public void setIndexedProperty(final Object bean, String name,
- final Object value)
- throws IllegalAccessException, InvocationTargetException,
- NoSuchMethodException {
- if (bean == null) {
- throw new IllegalArgumentException("No bean specified");
- }
- if (name == null) {
- throw new IllegalArgumentException("No name specified for bean class '" +
- bean.getClass() + "'");
- }
-
- // Identify the index of the requested individual property
- int index = -1;
- try {
- index = resolver.getIndex(name);
- } catch (final IllegalArgumentException e) {
- throw new IllegalArgumentException("Invalid indexed property '" +
- name + "' on bean class '" + bean.getClass() + "'");
- }
- if (index < 0) {
- throw new IllegalArgumentException("Invalid indexed property '" +
- name + "' on bean class '" + bean.getClass() + "'");
- }
-
- // Isolate the name
- name = resolver.getProperty(name);
+ public boolean removeBeanIntrospector(final BeanIntrospector introspector) {
+ return introspectors.remove(introspector);
+ }
- // Set the specified indexed property value
- setIndexedProperty(bean, name, index, value);
+ /**
+ * Resets the {@link BeanIntrospector} objects registered at this instance. After this
+ * method was called, only the default {@code BeanIntrospector} is registered.
+ *
+ * @since 1.9
+ */
+ public final void resetBeanIntrospectors() {
+ introspectors.clear();
+ introspectors.add(DefaultBeanIntrospector.INSTANCE);
+ introspectors.add(SuppressPropertiesBeanIntrospector.SUPPRESS_CLASS);
}
/**
@@ -1566,6 +1624,63 @@ public class PropertyUtilsBean {
}
}
+ /**
+ * Sets the value of the specified indexed property of the specified
+ * bean, with no type conversions. The zero-relative index of the
+ * required value must be included (in square brackets) as a suffix to
+ * the property name, or {@code IllegalArgumentException} will be
+ * thrown. In addition to supporting the JavaBeans specification, this
+ * method has been extended to support {@code List} objects as well.
+ *
+ * @param bean Bean whose property is to be modified
+ * @param name {@code propertyname[index]} of the property value
+ * to be modified
+ * @param value Value to which the specified property element
+ * should be set
+ *
+ * @throws IndexOutOfBoundsException if the specified index
+ * is outside the valid range for the underlying property
+ * @throws IllegalAccessException if the caller does not have
+ * access to the property accessor method
+ * @throws IllegalArgumentException if {@code bean} or
+ * {@code name} is null
+ * @throws InvocationTargetException if the property accessor method
+ * throws an exception
+ * @throws NoSuchMethodException if an accessor method for this
+ * property cannot be found
+ */
+ public void setIndexedProperty(final Object bean, String name,
+ final Object value)
+ throws IllegalAccessException, InvocationTargetException,
+ NoSuchMethodException {
+ if (bean == null) {
+ throw new IllegalArgumentException("No bean specified");
+ }
+ if (name == null) {
+ throw new IllegalArgumentException("No name specified for bean class '" +
+ bean.getClass() + "'");
+ }
+
+ // Identify the index of the requested individual property
+ int index = -1;
+ try {
+ index = resolver.getIndex(name);
+ } catch (final IllegalArgumentException e) {
+ throw new IllegalArgumentException("Invalid indexed property '" +
+ name + "' on bean class '" + bean.getClass() + "'");
+ }
+ if (index < 0) {
+ throw new IllegalArgumentException("Invalid indexed property '" +
+ name + "' on bean class '" + bean.getClass() + "'");
+ }
+
+ // Isolate the name
+ name = resolver.getProperty(name);
+
+ // Set the specified indexed property value
+ setIndexedProperty(bean, name, index, value);
+ }
+
/**
* Sets the value of the specified mapped property of the
* specified bean, with no type conversions. The key of the
@@ -1786,6 +1901,31 @@ public class PropertyUtilsBean {
}
}
+ /**
+ * Sets the value of the specified property of the specified bean,
+ * no matter which property reference format is used, with no
+ * type conversions.
+ *
+ * @param bean Bean whose property is to be modified
+ * @param name Possibly indexed and/or nested name of the property
+ * to be modified
+ * @param value Value to which this property is to be set
+ *
+ * @throws IllegalAccessException if the caller does not have
+ * access to the property accessor method
+ * @throws IllegalArgumentException if {@code bean} or
+ * {@code name} is null
+ * @throws InvocationTargetException if the property accessor method
+ * throws an exception
+ * @throws NoSuchMethodException if an accessor method for this
+ * property cannot be found
+ */
+ public void setProperty(final Object bean, final String name, final Object value)
+ throws IllegalAccessException, InvocationTargetException,
+ NoSuchMethodException {
+ setNestedProperty(bean, name, value);
+ }
+
/**
* This method is called by method setNestedProperty when the current bean
* is found to be a Map object, and defines how to deal with setting
@@ -1863,28 +2003,24 @@ public class PropertyUtilsBean {
}
/**
- * Sets the value of the specified property of the specified bean,
- * no matter which property reference format is used, with no
- * type conversions.
- *
- * @param bean Bean whose property is to be modified
- * @param name Possibly indexed and/or nested name of the property
- * to be modified
- * @param value Value to which this property is to be set
+ * Configure the {@link Resolver} implementation used by BeanUtils.
+ * <p>
+ * The {@link Resolver} handles the <i>property name</i>
+ * expressions and the implementation in use effectively
+ * controls the dialect of the <i>expression language</i>
+ * that BeanUtils recognizes.
+ * <p>
+ * {@link DefaultResolver} is the default implementation used.
*
- * @throws IllegalAccessException if the caller does not have
- * access to the property accessor method
- * @throws IllegalArgumentException if {@code bean} or
- * {@code name} is null
- * @throws InvocationTargetException if the property accessor method
- * throws an exception
- * @throws NoSuchMethodException if an accessor method for this
- * property cannot be found
+ * @param resolver The property expression resolver.
+ * @since 1.8.0
*/
- public void setProperty(final Object bean, final String name, final Object value)
- throws IllegalAccessException, InvocationTargetException,
- NoSuchMethodException {
- setNestedProperty(bean, name, value);
+ public void setResolver(final Resolver resolver) {
+ if (resolver == null) {
+ this.resolver = new DefaultResolver();
+ } else {
+ this.resolver = resolver;
+ }
}
/**
@@ -1958,140 +2094,4 @@ public class PropertyUtilsBean {
}
invokeMethod(writeMethod, bean, values);
}
-
- /** This just catches and wraps IllegalArgumentException. */
- private Object invokeMethod(
- final Method method,
- final Object bean,
- final Object[] values)
- throws
- IllegalAccessException,
- InvocationTargetException {
- if (bean == null) {
- throw new IllegalArgumentException("No bean specified " +
- "- this should have been checked before reaching this method");
- }
-
- try {
-
- return method.invoke(bean, values);
-
- } catch (final NullPointerException | IllegalArgumentException cause) {
- // JDK 1.3 and JDK 1.4 throw NullPointerException if an argument is
- // null for a primitive value (JDK 1.5+ throw IllegalArgumentException)
- final StringBuilder valueString = new StringBuilder();
- if (values != null) {
- for (int i = 0; i < values.length; i++) {
- if (i>0) {
- valueString.append(", ");
- }
- if (values[i] == null) {
- valueString.append("<null>");
... 25799 lines suppressed ...