org.grouplens.grapht.BindingImpl.java Source code

Java tutorial

Introduction

Here is the source code for org.grouplens.grapht.BindingImpl.java

Source

/*
 * Grapht, an open source dependency injector.
 * Copyright 2014-2015 various contributors (see CONTRIBUTORS.txt)
 * Copyright 2010-2014 Regents of the University of Minnesota
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License as
 * published by the Free Software Foundation; either version 2.1 of the
 * License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
 * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
 * details.
 *
 * You should have received a copy of the GNU General Public License along with
 * this program; if not, write to the Free Software Foundation, Inc., 51
 * Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
 */
package org.grouplens.grapht;

import org.apache.commons.lang3.ClassUtils;
import org.grouplens.grapht.BindingFunctionBuilder.RuleSet;
import org.grouplens.grapht.annotation.DefaultImplementation;
import org.grouplens.grapht.annotation.DefaultProvider;
import org.grouplens.grapht.context.ContextMatcher;
import org.grouplens.grapht.reflect.*;
import org.grouplens.grapht.solver.BindRuleBuilder;
import org.grouplens.grapht.solver.BindingFlag;
import org.grouplens.grapht.util.Preconditions;
import org.grouplens.grapht.util.Types;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import javax.inject.Provider;
import java.lang.annotation.Annotation;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;

/**
 * BindingImpl is the default implementation of Binding that is used by
 * {@link BindingFunctionBuilder}.
 * 
 * @author <a href="http://grouplens.org">GroupLens Research</a>
 * @param <T> The bindings source's type
 */
class BindingImpl<T> implements Binding<T> {
    private static final Logger logger = LoggerFactory.getLogger(BindingImpl.class);

    private final ContextImpl context;
    private final Class<T> sourceType;

    private final Set<Class<?>> excludeTypes;

    private final QualifierMatcher qualifier;

    private final CachePolicy cachePolicy;
    private final boolean fixed;

    public BindingImpl(ContextImpl context, Class<T> type) {
        this(context, type, context.getBuilder().getDefaultExclusions(), Qualifiers.matchDefault(),
                CachePolicy.NO_PREFERENCE, false);
    }

    public BindingImpl(ContextImpl context, Class<T> type, Set<Class<?>> excludes, QualifierMatcher matcher,
            CachePolicy cachePolicy, boolean fixed) {
        this.context = context;
        this.cachePolicy = cachePolicy;
        sourceType = type;
        excludeTypes = excludes;
        qualifier = matcher;
        this.fixed = fixed;
    }

    @Override
    public Binding<T> withQualifier(@Nonnull Class<? extends Annotation> qualifier) {
        QualifierMatcher q = Qualifiers.match(qualifier);
        return new BindingImpl<T>(context, sourceType, excludeTypes, q, cachePolicy, fixed);
    }

    @Override
    public Binding<T> withQualifier(@Nonnull Annotation annot) {
        QualifierMatcher q = Qualifiers.match(annot);
        return new BindingImpl<T>(context, sourceType, excludeTypes, q, cachePolicy, fixed);
    }

    @Override
    public Binding<T> withAnyQualifier() {
        QualifierMatcher q = Qualifiers.matchAny();
        return new BindingImpl<T>(context, sourceType, excludeTypes, q, cachePolicy, fixed);
    }

    @Override
    public Binding<T> unqualified() {
        QualifierMatcher q = Qualifiers.matchNone();
        return new BindingImpl<T>(context, sourceType, excludeTypes, q, cachePolicy, fixed);
    }

    @Override
    public Binding<T> exclude(@Nonnull Class<?> exclude) {
        Preconditions.notNull("exclude type", exclude);
        Set<Class<?>> excludes = new HashSet<Class<?>>(excludeTypes);
        excludes.add(exclude);
        return new BindingImpl<T>(context, sourceType, excludes, qualifier, cachePolicy, fixed);
    }

    @Override
    public Binding<T> shared() {
        return new BindingImpl<T>(context, sourceType, excludeTypes, qualifier, CachePolicy.MEMOIZE, fixed);
    }

    @Override
    public Binding<T> unshared() {
        return new BindingImpl<T>(context, sourceType, excludeTypes, qualifier, CachePolicy.NEW_INSTANCE, fixed);
    }

