aboutsummaryrefslogtreecommitdiffstats
path: root/utils/src
diff options
context:
space:
mode:
authorLiam Fallon <liam.fallon@est.tech>2019-03-05 21:03:06 +0000
committerGerrit Code Review <gerrit@onap.org>2019-03-05 21:03:06 +0000
commit23a3dc4ece2f1533fe1d6b627b5db05e7754a70c (patch)
tree007a918e60dfe15b022a5f637af2270eaa379b4d /utils/src
parentb25889ce1871ca2ad4f231666fbeaa6de6598a5c (diff)
parentfabde091ed5589552885265f8ed91bc1d72a9a6e (diff)
Merge "Add bean configurator"
Diffstat (limited to 'utils/src')
-rw-r--r--utils/src/main/java/org/onap/policy/common/utils/properties/BeanConfigurator.java567
-rw-r--r--utils/src/main/java/org/onap/policy/common/utils/properties/Property.java59
-rw-r--r--utils/src/test/java/org/onap/policy/common/utils/properties/BeanConfiguratorTest.java1424
3 files changed, 2050 insertions, 0 deletions
diff --git a/utils/src/main/java/org/onap/policy/common/utils/properties/BeanConfigurator.java b/utils/src/main/java/org/onap/policy/common/utils/properties/BeanConfigurator.java
new file mode 100644
index 00000000..9d02819a
--- /dev/null
+++ b/utils/src/main/java/org/onap/policy/common/utils/properties/BeanConfigurator.java
@@ -0,0 +1,567 @@
+/*
+ * ============LICENSE_START=======================================================
+ * ONAP - Common Modules
+ * ================================================================================
+ * Copyright (C) 2019 AT&T Intellectual Property. All rights reserved.
+ * ================================================================================
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ============LICENSE_END=========================================================
+ */
+
+package org.onap.policy.common.utils.properties;
+
+import java.lang.reflect.Field;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.lang.reflect.Modifier;
+import java.util.Properties;
+import org.apache.commons.lang3.StringUtils;
+import org.onap.policy.common.utils.properties.exception.PropertyAccessException;
+import org.onap.policy.common.utils.properties.exception.PropertyException;
+import org.onap.policy.common.utils.properties.exception.PropertyInvalidException;
+import org.onap.policy.common.utils.properties.exception.PropertyMissingException;
+
+/**
+ * Configurator for beans whose fields are initialized by reading from a set of
+ * {@link Properties}, as directed by the {@link Property} annotations that appear on
+ * fields within the bean. The values of the fields are set via <i>setXxx()</i> methods.
+ * As a result, if a field is annotated and there is no corresponding <i>setXxx()</i>
+ * method, then an exception will be thrown.
+ *
+ * <p>It is possible that an invalid <i>defaultValue</i> is specified via the
+ * {@link Property} annotation. This could remain undetected until an optional property is
+ * left out of the {@link Properties}. Consequently, this class will always validate a
+ * {@link Property}'s default value, if the <i>defaultValue</i> is not empty or if
+ * <i>accept</i> includes the "empty" option.
+ */
+public class BeanConfigurator {
+
+ /**
+ * The "empty" option that may appear within the {@link Property}'s <i>accept</i>
+ * attribute.
+ */
+ public static final String ACCEPT_EMPTY = "empty";
+
+ /**
+ * Walks the class hierarchy of the bean, populating fields defined in each class,
+ * using values extracted from the given property set.
+ *
+ * @param bean bean whose fields are to be configured from the properties
+ * @param props properties from which to extract the values
+ * @throws PropertyException if an error occurs
+ */
+ public <T> T configureFromProperties(T bean, Properties props) throws PropertyException {
+ Class<?> clazz = bean.getClass();
+
+ while (clazz != Object.class) {
+ for (Field field : clazz.getDeclaredFields()) {
+ setValue(bean, field, props);
+ }
+
+ clazz = clazz.getSuperclass();
+ }
+
+ return bean;
+ }
+
+ /**
+ * Sets a field's value, within an object, based on what's in the properties.
+ *
+ * @param bean bean whose fields are to be configured from the properties
+ * @param field field whose value is to be set
+ * @param props properties from which to get the value
+ * @return {@code true} if the property's value was set, {@code false} otherwise
+ * @throws PropertyException if an error occurs
+ */
+ protected boolean setValue(Object bean, Field field, Properties props) throws PropertyException {
+ Property prop = field.getAnnotation(Property.class);
+ if (prop == null) {
+ return false;
+ }
+
+ checkModifiable(field, prop);
+
+ Method setter = getSetter(field, prop);
+ checkMethod(setter, prop);
+
+ if (setValue(bean, setter, field, props, prop)) {
+ return true;
+ }
+
+ throw new PropertyAccessException(prop.name(), field.getName(), "unsupported field type");
+ }
+
+ /**
+ * Sets a field's value from a particular property.
+ *
+ * @param bean bean whose fields are to be configured from the properties
+ * @param setter method to be used to set the field's value
+ * @param field field whose value is to be set
+ * @param props properties from which to get the value
+ * @param prop property of interest
+ * @return {@code true} if the property's value was set, {@code false} otherwise
+ * @throws PropertyException if an error occurs
+ */
+ protected boolean setValue(Object bean, Method setter, Field field, Properties props, Property prop)
+ throws PropertyException {
+
+ try {
+ Object val = getValue(field, props, prop);
+ if (val == null) {
+ return false;
+
+ } else {
+ setter.invoke(bean, val);
+ return true;
+ }
+
+ } catch (IllegalArgumentException e) {
+ throw new PropertyInvalidException(prop.name(), field.getName(), e);
+
+ } catch (IllegalAccessException | InvocationTargetException e) {
+ throw new PropertyAccessException(prop.name(), setter.getName(), e);
+ }
+ }
+
+ /**
+ * Get the setter.
+ *
+ * @param field field whose value is to be set
+ * @param prop property of interest
+ * @return the method to be used to set the field's value
+ * @throws PropertyAccessException if a "set" method cannot be identified
+ */
+ private Method getSetter(Field field, Property prop) throws PropertyAccessException {
+ String nm = "set" + StringUtils.capitalize(field.getName());
+
+ try {
+ return field.getDeclaringClass().getMethod(nm, field.getType());
+
+ } catch (NoSuchMethodException | SecurityException e) {
+ throw new PropertyAccessException(prop.name(), nm, e);
+ }
+ }
+
+ /**
+ * Gets a property value, coercing it to the field's type.
+ *
+ * @param field field whose value is to be set
+ * @param props properties from which to get the value
+ * @param prop property of interest
+ * @return the value extracted from the property, or {@code null} if the field type is
+ * not supported
+ * @throws PropertyException if an error occurs
+ */
+ protected Object getValue(Field field, Properties props, Property prop) throws PropertyException {
+
+ Class<?> clazz = field.getType();
+ String fieldName = field.getName();
+
+ // can still add support for short, float, double, enum
+
+ if (clazz == String.class) {
+ return getStringValue(fieldName, props, prop);
+
+ } else if (clazz == Boolean.class || clazz == boolean.class) {
+ return getBooleanValue(fieldName, props, prop);
+
+ } else if (clazz == Integer.class || clazz == int.class) {
+ return getIntegerValue(fieldName, props, prop);
+
+ } else if (clazz == Long.class || clazz == long.class) {
+ return getLongValue(fieldName, props, prop);
+
+ } else {
+ return null;
+ }
+ }
+
+ /**
+ * Verifies that the field can be modified, i.e., it's neither <i>static</i>, nor
+ * <i>final</i>.
+ *
+ * @param field field whose value is to be set
+ * @param prop property of interest
+ * @throws PropertyAccessException if the field is not modifiable
+ */
+ protected void checkModifiable(Field field, Property prop) throws PropertyAccessException {
+ int mod = field.getModifiers();
+
+ if (Modifier.isStatic(mod)) {
+ throw new PropertyAccessException(prop.name(), field.getName(), "'static' variable cannot be modified");
+ }
+
+ if (Modifier.isFinal(mod)) {
+ throw new PropertyAccessException(prop.name(), field.getName(), "'final' variable cannot be modified");
+ }
+ }
+
+ /**
+ * Verifies that the method is not <i>static</i>.
+ *
+ * @param method method to be checked
+ * @param prop property of interest
+ * @throws PropertyAccessException if the method is static
+ */
+ private void checkMethod(Method method, Property prop) throws PropertyAccessException {
+ int mod = method.getModifiers();
+
+ if (Modifier.isStatic(mod)) {
+ throw new PropertyAccessException(prop.name(), method.getName(), "method is 'static'");
+ }
+ }
+
+ /**
+ * Gets a property value, coercing it to a String.
+ *
+ * @param fieldName field whose value is to be set
+ * @param props properties from which to get the value
+ * @param prop property of interest
+ * @return the value extracted from the property
+ * @throws PropertyException if an error occurs
+ */
+ protected String getStringValue(String fieldName, Properties props, Property prop) throws PropertyException {
+
+ /*
+ * Note: the default value for a String type is always valid, thus no need to
+ * check it.
+ */
+
+ return getPropValue(fieldName, props, prop);
+ }
+
+ /**
+ * Gets a property value, coercing it to a Boolean.
+ *
+ * @param fieldName field whose value is to be set
+ * @param props properties from which to get the value
+ * @param prop property of interest
+ * @return the value extracted from the property
+ * @throws PropertyException if an error occurs
+ */
+ protected Boolean getBooleanValue(String fieldName, Properties props, Property prop) throws PropertyException {
+ // validate the default value
+ checkDefaultValue(fieldName, prop, () -> makeBoolean(fieldName, prop, prop.defaultValue()));
+
+ return makeBoolean(fieldName, prop, getPropValue(fieldName, props, prop));
+ }
+
+ /**
+ * Gets a property value, coercing it to an Integer.
+ *
+ * @param fieldName field whose value is to be set
+ * @param props properties from which to get the value
+ * @param prop property of interest
+ * @return the value extracted from the property
+ * @throws PropertyException if an error occurs
+ */
+ protected Integer getIntegerValue(String fieldName, Properties props, Property prop) throws PropertyException {
+ // validate the default value
+ checkDefaultValue(fieldName, prop, () -> makeInteger(fieldName, prop, prop.defaultValue()));
+
+ return makeInteger(fieldName, prop, getPropValue(fieldName, props, prop));
+ }
+
+ /**
+ * Gets a property value, coercing it to a Long.
+ *
+ * @param fieldName field whose value is to be set
+ * @param props properties from which to get the value
+ * @param prop property of interest
+ * @return the value extracted from the property
+ * @throws PropertyException if an error occurs
+ */
+ protected Long getLongValue(String fieldName, Properties props, Property prop) throws PropertyException {
+ // validate the default value
+ checkDefaultValue(fieldName, prop, () -> makeLong(fieldName, prop, prop.defaultValue()));
+
+ return makeLong(fieldName, prop, getPropValue(fieldName, props, prop));
+ }
+
+ /**
+ * Gets a value from the property set.
+ *
+ * @param fieldName field whose value is to be set
+ * @param props properties from which to get the value
+ * @param prop property of interest
+ * @return the value extracted from the property, or the <i>defaultValue</i> if the
+ * value does not exist
+ * @throws PropertyMissingException if the property does not exist and the
+ * <i>defaultValue</i> is empty and <i>emptyOk</i> is {@code false}
+ */
+ protected String getPropValue(String fieldName, Properties props, Property prop) throws PropertyMissingException {
+ String propnm = prop.name();
+
+ String val = props.getProperty(propnm);
+ if (val != null && isEmptyOk(prop, val)) {
+ return val;
+ }
+
+ val = prop.defaultValue();
+ if (val != null && isEmptyOk(prop, val)) {
+ return val;
+ }
+
+ throw new PropertyMissingException(prop.name(), fieldName);
+ }
+
+ /**
+ * Coerces a String value into a Boolean.
+ *
+ * @param fieldName field whose value is to be set
+ * @param prop property of interest
+ * @param value value to be coerced
+ * @return the Boolean value represented by the String value
+ * @throws PropertyInvalidException if the value does not represent a valid Boolean
+ */
+ private Boolean makeBoolean(String fieldName, Property prop, String value) throws PropertyInvalidException {
+ if ("true".equalsIgnoreCase(value)) {
+ return Boolean.TRUE;
+
+ } else if ("false".equalsIgnoreCase(value)) {
+ return Boolean.FALSE;
+
+ } else {
+ throw new PropertyInvalidException(prop.name(), fieldName, "expecting 'true' or 'false'");
+ }
+ }
+
+ /**
+ * Coerces a String value into an Integer.
+ *
+ * @param fieldName field whose value is to be set
+ * @param prop property of interest
+ * @param value value to be coerced
+ * @return the Integer value represented by the String value
+ * @throws PropertyInvalidException if the value does not represent a valid Integer
+ */
+ private Integer makeInteger(String fieldName, Property prop, String value) throws PropertyInvalidException {
+ try {
+ return Integer.valueOf(value);
+
+ } catch (NumberFormatException e) {
+ throw new PropertyInvalidException(prop.name(), fieldName, e);
+ }
+ }
+
+ /**
+ * Coerces a String value into a Long.
+ *
+ * @param fieldName field whose value is to be set
+ * @param prop property of interest
+ * @param value value to be coerced
+ * @return the Long value represented by the String value
+ * @throws PropertyInvalidException if the value does not represent a valid Long
+ */
+ private Long makeLong(String fieldName, Property prop, String value) throws PropertyInvalidException {
+ try {
+ return Long.valueOf(value);
+
+ } catch (NumberFormatException e) {
+ throw new PropertyInvalidException(prop.name(), fieldName, e);
+ }
+ }
+
+ /**
+ * Applies a function to check a property's default value. If the function throws an
+ * exception about an invalid property, then it's re-thrown as an exception about an
+ * invalid <i>defaultValue</i>.
+ *
+ * @param fieldName name of the field being checked
+ * @param prop property of interest
+ * @param func function to invoke to check the default value
+ */
+ private void checkDefaultValue(String fieldName, Property prop, CheckDefaultValueFunction func)
+ throws PropertyInvalidException {
+
+ if (isEmptyOk(prop, prop.defaultValue())) {
+ try {
+ func.apply();
+
+ } catch (PropertyInvalidException ex) {
+ throw new PropertyInvalidException(ex.getPropertyName(), fieldName, "defaultValue is invalid", ex);
+ }
+ }
+ }
+
+ /**
+ * Determines if a value is OK, even if it's empty.
+ *
+ * @param prop property specifying what's acceptable
+ * @param value value to be checked
+ * @return {@code true} if the value is not empty or empty is allowed, {@code false}
+ * otherwise
+ */
+ protected boolean isEmptyOk(Property prop, String value) {
+ return !value.isEmpty() || isEmptyOk(prop);
+ }
+
+ /**
+ * Determines if a {@link Property}'s <i>accept</i> attribute includes the "empty"
+ * option.
+ *
+ * @param prop property whose <i>accept</i> attribute is to be examined
+ * @return {@code true} if the <i>accept</i> attribute includes "empty"
+ */
+ protected boolean isEmptyOk(Property prop) {
+ for (String option : prop.accept().split(",")) {
+ if (ACCEPT_EMPTY.equals(option)) {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ /**
+ * Copies the field values to a set of properties.
+ *
+ * @param bean bean whose fields are to be exported to the properties
+ * @param props properties into which the values should be added
+ * @param origPrefix prefix of the property names as they appear within the Property
+ * annotations
+ * @param finalPrefix prefix to use instead, when adding to the set of properties
+ * @throws PropertyException if an error occurs
+ */
+ public void addToProperties(Object bean, Properties props, String origPrefix, String finalPrefix)
+ throws PropertyException {
+
+ Class<?> clazz = bean.getClass();
+
+ String dottedOrig = (origPrefix.isEmpty() || origPrefix.endsWith(".") ? origPrefix : origPrefix + ".");
+ String dottedFinal = (finalPrefix.isEmpty() || finalPrefix.endsWith(".") ? finalPrefix : finalPrefix + ".");
+
+ while (clazz != Object.class) {
+ for (Field field : clazz.getDeclaredFields()) {
+ putProperty(bean, field, props, dottedOrig, dottedFinal);
+ }
+
+ clazz = clazz.getSuperclass();
+ }
+ }
+
+ /**
+ * Copies the field values to a set of properties.
+ *
+ * @param bean bean whose fields are to be exported to the properties
+ * @param field field whose value is to be copied
+ * @param props properties into which the values should be added
+ * @param origPrefix prefix of the property names as they appear within the Property
+ * annotations. Includes a trailing "." (if non-empty)
+ * @param finalPrefix prefix to use instead, when adding to the set of properties.
+ * Includes a trailing "." (if non-empty)
+ * @throws PropertyAccessException if an error occurs
+ */
+ private void putProperty(Object bean, Field field, Properties props, String origPrefix, String finalPrefix)
+ throws PropertyAccessException {
+
+ Property prop = field.getAnnotation(Property.class);
+ if (prop == null) {
+ return;
+ }
+
+ Method getter = getGetter(field, prop);
+ checkMethod(getter, prop);
+
+ Object value = getBeanValue(bean, field, getter, prop);
+ if (value == null) {
+ return;
+ }
+
+ String name = prop.name();
+ if (name.startsWith(origPrefix)) {
+ name = finalPrefix + name.substring(origPrefix.length());
+ }
+
+ props.setProperty(name, value.toString());
+ }
+
+ /**
+ * Get the getter.
+ *
+ * @param field field whose value is to be gotten
+ * @param prop property of interest
+ * @return the method to be used to get the field's value
+ * @throws PropertyAccessException if a "get" method cannot be identified
+ */
+ private Method getGetter(Field field, Property prop) throws PropertyAccessException {
+ String capnm = StringUtils.capitalize(field.getName());
+
+ try {
+ return getGetter(field, "get" + capnm);
+
+ } catch (NoSuchMethodException e) {
+ if (field.getType() == Boolean.class || field.getType() == boolean.class) {
+ // boolean - check for "isXxx" method, too
+ try {
+ return getGetter(field, "is" + capnm);
+
+ } catch (NoSuchMethodException | SecurityException e2) {
+ throw new PropertyAccessException(prop.name(), "is" + capnm, e2);
+ }
+ }
+
+ throw new PropertyAccessException(prop.name(), "get" + capnm, e);
+
+ } catch (SecurityException e) {
+ // problem with "get" method
+ throw new PropertyAccessException(prop.name(), "get" + capnm, e);
+ }
+ }
+
+ /**
+ * Get the getter. This may be overridden by junit tests.
+ *
+ * @param field field whose value is to be gotten
+ * @param methodName name of the method to return
+ * @return the method to be used to get the field's value
+ * @throws NoSuchMethodException if the method does not exist
+ * @throws SecurityException if the method cannot be accessed
+ */
+ protected Method getGetter(Field field, String methodName) throws NoSuchMethodException, SecurityException {
+ return field.getDeclaringClass().getMethod(methodName);
+ }
+
+ /**
+ * Gets a field's value for a particular property.
+ *
+ * @param bean bean whose fields are to be configured from the properties
+ * @param getter method to be used to get the field's value
+ * @param field field whose value is to be gotten
+ * @param prop property of interest
+ * @throws PropertyAccessException if an error occurs
+ */
+ private Object getBeanValue(Object bean, Field field, Method getter, Property prop)
+ throws PropertyAccessException {
+ try {
+ return getter.invoke(bean);
+
+ } catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException e) {
+ throw new PropertyAccessException(prop.name(), field.getName(), e);
+ }
+ }
+
+ /**
+ * Functions to check a default value.
+ */
+ @FunctionalInterface
+ private static interface CheckDefaultValueFunction {
+
+ /**
+ * Checks the default value.
+ *
+ * @throws PropertyInvalidException if an error occurs
+ */
+ public void apply() throws PropertyInvalidException;
+ }
+}
diff --git a/utils/src/main/java/org/onap/policy/common/utils/properties/Property.java b/utils/src/main/java/org/onap/policy/common/utils/properties/Property.java
new file mode 100644
index 00000000..bacedef9
--- /dev/null
+++ b/utils/src/main/java/org/onap/policy/common/utils/properties/Property.java
@@ -0,0 +1,59 @@
+/*
+ * ============LICENSE_START=======================================================
+ * ONAP
+ * ================================================================================
+ * Copyright (C) 2019 AT&T Intellectual Property. All rights reserved.
+ * ================================================================================
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ============LICENSE_END=========================================================
+ */
+
+package org.onap.policy.common.utils.properties;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+import java.util.Properties;
+
+
+/**
+ * Annotation that declares a variable to be configured via {@link Properties}.
+ */
+@Target(ElementType.FIELD)
+@Retention(RetentionPolicy.RUNTIME)
+
+public @interface Property {
+
+ /**
+ * Name of the property.
+ *
+ * @return the property name
+ */
+ public String name();
+
+ /**
+ * Default value, used when the property does not exist.
+ *
+ * @return the default value
+ */
+ public String defaultValue() default "";
+
+ /**
+ * Comma-separated options identifying what's acceptable. The word, "empty",
+ * indicates that an empty string, "", is an acceptable value.
+ *
+ * @return options identifying what's acceptable
+ */
+ public String accept() default "";
+}
diff --git a/utils/src/test/java/org/onap/policy/common/utils/properties/BeanConfiguratorTest.java b/utils/src/test/java/org/onap/policy/common/utils/properties/BeanConfiguratorTest.java
new file mode 100644
index 00000000..beca88f0
--- /dev/null
+++ b/utils/src/test/java/org/onap/policy/common/utils/properties/BeanConfiguratorTest.java
@@ -0,0 +1,1424 @@
+/*
+ * ============LICENSE_START=======================================================
+ * ONAP - Common Modules
+ * ================================================================================
+ * Copyright (C) 2019 AT&T Intellectual Property. All rights reserved.
+ * ================================================================================
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ============LICENSE_END=========================================================
+ */
+
+package org.onap.policy.common.utils.properties;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertSame;
+
+import java.lang.reflect.Field;
+import java.lang.reflect.Method;
+import java.util.Properties;
+import org.junit.Before;
+import org.junit.Test;
+import org.onap.policy.common.utils.properties.exception.PropertyAccessException;
+import org.onap.policy.common.utils.properties.exception.PropertyException;
+import org.onap.policy.common.utils.properties.exception.PropertyInvalidException;
+import org.onap.policy.common.utils.properties.exception.PropertyMissingException;
+
+/**
+ * Test class for PropertyConfiguration.
+ */
+public class BeanConfiguratorTest {
+
+ /**
+ * Property used for most of the simple configuration subclasses.
+ */
+ private static final String THE_VALUE = "the.value";
+
+ /**
+ * String property value.
+ */
+ private static final String STRING_VALUE = "a string";
+
+ /**
+ * Default value for string property.
+ */
+ private static final String STRING_VALUE_DEFAULT = "another string";
+
+ /**
+ * Value that cannot be coerced into any other type.
+ */
+ private static final String INVALID_VALUE = "invalid";
+
+ /**
+ * Properties used when invoking constructors.
+ */
+ private Properties props;
+
+ private BeanConfigurator beancfg;
+
+ @Before
+ public void setUp() {
+ props = new Properties();
+ beancfg = new BeanConfigurator();
+ }
+
+ @Test
+ public void testConfigureFromProperties() throws PropertyException {
+ props.setProperty(THE_VALUE, STRING_VALUE);
+ PlainStringConfig cfg = new PlainStringConfig();
+
+ assertSame(cfg, beancfg.configureFromProperties(cfg, props));
+
+ assertEquals(STRING_VALUE, cfg.value);
+ }
+
+ @Test
+ public void testSetAllFields() throws Exception {
+
+ /*
+ * Implements an extra interface, just to see that it doesn't cause issues.
+ */
+ class GrandParentConfig implements DoesNothing {
+
+ @Property(name = "grandparent.value")
+ protected boolean grandparentValue;
+
+ @SuppressWarnings("unused")
+ public void setGrandparentValue(boolean grandparentValue) {
+ this.grandparentValue = grandparentValue;
+ }
+ }
+
+ /*
+ * Implements the extra interface, too.
+ */
+ class ParentConfig extends GrandParentConfig implements DoesNothing {
+
+ @Property(name = "parent.value")
+ protected long parentValue;
+
+ @SuppressWarnings("unused")
+ public void setParentValue(long parentValue) {
+ this.parentValue = parentValue;
+ }
+ }
+
+ class Config extends ParentConfig {
+
+ @Property(name = THE_VALUE)
+ private String value;
+
+ @SuppressWarnings("unused")
+ public void setValue(String value) {
+ this.value = value;
+ }
+ }
+
+
+ final Config cfg = new Config();
+
+ // try one set of values
+ props.setProperty(THE_VALUE, STRING_VALUE);
+ props.setProperty("parent.value", "50000");
+ props.setProperty("grandparent.value", "true");
+ beancfg.configureFromProperties(cfg, props);
+
+ assertEquals(STRING_VALUE, cfg.value);
+ assertEquals(50000L, cfg.parentValue);
+ assertEquals(true, cfg.grandparentValue);
+
+ // now a different set of values
+ props.setProperty(THE_VALUE, STRING_VALUE + "x");
+ props.setProperty("parent.value", "50001");
+ props.setProperty("grandparent.value", "false");
+ beancfg.configureFromProperties(cfg, props);
+
+ assertEquals(STRING_VALUE + "x", cfg.value);
+ assertEquals(50001L, cfg.parentValue);
+ assertEquals(false, cfg.grandparentValue);
+ }
+
+ @Test
+ public void testSetAllFields_NoProperties() throws Exception {
+
+ class Config {
+
+ private String value;
+
+ @SuppressWarnings("unused")
+ public void setValue(String value) {
+ this.value = value;
+ }
+ }
+
+
+ props.setProperty(THE_VALUE, STRING_VALUE);
+ Config cfg = new Config();
+ beancfg.configureFromProperties(cfg, props);
+
+ assertEquals(null, cfg.value);
+ }
+
+ @Test
+ public void testSetValueObjectFieldProperties_FieldSet() throws PropertyException {
+ props.setProperty(THE_VALUE, STRING_VALUE);
+ PlainStringConfig cfg = new PlainStringConfig();
+ beancfg.configureFromProperties(cfg, props);
+
+ assertEquals(STRING_VALUE, cfg.value);
+ }
+
+ @Test
+ public void testSetValueObjectFieldProperties_NoAnnotation() throws PropertyException {
+ class Config {
+
+ private String value;
+
+ @SuppressWarnings("unused")
+ public void setValue(String value) {
+ this.value = value;
+ }
+ }
+
+ props.setProperty(THE_VALUE, STRING_VALUE);
+ Config cfg = new Config();
+ beancfg.configureFromProperties(cfg, props);
+
+ assertNull(cfg.value);
+ }
+
+ @Test(expected = PropertyAccessException.class)
+ public void testSetValueObjectFieldProperties_WrongFieldType() throws PropertyException {
+ class Config {
+
+ // Cannot set a property into an "Exception" field
+ @Property(name = THE_VALUE)
+ private Exception value;
+
+ @SuppressWarnings("unused")
+ public void setValue(Exception value) {
+ this.value = value;
+ }
+ }
+
+ props.setProperty(THE_VALUE, STRING_VALUE);
+ beancfg.configureFromProperties(new Config(), props);
+ }
+
+ @Test(expected = PropertyAccessException.class)
+ public void testGetSetter_NoSetter() throws PropertyException {
+ class Config {
+
+ @Property(name = THE_VALUE)
+ private String value;
+ }
+
+ props.setProperty(THE_VALUE, STRING_VALUE);
+ beancfg.configureFromProperties(new Config(), props);
+ }
+
+ @Test(expected = PropertyMissingException.class)
+ public void testSetValueObjectMethodFieldPropertiesProperty_NoProperty_NoDefault() throws PropertyException {
+ beancfg.configureFromProperties(new PlainStringConfig(), props);
+ }
+
+ @Test(expected = PropertyInvalidException.class)
+ public void testSetValueObjectMethodFieldPropertiesProperty_IllegalArgEx() throws PropertyException {
+ props.setProperty(THE_VALUE, STRING_VALUE);
+
+ beancfg = new BeanConfigurator() {
+ @Override
+ protected Object getValue(Field field, Properties props, Property prop) {
+ throw new IllegalArgumentException("expected exception");
+ }
+ };
+
+ beancfg.configureFromProperties(new PlainStringConfig(), props);
+ }
+
+ @Test(expected = PropertyAccessException.class)
+ public void testSetValueObjectMethodFieldPropertiesProperty_MethodEx() throws PropertyException {
+ class Config extends PlainStringConfig {
+
+ @Override
+ public void setValue(String value) {
+ throw new IllegalArgumentException("expected exception");
+ }
+ }
+
+ props.setProperty(THE_VALUE, STRING_VALUE);
+ beancfg.configureFromProperties(new Config(), props);
+ }
+
+ @Test
+ public void testGetValue() throws PropertyException {
+ // this class contains all of the supported field types
+ class Config {
+
+ @Property(name = "string")
+ private String stringValue;
+
+ @Property(name = "boolean.true")
+ private Boolean boolTrueValue;
+
+ @Property(name = "boolean.false")
+ private Boolean boolFalseValue;
+
+ @Property(name = "primitive.boolean.true")
+ private boolean primBoolTrueValue;
+
+ @Property(name = "primitive.boolean.false")
+ private boolean primBoolFalseValue;
+
+ @Property(name = "integer")
+ private Integer intValue;
+
+ @Property(name = "primitive.integer")
+ private int primIntValue;
+
+ @Property(name = "long")
+ private Long longValue;
+
+ @Property(name = "primitive.long")
+ private long primLongValue;
+
+ @SuppressWarnings("unused")
+ public String getStringValue() {
+ return stringValue;
+ }
+
+ @SuppressWarnings("unused")
+ public void setStringValue(String stringValue) {
+ this.stringValue = stringValue;
+ }
+
+ @SuppressWarnings("unused")
+ public Boolean getBoolTrueValue() {
+ return boolTrueValue;
+ }
+
+ @SuppressWarnings("unused")
+ public void setBoolTrueValue(Boolean boolTrueValue) {
+ this.boolTrueValue = boolTrueValue;
+ }
+
+ @SuppressWarnings("unused")
+ public Boolean getBoolFalseValue() {
+ return boolFalseValue;
+ }
+
+ @SuppressWarnings("unused")
+ public void setBoolFalseValue(Boolean boolFalseValue) {
+ this.boolFalseValue = boolFalseValue;
+ }
+
+ @SuppressWarnings("unused")
+ public boolean isPrimBoolTrueValue() {
+ return primBoolTrueValue;
+ }
+
+ @SuppressWarnings("unused")
+ public void setPrimBoolTrueValue(boolean primBoolTrueValue) {
+ this.primBoolTrueValue = primBoolTrueValue;
+ }
+
+ @SuppressWarnings("unused")
+ public boolean isPrimBoolFalseValue() {
+ return primBoolFalseValue;
+ }
+
+ @SuppressWarnings("unused")
+ public void setPrimBoolFalseValue(boolean primBoolFalseValue) {
+ this.primBoolFalseValue = primBoolFalseValue;
+ }
+
+ @SuppressWarnings("unused")
+ public Integer getIntValue() {
+ return intValue;
+ }
+
+ @SuppressWarnings("unused")
+ public void setIntValue(Integer intValue) {
+ this.intValue = intValue;
+ }
+
+ @SuppressWarnings("unused")
+ public int getPrimIntValue() {
+ return primIntValue;
+ }
+
+ @SuppressWarnings("unused")
+ public void setPrimIntValue(int primIntValue) {
+ this.primIntValue = primIntValue;
+ }
+
+ @SuppressWarnings("unused")
+ public Long getLongValue() {
+ return longValue;
+ }
+
+ @SuppressWarnings("unused")
+ public void setLongValue(Long longValue) {
+ this.longValue = longValue;
+ }
+
+ @SuppressWarnings("unused")
+ public long getPrimLongValue() {
+ return primLongValue;
+ }
+
+ @SuppressWarnings("unused")
+ public void setPrimLongValue(long primLongValue) {
+ this.primLongValue = primLongValue;
+ }
+ }
+
+ props.setProperty("string", "a string");
+ props.setProperty("boolean.true", "true");
+ props.setProperty("boolean.false", "false");
+ props.setProperty("primitive.boolean.true", "true");
+ props.setProperty("primitive.boolean.false", "false");
+ props.setProperty("integer", "100");
+ props.setProperty("primitive.integer", "101");
+ props.setProperty("long", "10000");
+ props.setProperty("primitive.long", "10001");
+
+ Config cfg = new Config();
+ beancfg.configureFromProperties(cfg, props);
+
+ assertEquals("a string", cfg.stringValue);
+ assertEquals(true, cfg.boolTrueValue);
+ assertEquals(false, cfg.boolFalseValue);
+ assertEquals(true, cfg.primBoolTrueValue);
+ assertEquals(false, cfg.primBoolFalseValue);
+ assertEquals(100, cfg.intValue.intValue());
+ assertEquals(101, cfg.primIntValue);
+ assertEquals(10000, cfg.longValue.longValue());
+ assertEquals(10001, cfg.primLongValue);
+ }
+
+ @Test(expected = PropertyAccessException.class)
+ public void testGetValue_UnsupportedType() throws PropertyException {
+ class Config {
+
+ // Cannot set a property into an "Exception" field
+ @Property(name = THE_VALUE)
+ private Exception value;
+
+ @SuppressWarnings("unused")
+ public void setValue(Exception value) {
+ this.value = value;
+ }
+ }
+
+ props.setProperty(THE_VALUE, STRING_VALUE);
+ beancfg.configureFromProperties(new Config(), props);
+ }
+
+ @Test
+ public void testCheckModifiable_OtherModifiers() throws PropertyException {
+ // this class contains all of the supported field types
+ class Config {
+
+ @Property(name = "public")
+ public String publicString;
+
+ @Property(name = "private")
+ private String privateString;
+
+ @Property(name = "protected")
+ protected String protectedString;
+
+ @SuppressWarnings("unused")
+ public void setPublicString(String publicString) {
+ this.publicString = publicString;
+ }
+
+ @SuppressWarnings("unused")
+ public void setPrivateString(String privateString) {
+ this.privateString = privateString;
+ }
+
+ @SuppressWarnings("unused")
+ public void setProtectedString(String protectedString) {
+ this.protectedString = protectedString;
+ }
+ }
+
+ props.setProperty("public", "a public string");
+ props.setProperty("private", "a private string");
+ props.setProperty("protected", "a protected string");
+
+ Config cfg = new Config();
+ beancfg.configureFromProperties(cfg, props);
+
+ assertEquals("a public string", cfg.publicString);
+ assertEquals("a private string", cfg.privateString);
+ assertEquals("a protected string", cfg.protectedString);
+ }
+
+ @Test(expected = PropertyAccessException.class)
+ public void testCheckModifiable_Static() throws PropertyException {
+ props.setProperty(THE_VALUE, STRING_VALUE);
+ beancfg.configureFromProperties(new StaticPropConfig(), props);
+ }
+
+ @Test(expected = PropertyAccessException.class)
+ public void testCheckModifiable_Final() throws PropertyException {
+ class Config {
+
+ // Cannot set a property into an "final" field
+ @Property(name = THE_VALUE)
+ private final String value = "";
+ }
+
+ props.setProperty(THE_VALUE, STRING_VALUE);
+ beancfg.configureFromProperties(new Config(), props);
+ }
+
+ @Test(expected = PropertyAccessException.class)
+ public void testCheckMethod_Static() throws PropertyException {
+ props.setProperty(THE_VALUE, STRING_VALUE);
+ beancfg.configureFromProperties(new StaticMethodConfig(), props);
+ }
+
+ @Test
+ public void testGetStringValue() throws PropertyException {
+ props.setProperty(THE_VALUE, STRING_VALUE);
+ PlainStringConfig cfg = new PlainStringConfig();
+ beancfg.configureFromProperties(cfg, props);
+
+ assertEquals(STRING_VALUE, cfg.value);
+ }
+
+ @Test
+ public void testGetBooleanValue_NoDefault() throws PropertyException {
+ props.setProperty(THE_VALUE, "true");
+ PlainBooleanConfig cfg = new PlainBooleanConfig();
+ beancfg.configureFromProperties(cfg, props);
+
+ assertEquals(true, cfg.value);
+ }
+
+ @Test(expected = PropertyInvalidException.class)
+ public void testGetBooleanValue_InvalidDefault() throws PropertyException {
+ class Config {
+
+ @Property(name = THE_VALUE, defaultValue = INVALID_VALUE)
+ private Boolean value;
+
+ @SuppressWarnings("unused")
+ public void setValue(Boolean value) {
+ this.value = value;
+ }
+ }
+
+ props.setProperty(THE_VALUE, "true");
+ beancfg.configureFromProperties(new Config(), props);
+ }
+
+ @Test
+ public void testGetBooleanValue_ValidDefault_True() throws PropertyException {
+ class Config {
+
+ @Property(name = THE_VALUE, defaultValue = "true")
+ private Boolean value;
+
+ @SuppressWarnings("unused")
+ public void setValue(Boolean value) {
+ this.value = value;
+ }
+ }
+
+ // property not defined
+ Config cfg = new Config();
+ beancfg.configureFromProperties(cfg, props);
+ assertEquals(true, cfg.value);
+
+ // try again, with the property defined as true
+ props.setProperty(THE_VALUE, "true");
+ cfg = new Config();
+ beancfg.configureFromProperties(cfg, props);
+ assertEquals(true, cfg.value);
+
+ // try again, with the property defined as false
+ props.setProperty(THE_VALUE, "false");
+ cfg = new Config();
+ beancfg.configureFromProperties(cfg, props);
+ assertEquals(false, cfg.value);
+ }
+
+ @Test
+ public void testGetBooleanValue_ValidDefault_False() throws PropertyException {
+ class Config {
+
+ @Property(name = THE_VALUE, defaultValue = "false")
+ private Boolean value;
+
+ @SuppressWarnings("unused")
+ public void setValue(Boolean value) {
+ this.value = value;
+ }
+ }
+
+ // property not defined
+ Config cfg = new Config();
+ beancfg.configureFromProperties(cfg, props);
+ assertEquals(false, cfg.value);
+
+ // try again, with the property defined as true
+ props.setProperty(THE_VALUE, "true");
+ cfg = new Config();
+ beancfg.configureFromProperties(cfg, props);
+ assertEquals(true, cfg.value);
+
+ // try again, with the property defined as false
+ props.setProperty(THE_VALUE, "false");
+ cfg = new Config();
+ beancfg.configureFromProperties(cfg, props);
+ assertEquals(false, cfg.value);
+ }
+
+ @Test
+ public void testGetIntegerValue_NoDefault() throws PropertyException {
+ class Config {
+
+ @Property(name = THE_VALUE)
+ private Integer value;
+
+ @SuppressWarnings("unused")
+ public void setValue(Integer value) {
+ this.value = value;
+ }
+ }
+
+ props.setProperty(THE_VALUE, "200");
+ Config cfg = new Config();
+ beancfg.configureFromProperties(cfg, props);
+
+ assertEquals(200, cfg.value.intValue());
+ }
+
+ @Test(expected = PropertyInvalidException.class)
+ public void testGetIntegerValue_InvalidDefault() throws PropertyException {
+ class Config {
+
+ @Property(name = THE_VALUE, defaultValue = INVALID_VALUE)
+ private Integer value;
+
+ @SuppressWarnings("unused")
+ public void setValue(Integer value) {
+ this.value = value;
+ }
+ }
+
+ props.setProperty(THE_VALUE, "200");
+ beancfg.configureFromProperties(new Config(), props);
+ }
+
+ @Test
+ public void testGetIntegerValue_ValidDefault() throws PropertyException {
+ class Config {
+
+ @Property(name = THE_VALUE, defaultValue = "201")
+ private Integer value;
+
+ @SuppressWarnings("unused")
+ public void setValue(Integer value) {
+ this.value = value;
+ }
+ }
+
+ // property not defined
+ Config cfg = new Config();
+ beancfg.configureFromProperties(cfg, props);
+ assertEquals(201, cfg.value.intValue());
+
+ // try again, with the property defined
+ props.setProperty(THE_VALUE, "200");
+ cfg = new Config();
+ beancfg.configureFromProperties(cfg, props);
+ assertEquals(200, cfg.value.intValue());
+ }
+
+ @Test
+ public void testGetLongValue_NoDefault() throws PropertyException {
+ class Config {
+
+ @Property(name = THE_VALUE)
+ private Long value;
+
+ @SuppressWarnings("unused")
+ public void setValue(Long value) {
+ this.value = value;
+ }
+ }
+
+ props.setProperty(THE_VALUE, "20000");
+ Config cfg = new Config();
+ beancfg.configureFromProperties(cfg, props);
+
+ assertEquals(20000L, cfg.value.longValue());
+ }
+
+ @Test(expected = PropertyInvalidException.class)
+ public void testGetLongValue_InvalidDefault() throws PropertyException {
+ class Config {
+
+ @Property(name = THE_VALUE, defaultValue = INVALID_VALUE)
+ private Long value;
+
+ @SuppressWarnings("unused")
+ public void setValue(Long value) {
+ this.value = value;
+ }
+ }
+
+ props.setProperty(THE_VALUE, "20000");
+ beancfg.configureFromProperties(new Config(), props);
+ }
+
+ @Test
+ public void testGetLongValue_ValidDefault() throws PropertyException {
+ class Config {
+
+ @Property(name = THE_VALUE, defaultValue = "20001")
+ private Long value;
+
+ @SuppressWarnings("unused")
+ public void setValue(Long value) {
+ this.value = value;
+ }
+ }
+
+ // property not defined
+ Config cfg = new Config();
+ beancfg.configureFromProperties(cfg, props);
+ assertEquals(20001L, cfg.value.longValue());
+
+ // try again, with the property defined
+ props.setProperty(THE_VALUE, "20000");
+ cfg = new Config();
+ beancfg.configureFromProperties(cfg, props);
+ assertEquals(20000L, cfg.value.longValue());
+ }
+
+ @Test
+ public void testGetPropValue_Prop_NoDefault() throws PropertyException {
+ props.setProperty(THE_VALUE, STRING_VALUE);
+
+ PlainStringConfig cfg = new PlainStringConfig();
+ beancfg.configureFromProperties(cfg, props);
+
+ assertEquals(STRING_VALUE, cfg.value);
+ }
+
+ @Test
+ public void testGetPropValue_Prop_Default() throws PropertyException {
+ class Config {
+
+ @Property(name = THE_VALUE, defaultValue = STRING_VALUE_DEFAULT)
+ private String value;
+
+ @SuppressWarnings("unused")
+ public void setValue(String value) {
+ this.value = value;
+ }
+ }
+
+ props.setProperty(THE_VALUE, STRING_VALUE);
+
+ Config cfg = new Config();
+ beancfg.configureFromProperties(cfg, props);
+
+ assertEquals(STRING_VALUE, cfg.value);
+ }
+
+ @Test
+ public void testGetPropValue_EmptyProp_EmptyOk() throws PropertyException {
+ class Config {
+
+ @Property(name = THE_VALUE, accept = "empty")
+ private String value;
+
+ @SuppressWarnings("unused")
+ public void setValue(String value) {
+ this.value = value;
+ }
+ }
+
+ props.setProperty(THE_VALUE, "");
+
+ Config cfg = new Config();
+ beancfg.configureFromProperties(cfg, props);
+
+ assertEquals("", cfg.value);
+ }
+
+ @Test
+ public void testGetPropValue_NullProp_EmptyOk() throws PropertyException {
+ class Config {
+
+ @Property(name = THE_VALUE, accept = "empty")
+ private String value;
+
+ @SuppressWarnings("unused")
+ public void setValue(String value) {
+ this.value = value;
+ }
+ }
+
+ Config cfg = new Config();
+ beancfg.configureFromProperties(cfg, props);
+
+ assertEquals("", cfg.value);
+ }
+
+ @Test
+ public void testGetPropValue_EmptyDefault_EmptyOk() throws PropertyException {
+ class Config {
+
+ @Property(name = THE_VALUE, defaultValue = "", accept = "empty")
+ private String value;
+
+ @SuppressWarnings("unused")
+ public void setValue(String value) {
+ this.value = value;
+ }
+ }
+
+ Config cfg = new Config();
+ beancfg.configureFromProperties(cfg, props);
+
+ assertEquals("", cfg.value);
+ }
+
+ @Test
+ public void testGetPropValue_Default_EmptyOk() throws PropertyException {
+ class Config {
+
+ @Property(name = THE_VALUE, defaultValue = STRING_VALUE, accept = "empty")
+ private String value;
+
+ @SuppressWarnings("unused")
+ public void setValue(String value) {
+ this.value = value;
+ }
+ }
+
+ Config cfg = new Config();
+ beancfg.configureFromProperties(cfg, props);
+
+ assertEquals(STRING_VALUE, cfg.value);
+ }
+
+ @Test(expected = PropertyMissingException.class)
+ public void testGetPropValue_EmptyDefault_EmptyNotOk() throws PropertyException {
+ class Config {
+
+ @Property(name = THE_VALUE, defaultValue = "")
+ private String value;
+
+ @SuppressWarnings("unused")
+ public void setValue(String value) {
+ this.value = value;
+ }
+ }
+
+ beancfg.configureFromProperties(new Config(), props);
+ }
+
+ @Test
+ public void testGetPropValue_Default_EmptyNotOk() throws PropertyException {
+ class Config {
+
+ @Property(name = THE_VALUE, defaultValue = STRING_VALUE, accept = "")
+ private String value;
+
+ @SuppressWarnings("unused")
+ public void setValue(String value) {
+ this.value = value;
+ }
+ }
+
+ Config cfg = new Config();
+ beancfg.configureFromProperties(cfg, props);
+
+ assertEquals(STRING_VALUE, cfg.value);
+ }
+
+ @Test
+ public void testMakeBoolean_True() throws PropertyException {
+ props.setProperty(THE_VALUE, "true");
+ PlainBooleanConfig cfg = new PlainBooleanConfig();
+ beancfg.configureFromProperties(cfg, props);
+
+ assertEquals(true, cfg.value);
+ }
+
+ @Test
+ public void testMakeBoolean_False() throws PropertyException {
+ props.setProperty(THE_VALUE, "false");
+ PlainBooleanConfig cfg = new PlainBooleanConfig();
+ beancfg.configureFromProperties(cfg, props);
+
+ assertEquals(false, cfg.value);
+ }
+
+ @Test(expected = PropertyInvalidException.class)
+ public void testMakeBoolean_Invalid() throws PropertyException {
+ props.setProperty(THE_VALUE, INVALID_VALUE);
+ beancfg.configureFromProperties(new PlainBooleanConfig(), props);
+ }
+
+ @Test
+ public void testMakeInteger_Valid() throws PropertyException {
+ props.setProperty(THE_VALUE, "300");
+ PlainPrimIntConfig cfg = new PlainPrimIntConfig();
+ beancfg.configureFromProperties(cfg, props);
+
+ assertEquals(300, cfg.value);
+ }
+
+ @Test(expected = PropertyInvalidException.class)
+ public void testMakeInteger_Invalid() throws PropertyException {
+ props.setProperty(THE_VALUE, INVALID_VALUE);
+ beancfg.configureFromProperties(new PlainPrimIntConfig(), props);
+ }
+
+ @Test(expected = PropertyInvalidException.class)
+ public void testMakeInteger_TooBig() throws PropertyException {
+ props.setProperty(THE_VALUE, String.valueOf(Integer.MAX_VALUE + 10L));
+ beancfg.configureFromProperties(new PlainPrimIntConfig(), props);
+ }
+
+ @Test
+ public void testMakeLong_Valid() throws PropertyException {
+ props.setProperty(THE_VALUE, "30000");
+ PlainPrimLongConfig cfg = new PlainPrimLongConfig();
+ beancfg.configureFromProperties(cfg, props);
+
+ assertEquals(30000L, cfg.value);
+ }
+
+ @Test(expected = PropertyInvalidException.class)
+ public void testMakeLong_Invalid() throws PropertyException {
+ props.setProperty(THE_VALUE, INVALID_VALUE);
+ beancfg.configureFromProperties(new PlainPrimLongConfig(), props);
+ }
+
+ @Test
+ public void testCheckDefaultValue_NotEmpty_Valid() throws PropertyException {
+ class Config {
+
+ @Property(name = THE_VALUE, defaultValue = "700")
+ private long value;
+
+ @SuppressWarnings("unused")
+ public void setValue(long value) {
+ this.value = value;
+ }
+ }
+
+ Config cfg = new Config();
+ beancfg.configureFromProperties(cfg, props);
+
+ assertEquals(700L, cfg.value);
+ }
+
+ @Test(expected = PropertyInvalidException.class)
+ public void testCheckDefaultValue_NotEmpty_Invalid() throws PropertyException {
+ class Config {
+
+ @Property(name = THE_VALUE, defaultValue = INVALID_VALUE)
+ private long value;
+
+ @SuppressWarnings("unused")
+ public void setValue(long value) {
+ this.value = value;
+ }
+ }
+
+ beancfg.configureFromProperties(new Config(), props);
+ }
+
+ @Test(expected = PropertyInvalidException.class)
+ public void testCheckDefaultValue_Empty_EmptyOk_Invalid() throws PropertyException {
+ class Config {
+
+ @Property(name = THE_VALUE, defaultValue = "", accept = "empty")
+ private long value;
+
+ @SuppressWarnings("unused")
+ public void setValue(long value) {
+ this.value = value;
+ }
+ }
+
+ beancfg.configureFromProperties(new Config(), props);
+ }
+
+ @Test
+ public void testIsEmptyOkPropertyString_True() throws PropertyException {
+ class Config {
+
+ @Property(name = THE_VALUE, defaultValue = "", accept = "empty")
+ private String value;
+
+ @SuppressWarnings("unused")
+ public void setValue(String value) {
+ this.value = value;
+ }
+ }
+
+ // missing property - should default to ""
+ Config cfg = new Config();
+ beancfg.configureFromProperties(cfg, props);
+ assertEquals("", cfg.value);
+
+ // add an empty property - should take the property's value
+ props.setProperty(THE_VALUE, "");
+ beancfg.configureFromProperties(cfg, props);
+ assertEquals("", cfg.value);
+
+ // add the property - should take the property's value
+ props.setProperty(THE_VALUE, STRING_VALUE);
+ beancfg.configureFromProperties(cfg, props);
+ assertEquals(STRING_VALUE, cfg.value);
+ }
+
+ @Test(expected = PropertyMissingException.class)
+ public void testIsEmptyOkPropertyString_False() throws PropertyException {
+ class Config {
+
+ @Property(name = THE_VALUE, defaultValue = "", accept = "")
+ private long value;
+
+ @SuppressWarnings("unused")
+ public void setValue(long value) {
+ this.value = value;
+ }
+ }
+
+ beancfg.configureFromProperties(new Config(), props);
+ }
+
+ @Test
+ public void testIsEmptyOkProperty_True() throws PropertyException {
+ class Config {
+
+ @Property(name = THE_VALUE, defaultValue = "", accept = "empty")
+ private String value;
+
+ @SuppressWarnings("unused")
+ public void setValue(String value) {
+ this.value = value;
+ }
+ }
+
+ Config cfg = new Config();
+ beancfg.configureFromProperties(cfg, props);
+
+ assertEquals("", cfg.value);
+ }
+
+ @Test(expected = PropertyMissingException.class)
+ public void testIsEmptyOkProperty_False() throws PropertyException {
+ class Config {
+
+ @Property(name = THE_VALUE, defaultValue = "", accept = "")
+ private long value;
+
+ @SuppressWarnings("unused")
+ public void setValue(long value) {
+ this.value = value;
+ }
+ }
+
+ beancfg.configureFromProperties(new Config(), props);
+ }
+
+ @Test
+ public void testPutToProperties() throws Exception {
+
+ /*
+ * Implements the extra interface, too.
+ */
+ class ParentConfig implements DoesNothing {
+
+ @Property(name = "the.parent.value")
+ protected long parentValue;
+
+ @SuppressWarnings("unused")
+ public long getParentValue() {
+ return parentValue;
+ }
+ }
+
+ class Config extends ParentConfig {
+
+ @Property(name = THE_VALUE)
+ private String value;
+
+ @Property(name = "the.other.value")
+ private String other;
+
+ @SuppressWarnings("unused")
+ public String getValue() {
+ return value;
+ }
+
+ @SuppressWarnings("unused")
+ public String getOther() {
+ return other;
+ }
+ }
+
+
+ final Config cfg = new Config();
+
+ cfg.parentValue = 1010;
+ cfg.value = STRING_VALUE;
+ cfg.other = "other";
+
+ beancfg.addToProperties(cfg, props, "the", "a");
+
+ assertEquals("1010", props.getProperty("a.parent.value"));
+ assertEquals(STRING_VALUE, props.getProperty("a.value"));
+ assertEquals("other", props.getProperty("a.other.value"));
+
+ // original prefix is empty
+ beancfg.addToProperties(cfg, props, "", "not");
+ assertEquals(STRING_VALUE, props.getProperty("not.the.value"));
+
+ // original prefix is ends with "."
+ beancfg.addToProperties(cfg, props, "the.", "a");
+ assertEquals(STRING_VALUE, props.getProperty("a.value"));
+
+ // new prefix is empty
+ beancfg.addToProperties(cfg, props, "", "");
+ assertEquals(STRING_VALUE, props.getProperty(THE_VALUE));
+
+ // new prefix is ends with "."
+ beancfg.addToProperties(cfg, props, "the", "xxx.");
+ assertEquals(STRING_VALUE, props.getProperty("xxx.value"));
+ }
+
+ @Test
+ public void testPutProperty() throws Exception {
+
+ class Config {
+ // no annotation - should not be copied
+ private String noAnnotation;
+
+ @Property(name = THE_VALUE)
+ private String value;
+
+ // null value - should not be copied
+ @Property(name = "the.null.value")
+ private String nullValue;
+
+ // should be copied, but retain its prefix
+ @Property(name = "some.other.prefix")
+ private String other;
+
+ @SuppressWarnings("unused")
+ public String getNoAnnotation() {
+ return noAnnotation;
+ }
+
+ @SuppressWarnings("unused")
+ public String getValue() {
+ return value;
+ }
+
+ @SuppressWarnings("unused")
+ public String getNullValue() {
+ return nullValue;
+ }
+
+ @SuppressWarnings("unused")
+ public String getOther() {
+ return other;
+ }
+ }
+
+ Config cfg = new Config();
+ cfg.noAnnotation = "no annotation";
+ cfg.value = STRING_VALUE;
+ cfg.nullValue = null;
+ cfg.other = "some other value";
+ beancfg.addToProperties(cfg, props, "the", "a");
+
+ assertFalse(props.contains("noAnnotation"));
+ assertEquals(STRING_VALUE, props.getProperty("a.value"));
+ assertFalse(props.contains("a.null.value"));
+ assertEquals("some other value", props.getProperty("some.other.prefix"));
+ }
+
+ @Test
+ public void testGetGetter() throws Exception {
+
+ class Config {
+ // getter method starts with "is" for these
+ @Property(name = "plain.bool")
+ private Boolean plainBool;
+
+ @Property(name = "prim.bool")
+ private boolean primBool;
+
+ // getter method starts with "get" for these
+ @Property(name = "plain.bool.get")
+ private Boolean plainBoolGet;
+
+ @Property(name = "prim.bool.get")
+ private boolean primBoolGet;
+
+ @Property(name = "int")
+ private int intValue;
+
+ @Property(name = "string")
+ private String stringValue;
+
+ @SuppressWarnings("unused")
+ public Boolean isPlainBool() {
+ return plainBool;
+ }
+
+ @SuppressWarnings("unused")
+ public boolean isPrimBool() {
+ return primBool;
+ }
+
+ @SuppressWarnings("unused")
+ public Boolean getPlainBoolGet() {
+ return plainBoolGet;
+ }
+
+ @SuppressWarnings("unused")
+ public boolean getPrimBoolGet() {
+ return primBoolGet;
+ }
+
+ @SuppressWarnings("unused")
+ public int getIntValue() {
+ return intValue;
+ }
+
+ @SuppressWarnings("unused")
+ public String getStringValue() {
+ return stringValue;
+ }
+ }
+
+ Config cfg = new Config();
+ cfg.plainBool = true;
+ cfg.primBool = false;
+ cfg.plainBoolGet = false;
+ cfg.primBoolGet = true;
+ cfg.intValue = 1100;
+ cfg.stringValue = STRING_VALUE;
+ beancfg.addToProperties(cfg, props, "", "");
+
+ assertEquals("true", props.getProperty("plain.bool"));
+ assertEquals("false", props.getProperty("prim.bool"));
+ assertEquals("false", props.getProperty("plain.bool.get"));
+ assertEquals("true", props.getProperty("prim.bool.get"));
+ assertEquals("1100", props.getProperty("int"));
+ assertEquals(STRING_VALUE, props.getProperty("string"));
+ }
+
+ @Test(expected = PropertyAccessException.class)
+ public void testGetGetter_NoGetter() throws Exception {
+
+ class Config {
+ @Property(name = THE_VALUE)
+ private String value;
+ }
+
+ Config cfg = new Config();
+ cfg.value = STRING_VALUE;
+ beancfg.addToProperties(cfg, props, "", "");
+ }
+
+ @Test(expected = PropertyAccessException.class)
+ public void testGetGetter_NoGetterForBoolean() throws Exception {
+
+ class Config {
+ @Property(name = THE_VALUE)
+ private boolean value;
+ }
+
+ Config cfg = new Config();
+ cfg.value = true;
+ beancfg.addToProperties(cfg, props, "", "");
+ }
+
+ @Test(expected = PropertyAccessException.class)
+ public void testGetGetter_PrivateGetter() throws Exception {
+
+ class Config {
+ @Property(name = THE_VALUE)
+ private String value;
+
+ @SuppressWarnings("unused")
+ private String getValue() {
+ return value;
+ }
+ }
+
+ Config cfg = new Config();
+ cfg.value = STRING_VALUE;
+ beancfg.addToProperties(cfg, props, "", "");
+ }
+
+ @Test(expected = PropertyAccessException.class)
+ public void testGetGetter_SecurityEx() throws Exception {
+
+ class Config {
+ @Property(name = THE_VALUE)
+ private String value;
+
+ @SuppressWarnings("unused")
+ private String getValue() {
+ return value;
+ }
+ }
+
+ Config cfg = new Config();
+ cfg.value = STRING_VALUE;
+
+ beancfg = new BeanConfigurator() {
+ @Override
+ protected Method getGetter(Field field, String methodName) throws SecurityException {
+ throw new SecurityException("expected exception");
+ }
+ };
+
+ beancfg.addToProperties(cfg, props, "", "");
+ }
+
+ @Test(expected = PropertyAccessException.class)
+ public void testGetBeanValue_Ex() throws Exception {
+
+ class Config {
+
+ @Property(name = THE_VALUE)
+ private String value;
+
+ @SuppressWarnings("unused")
+ public String getValue() {
+ throw new RuntimeException("expected exception");
+ }
+ }
+
+
+ final Config cfg = new Config();
+ cfg.value = STRING_VALUE;
+
+ beancfg.addToProperties(cfg, props, "the", "a");
+
+ }
+
+ /**
+ * Config with a String value having no qualifiers.
+ */
+ public class PlainStringConfig {
+
+ @Property(name = THE_VALUE)
+ private String value;
+
+ public String getValue() {
+ return value;
+ }
+
+ public void setValue(String value) {
+ this.value = value;
+ }
+ }
+
+ /**
+ * Config with a Boolean value having no qualifiers.
+ */
+ public class PlainBooleanConfig {
+
+ @Property(name = THE_VALUE)
+ private Boolean value;
+
+ public void setValue(Boolean value) {
+ this.value = value;
+ }
+ }
+
+ /**
+ * Config with an int value having no qualifiers.
+ */
+ public class PlainPrimIntConfig {
+
+ @Property(name = THE_VALUE)
+ private int value;
+
+ public void setValue(int value) {
+ this.value = value;
+ }
+ }
+
+ /**
+ * Config with a long value having no qualifiers.
+ */
+ public class PlainPrimLongConfig {
+
+ @Property(name = THE_VALUE)
+ private long value;
+
+ public void setValue(long value) {
+ this.value = value;
+ }
+ }
+
+ /**
+ * A config whose field is "static".
+ */
+ public static class StaticPropConfig {
+
+ // "static" field cannot be set
+ @Property(name = THE_VALUE)
+ private static String value;
+
+ public static void setValue(String value) {
+ StaticPropConfig.value = value;
+ }
+ }
+
+ /**
+ * A config whose method is "static".
+ */
+ public static class StaticMethodConfig {
+
+ // "static" field cannot be set
+ @Property(name = THE_VALUE)
+ private String value;
+
+ public static void setValue(String value) {
+
+ }
+ }
+
+ /**
+ * This is just used as a mix-in to ensure that the configuration ignores interfaces.
+ */
+ public static interface DoesNothing {
+
+ }
+}