View Javadoc

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 }