    @Override
    public Binding<T> fixed() {
        return new BindingImpl<T>(context, sourceType, excludeTypes, qualifier, cachePolicy, true);
    }

    @Override
    public void to(@Nonnull Class<? extends T> impl, boolean chained) {
        Preconditions.isAssignable(sourceType, impl);
        if (logger.isWarnEnabled()) {
            if (Types.shouldBeInstantiable(impl) && !Types.isInstantiable(impl)
                    && impl.getAnnotation(DefaultProvider.class) == null
                    && impl.getAnnotation(DefaultImplementation.class) == null) {
                logger.warn(
                        "Concrete type {} does not have an injectable or public default constructor, but probably should",
                        impl);
            }
        }

        BindRuleBuilder brb = startRule();
        if (Types.isInstantiable(impl)) {
            brb.setSatisfaction(Satisfactions.type(impl));
        } else {
            brb.setImplementation(impl);
        }
        brb.setTerminal(!chained);
        generateBindings(brb, impl);
    }

    @Override
    public void to(@Nonnull Class<? extends T> impl) {
        to(impl, true);
    }

    @Override
    public void to(@Nullable T instance) {
        if (instance == null) {
            toNull();
            return;
        } else if (!(instance instanceof Number) && !ClassUtils.isPrimitiveWrapper(instance.getClass())
                && !sourceType.isInstance(instance)) {
            String msg = String.format("%s is not an instance of %s", instance, sourceType);
            throw new InvalidBindingException(sourceType, msg);
        }

        // Apply some type coercing if we're dealing with primitive types
        Object coerced = coerce(instance);
        Satisfaction s = Satisfactions.instance(coerced);
        BindRuleBuilder brb = startRule().setSatisfaction(s);
        generateBindings(brb, coerced.getClass());
    }

    @Override
    public void toProvider(@Nonnull Class<? extends Provider<? extends T>> provider) {
        Satisfaction s = Satisfactions.providerType(provider);
        BindRuleBuilder brb = startRule().setSatisfaction(s);
        Class<?> provided;
        try {
            provided = Types.getProvidedType(provider);
        } catch (IllegalArgumentException e) {
            if (e.getMessage().endsWith("is generic")) {
                throw new InvalidBindingException(sourceType, "cannot bind to generic provider");
            } else {
                throw e;
            }
        }
        generateBindings(brb, provided);
    }

    @Override
    public void toProvider(@Nonnull Provider<? extends T> provider) {
        Satisfaction s = Satisfactions.providerInstance(provider);
        BindRuleBuilder brb = startRule().setSatisfaction(s);
        Class<?> provided;
        try {
            provided = Types.getProvidedType(provider);
        } catch (IllegalArgumentException e) {
            if (e.getMessage().endsWith("is generic")) {
                throw new InvalidBindingException(sourceType, "cannot bind to generic provider");
            } else {
                throw e;
            }
        }
        generateBindings(brb, provided);
    }

    @Override
    public void toNull() {
        toNull(sourceType);
    }

    @Override
    public void toNull(Class<? extends T> type) {
        Satisfaction s = Satisfactions.nullOfType(type);
        BindRuleBuilder brb = startRule().setSatisfaction(s);
        generateBindings(brb, type);
    }

    @Override
    public void toSatisfaction(@Nonnull Satisfaction sat) {
        Preconditions.notNull("satisfaction", sat);

        BindRuleBuilder brb = startRule().setSatisfaction(sat);
        generateBindings(brb, sat.getErasedType());
    }

    /**
     * Generate bindings.
     * @param brb A bind rule builder, completely populated except for its {@linkplain BindRuleBuilder#setDependencyType(Class) dependency type}.
     * @param type The search type for {@link #generateBindPoints(Class)}.
     */
    private void generateBindings(BindRuleBuilder brb, Class<?> type) {
        ContextMatcher matcher = context.getContextPattern();
        BindingFunctionBuilder config = context.getBuilder();
        if (config.getGenerateRules()) {
            Map<Class<?>, RuleSet> bindPoints = generateBindPoints(type);
            for (Entry<Class<?>, RuleSet> e : bindPoints.entrySet()) {
                config.addBindRule(e.getValue(), matcher, brb.setDependencyType(e.getKey()).build());
            }
        } else {
            config.addBindRule(RuleSet.EXPLICIT, matcher, brb.setDependencyType(sourceType).build());
        }
    }

