Package com.avast.syringe.config.internal

Source Code of com.avast.syringe.config.internal.ReflectionInjectableProperty

package com.avast.syringe.config.internal;

import com.avast.syringe.Provider;
import com.avast.syringe.config.ConfigProperty;
import com.avast.syringe.config.MutableReference;
import com.avast.syringe.config.PropertyValueConverter;
import com.google.common.base.Preconditions;
import com.google.common.primitives.Primitives;

import javax.annotation.Nullable;
import java.lang.reflect.*;
import java.util.Collection;
import java.util.Map;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicLong;
import java.util.concurrent.atomic.AtomicReference;

/**
* User: slajchrt
* Date: 4/2/12
* Time: 3:10 PM
*/
public class ReflectionInjectableProperty implements InjectableProperty {

    // Right now we only support fields.
    private final Field field;
    private final Method atomicSetter;
    private final Method atomicGetter;
    private final boolean optional;
    private final String name;
    private final boolean delegate;
    private final ConfigProperty.Habitat habitat;
    private final PropertyValueConverter converter;
    private final Class referenceType;

    public ReflectionInjectableProperty(Field field, boolean optional, String name, ConfigProperty.Habitat habitat,
                                        boolean delegate, @Nullable PropertyValueConverter converter) {
        this.field = field;
        this.optional = optional;
        this.name = name;
        this.habitat = habitat;
        this.converter = converter;
        this.delegate = delegate;

        if (isAtomic()) {
            Preconditions.checkArgument(Modifier.isFinal(field.getModifiers()), "Property %s in %s must be final",
                    field.getName(), field.getDeclaringClass().getName());
            try {
                Class setterArgType = isAtomicReference() ? Object.class : getType();
                atomicSetter = field.getType().getMethod("set", setterArgType);
                atomicGetter = field.getType().getMethod("get");
            } catch (NoSuchMethodException e) {
                throw new IllegalArgumentException(e.getMessage(), e);
            }
        } else {
            atomicSetter = null;
            atomicGetter = null;
        }

        if (isAtomicReference()) {
            // Determine the reference type
            Type refType;
            try {
                Class fieldClass = field.getType();
                Method getRefTypeMethod = fieldClass.getMethod("getReferenceType", Field.class);
                refType = (Type) getRefTypeMethod.invoke(null, field);
            } catch (NoSuchMethodException e) {
                refType = resolveRefType(field);
            } catch (IllegalAccessException e) {
                refType = resolveRefType(field);
            } catch (InvocationTargetException e) {
                refType = resolveRefType(field);
            }

            if (refType instanceof ParameterizedType) {
                Type rawRefType = ((ParameterizedType) refType).getRawType();

                if (rawRefType instanceof Class) {
                    referenceType = (Class) rawRefType;
                } else {
                    throw new UnsupportedOperationException("Unsupported reference type: " + refType);
                }

            } else if (refType instanceof Class) {
                referenceType = (Class) refType;
            } else {
                throw new UnsupportedOperationException("Unsupported reference type: " + refType);
            }

        } else {
            referenceType = null;
        }
    }

    private Type resolveRefType(Field field) {
        Type fieldGenericType = field.getGenericType();
        if (!(fieldGenericType instanceof ParameterizedType)) {
            throw new UnsupportedOperationException("Only simple generic type supported. Found: " + fieldGenericType);
        }

        ParameterizedType parameterizedType = (ParameterizedType) fieldGenericType;
        Preconditions.checkArgument(parameterizedType.getActualTypeArguments().length == 1,
                "Type %s of reference %s can have only one generic parameter", parameterizedType, field.getName());

        return parameterizedType.getActualTypeArguments()[0];
    }

    public boolean isContextual() {
        return habitat == ConfigProperty.Habitat.CONTEXT;
    }

    public static boolean isReference(Class<?> type) {
        if (isArray(type) ||
                isCollection(type) ||
                isMap(type) ||
                type.isPrimitive() ||
                Primitives.isWrapperType(type) ||
                Number.class.isAssignableFrom(type) ||
                AtomicBoolean.class.isAssignableFrom(type) ||
                Void.class == type ||
                String.class == type) {
            return false;
        }
        return true;
    }

    public boolean isDelegate() {
        return delegate;
    }

    public boolean isReference() {
        return isReference(getType());
    }

