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 }