    /**
     * Start building a bind rule.
     * @return A bind rule builder, with the common configuration already applied.
     */
    private BindRuleBuilder startRule() {
        BindRuleBuilder brb = new BindRuleBuilder();
        brb.setQualifierMatcher(qualifier).setCachePolicy(cachePolicy).setTerminal(true);
        if (fixed) {
            brb.addFlag(BindingFlag.FIXED);
        }
        return brb;
    }

    private Object coerce(Object in) {
        Class<?> boxedSource = Types.box(sourceType);
        if (Integer.class.equals(boxedSource)) {
            // normalize to BigInteger and then cast to int
            return Integer.valueOf(toBigInteger(in).intValue());
        } else if (Short.class.equals(boxedSource)) {
            // normalize to BigInteger and then cast to short
            return Short.valueOf(toBigInteger(in).shortValue());
        } else if (Byte.class.equals(boxedSource)) {
            // normalize to BigInteger and then cast to byte
            return Byte.valueOf(toBigInteger(in).byteValue());
        } else if (Long.class.equals(boxedSource)) {
            // normalize to BigInteger and then cast to long
            return Long.valueOf(toBigInteger(in).longValue());
        } else if (Float.class.equals(boxedSource)) {
            // normalize to BigDecimal and then cast to float
            return Float.valueOf(toBigDecimal(in).floatValue());
        } else if (Double.class.equals(boxedSource)) {
            // normalize to BigDecimal and then cast to double
            return Double.valueOf(toBigDecimal(in).doubleValue());
        } else if (BigDecimal.class.equals(boxedSource)) {
            // normalize to BigDecimal
            return toBigDecimal(in);
        } else if (BigInteger.class.equals(boxedSource)) {
            // normalize to BigInteger
            return toBigInteger(in);
        } else {
            // don't perform any type coercion
            return in;
        }
    }

    private BigDecimal toBigDecimal(Object in) {
        // We assume in is a floating primitive boxed type, so its toString()
        // converts its value to a form parsed by BigDecimal's constructor
        return new BigDecimal(in.toString());
    }

    private BigInteger toBigInteger(Object in) {
        // We assume in is a discrete primitive boxed type, so its toString()
        // converts its value to a textual form that can be parsed by 
        // BigInteger's constructor
        return new BigInteger(in.toString());
    }

    private Map<Class<?>, RuleSet> generateBindPoints(Class<?> target) {
        Map<Class<?>, RuleSet> bindPoints = new HashMap<Class<?>, RuleSet>();
        // start the recursion up the type hierarchy, starting at the target type
        recordTypes(Types.box(sourceType), target, bindPoints);
        return bindPoints;
    }

    private void recordTypes(Class<?> src, Class<?> type, Map<Class<?>, RuleSet> bindPoints) {
        // check exclusions
        if (type == null || excludeTypes.contains(type)) {
            // the type is excluded, terminate recursion (this relies on Object
            // being included in the exclude set)
            return;
        }

        RuleSet set;
        if (type.equals(src)) {
            // type is the source type, so this is the manual rule
            set = RuleSet.EXPLICIT;
        } else if (src.isAssignableFrom(type)) {
            // type is a subclass of the source type, and a superclass
            // of the target type
            set = RuleSet.INTERMEDIATE_TYPES;
        } else if (type.isAssignableFrom(src)) {
            // type is a superclass of the source type, so it is also a superclass
            // of the target type
            set = RuleSet.SUPER_TYPES;
        } else {
            // type is a superclass of the target type, but not of the source type
            // so we don't generate any bindings
            return;
        }

        // record the type's weight
        bindPoints.put(type, set);

        // recurse to superclass and implemented interfaces
        // - superclass is null for Object, interfaces, and primitives
        // - interfaces holds implemented or extended interfaces depending on
        //   if the type is a class or interface
        recordTypes(src, type.getSuperclass(), bindPoints);
        for (Class<?> i : type.getInterfaces()) {
            recordTypes(src, i, bindPoints);
        }
    }
}