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.collect.ImmutableListMultimap;
23 import com.google.common.collect.ListMultimap;
24 import com.google.common.collect.Multimap;
25 import org.apache.commons.lang3.tuple.Pair;
26 import org.grouplens.grapht.ResolutionException;
27 import org.grouplens.grapht.context.ContextMatch;
28 import org.grouplens.grapht.context.ContextMatcher;
29 import org.grouplens.grapht.reflect.QualifierMatcher;
30 import org.grouplens.grapht.util.Preconditions;
31 import org.slf4j.Logger;
32 import org.slf4j.LoggerFactory;
33
34 import java.util.*;
35
36 /**
37 * <p>
38 * BindingFunction that uses BindRules created by the fluent API to bind desires
39 * to other desires or satisfactions.
40 * <p>
41 * For more details on context management, see {@link org.grouplens.grapht.context.ContextPattern},
42 * {@link org.grouplens.grapht.context.ContextElementMatcher}, and {@link QualifierMatcher}. This function uses the
43 * context to activate and select BindRules. A number of rules are used to order
44 * applicable BindRules and choose the best. When any of these rules rely on the
45 * current dependency context, the deepest node in the context has the most
46 * influence. Put another way, if contexts were strings, they could be ordered
47 * lexicographically from the right to the left.
48 * <p>
49 * When selecting BindRules to apply to a Desire, BindRules are ordered first by
50 * {@linkplain ContextMatch context match}, then by the ordering defined by the bind rule itself.
51 * <p>
52 * A summary of these rules is that the best specified BindRule is applied,
53 * where the context that the BindRule is activated in has more priority than
54 * the type of the BindRule. If multiple rules tie for best, then the solver
55 * fails with a checked exception.
56 *
57 * @author <a href="http://grouplens.org">GroupLens Research</a>
58 */
59 public class RuleBasedBindingFunction implements BindingFunction {
60 private static final Map<Object,Set<BindRule>> bindRuleMemory
61 = new WeakHashMap<Object, Set<BindRule>>();
62
63 private static final Logger logger = LoggerFactory.getLogger(RuleBasedBindingFunction.class);
64
65 private final ImmutableListMultimap<ContextMatcher, BindRule> rules;
66
67 public RuleBasedBindingFunction(Multimap<ContextMatcher, BindRule> rules) {
68 Preconditions.notNull("rules", rules);
69
70 this.rules = ImmutableListMultimap.copyOf(rules);
71 }
72
73 /**
74 * Get the rules underlying this binding function.
75 * @return The rules used by this BindingFunction
76 */
77 public ListMultimap<ContextMatcher, BindRule> getRules() {
78 return rules;
79 }
80
81 @Override
82 public BindingResult bind(InjectionContext context, DesireChain desire) throws ResolutionException {
83 // FIXME Build a better way to remember the applied rules
84 Set<BindRule> appliedRules;
85 synchronized (bindRuleMemory) {
86 appliedRules = bindRuleMemory.get(desire.getKey());
87 if (appliedRules == null) {
88 appliedRules = new HashSet<BindRule>();
89 bindRuleMemory.put(desire.getKey(), appliedRules);
90 }
91 }
92
93 // collect all bind rules that apply to this desire
94 List<Pair<ContextMatch, BindRule>> validRules = new ArrayList<Pair<ContextMatch, BindRule>>();
95 for (ContextMatcher matcher: rules.keySet()) {
96 ContextMatch match = matcher.matches(context);
97 if (match != null) {
98 // the context applies to the current context, so go through all
99 // bind rules within it and record those that match the desire
100 for (BindRule br: rules.get(matcher)) {
101 if (br.matches(desire.getCurrentDesire()) && !appliedRules.contains(br)) {
102 validRules.add(Pair.of(match, br));
103 logger.trace("Matching rule, context: {}, rule: {}", matcher, br);
104 }
105 }
106 }
107 }
108
109 if (!validRules.isEmpty()) {
110 // we have a bind rule to apply
111 // pair's ordering is suitable for sorting the bind rules
112 Collections.sort(validRules);
113
114 if (validRules.size() > 1) {
115 // must check if other rules are equal to the first
116 // we find the whole list of dupes for error reporting purposes
117 List<BindRule> topRules = new ArrayList<BindRule>();
118 topRules.add(validRules.get(0).getRight());
119 for (int i = 1; i < validRules.size(); i++) {
120 if (validRules.get(0).compareTo(validRules.get(i)) == 0) {
121 topRules.add(validRules.get(i).getRight());
122 }
123 }
124
125 if (topRules.size() > 1) {
126 logger.error("{} bindings for {} in {}", topRules.size(),
127 desire, context);
128 for (BindRule rule: topRules) {
129 logger.info("matching rule: {}", rule);
130 }
131 // additional rules match just as well as the first, so fail
132 throw new MultipleBindingsException(desire, context, topRules);
133 }
134 }
135
136 // apply the bind rule to get a new desire
137 BindRule selectedRule = validRules.get(0).getRight();
138 appliedRules.add(selectedRule);
139
140 logger.debug("Applying rule: {} to desire: {}", selectedRule, desire);
141 return BindingResult.newBuilder()
142 .setDesire(selectedRule.apply(desire.getCurrentDesire()))
143 .setCachePolicy(selectedRule.getCachePolicy())
144 .setFlags(selectedRule.getFlags())
145 .build();
146 }
147
148 // No rule to apply, so return null to delegate to the next binding function
149 return null;
150 }
151 }