    public String getName() {
        if (name != null && name.length() > 0) {
            return this.name;
        } else {
            return field.getName();
        }
    }

    @Override
    public String getXmlSchemaTypeName() {
        return TypeConversion.getXmlSchemaTypeName(getType());
    }

    public Class<?> getType() {
        if (isAtomic()) {
            if (AtomicBoolean.class.isAssignableFrom(field.getType())) {
                return boolean.class;
            }

            if (AtomicInteger.class.isAssignableFrom(field.getType())) {
                return int.class;
            }

            if (AtomicLong.class.isAssignableFrom(field.getType())) {
                return long.class;
            }

            if (isAtomicReference()) {
                return referenceType;
            }

            throw new UnsupportedOperationException("Unsupported atomic type: " + field.getType());
        } else {
            return field.getType();
        }
    }


    public boolean isOptional() {
        return optional || delegate;
    }

    public static boolean isArray(Class type) {
        return type.isArray();
    }

    public boolean isArray() {
        return isArray(getType());
    }

    public boolean isAtomic() {
        return AtomicBoolean.class.isAssignableFrom(field.getType()) ||
                AtomicInteger.class.isAssignableFrom(field.getType()) ||
                AtomicLong.class.isAssignableFrom(field.getType()) ||
                isAtomicReference();
    }

    public boolean isAtomicReference() {
        return AtomicReference.class.isAssignableFrom(field.getType()) ||
                MutableReference.class.isAssignableFrom(field.getType());
    }

    public Class getAtomicType() {
        return isAtomic() ? field.getType() : null;
    }

    public static boolean isCollection(Class type) {
        return Collection.class.isAssignableFrom(type);
    }

    public boolean isCollection() {
        return isCollection(getType());
    }

    public static boolean isMap(Class type) {
        return Map.class.isAssignableFrom(type);
    }

    public boolean isMap() {
        return isMap(getType());
    }

    /**
     * For collection- and array-typed properties (i.e., only applies when
     * {@link #isCollection()} or {@link #isArray()} is {@code true}.
     */
    public Class<?> getArrayOrCollectionComponentType() {
        if (!isCollection() && !isArray()) {
            throw new IllegalStateException();
        }

        if (isCollection()) {
            Type type = field.getGenericType();
            if (type instanceof ParameterizedType) {
                return toClass(((ParameterizedType) type).getActualTypeArguments()[0]);
            } else {
                String message = String.format("Cannot determine the type parameter of the collection-typed field %s. Raw types are not supported", field);
                throw new IllegalArgumentException(message);
            }
        } else {
            return field.getType().getComponentType();
        }
    }

    /**
     * For map-typed properties (i.e., only applies when {@link #isMap()} is {@code true}.
     *
     * @return
     */
    public Class<?> getMapKeyType() {
        return toClass(getMapTypes()[0]);
    }

    /**
     * For map-typed properties (i.e., only applies when {@link #isMap()} is {@code true}.
     *
     * @return
     */
    public Class<?> getMapValueType() {
        return toClass(getMapTypes()[1]);
    }

    private Type[] getMapTypes() {
        if (!isMap()) {
            throw new IllegalStateException();
        }

        Type type = field.getGenericType();
        if (type instanceof ParameterizedType) {
            return ((ParameterizedType) type).getActualTypeArguments();
        } else {
            String message = String.format("Cannot determine one of the type parameters of the map-typed field %s. Raw types are not supported", field);
            throw new IllegalArgumentException(message);
        }
    }

    private static Class<?> toClass(Type type) {
        if (type instanceof Class<?>) {
            return (Class<?>) type;
        }
        if (type instanceof ParameterizedType) {
            return (Class<?>) ((ParameterizedType) type).getRawType();
        }
        if (type instanceof WildcardType) {
            Type[] bounds = ((WildcardType) type).getLowerBounds();
            if (bounds.length == 0) {
                bounds = ((WildcardType) type).getUpperBounds();
            }
            if (bounds.length == 1) {
                return toClass(bounds[0]);
            }
        }
        String message = String.format("Conversion of type %s is not supported", type);
        throw new IllegalArgumentException(message);
    }

    public Object getValue(Object instance) throws Exception {
        field.setAccessible(true);
        if (isAtomic()) {
            Object atomicValue = field.get(instance);
            return atomicGetter.invoke(atomicValue);
        } else {
            return field.get(instance);
        }

    }

