1 /* 2 * Grapht, an open source dependency injector. 3 * Copyright 2014-2015 various contributors (see CONTRIBUTORS.txt) 4 * Copyright 2010-2014 Regents of the University of Minnesota 5 * 6 * This program is free software; you can redistribute it and/or modify 7 * it under the terms of the GNU Lesser General Public License as 8 * published by the Free Software Foundation; either version 2.1 of the 9 * License, or (at your option) any later version. 10 * 11 * This program is distributed in the hope that it will be useful, but WITHOUT 12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS 13 * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more 14 * details. 15 * 16 * You should have received a copy of the GNU General Public License along with 17 * this program; if not, write to the Free Software Foundation, Inc., 51 18 * Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. 19 */ 20 package org.grouplens.grapht.solver; 21 22 import com.google.common.base.Predicate; 23 import org.grouplens.grapht.*; 24 import org.grouplens.grapht.graph.DAGEdge; 25 import org.grouplens.grapht.graph.DAGNode; 26 import org.grouplens.grapht.reflect.Desire; 27 import org.grouplens.grapht.reflect.Desires; 28 import org.slf4j.Logger; 29 import org.slf4j.LoggerFactory; 30 31 import javax.annotation.Nonnull; 32 import javax.annotation.Nullable; 33 import javax.annotation.concurrent.ThreadSafe; 34 import java.lang.annotation.Annotation; 35 36 /** 37 * <p> 38 * DefaultInjector is the default Injector implementation. When resolving the 39 * dependency graph for a desire, a "context" is built which consists of an 40 * ordering of qualified types that satisfy each dependency. The DefaultInjector 41 * uses the {@link DependencySolver} to manage dependency resolution. New 42 * injectors can easily be built to also use this solver. 43 * 44 * @author <a href="http://grouplens.org">GroupLens Research</a> 45 */ 46 @ThreadSafe 47 public class DefaultInjector implements Injector { 48 private static final Logger logger = LoggerFactory.getLogger(DefaultInjector.class); 49 50 private final DependencySolver solver; 51 private final InjectionContainer instantiator; 52 private final LifecycleManager manager; 53 54 /** 55 * <p> 56 * Create a new DefaultInjector. The created resolver will use a max 57 * dependency depth of 100 to estimate if there are cycles in the dependency 58 * hierarchy. Bindings with a NO_PREFERENCE cache policy will be treated as 59 * NEW_INSTANCE. 60 * 61 * @param functions The BindingFunctions to use, ordered with highest 62 * priority function first 63 * @throws NullPointerException if spi or functions ar enull 64 */ 65 public DefaultInjector(BindingFunction... functions) { 66 this(CachePolicy.MEMOIZE, functions); 67 } 68 69 /** 70 * <p> 71 * Create a new DefaultInjector. The created resolver will use a max 72 * dependency depth of 100 to estimate if there are cycles in the dependency 73 * hierarchy. Bindings with a NO_PREFERENCE cache policy will use 74 * <tt>defaultPolicy</tt>. 75 * 76 * @param defaultPolicy The CachePolicy used in place of NO_PREFERENCE 77 * @param functions The BindingFunctions to use, ordered with highest 78 * priority functions first 79 * @throws IllegalArgumentException if defaultPolicy is NO_PREFERENCE 80 * @throws NullPointerException if spi or functions are null 81 */ 82 public DefaultInjector(CachePolicy defaultPolicy, BindingFunction... functions) { 83 this(defaultPolicy, 100, functions); 84 } 85 86 /** 87 * <p> 88 * Create a new DefaultInjector. <tt>maxDepth</tt> represents the maximum 89 * depth of the dependency hierarchy before it is assume that there is a 90 * cycle. Bindings with a NO_PREFERENCE cache policy will use 91 * <tt>defaultPolicy</tt>. 92 * <p> 93 * This constructor can be used to increase this depth in the event that 94 * configuration requires it, although for most purposes the default 100 95 * should be sufficient. 96 * 97 * @param defaultPolicy The CachePolicy used in place of NO_PREFERENCE 98 * @param maxDepth The maximum depth of the dependency hierarchy 99 * @param functions The BindingFunctions to use, ordered with highest 100 * priority functions first 101 * @throws IllegalArgumentException if maxDepth is less than 1, or if 102 * defaultPolicy is NO_PREFERENCE 103 * @throws NullPointerException if spi or functions are null 104 */ 105 public DefaultInjector(CachePolicy defaultPolicy, int maxDepth, BindingFunction... functions) { 106 if (defaultPolicy.equals(CachePolicy.NO_PREFERENCE)) { 107 throw new IllegalArgumentException("Default CachePolicy cannot be NO_PREFERENCE"); 108 } 109 110 solver = DependencySolver.newBuilder() 111 .addBindingFunctions(functions) 112 .setMaxDepth(maxDepth) 113 .build(); 114 manager = new LifecycleManager(); 115 instantiator = InjectionContainer.create(defaultPolicy, manager); 116 } 117 118 /** 119 * @return The DependencySolver backing this injector 120 */ 121 public DependencySolver getSolver() { 122 return solver; 123 } 124 125 @Nonnull 126 @Override 127 public <T> T getInstance(Class<T> type) throws InjectionException { 128 return getInstance(null, type); 129 } 130 131 @Nonnull 132 @Override 133 @SuppressWarnings("unchecked") 134 public <T> T getInstance(Annotation qualifier, Class<T> type) throws InjectionException { 135 Object obj = getInstance(Desires.create(qualifier, type, false)); 136 assert obj != null; 137 return type.cast(obj); 138 } 139 140 @Nullable 141 @Override 142 public <T> T tryGetInstance(Annotation qualifier, Class<T> type) throws InjectionException { 143 Object obj = getInstance(Desires.create(qualifier, type, true)); 144 return type.cast(obj); 145 } 146 147 private Object getInstance(Desire desire) throws InjectionException { 148 // All Provider cache access, graph resolution, etc. occur 149 // within this exclusive lock so we know everything is thread safe 150 // albeit in a non-optimal way. 151 synchronized(this) { 152 Predicate<Dependency> pred = Dependency.hasInitialDesire(desire); 153 154 // check if the desire is already in the graph 155 DAGEdge<Component, Dependency> resolved = 156 solver.getGraph().getOutgoingEdgeWithLabel(pred); 157 158 // The edge is only non-null if instantiate() has been called before, 159 // it may be present in the graph at a deeper node. If that's the case 160 // it will be properly merged after regenerating the graph at the root context. 161 if (resolved == null) { 162 logger.info("Must resolve desire: {}", desire); 163 solver.resolve(desire); 164 resolved = solver.getGraph().getOutgoingEdgeWithLabel(pred); 165 } 166 167 // Check if the provider for the resolved node is in our cache 168 DAGNode<Component, Dependency> resolvedNode = resolved.getTail(); 169 return instantiator.makeInstantiator(resolvedNode, solver.getBackEdges()).instantiate(); 170 } 171 } 172 173 @Override 174 public void close() { 175 if (manager != null) { 176 manager.close(); 177 } 178 } 179 }