Java tutorial
/* * 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); } } }