    public void setValue(Object instance, Object value) throws Exception {
        value = getInstanceFromProvider(value, getType());

        if (converter != null) {
            value = converter.convertTo(this, instance, getType(), value);
        }

        field.setAccessible(true);
        if (isAtomic()) {

            if (isAtomicReference() && value != null) {
                Preconditions.checkArgument(
                        referenceType.isInstance(value), "Incompatible instance %s for reference %s",
                        value.getClass(), field.getName());
            }

            // The field must be final, ie. it is assumed its value is not null
            Object atomicValue = field.get(instance);
            Preconditions.checkNotNull(atomicValue, "Atomic field must be final and not null");

            atomicSetter.invoke(atomicValue, value);
        } else {
            field.set(instance, value);
        }

    }

    private Object getInstanceFromProvider(Object value, Class type) throws Exception {
        // Try to cast to Provider and use its product
        if (value instanceof Provider && !isProvider(type)
                && !type.isInstance(value) // allow injecting providers
                ) {
            value = ((Provider) value).getInstance();

            if (value != null && !type.isInstance(value)) {
                throw new IllegalArgumentException("Cannot assign " + value.getClass().getName() + " instance to "
                        + getType().getName());
            }
        }
        return value;
    }

    private boolean isProvider(Class type) {
        return Provider.class.isAssignableFrom(type);
    }

    @Override
    public void putMapEntry(Object instance, Object mapKey, Object mapValue) throws Exception {
        if (converter != null) {
            mapKey = getInstanceFromProvider(mapKey, getMapKeyType());
            mapKey = converter.convertTo(this, instance, getMapKeyType(), mapKey);

            mapValue = getInstanceFromProvider(mapValue, getMapValueType());
            mapValue = converter.convertTo(this, instance, getMapValueType(), mapValue);
        }

        if (!isInstanceOf(getMapKeyType(), mapKey)) {
            throw new IllegalArgumentException("key type mismatch in map property " + getName() +
                    " of class " + field.getDeclaringClass().getName());
        }

        if (!isInstanceOf(getMapValueType(), mapValue)) {
            throw new IllegalArgumentException("Value type mismatch in property '" + getName() +
                    "' of class " + field.getDeclaringClass() + ". Found: " + mapValue + ", required:" + getMapValueType());
        }

        ((Map) getValue(instance)).put(mapKey, mapValue);
    }

    @Override
    public void addCollectionElement(Object instance, Object element) throws Exception {
        if (converter != null) {
            element = getInstanceFromProvider(element, getArrayOrCollectionComponentType());
            element = converter.convertTo(this, instance, getArrayOrCollectionComponentType(), element);
        }

        if (!isInstanceOf(getArrayOrCollectionComponentType(), element)) {
            throw new IllegalArgumentException("Collection element type mismatch in property '" + getName() +
                    "' of class " + field.getDeclaringClass() + ". Found: " + element + ", required:" + getArrayOrCollectionComponentType());
        }

        ((Collection) getValue(instance)).add(element);
    }

    @Override
    public void setArrayElement(Object instance, int i, Object element) throws Exception {
        element = getInstanceFromProvider(element, getArrayOrCollectionComponentType());
        if (converter != null) {
            element = converter.convertTo(this, instance, getArrayOrCollectionComponentType(), element);
        }
        Object array = getValue(instance);

        if (!isInstanceOf(getArrayOrCollectionComponentType(), element)) {
            throw new IllegalArgumentException("Array element type mismatch in property '" + getName() +
                    "' of class " + field.getDeclaringClass() + ". Found: " + element + ", required:" + getArrayOrCollectionComponentType());
        }

        Array.set(array, i, element);
    }

    @Override
    public boolean hasTag(String tag) {
        ConfigProperty configPropertyAnnotation = field.getAnnotation(ConfigProperty.class);
        if (configPropertyAnnotation == null) {
            return field.getName().equals(tag);
        }

        for (String t : configPropertyAnnotation.tags()) {
            if (t.equals(tag)) {
                return true;
            }
        }

        return field.getName().equals(tag);
    }

    @Override
    public Class getOwner() {
        return field.getDeclaringClass();
    }

    private boolean isInstanceOf(Class type, Object value) {
        return Primitives.wrap(type).isInstance(value);
    }

}
TOP

Related Classes of com.avast.syringe.config.internal.ReflectionInjectableProperty

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.