Package org.constretto.internal

Source Code of org.constretto.internal.DefaultConstrettoConfiguration

/*
* 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.ref.WeakReference;
import java.lang.reflect.*;
import java.util.*;
import java.util.concurrent.CopyOnWriteArraySet;

import static java.util.Arrays.asList;
import static org.constretto.internal.GenericCollectionTypeResolver.*;

/**
* @author <a href="mailto:kaare.nilsen@gmail.com">Kaare Nilsen</a>
*/
public class DefaultConstrettoConfiguration implements ConstrettoConfiguration {
    private static final String NULL_STRING = "![![Null]!]!";

    private final Paranamer paranamer = new BytecodeReadingParanamer();

    protected final Map<String, List<ConfigurationValue>> configuration;
    private Set<WeakReference<Object>> configuredObjects = new CopyOnWriteArraySet<WeakReference<Object>>();
    private final List<String> originalTags = new ArrayList<String>();
    protected final List<String> currentTags = new ArrayList<String>();

    public DefaultConstrettoConfiguration(Map<String, List<ConfigurationValue>> configuration, List<String> originalTags) {
        this.configuration = configuration;
        this.originalTags.addAll(originalTags);
        this.currentTags.addAll(originalTags);
    }

    public DefaultConstrettoConfiguration(Map<String, List<ConfigurationValue>> configuration) {
        this.configuration = configuration;
    }

    @SuppressWarnings("unchecked")
    public <K> 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> T evaluateWith(GenericConverter<T> 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 <K> List<K> evaluateToList(Class<K> targetClass, String expression) {
        ConfigurationValue value = findElementOrThrowException(expression);
        return (List<K>) ValueConverterRegistry.convert(targetClass, targetClass, value.value());
    }

    @SuppressWarnings("unchecked")
    public <K, V> Map<K, V> evaluateToMap(Class<K> keyClass, Class<V> valueClass, String expression) {
        ConfigurationValue value = findElementOrThrowException(expression);
        return (Map<K, V>) ValueConverterRegistry.convert(valueClass, keyClass, value.value());
    }

    public <K> K evaluateTo(Class<K> 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> T as(Class<T> 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> T on(T objectToConfigure) throws ConstrettoException {
        injectConfiguration(objectToConfigure);
        return objectToConfigure;
    }

    public Map<String, String> asMap() {
        Map<String, String> properties = new HashMap<String, String>();
        for (Map.Entry<String, List<ConfigurationValue>> 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 void appendTag(String... newtags) {
        currentTags.addAll(asList(newtags));
        reconfigure();
    }

    public void prependTag(String... newtags) {
        currentTags.addAll(0, asList(newtags));
        reconfigure();
    }

    public void resetTags(boolean reconfigure) {
        currentTags.clear();
        currentTags.addAll(originalTags);
        if (reconfigure)
            reconfigure();
    }

    public void clearTags(boolean reconfigure) {
        currentTags.clear();
        originalTags.clear();
        if (reconfigure)
            reconfigure();
    }

    public void removeTag(String... newTags) {
        for (String newTag : newTags) {
            currentTags.remove(newTag);
        }
        reconfigure();
    }

    public List<String> getCurrentTags() {
        return currentTags;
    }

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

    @Override
    public void reconfigure() {
        WeakReference[] references = configuredObjects.toArray(new WeakReference[configuredObjects.size()]);
        for (WeakReference reference : references) {
            if (reference != null && reference.get() != null) {
                on(reference.get());
            }
        }
    }

    //
    // Helper methods
    //
    private <T> T createInstance(final Class<T> 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<T>. To inject configuration in to inner or anonymous classes, " +
                                                  "instantiate it first and call the on(T configuredObjecT) method");
        }
        Constructor<T>[] 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<T> 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 <T> Constructor<T>[] findAnnotatedConstructorsOnClass(final Class<T> configurationClass) {
        return Constructors.findConstructorsWithConfigureAnnotation(configurationClass);
    }

    protected ConfigurationValue findElementOrThrowException(String expression) {
        if (!configuration.containsKey(expression)) {
            throw new ConstrettoExpressionException(expression, currentTags);
        }
        List<ConfigurationValue> 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<ConfigurationValue> 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> T processAndConvert(Class<T> clazz, String expression) throws ConstrettoException {
        ConfigurationValue value = findElementOrThrowException(expression);
        return (T) ValueConverterRegistry.convert(clazz, clazz, value.value());
    }

    private ConfigurationValue resolveMatch(List<ConfigurationValue> 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 <T> void injectConfiguration(T objectToConfigure) {
        injectFields(objectToConfigure);
        injectMethods(objectToConfigure);
        boolean found = false;
        for (WeakReference<Object> configuredObject : configuredObjects) {
            if (configuredObject.get() == objectToConfigure) {
                found = true;
                break;
            }
        }
        if (!found) {
            this.configuredObjects.add(new WeakReference<Object>(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 <T> 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 <T extends AccessibleObject> 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 <T> 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));
    }
}
TOP

Related Classes of org.constretto.internal.DefaultConstrettoConfiguration

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.