org.grouplens.grapht.solver.RuleBasedBindingFunction.java Source code

Java tutorial

Introduction

Here is the source code for org.grouplens.grapht.solver.RuleBasedBindingFunction.java

Source

/*
 * 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;
    }
}