Java tutorial
/* * Grapht, an open source dependency injector. * Copyright 2014-2015 various contributors (see CONTRIBUTORS.txt) * Copyright 2010-2014 Regents of the University of Minnesota * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation; either version 2.1 of the * License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more * details. * * You should have received a copy of the GNU General Public License along with * this program; if not, write to the Free Software Foundation, Inc., 51 * Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ package org.grouplens.grapht.solver; import com.google.common.collect.ImmutableListMultimap; import com.google.common.collect.ListMultimap; import com.google.common.collect.Multimap; import org.apache.commons.lang3.tuple.Pair; import org.grouplens.grapht.ResolutionException; import org.grouplens.grapht.context.ContextMatch; import org.grouplens.grapht.context.ContextMatcher; import org.grouplens.grapht.reflect.QualifierMatcher; import org.grouplens.grapht.util.Preconditions; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.util.*; /** * <p> * BindingFunction that uses BindRules created by the fluent API to bind desires * to other desires or satisfactions. * <p> * For more details on context management, see {@link org.grouplens.grapht.context.ContextPattern}, * {@link org.grouplens.grapht.context.ContextElementMatcher}, and {@link QualifierMatcher}. This function uses the * context to activate and select BindRules. A number of rules are used to order * applicable BindRules and choose the best. When any of these rules rely on the * current dependency context, the deepest node in the context has the most * influence. Put another way, if contexts were strings, they could be ordered * lexicographically from the right to the left. * <p> * When selecting BindRules to apply to a Desire, BindRules are ordered first by * {@linkplain ContextMatch context match}, then by the ordering defined by the bind rule itself. * <p> * A summary of these rules is that the best specified BindRule is applied, * where the context that the BindRule is activated in has more priority than * the type of the BindRule. If multiple rules tie for best, then the solver * fails with a checked exception. * * @author <a href="http://grouplens.org">GroupLens Research</a> */ public class RuleBasedBindingFunction implements BindingFunction { private static final Map<Object, Set<BindRule>> bindRuleMemory = new WeakHashMap<Object, Set<BindRule>>(); private static final Logger logger = LoggerFactory.getLogger(RuleBasedBindingFunction.class); private final ImmutableListMultimap<ContextMatcher, BindRule> rules; public RuleBasedBindingFunction(Multimap<ContextMatcher, BindRule> rules) { Preconditions.notNull("rules", rules); this.rules = ImmutableListMultimap.copyOf(rules); } /** * Get the rules underlying this binding function. * @return The rules used by this BindingFunction */ public ListMultimap<ContextMatcher, BindRule> getRules() { return rules; } @Override public BindingResult bind(InjectionContext context, DesireChain desire) throws ResolutionException { // FIXME Build a better way to remember the applied rules Set<BindRule> appliedRules; synchronized (bindRuleMemory) { appliedRules = bindRuleMemory.get(desire.getKey()); if (appliedRules == null) { appliedRules = new HashSet<BindRule>(); bindRuleMemory.put(desire.getKey(), appliedRules); } } // collect all bind rules that apply to this desire List<Pair<ContextMatch, BindRule>> validRules = new ArrayList<Pair<ContextMatch, BindRule>>(); for (ContextMatcher matcher : rules.keySet()) { ContextMatch match = matcher.matches(context); if (match != null) { // the context applies to the current context, so go through all // bind rules within it and record those that match the desire for (BindRule br : rules.get(matcher)) { if (br.matches(desire.getCurrentDesire()) && !appliedRules.contains(br)) { validRules.add(Pair.of(match, br)); logger.trace("Matching rule, context: {}, rule: {}", matcher, br); } } } } if (!validRules.isEmpty()) { // we have a bind rule to apply // pair's ordering is suitable for sorting the bind rules Collections.sort(validRules); if (validRules.size() > 1) { // must check if other rules are equal to the first // we find the whole list of dupes for error reporting purposes List<BindRule> topRules = new ArrayList<BindRule>(); topRules.add(validRules.get(0).getRight()); for (int i = 1; i < validRules.size(); i++) { if (validRules.get(0).compareTo(validRules.get(i)) == 0) { topRules.add(validRules.get(i).getRight()); } } if (topRules.size() > 1) { logger.error("{} bindings for {} in {}", topRules.size(), desire, context); for (BindRule rule : topRules) { logger.info("matching rule: {}", rule); } // additional rules match just as well as the first, so fail throw new MultipleBindingsException(desire, context, topRules); } } // apply the bind rule to get a new desire BindRule selectedRule = validRules.get(0).getRight(); appliedRules.add(selectedRule); logger.debug("Applying rule: {} to desire: {}", selectedRule, desire); return BindingResult.newBuilder().setDesire(selectedRule.apply(desire.getCurrentDesire())) .setCachePolicy(selectedRule.getCachePolicy()).setFlags(selectedRule.getFlags()).build(); } // No rule to apply, so return null to delegate to the next binding function return null; } }