1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20 package org.grouplens.grapht.solver;
21
22 import com.google.common.base.Functions;
23 import com.google.common.base.Predicate;
24 import com.google.common.collect.*;
25 import org.apache.commons.lang3.tuple.Pair;
26 import org.grouplens.grapht.CachePolicy;
27 import org.grouplens.grapht.Component;
28 import org.grouplens.grapht.Dependency;
29 import org.grouplens.grapht.ResolutionException;
30 import org.grouplens.grapht.graph.DAGEdge;
31 import org.grouplens.grapht.graph.DAGNode;
32 import org.grouplens.grapht.graph.DAGNodeBuilder;
33 import org.grouplens.grapht.graph.MergePool;
34 import org.grouplens.grapht.reflect.Desire;
35 import org.grouplens.grapht.reflect.Satisfaction;
36 import org.grouplens.grapht.reflect.internal.NullSatisfaction;
37 import org.grouplens.grapht.util.Preconditions;
38 import org.slf4j.Logger;
39 import org.slf4j.LoggerFactory;
40
41 import java.util.*;
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62 public class DependencySolver {
63 private static final Logger logger = LoggerFactory.getLogger(DependencySolver.class);
64 public static final Component ROOT_SATISFACTION =
65 Component.create(new NullSatisfaction(Void.TYPE), CachePolicy.NO_PREFERENCE);
66
67
68
69
70
71 public static InjectionContext initialContext() {
72 return InjectionContext.singleton(ROOT_SATISFACTION.getSatisfaction());
73 }
74
75
76
77
78
79 public static DAGNode<Component,Dependency> rootNode() {
80 return DAGNode.singleton(ROOT_SATISFACTION);
81 }
82
83 private final int maxDepth;
84 private final CachePolicy defaultPolicy;
85
86 private final List<BindingFunction> functions;
87 private final List<BindingFunction> triggerFunctions;
88
89 private DAGNode<Component,Dependency> graph;
90 private SetMultimap<DAGNode<Component,Dependency>, DAGEdge<Component,Dependency>> backEdges;
91 private MergePool<Component,Dependency> mergePool;
92
93
94
95
96
97
98
99
100
101
102
103 DependencySolver(List<BindingFunction> bindFunctions,
104 List<BindingFunction> triggers,
105 CachePolicy defaultPolicy, int maxDepth) {
106 Preconditions.notNull("bindFunctions", bindFunctions);
107 Preconditions.notNull("defaultPolicy", defaultPolicy);
108 if (maxDepth <= 0) {
109 throw new IllegalArgumentException("Max depth must be at least 1");
110 }
111
112 this.functions = new ArrayList<BindingFunction>(bindFunctions);
113 this.triggerFunctions = new ArrayList<BindingFunction>(triggers);
114 this.maxDepth = maxDepth;
115 this.defaultPolicy = defaultPolicy;
116
117 graph = DAGNode.singleton(ROOT_SATISFACTION);
118 backEdges = HashMultimap.create();
119 mergePool = MergePool.create();
120
121 logger.info("DependencySolver created, max depth: {}", maxDepth);
122 }
123
124
125
126
127
128
129 public static DependencySolverBuilder newBuilder() {
130 return new DependencySolverBuilder();
131 }
132
133
134
135
136
137
138 public DAGNode<Component, Dependency> getGraph() {
139 return graph;
140 }
141
142
143
144
145
146
147
148
149
150
151 public SetMultimap<DAGNode<Component, Dependency>, DAGEdge<Component, Dependency>> getBackEdges() {
152 return ImmutableSetMultimap.copyOf(backEdges);
153 }
154
155
156
157
158
159
160 public synchronized DAGNode<Component, Dependency> getBackEdge(DAGNode<Component, Dependency> parent,
161 Desire desire) {
162 Predicate<DAGEdge<?, Dependency>> pred = DAGEdge.labelMatches(Dependency.hasInitialDesire(desire));
163 return FluentIterable.from(backEdges.get(parent))
164 .filter(pred)
165 .first()
166 .transform(DAGEdge.<Component, Dependency>extractTail())
167 .orNull();
168 }
169
170
171
172
173
174 @Deprecated
175 public DAGNode<Component, Dependency> getRootNode() {
176 return graph;
177 }
178
179
180
181
182
183
184
185
186 public synchronized void resolve(Desire desire) throws ResolutionException {
187 logger.info("Resolving desire: {}", desire);
188
189 Queue<Deferral> deferralQueue = new ArrayDeque<Deferral>();
190
191
192
193 deferralQueue.add(new Deferral(rootNode(), initialContext()));
194
195 while(!deferralQueue.isEmpty()) {
196 Deferral current = deferralQueue.poll();
197 DAGNode<Component, Dependency> parent = current.node;
198
199 assert parent.getOutgoingEdges().isEmpty();
200
201 if (current.node.getLabel().equals(ROOT_SATISFACTION)) {
202 Pair<DAGNode<Component, Dependency>, Dependency> rootNode =
203 resolveFully(desire, current.context, deferralQueue);
204
205 graph = DAGNode.copyBuilder(graph)
206 .addEdge(mergePool.merge(rootNode.getLeft()),
207 rootNode.getRight())
208 .build();
209 } else if (graph.getReachableNodes().contains(parent)) {
210
211
212 Satisfaction sat = parent.getLabel().getSatisfaction();
213 for (Desire d: sat.getDependencies()) {
214 logger.debug("Attempting to resolve deferred dependency {} of {}", d, sat);
215
216 Pair<DAGNode<Component, Dependency>, Dependency> result =
217 resolveFully(d, current.context, deferralQueue);
218
219 DAGNode<Component, Dependency> merged = mergePool.merge(result.getLeft());
220
221 if (merged.getReachableNodes().contains(parent)) {
222
223
224 backEdges.put(parent, DAGEdge.create(parent, merged, result.getRight()));
225 } else {
226
227
228
229 DAGNode<Component, Dependency> newP =
230 DAGNode.copyBuilder(parent)
231 .addEdge(merged, result.getRight())
232 .build();
233 replaceNode(parent, newP);
234 parent = newP;
235 }
236 }
237 } else {
238
239 logger.debug("node {} not in graph, ignoring", parent);
240 }
241 }
242 }
243
244 private void replaceNode(DAGNode<Component,Dependency> old,
245 DAGNode<Component,Dependency> repl) {
246 Map<DAGNode<Component,Dependency>,
247 DAGNode<Component,Dependency>> memory = Maps.newHashMap();
248 graph = graph.replaceNode(old, repl, memory);
249
250
251 Collection<DAGEdge<Component, Dependency>> oldBackEdges = backEdges.values();
252 backEdges = HashMultimap.create();
253 for (DAGEdge<Component,Dependency> edge: oldBackEdges) {
254 DAGNode<Component,Dependency> newHead, newTail;
255 newHead = memory.get(edge.getHead());
256 if (newHead == null) {
257 newHead = edge.getHead();
258 }
259 newTail = memory.get(edge.getTail());
260 if (newTail == null) {
261 newTail = edge.getTail();
262 }
263 DAGEdge<Component,Dependency> newEdge;
264 if (newHead.equals(edge.getHead()) && newTail.equals(edge.getTail())) {
265 newEdge = edge;
266 } else {
267 newEdge = DAGEdge.create(newHead, newTail, edge.getLabel());
268 }
269 backEdges.put(newHead, newEdge);
270 }
271 }
272
273
274
275
276
277
278
279
280
281
282
283
284 public DAGNode<Component,Dependency> rewrite(DAGNode<Component,Dependency> graph) throws ResolutionException {
285 if (!graph.getLabel().getSatisfaction().getErasedType().equals(Void.TYPE)) {
286 throw new IllegalArgumentException("only full dependency graphs can be rewritten");
287 }
288
289 logger.debug("rewriting graph with {} nodes", graph.getReachableNodes().size());
290
291 Map<DAGEdge<Component, Dependency>, DAGEdge<Component,Dependency>> replacementSubtrees =
292 Maps.newHashMap();
293 walkGraphForReplacements(graph,
294 InjectionContext.singleton(graph.getLabel().getSatisfaction()),
295 replacementSubtrees);
296
297 DAGNode<Component, Dependency> stage2 =
298 graph.transformEdges(Functions.forMap(replacementSubtrees, null));
299
300 logger.debug("merging rewritten graph");
301
302
303 MergePool<Component,Dependency> pool = MergePool.create();
304 pool.merge(graph);
305 return pool.merge(stage2);
306 }
307
308
309
310
311
312
313
314
315
316 private void walkGraphForReplacements(DAGNode<Component, Dependency> root,
317 InjectionContext context,
318 Map<DAGEdge<Component, Dependency>, DAGEdge<Component, Dependency>> replacements) throws ResolutionException {
319 assert context.getTailValue().getLeft().equals(root.getLabel().getSatisfaction());
320 for (DAGEdge<Component, Dependency> edge: root.getOutgoingEdges()) {
321 logger.debug("considering {} for replacement", edge.getTail().getLabel());
322 Desire desire = edge.getLabel().getDesireChain().getInitialDesire();
323 DesireChain chain = DesireChain.singleton(desire);
324 Pair<DAGNode<Component, Dependency>, Dependency> repl = null;
325 if (!edge.getLabel().isFixed()) {
326 for (BindingFunction bf: triggerFunctions) {
327 BindingResult result = bf.bind(context, chain);
328 if (result != null) {
329
330
331 repl = resolveFully(desire, context, null);
332 break;
333 }
334 }
335 } else {
336 logger.debug("{} is fixed, skipping", edge.getTail().getLabel());
337 }
338 if (repl == null) {
339
340 InjectionContext next = context.extend(edge.getTail()
341 .getLabel()
342 .getSatisfaction(),
343 edge.getLabel()
344 .getDesireChain()
345 .getInitialDesire()
346 .getInjectionPoint());
347 walkGraphForReplacements(edge.getTail(), next, replacements);
348 } else {
349
350 logger.info("replacing {} with {}",
351 edge.getTail().getLabel(),
352 repl.getLeft().getLabel());
353 replacements.put(edge, DAGEdge.create(root, repl.getLeft(), repl.getRight()));
354 }
355 }
356 }
357
358
359
360
361
362
363
364
365
366 private Pair<DAGNode<Component,Dependency>,Dependency>
367 resolveFully(Desire desire, InjectionContext context, Queue<Deferral> deferQueue) throws ResolutionException {
368
369 if (context.size() > maxDepth) {
370 throw new CyclicDependencyException(desire, "Maximum context depth of " + maxDepth + " was reached");
371 }
372
373
374 Resolution result = resolve(desire, context);
375
376 InjectionContext newContext = context.extend(result.satisfaction, desire.getInjectionPoint());
377
378 DAGNode<Component, Dependency> node;
379 if (result.deferDependencies) {
380
381 logger.debug("Deferring dependencies of {}", result.satisfaction);
382 node = DAGNode.singleton(result.makeSatisfaction());
383
384 deferQueue.add(new Deferral(node, newContext));
385 return Pair.of(node, result.makeDependency());
386 } else {
387 return resolveDepsAndMakeNode(deferQueue, result, newContext);
388 }
389 }
390
391 private Pair<DAGNode<Component, Dependency>,Dependency> resolveDepsAndMakeNode(Queue<Deferral> deferQueue,
392 Resolution result,
393 InjectionContext newContext) throws ResolutionException {
394 DAGNode<Component, Dependency> node;
395 DAGNodeBuilder<Component,Dependency> nodeBuilder = DAGNode.newBuilder();
396 nodeBuilder.setLabel(result.makeSatisfaction());
397 for (Desire d: result.satisfaction.getDependencies()) {
398
399
400
401 logger.debug("Attempting to satisfy dependency {} of {}", d, result.satisfaction);
402 Pair<DAGNode<Component, Dependency>, Dependency> dep;
403 try {
404 dep = resolveFully(d, newContext, deferQueue);
405 } catch (UnresolvableDependencyException ex) {
406 if (!d.equals(ex.getDesireChain().getInitialDesire())) {
407
408 throw ex;
409 }
410
411 Resolution back = result.skippable ? result.backtrack() : null;
412 if (back != null) {
413 InjectionContext popped = newContext.getLeading();
414 InjectionContext forked = InjectionContext.extend(popped, back.satisfaction,
415 back.desires.getInitialDesire().getInjectionPoint());
416 return resolveDepsAndMakeNode(deferQueue, back, forked);
417 } else if (result.backtracked || result.skippable) {
418
419 throw new UnresolvableDependencyException(result.desires, newContext.getLeading(), ex);
420 } else {
421 throw ex;
422 }
423 }
424 nodeBuilder.addEdge(dep);
425 }
426 node = nodeBuilder.build();
427 return Pair.of(node, result.makeDependency());
428 }
429
430 private Resolution resolve(Desire desire, InjectionContext context) throws ResolutionException {
431 DesireChain chain = DesireChain.singleton(desire);
432
433 CachePolicy policy = CachePolicy.NO_PREFERENCE;
434 boolean fixed = false;
435 boolean skippable = false;
436
437 while(true) {
438 logger.debug("Current desire: {}", chain.getCurrentDesire());
439
440 BindingResult binding = null;
441 for (BindingFunction bf: functions) {
442 binding = bf.bind(context, chain);
443 if (binding != null && !chain.getPreviousDesires().contains(binding.getDesire())) {
444
445 break;
446 }
447 }
448
449 boolean defer = false;
450 boolean terminate = true;
451 if (binding != null) {
452
453 chain = chain.extend(binding.getDesire());
454
455 terminate = binding.terminates();
456 defer = binding.isDeferred();
457 fixed |= binding.isFixed();
458 skippable = binding.isSkippable();
459
460
461 if (binding.getCachePolicy().compareTo(policy) > 0) {
462 policy = binding.getCachePolicy();
463 }
464 }
465
466 if (terminate && chain.getCurrentDesire().isInstantiable()) {
467 logger.info("Satisfied {} with {}", desire, chain.getCurrentDesire().getSatisfaction());
468
469
470 if (policy.equals(CachePolicy.NO_PREFERENCE)) {
471 policy = chain.getCurrentDesire().getSatisfaction().getDefaultCachePolicy();
472 if (policy.equals(CachePolicy.NO_PREFERENCE)) {
473 policy = defaultPolicy;
474 }
475 }
476
477 return new Resolution(chain.getCurrentDesire().getSatisfaction(), policy, chain, fixed, defer, skippable, false);
478 } else if (binding == null) {
479
480 throw new UnresolvableDependencyException(chain, context);
481 }
482 }
483 }
484
485
486
487
488 private static class Resolution {
489 private final Satisfaction satisfaction;
490 private final CachePolicy policy;
491 private final DesireChain desires;
492 private final boolean fixed;
493 private final boolean deferDependencies;
494 private final boolean skippable;
495 private final boolean backtracked;
496
497 public Resolution(Satisfaction satisfaction, CachePolicy policy,
498 DesireChain desires, boolean fixed,
499 boolean deferDependencies,
500 boolean skippable,
501 boolean backtracked) {
502 this.satisfaction = satisfaction;
503 this.policy = policy;
504 this.desires = desires;
505 this.fixed = fixed;
506 this.deferDependencies = deferDependencies;
507 this.skippable = skippable;
508 this.backtracked = backtracked;
509 }
510
511 public Component makeSatisfaction() {
512 return Component.create(satisfaction, policy);
513 }
514
515 public Dependency makeDependency() {
516 EnumSet<Dependency.Flag> flags = Dependency.Flag.emptySet();
517 if (fixed) {
518 flags.add(Dependency.Flag.FIXED);
519 }
520 return Dependency.create(desires, flags);
521 }
522
523
524
525
526
527
528
529
530
531 public Resolution backtrack() {
532 if (!skippable) {
533 throw new IllegalArgumentException("unskippable resolution can't backtrack");
534 }
535 if (desires.size() <= 1) {
536 throw new IllegalArgumentException("singleton desire chain can't backtrack");
537 }
538 DesireChain shrunk = desires.getPreviousDesireChain();
539 if (shrunk.getCurrentDesire().isInstantiable()) {
540 return new Resolution(shrunk.getCurrentDesire().getSatisfaction(),
541 policy,
542 shrunk,
543 fixed,
544 deferDependencies,
545 false, true);
546 } else {
547 return null;
548 }
549 }
550
551 @Override
552 public String toString() {
553 return "(" + satisfaction + ", " + policy + ")";
554 }
555 }
556
557
558
559
560 private static class Deferral {
561 private final DAGNode<Component, Dependency> node;
562 private final InjectionContext context;
563
564 public Deferral(DAGNode<Component, Dependency> node,
565 InjectionContext context) {
566 this.node = node;
567 this.context = context;
568 }
569 }
570 }