import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
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;
/**
* Configuration 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
- * subclass.
+ * subclass. 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
checkModifiable(field, prop);
- if (setValue(field, props, prop)) {
+ Method setter = getSetter(field, prop);
+ checkSetter(setter, prop);
+
+ if (setValue(setter, field, props, prop)) {
return true;
}
throw new PropertyAccessException(prop.name(), field.getName(), "unsupported field type");
}
+ /**
+ * @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 this.getClass().getMethod(nm, field.getType());
+
+ } catch (NoSuchMethodException | SecurityException e) {
+ throw new PropertyAccessException(prop.name(), nm, e);
+ }
+ }
+
/**
* Sets a field's value from a particular property.
*
+ * @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(Field field, Properties props, Property prop) throws PropertyException {
+ protected boolean setValue(Method setter, Field field, Properties props, Property prop) throws PropertyException {
try {
Object val = getValue(field, props, prop);
return false;
} else {
-
- /*
- * According to java docs & blogs, "field" is our own copy, so we're free
- * to change the flags without impacting the real permissions of the field
- * within the real class.
- */
- field.setAccessible(true);
-
- field.set(this, val);
+ setter.invoke(this, val);
return true;
}
} catch (IllegalArgumentException e) {
throw new PropertyInvalidException(prop.name(), field.getName(), e);
- } catch (IllegalAccessException e) {
- throw new PropertyAccessException(prop.name(), field.getName(), e);
+ } catch (IllegalAccessException | InvocationTargetException e) {
+ throw new PropertyAccessException(prop.name(), setter.getName(), e);
}
}
}
}
+ /**
+ * Verifies that the setter method is not <i>static</i>.
+ *
+ * @param setter method to be checked
+ * @param prop property of interest
+ * @throws PropertyAccessException if the method is static
+ */
+ private void checkSetter(Method setter, Property prop) throws PropertyAccessException {
+ int mod = setter.getModifiers();
+
+ if (Modifier.isStatic(mod)) {
+ throw new PropertyAccessException(prop.name(), setter.getName(), "method is 'static'");
+ }
+ }
+
/**
* Gets a property value, coercing it to a String.
*
@Test
public void testPropertyConfiguration() throws PropertyException {
- class Config extends PropertyConfiguration {
-
- @Property(name = THE_VALUE)
- private String value;
- };
-
props.setProperty(THE_VALUE, STRING_VALUE);
- Config cfg = new Config();
+ PlainStringConfig cfg = new PlainStringConfig();
assertEquals(null, cfg.value);
cfg.setAllFields(props);
@Test
public void testPropertyConfigurationProperties() throws PropertyException {
- class Config extends PropertyConfiguration {
-
- @Property(name = THE_VALUE)
- private String value;
-
- public Config(Properties props) throws PropertyException {
- super(props);
- }
- };
-
props.setProperty(THE_VALUE, STRING_VALUE);
- Config cfg = new Config(props);
+ PlainStringConfig cfg = new PlainStringConfig(props);
assertEquals(STRING_VALUE, cfg.value);
}
@Property(name = "grandparent.value")
protected boolean grandparentValue;
+
+ @SuppressWarnings("unused")
+ public void setGrandparentValue(boolean grandparentValue) {
+ this.grandparentValue = grandparentValue;
+ }
};
/*
@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;
+ }
};
class Config extends PropertyConfiguration {
private String value;
+
+ @SuppressWarnings("unused")
+ public void setValue(String value) {
+ this.value = value;
+ }
};
@Test
public void testSetValueFieldProperties_FieldSet() throws PropertyException {
- class Config extends PropertyConfiguration {
-
- @Property(name = THE_VALUE)
- private String value;
-
- public Config(Properties props) throws PropertyException {
- super(props);
- }
- };
-
props.setProperty(THE_VALUE, STRING_VALUE);
- Config cfg = new Config(props);
+ PlainStringConfig cfg = new PlainStringConfig(props);
assertEquals(STRING_VALUE, cfg.value);
}
public Config(Properties props) throws PropertyException {
super(props);
}
+
+ @SuppressWarnings("unused")
+ public void setValue(String value) {
+ this.value = value;
+ }
};
props.setProperty(THE_VALUE, STRING_VALUE);
public Config(Properties props) throws PropertyException {
super(props);
}
+
+ @SuppressWarnings("unused")
+ public void setValue(Exception value) {
+ this.value = value;
+ }
};
props.setProperty(THE_VALUE, STRING_VALUE);
new Config(props);
}
- @Test(expected = PropertyMissingException.class)
- public void testSetValueFieldPropertyProperties_NoProperty_NoDefault() throws PropertyException {
+ @Test(expected = PropertyAccessException.class)
+ public void testGetSetter_NoSetter() throws PropertyException {
class Config extends PropertyConfiguration {
@Property(name = THE_VALUE)
}
};
+ props.setProperty(THE_VALUE, STRING_VALUE);
new Config(props);
}
- @Test(expected = PropertyInvalidException.class)
- public void testSetValueFieldPropertyProperties_InvalidValue() throws PropertyException {
- class Config extends PropertyConfiguration {
+ @Test(expected = PropertyMissingException.class)
+ public void testSetValueMethodFieldPropertiesProperty_NoProperty_NoDefault() throws PropertyException {
+ new PlainStringConfig(props);
+ }
- @Property(name = THE_VALUE)
- private int value;
+ @Test(expected = PropertyInvalidException.class)
+ public void testSetValueMethodFieldPropertiesProperty_InvalidValue() throws PropertyException {
+ class Config extends PlainPrimIntConfig {
public Config(Properties props) throws PropertyException {
super(props);
new Config(props);
}
+ @Test(expected = PropertyAccessException.class)
+ public void testSetValueMethodFieldPropertiesProperty_MethodEx() throws PropertyException {
+ class Config extends PlainStringConfig {
+
+ public Config(Properties props) throws PropertyException {
+ super(props);
+ }
+
+ @Override
+ public void setValue(String value) {
+ throw new IllegalArgumentException("expected exception");
+ }
+ };
+
+ props.setProperty(THE_VALUE, STRING_VALUE);
+ new Config(props);
+ }
+
@Test
public void testGetValue() throws PropertyException {
// this class contains all of the supported field types
public Config(Properties props) throws PropertyException {
super(props);
}
+
+ @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");
public Config(Properties props) throws PropertyException {
super(props);
}
+
+ @SuppressWarnings("unused")
+ public void setValue(Exception value) {
+ this.value = value;
+ }
};
props.setProperty(THE_VALUE, STRING_VALUE);
public Config(Properties props) throws PropertyException {
super(props);
}
+
+ @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");
@Test(expected = PropertyAccessException.class)
public void testCheckModifiable_Static() throws PropertyException {
props.setProperty(THE_VALUE, STRING_VALUE);
- new StaticConfig(props);
+ new StaticPropConfig(props);
}
@Test(expected = PropertyAccessException.class)
new Config(props);
}
+ @Test(expected = PropertyAccessException.class)
+ public void testCheckSetter_Static() throws PropertyException {
+ props.setProperty(THE_VALUE, STRING_VALUE);
+ new StaticMethodConfig(props);
+ }
+
@Test
public void testGetStringValue() throws PropertyException {
- class Config extends PropertyConfiguration {
-
- @Property(name = THE_VALUE)
- private String value;
-
- public Config(Properties props) throws PropertyException {
- super(props);
- }
- };
-
props.setProperty(THE_VALUE, STRING_VALUE);
- Config cfg = new Config(props);
+ PlainStringConfig cfg = new PlainStringConfig(props);
assertEquals(STRING_VALUE, cfg.value);
}
@Test
public void testGetBooleanValue_NoDefault() throws PropertyException {
- class Config extends PropertyConfiguration {
-
- @Property(name = THE_VALUE)
- private Boolean value;
-
- public Config(Properties props) throws PropertyException {
- super(props);
- }
- };
-
props.setProperty(THE_VALUE, "true");
- Config cfg = new Config(props);
+ PlainBooleanConfig cfg = new PlainBooleanConfig(props);
assertEquals(true, cfg.value);
}
public Config(Properties props) throws PropertyException {
super(props);
}
+
+ @SuppressWarnings("unused")
+ public void setValue(Boolean value) {
+ this.value = value;
+ }
};
props.setProperty(THE_VALUE, "true");
public Config(Properties props) throws PropertyException {
super(props);
}
+
+ @SuppressWarnings("unused")
+ public void setValue(Boolean value) {
+ this.value = value;
+ }
};
// property not defined
public Config(Properties props) throws PropertyException {
super(props);
}
+
+ @SuppressWarnings("unused")
+ public void setValue(Boolean value) {
+ this.value = value;
+ }
};
// property not defined
public Config(Properties props) throws PropertyException {
super(props);
}
+
+ @SuppressWarnings("unused")
+ public void setValue(Integer value) {
+ this.value = value;
+ }
};
props.setProperty(THE_VALUE, "200");
public Config(Properties props) throws PropertyException {
super(props);
}
+
+ @SuppressWarnings("unused")
+ public void setValue(Integer value) {
+ this.value = value;
+ }
};
props.setProperty(THE_VALUE, "200");
public Config(Properties props) throws PropertyException {
super(props);
}
+
+ @SuppressWarnings("unused")
+ public void setValue(Integer value) {
+ this.value = value;
+ }
};
// property not defined
public Config(Properties props) throws PropertyException {
super(props);
}
+
+ @SuppressWarnings("unused")
+ public void setValue(Long value) {
+ this.value = value;
+ }
};
props.setProperty(THE_VALUE, "20000");
public Config(Properties props) throws PropertyException {
super(props);
}
+
+ @SuppressWarnings("unused")
+ public void setValue(Long value) {
+ this.value = value;
+ }
};
props.setProperty(THE_VALUE, "20000");
public Config(Properties props) throws PropertyException {
super(props);
}
+
+ @SuppressWarnings("unused")
+ public void setValue(Long value) {
+ this.value = value;
+ }
};
// property not defined
@Test
public void testGetPropValue_Prop_NoDefault() throws PropertyException {
- class Config extends PropertyConfiguration {
-
- @Property(name = THE_VALUE)
- private String value;
-
- public Config(Properties props) throws PropertyException {
- super(props);
- }
- };
-
props.setProperty(THE_VALUE, STRING_VALUE);
- Config cfg = new Config(props);
+ PlainStringConfig cfg = new PlainStringConfig(props);
assertEquals(STRING_VALUE, cfg.value);
}
public Config(Properties props) throws PropertyException {
super(props);
}
+
+ @SuppressWarnings("unused")
+ public void setValue(String value) {
+ this.value = value;
+ }
};
props.setProperty(THE_VALUE, STRING_VALUE);
public Config(Properties props) throws PropertyException {
super(props);
}
+
+ @SuppressWarnings("unused")
+ public void setValue(String value) {
+ this.value = value;
+ }
};
props.setProperty(THE_VALUE, "");
public Config(Properties props) throws PropertyException {
super(props);
}
+
+ @SuppressWarnings("unused")
+ public void setValue(String value) {
+ this.value = value;
+ }
};
Config cfg = new Config(props);
public Config(Properties props) throws PropertyException {
super(props);
}
+
+ @SuppressWarnings("unused")
+ public void setValue(String value) {
+ this.value = value;
+ }
};
Config cfg = new Config(props);
public Config(Properties props) throws PropertyException {
super(props);
}
+
+ @SuppressWarnings("unused")
+ public void setValue(String value) {
+ this.value = value;
+ }
};
new Config(props);
public Config(Properties props) throws PropertyException {
super(props);
}
+
+ @SuppressWarnings("unused")
+ public void setValue(String value) {
+ this.value = value;
+ }
};
Config cfg = new Config(props);
@Test
public void testGetRawPropertyValue() throws PropertyException {
- class Config extends PropertyConfiguration {
-
- @Property(name = THE_VALUE)
- private String value;
+ class Config extends PlainStringConfig {
public Config(Properties props) throws PropertyException {
super(props);
Config cfg = new Config(props);
- assertEquals(STRING_VALUE, cfg.value);
+ assertEquals(STRING_VALUE, cfg.getValue());
}
@Test
public void testMakeBoolean_True() throws PropertyException {
- class Config extends PropertyConfiguration {
-
- @Property(name = THE_VALUE)
- private Boolean value;
-
- public Config(Properties props) throws PropertyException {
- super(props);
- }
- };
-
props.setProperty(THE_VALUE, "true");
- Config cfg = new Config(props);
+ PlainBooleanConfig cfg = new PlainBooleanConfig(props);
assertEquals(true, cfg.value);
}
@Test
public void testMakeBoolean_False() throws PropertyException {
- class Config extends PropertyConfiguration {
-
- @Property(name = THE_VALUE)
- private Boolean value;
-
- public Config(Properties props) throws PropertyException {
- super(props);
- }
- };
-
props.setProperty(THE_VALUE, "false");
- Config cfg = new Config(props);
+ PlainBooleanConfig cfg = new PlainBooleanConfig(props);
assertEquals(false, cfg.value);
}
@Test(expected = PropertyInvalidException.class)
public void testMakeBoolean_Invalid() throws PropertyException {
- class Config extends PropertyConfiguration {
-
- @Property(name = THE_VALUE)
- private Boolean value;
-
- public Config(Properties props) throws PropertyException {
- super(props);
- }
- };
-
props.setProperty(THE_VALUE, INVALID_VALUE);
- new Config(props);
+ new PlainBooleanConfig(props);
}
@Test
public void testMakeInteger_Valid() throws PropertyException {
- class Config extends PropertyConfiguration {
-
- @Property(name = THE_VALUE)
- private int value;
-
- public Config(Properties props) throws PropertyException {
- super(props);
- }
- };
-
props.setProperty(THE_VALUE, "300");
- Config cfg = new Config(props);
+ PlainPrimIntConfig cfg = new PlainPrimIntConfig(props);
assertEquals(300, cfg.value);
}
@Test(expected = PropertyInvalidException.class)
public void testMakeInteger_Invalid() throws PropertyException {
- class Config extends PropertyConfiguration {
-
- @Property(name = THE_VALUE)
- private int value;
-
- public Config(Properties props) throws PropertyException {
- super(props);
- }
- };
-
props.setProperty(THE_VALUE, INVALID_VALUE);
- new Config(props);
+ new PlainPrimIntConfig(props);
}
@Test(expected = PropertyInvalidException.class)
public void testMakeInteger_TooBig() throws PropertyException {
- class Config extends PropertyConfiguration {
-
- @Property(name = THE_VALUE)
- private int value;
-
- public Config(Properties props) throws PropertyException {
- super(props);
- }
- };
-
props.setProperty(THE_VALUE, String.valueOf(Integer.MAX_VALUE + 10L));
- new Config(props);
+ new PlainPrimIntConfig(props);
}
@Test
public void testMakeLong_Valid() throws PropertyException {
- class Config extends PropertyConfiguration {
-
- @Property(name = THE_VALUE)
- private long value;
-
- public Config(Properties props) throws PropertyException {
- super(props);
- }
- };
-
props.setProperty(THE_VALUE, "30000");
- Config cfg = new Config(props);
+ PlainPrimLongConfig cfg = new PlainPrimLongConfig(props);
assertEquals(30000L, cfg.value);
}
@Test(expected = PropertyInvalidException.class)
public void testMakeLong_Invalid() throws PropertyException {
- class Config extends PropertyConfiguration {
-
- @Property(name = THE_VALUE)
- private long value;
-
- public Config(Properties props) throws PropertyException {
- super(props);
- }
- };
-
props.setProperty(THE_VALUE, INVALID_VALUE);
- new Config(props);
+ new PlainPrimLongConfig(props);
}
@Test
public Config(Properties props) throws PropertyException {
super(props);
}
+
+ @SuppressWarnings("unused")
+ public void setValue(long value) {
+ this.value = value;
+ }
};
Config cfg = new Config(props);
public Config(Properties props) throws PropertyException {
super(props);
}
+
+ @SuppressWarnings("unused")
+ public void setValue(long value) {
+ this.value = value;
+ }
};
new Config(props);
public Config(Properties props) throws PropertyException {
super(props);
}
+
+ @SuppressWarnings("unused")
+ public void setValue(long value) {
+ this.value = value;
+ }
};
new Config(props);
public Config(Properties props) throws PropertyException {
super(props);
}
+
+ @SuppressWarnings("unused")
+ public void setValue(String value) {
+ this.value = value;
+ }
};
// missing property - should default to ""
public Config(Properties props) throws PropertyException {
super(props);
}
+
+ @SuppressWarnings("unused")
+ public void setValue(long value) {
+ this.value = value;
+ }
};
new Config(props);
public Config(Properties props) throws PropertyException {
super(props);
}
+
+ @SuppressWarnings("unused")
+ public void setValue(String value) {
+ this.value = value;
+ }
};
Config cfg = new Config(props);
public Config(Properties props) throws PropertyException {
super(props);
}
+
+ @SuppressWarnings("unused")
+ public void setValue(long value) {
+ this.value = value;
+ }
};
new Config(props);
}
/**
- * A config whose annotated property is "static".
+ * Config with a String value having no qualifiers.
*/
- public static class StaticConfig extends PropertyConfiguration {
+ public class PlainStringConfig extends PropertyConfiguration {
+
+ @Property(name = THE_VALUE)
+ private String value;
+
+ public PlainStringConfig() {
+
+ }
+
+ public PlainStringConfig(Properties props) throws PropertyException {
+ super(props);
+ }
+
+ public String getValue() {
+ return value;
+ }
+
+ public void setValue(String value) {
+ this.value = value;
+ }
+ };
+
+ /**
+ * Config with a Boolean value having no qualifiers.
+ */
+ public class PlainBooleanConfig extends PropertyConfiguration {
+
+ @Property(name = THE_VALUE)
+ private Boolean value;
+
+ public PlainBooleanConfig(Properties props) throws PropertyException {
+ super(props);
+ }
+
+ public void setValue(Boolean value) {
+ this.value = value;
+ }
+ };
+
+ /**
+ * Config with an int value having no qualifiers.
+ */
+ public class PlainPrimIntConfig extends PropertyConfiguration {
+
+ @Property(name = THE_VALUE)
+ private int value;
+
+ public PlainPrimIntConfig(Properties props) throws PropertyException {
+ super(props);
+ }
+
+ public void setValue(int value) {
+ this.value = value;
+ }
+ };
+
+ /**
+ * Config with a long value having no qualifiers.
+ */
+ public class PlainPrimLongConfig extends PropertyConfiguration {
+
+ @Property(name = THE_VALUE)
+ private long value;
+
+ public PlainPrimLongConfig(Properties props) throws PropertyException {
+ super(props);
+ }
+
+ public void setValue(long value) {
+ this.value = value;
+ }
+ };
+
+ /**
+ * A config whose field is "static".
+ */
+ public static class StaticPropConfig extends PropertyConfiguration {
// "static" field cannot be set
@Property(name = THE_VALUE)
private static String value;
- public StaticConfig(Properties props) throws PropertyException {
+ public StaticPropConfig(Properties props) throws PropertyException {
super(props);
}
+
+ public static void setValue(String value) {
+ StaticPropConfig.value = value;
+ }
+ };
+
+ /**
+ * A config whose method is "static".
+ */
+ public static class StaticMethodConfig extends PropertyConfiguration {
+
+ // "static" field cannot be set
+ @Property(name = THE_VALUE)
+ private String value;
+
+ public StaticMethodConfig(Properties props) throws PropertyException {
+ super(props);
+ }
+
+ public static void setValue(String value) {
+
+ }
};
/**