org.constretto.internal.DefaultConstrettoConfiguration Maven / Gradle / Ivy
/*
* Copyright 2008 the original author or authors.
*
* 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.
*/
package org.constretto.internal;
import com.thoughtworks.paranamer.BytecodeReadingParanamer;
import com.thoughtworks.paranamer.Paranamer;
import org.constretto.ConfigurationDefaultValueFactory;
import org.constretto.ConstrettoConfiguration;
import org.constretto.GenericConverter;
import org.constretto.Property;
import org.constretto.annotation.Configuration;
import org.constretto.annotation.Configure;
import org.constretto.annotation.Tags;
import org.constretto.exception.ConstrettoConversionException;
import org.constretto.exception.ConstrettoException;
import org.constretto.exception.ConstrettoExpressionException;
import org.constretto.internal.converter.ValueConverterRegistry;
import org.constretto.internal.introspect.Constructors;
import org.constretto.model.CPrimitive;
import org.constretto.model.CValue;
import org.constretto.model.ConfigurationValue;
import java.lang.annotation.Annotation;
import java.lang.reflect.*;
import java.util.*;
import static org.constretto.internal.GenericCollectionTypeResolver.*;
/**
* @author Kaare Nilsen
*/
public class DefaultConstrettoConfiguration implements ConstrettoConfiguration {
private final Paranamer paranamer = new BytecodeReadingParanamer();
protected final Map> configuration;
private final List originalTags = new ArrayList<>();
protected final List currentTags = new ArrayList<>();
public DefaultConstrettoConfiguration(Map> configuration, List originalTags) {
this.configuration = configuration;
this.originalTags.addAll(originalTags);
this.currentTags.addAll(originalTags);
}
public DefaultConstrettoConfiguration(Map> configuration) {
this.configuration = configuration;
}
@SuppressWarnings("unchecked")
public K evaluateTo(String expression, K defaultValue) {
if (!hasValue(expression)) {
return defaultValue;
}
K value;
try {
value = (K) processAndConvert(defaultValue.getClass(), expression);
} catch (ConstrettoConversionException e) {
value = null;
}
return null != value ? value : defaultValue;
}
public T evaluateWith(GenericConverter converter, String expression) {
ConfigurationValue value = findElementOrThrowException(expression);
return converter.fromValue(value.value());
}
public CValue evaluate(String expression) throws ConstrettoExpressionException {
return findElementOrThrowException(expression).value();
}
@SuppressWarnings("unchecked")
public List evaluateToList(Class targetClass, String expression) {
ConfigurationValue value = findElementOrThrowException(expression);
return (List) ValueConverterRegistry.convert(targetClass, targetClass, value.value());
}
@SuppressWarnings("unchecked")
public Map evaluateToMap(Class keyClass, Class valueClass, String expression) {
ConfigurationValue value = findElementOrThrowException(expression);
return (Map) ValueConverterRegistry.convert(valueClass, keyClass, value.value());
}
public K evaluateTo(Class targetClass, String expression) throws ConstrettoExpressionException {
return processAndConvert(targetClass, expression);
}
public String evaluateToString(String expression) throws ConstrettoExpressionException {
return processAndConvert(String.class, expression);
}
public Boolean evaluateToBoolean(String expression) throws ConstrettoExpressionException {
return processAndConvert(Boolean.class, expression);
}
public Double evaluateToDouble(String expression) throws ConstrettoExpressionException {
return processAndConvert(Double.class, expression);
}
public Long evaluateToLong(String expression) throws ConstrettoExpressionException {
return processAndConvert(Long.class, expression);
}
public Float evaluateToFloat(String expression) throws ConstrettoExpressionException {
return processAndConvert(Float.class, expression);
}
public Integer evaluateToInt(String expression) throws ConstrettoExpressionException {
return processAndConvert(Integer.class, expression);
}
public Short evaluateToShort(String expression) throws ConstrettoExpressionException {
return processAndConvert(Short.class, expression);
}
public Byte evaluateToByte(String expression) throws ConstrettoExpressionException {
return processAndConvert(Byte.class, expression);
}
public T as(Class configurationClass) throws ConstrettoException {
T objectToConfigure;
try {
objectToConfigure = createInstance(configurationClass);
} catch (ConstrettoException e) {
throw e;
}
catch (Exception e) {
throw new ConstrettoException("Could not instansiate class of type: " + configurationClass.getName()
+ " when trying to inject it with configuration, It may be missing a default or @Configure annotated constructor", e);
}
injectConfiguration(objectToConfigure);
return objectToConfigure;
}
public T on(T objectToConfigure) throws ConstrettoException {
injectConfiguration(objectToConfigure);
return objectToConfigure;
}
public Map asMap() {
Map properties = new HashMap<>();
for (Map.Entry> entry : configuration.entrySet()) {
ConfigurationValue value = findElementOrNull(entry.getKey());
if (value != null){
properties.put(entry.getKey(), value.value().toString());
}
}
return properties;
}
public boolean hasValue(String expression) {
return findElementOrNull(expression) != null;
}
public Iterator iterator() {
Map map = asMap();
List properties = new ArrayList<>(map.size());
for (Map.Entry entry : map.entrySet()) {
properties.add(new Property(entry.getKey(), entry.getValue()));
}
return properties.iterator();
}
//
// Helper methods
//
private T createInstance(final Class configurationClass) throws InstantiationException, IllegalAccessException {
if(configurationClass.isInterface()) {
throw new ConstrettoException("Can not instantiate interfaces. You need to create an concrete implementing class first");
}
if (configurationClass.isAnonymousClass()) {
throw new ConstrettoException("Can not instantiate anonymous classes using as(Class. To inject configuration in to inner or anonymous classes, " +
"instantiate it first and call the on(T configuredObjecT) method");
}
Constructor[] annotatedConstructors = findAnnotatedConstructorsOnClass(configurationClass);
if(configurationClass.isMemberClass() && annotatedConstructors != null) {
throw new ConstrettoException("Can not instantiate inner classes using a @Configure annotated constructor. " +
"To inject configuration, construct the instance yourself use the \"on(T configuredObject)\" method");
}
if(annotatedConstructors == null) {
return configurationClass.newInstance();
} else {
if(annotatedConstructors.length > 1) {
throw new ConstrettoException("More than one @Configure annotated constructor defined for class \"" + configurationClass.getName() + "\". It can only be one");
}
Constructor constructor = annotatedConstructors[0];
final Object[] resolvedParameters = resolveParameters(constructor);
try {
constructor.setAccessible(true);
return constructor.newInstance(resolvedParameters);
} catch (InvocationTargetException e) {
throw new ConstrettoException("Could not instantiate class with @Configure annotated constructor");
}
}
}
private Constructor[] findAnnotatedConstructorsOnClass(final Class configurationClass) {
return Constructors.findConstructorsWithConfigureAnnotation(configurationClass);
}
protected ConfigurationValue findElementOrThrowException(String expression) {
if (!configuration.containsKey(expression)) {
throw new ConstrettoExpressionException(expression, currentTags);
}
List values = configuration.get(expression);
ConfigurationValue resolvedNode = resolveMatch(values);
if (resolvedNode == null) {
throw new ConstrettoExpressionException(expression, currentTags);
}
if (resolvedNode.value().containsVariables()) {
for (String key : resolvedNode.value().referencedKeys()) {
resolvedNode.value().replace(key, evaluateToString(key));
}
}
return resolvedNode;
}
protected ConfigurationValue findElementOrNull(String expression) {
if (!configuration.containsKey(expression)) {
return null;
}
List values = configuration.get(expression);
ConfigurationValue resolvedNode = resolveMatch(values);
if (resolvedNode == null) {
return null;
}
if (resolvedNode.value().containsVariables()) {
for (String key : resolvedNode.value().referencedKeys()) {
resolvedNode.value().replace(key, evaluateToString(key));
}
}
return resolvedNode;
}
@SuppressWarnings("unchecked")
private T processAndConvert(Class clazz, String expression) throws ConstrettoException {
ConfigurationValue value = findElementOrThrowException(expression);
return (T) ValueConverterRegistry.convert(clazz, clazz, value.value());
}
private ConfigurationValue resolveMatch(List values) {
ConfigurationValue bestMatch = null;
for (ConfigurationValue configurationNode : values) {
if (ConfigurationValue.DEFAULT_TAG.equals(configurationNode.tag())) {
if (bestMatch == null || bestMatch.tag().equals(ConfigurationValue.DEFAULT_TAG)) {
bestMatch = configurationNode;
}
} else if (currentTags.contains(configurationNode.tag())) {
if (bestMatch == null) {
bestMatch = configurationNode;
} else {
int previousFoundPriority =
ConfigurationValue.DEFAULT_TAG.equals(bestMatch.tag()) ?
Integer.MAX_VALUE : currentTags.indexOf(bestMatch.tag());
if (currentTags.indexOf(configurationNode.tag()) <= previousFoundPriority) {
bestMatch = configurationNode;
}
}
} else if (ConfigurationValue.ALL_TAG.equals(configurationNode.tag())) {
bestMatch = configurationNode;
}
}
return bestMatch;
}
private void injectConfiguration(T objectToConfigure) {
injectFields(objectToConfigure);
injectMethods(objectToConfigure);
}
private Object[] resolveParameters(AccessibleObject accessibleObject) throws IllegalAccessException, InstantiationException {
Annotation[][] methodAnnotations;
String[] parameterNames;
Class>[] parameterTargetTypes;
if(accessibleObject instanceof Method) {
Method method = (Method) accessibleObject;
methodAnnotations = method.getParameterAnnotations();
parameterNames = paranamer.lookupParameterNames(method);
parameterTargetTypes = method.getParameterTypes();
} else if(accessibleObject instanceof Constructor) {
Constructor constructor = (Constructor) accessibleObject;
methodAnnotations = constructor.getParameterAnnotations();
parameterNames = paranamer.lookupParameterNames(constructor);
parameterTargetTypes = constructor.getParameterTypes();
} else {
throw new ConstrettoException("Could not resolve parameter names ");
}
Object[] resolvedArguments = new Object[methodAnnotations.length];
int i = 0;
for (Annotation[] parameterAnnotations : methodAnnotations) {
Object defaultValue = null;
boolean required = true;
String expression = "";
Class> parameterTargetClass = parameterTargetTypes[i];
if (parameterAnnotations.length != 0) {
for (Annotation parameterAnnotation : parameterAnnotations) {
if (parameterAnnotation.annotationType() == Configuration.class) {
Configuration configurationAnnotation = (Configuration) parameterAnnotation;
expression = configurationAnnotation.value();
required = configurationAnnotation.required();
if (hasAnnotationDefaults(configurationAnnotation)) {
if (configurationAnnotation.defaultValueFactory().equals(Configuration.EmptyValueFactory.class)) {
defaultValue = ValueConverterRegistry.convert(parameterTargetClass, parameterTargetClass, new CPrimitive(configurationAnnotation.defaultValue()));
} else {
ConfigurationDefaultValueFactory valueFactory = configurationAnnotation.defaultValueFactory().newInstance();
defaultValue = valueFactory.getDefaultValue();
}
}
}
}
}
if (expression.equals("")) {
if (parameterNames == null) {
throw new ConstrettoException("Could not resolve the expression of the property to look up. " +
"The cause of this could be that the class is compiled without debug enabled. " +
"when a class is compiled without debug, the @Configuration with a value attribute is required " +
"to correctly resolve the property expression.");
} else {
expression = parameterNames[i];
}
}
if (hasValue(expression)) {
if (parameterTargetClass.isAssignableFrom(List.class)) {
Class> collectionParameterType = getCollectionParameterType(createMethodParameter(accessibleObject, i));
resolvedArguments[i] = evaluateToList(collectionParameterType, expression);
} else if (parameterTargetClass.isAssignableFrom(Map.class)) {
Class> mapKeyType = getMapKeyParameterType(createMethodParameter(accessibleObject, i));
Class> mapValueType = getMapValueParameterType(createMethodParameter(accessibleObject, i));
resolvedArguments[i] = evaluateToMap(mapKeyType, mapValueType, expression);
} else {
resolvedArguments[i] = processAndConvert(parameterTargetClass, expression);
}
} else {
if (defaultValue != null || !required) {
resolvedArguments[i] = defaultValue;
} else {
if(accessibleObject instanceof Constructor) {
Constructor constructor = (Constructor) accessibleObject;
throw new ConstrettoException("Missing value or default value for expression [" + expression + "], in annotated constructor in class [" + constructor.getClass().getName() + "], with tags " + currentTags + ".");
}
else {
Method method = (Method) accessibleObject;
throw new ConstrettoException("Missing value or default value for expression [" + expression + "], in method [" + method.getName() + "], in class [" + method.getClass().getName() + "], with tags " + currentTags + ".");
}
}
}
i++;
}
return resolvedArguments;
}
private void injectMethods(T objectToConfigure) {
Method[] methods = objectToConfigure.getClass().getMethods();
for (Method method : methods) {
try {
if (method.isAnnotationPresent(Configure.class)) {
Object[] resolvedArguments = resolveParameters(method);
method.setAccessible(true);
method.invoke(objectToConfigure, resolvedArguments);
}
} catch (IllegalAccessException e) {
throw new ConstrettoException("Cold not invoke method ["
+ method.getName() + "] annotated with @Configured,", e);
} catch (InvocationTargetException e) {
throw new ConstrettoException("Cold not invoke method ["
+ method.getName() + "] annotated with @Configured,", e);
} catch (InstantiationException e) {
throw new ConstrettoException("Cold not invoke method ["
+ method.getName() + "] annotated with @Configured,", e);
}
}
}
private MethodParameter createMethodParameter(T accessibleObject, final int parameterIndex) {
if(accessibleObject instanceof Constructor) {
return new MethodParameter((Constructor) accessibleObject, parameterIndex);
} else {
return new MethodParameter((Method) accessibleObject, parameterIndex);
}
}
private void injectFields(T objectToConfigure) {
Class objectToConfigureClass = objectToConfigure.getClass();
do {
Field[] fields = objectToConfigureClass.getDeclaredFields();
for (Field field : fields) {
try {
if (field.isAnnotationPresent(Configuration.class)) {
Configuration configurationAnnotation = field.getAnnotation(Configuration.class);
String expression = "".equals(configurationAnnotation.value()) ? field.getName() : configurationAnnotation.value();
field.setAccessible(true);
Class> fieldType = field.getType();
if (hasValue(expression)) {
ConfigurationValue node = findElementOrThrowException(expression);
if (fieldType.isAssignableFrom(List.class)) {
field.set(objectToConfigure, evaluateToList(getCollectionFieldType(field), expression));
} else if (fieldType.isAssignableFrom(Map.class)) {
field.set(objectToConfigure, evaluateToMap(getMapKeyFieldType(field), getMapValueFieldType(field), expression));
} else {
field.set(objectToConfigure, processAndConvert(fieldType, expression));
}
} else {
if (hasAnnotationDefaults(configurationAnnotation)) {
if (configurationAnnotation.defaultValueFactory().equals(Configuration.EmptyValueFactory.class)) {
field.set(objectToConfigure, ValueConverterRegistry.convert(fieldType, fieldType, new CPrimitive(configurationAnnotation.defaultValue())));
} else {
ConfigurationDefaultValueFactory valueFactory = configurationAnnotation.defaultValueFactory().newInstance();
field.set(objectToConfigure, valueFactory.getDefaultValue());
}
} else if (configurationAnnotation.required()) {
throw new ConstrettoException("Missing value or default value for expression [" + expression + "] for field [" + field.getName() + "], in class [" + objectToConfigure.getClass().getName() + "] with tags " + currentTags + ".");
}
}
} else if (field.isAnnotationPresent(Tags.class)) {
field.setAccessible(true);
field.set(objectToConfigure, currentTags);
}
} catch (IllegalAccessException e) {
throw new ConstrettoException("Cold not inject configuration into field ["
+ field.getName() + "] annotated with @Configuration, in class [" + objectToConfigure.getClass().getName() + "] with tags " + currentTags, e);
} catch (InstantiationException e) {
throw new ConstrettoException("Cold not inject configuration into field ["
+ field.getName() + "] annotated with @Configuration, in class [" + objectToConfigure.getClass().getName() + "] with tags " + currentTags, e);
}
}
} while ((objectToConfigureClass = objectToConfigureClass.getSuperclass()) != null);
}
private boolean hasAnnotationDefaults(Configuration configurationAnnotation) {
return !("N/A".equals(configurationAnnotation.defaultValue()) && configurationAnnotation.defaultValueFactory().equals(Configuration.EmptyValueFactory.class));
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy