package de.unikiel.inf.comsys.neo4j.inference; /* * #%L * neo4j-sparql-extension * %% * Copyright (C) 2014 Niclas Hoyer * %% * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as * published by the Free Software Foundation, either version 3 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, see * <http://www.gnu.org/licenses/gpl-3.0.html>. * #L% */ import de.unikiel.inf.comsys.neo4j.inference.rules.Rule; import java.util.ArrayList; import java.util.IdentityHashMap; import java.util.List; import org.openrdf.query.algebra.QueryModelNode; import org.openrdf.query.algebra.StatementPattern; import org.openrdf.query.algebra.helpers.QueryModelVisitorBase; /** * A {@link QueryModelVisitorBase} implementation that transforms an expression * according to a list of rules. * * This visitor visits each node and tries to apply all rules. If a rules * has been applied to a parent node it won't be applied in any child nodes. */ class RuleTransformationVisitor extends QueryModelVisitorBase<RuntimeException> { private final ArrayList<Rule> rules; /** * A map to store reduced rule sets for rules. * * An identity hash map is used because the Sesame implementation of * the hash method of {@link QueryModelNode} is misleading. * The hash method returns equal hashes for different statement patterns * with the same entries. But the visitor needs to distinguish them, so * the identity of the Java object is used instead of the hash value. */ private final IdentityHashMap<QueryModelNode, ArrayList<Rule>> applied; /** * Creates a new rule transformation visitor that uses the given rules * for query rewriting. * * @param rules the rules used for rewriting */ public RuleTransformationVisitor(ArrayList<Rule> rules) { this.rules = rules; this.applied = new IdentityHashMap<>(); } /** * Returns the rules that may be applicable for a given node. * * @param node a node * @return rules that may be applicable to the node */ private ArrayList<Rule> getRules(QueryModelNode node) { // check if there is an entry in the map if (applied.containsKey(node)) { return applied.get(node); } else { // if there is no entry in the map, // traverse branch up until root to find a reduced rule set QueryModelNode parent = node.getParentNode(); while (parent != null) { if (applied.containsKey(parent)) { return applied.get(parent); } else { parent = parent.getParentNode(); } } // if there are no reduced rule sets return the full set return rules; } } /** * Removes a rule from a rule set for a node. * * If there is no entry in the map use the base rule set instead and * save it in the map. * @param base the rule set to use if there is no reduced set in the map * @param node a node * @param r the rule to remove */ private void removeRule(List<Rule> base, QueryModelNode node, Rule r) { if (applied.containsKey(node)) { applied.get(node).remove(r); } else { ArrayList<Rule> reduced = new ArrayList<>(base); reduced.remove(r); applied.put(node, reduced); } } /** * Visits a statement pattern and tries to transform using all rules * in the given rule set. * * Tries to apply a rule from the rule set. If it is applicable * it will recurse using the visitor pattern to the nodes returned * from the rule after application. * @param node * @throws RuntimeException */ @Override public void meet(StatementPattern node) throws RuntimeException { // get a list of rules that may be applicable ArrayList<Rule> toApply = new ArrayList<>(getRules(node)); for (Rule r : toApply) { // if a rule can be applied if (r.canApply(node)) { // apply the rule List<QueryModelNode> next = r.apply(node); // visit all nodes that the rule returned for (QueryModelNode toVisit : next) { removeRule(toApply, toVisit, r); toVisit.visit(this); } // halt execution, because the rule transformed the original // node and this visit can't transform further. Remaining rules // are applied in the recursion (see above). break; } } } }