diff options
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 { + + } +} |