All Downloads are FREE. Search and download functionalities are using the official Maven repository.

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