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 org.constretto.ConfigurationDefaultValueFactory;
import org.constretto.ConstrettoConfiguration;
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.model.ConfigurationNode;
import org.springframework.core.LocalVariableTableParameterNameDiscoverer;

import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.*;

/**
 * @author Kaare Nilsen
 */
public class DefaultConstrettoConfiguration implements ConstrettoConfiguration {
    private static final String NULL_STRING = "![![Null]!]!";
    private static final String VARIABLE_PREFIX = "#{";
    private static final String VARIABLE_SUFFIX = "}";
    private List currentTags;
    private final ConfigurationNode configuration;
    private LocalVariableTableParameterNameDiscoverer nameDiscoverer = new LocalVariableTableParameterNameDiscoverer();

    public DefaultConstrettoConfiguration(ConfigurationNode configuration, List currentTags) {
        this.configuration = configuration;
        this.currentTags = currentTags;
    }

    @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  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 = configurationClass.newInstance();
        } 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 constructor", e);
        }

        injectConfiguration(objectToConfigure);

        return objectToConfigure;
    }

    public  T on(T objectToConfigure) throws ConstrettoException {
        injectConfiguration(objectToConfigure);
        return objectToConfigure;
    }

    public ConstrettoConfiguration at(String expression) throws ConstrettoException {
        ConfigurationNode currentConfigurationNode = findElementOrThrowException(expression);
        ConfigurationNode.createRootElementOf(currentConfigurationNode);
        return new DefaultConstrettoConfiguration(currentConfigurationNode, currentTags);
    }

    public ConstrettoConfiguration from(String expression) throws ConstrettoException {
        return at(expression);
    }

    public boolean hasValue(String expression) {
        ConfigurationNode node = findElementOrNull(expression);
        return null != node;
    }

    public Iterator iterator() {
        List properties = new ArrayList();
        Map map = asMap();
        for (Map.Entry entry : map.entrySet()) {
            properties.add(new Property(entry.getKey(),entry.getValue()));                        
        }
        return properties.iterator();
    }

     private Map asMap(){
        Map properties = new HashMap();
        extractProperties(configuration,properties);
        return properties;
    }

    private void extractProperties(ConfigurationNode currentNode, Map properties) {
        String value = evaluateTo(currentNode.getExpression(),NULL_STRING);
        if (!value.equals(NULL_STRING)){
            properties.put(currentNode.getExpression(),value);
        }
        if (currentNode.hasChildren()){
            for (ConfigurationNode child : currentNode.children()) {
                extractProperties(child,properties);
            }
        }
    }

    //
    // Helper methods
    //
    private ConfigurationNode findElementOrThrowException(String expression) {
        List node = configuration.findAllBy(expression);
        ConfigurationNode resolvedNode = resolveMatch(node);
        if (resolvedNode == null) {
            throw new ConstrettoExpressionException(expression, currentTags);
        }
        return resolvedNode;
    }

    private  T processAndConvert(Class clazz, String expression) throws ConstrettoException {
        String parsedValue = processVariablesInProperty(expression, new ArrayList());
        return ValueConverterRegistry.convert(clazz, parsedValue);
    }

    private ConfigurationNode findElementOrNull(String expression) {
        List node = configuration.findAllBy(expression);
        return resolveMatch(node);
    }

    private ConfigurationNode resolveMatch(List node) {
        ConfigurationNode bestMatch = null;
        for (ConfigurationNode configurationNode : node) {
            if (ConfigurationNode.DEFAULT_TAG.equals(configurationNode.getTag())) {
                if (bestMatch == null || bestMatch.getTag().equals(ConfigurationNode.DEFAULT_TAG)) {
                    bestMatch = configurationNode;
                }
            } else if (currentTags.contains(configurationNode.getTag())) {
                if (bestMatch == null) {
                    bestMatch = configurationNode;
                } else {
                    int previousFoundPriority =
                            ConfigurationNode.DEFAULT_TAG.equals(bestMatch.getTag()) ?
                                    Integer.MAX_VALUE : currentTags.indexOf(bestMatch.getTag());
                    if (currentTags.indexOf(configurationNode.getTag()) <= previousFoundPriority) {
                        bestMatch = configurationNode;
                    }
                }
            } else if (ConfigurationNode.ALL_TAG.equals(configurationNode.getTag())) {
                bestMatch = configurationNode;
            }
        }
        return bestMatch;
    }

    private  void injectConfiguration(T objectToConfigure) {
        injectFields(objectToConfigure);
        injectMethods(objectToConfigure);
    }

    private  void injectMethods(T objectToConfigure) {
        Method[] methods = objectToConfigure.getClass().getMethods();
        for (Method method : methods) {
            try {
                if (method.isAnnotationPresent(Configure.class)) {
                    Annotation[][] methodAnnotations = method.getParameterAnnotations();
                    String[] parameterNames = nameDiscoverer.getParameterNames(method);
                    Object[] resolvedArguments = new Object[methodAnnotations.length];
                    int i = 0;
                    Object defaultValue = null;
                    boolean required = true;
                    for (Annotation[] parameterAnnotations : methodAnnotations) {
                        String expression = "";
                        Class parameterTargetClass = method.getParameterTypes()[i];
                        if (parameterAnnotations.length != 0) {
                            for (Annotation parameterAnnotation : parameterAnnotations) {
                                if (parameterAnnotation.annotationType() == Configuration.class) {
                                    Configuration configurationAnnotation = (Configuration) parameterAnnotation;
                                    expression = configurationAnnotation.expression();
                                    required = configurationAnnotation.required();
                                    if (hasAnnotationDefaults(configurationAnnotation)) {
                                        if (configurationAnnotation.defaultValueFactory().equals(Configuration.EmptyValueFactory.class)) {
                                            defaultValue = ValueConverterRegistry.convert(parameterTargetClass, 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 expression attribute is required " +
                                        "to correctly resolve the property expression.");
                            } else {
                                expression = parameterNames[i];
                            }
                        }
                        if (hasValue(expression)) {
                            ConfigurationNode node = findElementOrThrowException(expression);
                            resolvedArguments[i] = processAndConvert(parameterTargetClass, node.getExpression());
                        } else {
                            if (defaultValue != null || !required) {
                                resolvedArguments[i] = defaultValue;
                            } else {
                                throw new ConstrettoException("Missing value or default value for expression [" + expression + "], in method [" + method.getName() + "], in class [" + objectToConfigure.getClass().getName() + "], with tags " + currentTags + ".");
                            }
                        }

                        i++;
                    }

                    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  void injectFields(T objectToConfigure) {

        Field[] fields = objectToConfigure.getClass().getDeclaredFields();
        for (Field field : fields) {
            try {
                if (field.isAnnotationPresent(Configuration.class)) {
                    Configuration configurationAnnotation = field.getAnnotation(Configuration.class);
                    String expression = "".equals(configurationAnnotation.expression()) ? field.getName() : configurationAnnotation.expression();
                    field.setAccessible(true);
                    Class fieldType = field.getType();
                    if (hasValue(expression)) {
                        ConfigurationNode node = findElementOrThrowException(expression);
                        field.set(objectToConfigure, processAndConvert(fieldType, node.getExpression()));
                    } else {
                        if (hasAnnotationDefaults(configurationAnnotation)) {
                            if (configurationAnnotation.defaultValueFactory().equals(Configuration.EmptyValueFactory.class)) {
                                field.set(objectToConfigure, ValueConverterRegistry.convert(fieldType, 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);
            }
        }

    }

    private boolean hasAnnotationDefaults(Configuration configurationAnnotation) {
        return !("N/A".equals(configurationAnnotation.defaultValue()) && configurationAnnotation.defaultValueFactory().equals(Configuration.EmptyValueFactory.class));
    }

    private String processVariablesInProperty(final String expression, final Collection visitedPlaceholders) {
        visitedPlaceholders.add(expression);
        ConfigurationNode currentNode = findElementOrThrowException(expression);

        String value = currentNode.getValue();
        if (valueNeedsVariableResolving(value)) {
            value = substituteVariablesinValue(value, visitedPlaceholders);
        }
        return value;
    }

    private String substituteVariablesinValue(String value, final Collection visitedPlaceholders) {
        while (valueNeedsVariableResolving(value)) {
            ConfigurationVariable expresionToLookup = extractConfigurationVariable(value);
            if (visitedPlaceholders.contains(expresionToLookup.expression)) {
                throw new ConstrettoException(
                        "A cyclic dependency found in a property");
            }
            DefaultConstrettoConfiguration rootConfig = new DefaultConstrettoConfiguration(configuration.root(), currentTags);

            value = value.substring(0, expresionToLookup.startIndex)
                    + rootConfig.processVariablesInProperty(expresionToLookup.expression, visitedPlaceholders)
                    + value.subSequence(expresionToLookup.endIndex + 1, value.length());
        }
        return value;
    }

    private ConfigurationVariable extractConfigurationVariable(String expression) {
        int startIndex = expression.indexOf(VARIABLE_PREFIX);
        int endindex = expression.indexOf(VARIABLE_SUFFIX, startIndex);
        String parsedExpression = expression.substring(startIndex + 2, endindex);
        return new ConfigurationVariable(startIndex, endindex, parsedExpression);
    }

    private boolean valueNeedsVariableResolving(String value) {
        return null != value && value.contains(VARIABLE_PREFIX) && value.contains(VARIABLE_SUFFIX);
    }

    private static class ConfigurationVariable {
        private final int startIndex;
        private final int endIndex;
        private final String expression;

        public ConfigurationVariable(int startIndex, int endIndex, String expression) {
            this.startIndex = startIndex;
            this.endIndex = endIndex;
            this.expression = expression;
        }

        @Override
        public String toString() {
            return expression + ", at: " + startIndex + " to: " + endIndex;
        }
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy