/* * #! * Ontopia Engine * #- * Copyright (C) 2001 - 2013 The Ontopia Project * #- * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * !# */ package net.ontopia.topicmaps.query.impl.basic; import java.util.Collection; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Set; import net.ontopia.utils.CompactHashSet; import net.ontopia.topicmaps.impl.utils.ArgumentValidator; import net.ontopia.topicmaps.query.core.InvalidQueryException; import net.ontopia.topicmaps.query.impl.utils.QueryAnalyzer; import net.ontopia.topicmaps.query.impl.utils.QueryOptimizer; import net.ontopia.topicmaps.query.parser.AbstractClause; import net.ontopia.topicmaps.query.parser.Pair; import net.ontopia.topicmaps.query.parser.ParsedRule; import net.ontopia.topicmaps.query.parser.PredicateClause; import net.ontopia.topicmaps.query.parser.Variable; import net.ontopia.topicmaps.query.impl.utils.PredicateDrivenCostEstimator; import net.ontopia.topicmaps.query.impl.utils.CostEstimator; import net.ontopia.topicmaps.query.impl.utils.SimpleCostEstimator; /** * INTERNAL: Implements rule predicates. */ public class RulePredicate extends AbstractQueryProcessor implements BasicPredicateIF { protected ParsedRule rule; protected String signature; public RulePredicate(ParsedRule rule) { this.rule = rule; } // --- PredicateIF implementation public String getName() { return rule.getName(); } public String getSignature() throws InvalidQueryException { if (signature != null) return signature; // protect against infinite recursion List params = rule.getParameters(); StringBuilder sign = new StringBuilder(); for (int ix = 0; ix < params.size(); ix++) { if (ix > 0) sign.append(' '); sign.append('.'); } signature = sign.toString(); // we do not return here, but try to produce a stricter value // do type analysis on rule boolean strict = rule.getOptions().getBooleanValue("compiler.typecheck"); Map vartypes = QueryAnalyzer.analyzeTypes(rule.getClauses(), strict) .getVariableTypes(); // produce corresponding signature sign = new StringBuilder(); for (int ix = 0; ix < params.size(); ix++) { Variable var = (Variable) params.get(ix); if (ix > 0) sign.append(' '); sign.append(ArgumentValidator.makeSignature((Object[]) vartypes.get(var.getName()))); } signature = sign.toString(); return sign.toString(); } public int getCost(boolean[] boundparams) { int open = 0; for (int ix = 0; ix < boundparams.length; ix++) if (!boundparams[ix]) open++; if (open == 0) return PredicateDrivenCostEstimator.FILTER_RESULT; else // we want to punish rules which have many open variables; // at the same time we want to run rules early. this represents // a compromise. return PredicateDrivenCostEstimator.BIG_RESULT + open - 1; } // --- BasicPredicateIF implementation public QueryMatches satisfy(QueryMatches extmatches, Object[] extarguments) throws InvalidQueryException { QueryContext extcontext = extmatches.getQueryContext(); // build a new matches object for the internal clauses Collection items = findClauseItems(rule.getClauses(), extcontext.getParameters()); QueryContext intcontext = new QueryContext(extcontext.getTopicMap(), null, extcontext.getParameters(), extcontext.getTologOptions()); QueryMatches intmatches = new QueryMatches(items, intcontext); Object[] params = rule.getParameters().toArray(); // find connections between internal and external matches int[][] translationSpec = extmatches.getTranslationSpec(extarguments, intmatches, params); int[] extspec = translationSpec[0]; int[] intspec = translationSpec[1]; // QueryTracer.trace("=====>" + getName()); // QueryTracer.trace("extspec", extspec); // QueryTracer.trace("intspec", intspec); // QueryTracer.trace("extcols", extmatches.columnDefinitions); // QueryTracer.trace("intcols", intmatches.columnDefinitions); // translate external matches into internal matches extmatches.translate(extspec, intmatches, intspec); // insert the constants in their columns intmatches.insertConstants(); // run satisfy in the usual way Set bound = getBoundVariables(params, extarguments, extmatches); Set litvars = getLiteralVariables(params, extarguments); List theclauses = rule.getClauses(); if (extcontext.getTologOptions().getBooleanValue("optimizer.reorder")) { CostEstimator estimator; if (extcontext.getTologOptions().getBooleanValue("optimizer.reorder.predicate-based")) estimator = new PredicateDrivenCostEstimator(); else estimator = new SimpleCostEstimator(); theclauses = QueryOptimizer.reorder(theclauses, bound, litvars, getName(), estimator); } intmatches = satisfy(theclauses, intmatches); // merge external matches with internal matches return extmatches.merge(extspec, intmatches, intspec, getEqualPairs(extarguments)); } /** * INTERNAL: Finds the variables that are bound inside the rule in * this particular invocation of it. * @param params The parameters to the rule given in its declaration * (an array of Variable objects) * @param extarguments The parameters passed to this invocation * @param extmatches The current temporary query result. */ private static Set getBoundVariables(Object[] params, Object[] extarguments, QueryMatches extmatches) { Set bound = new CompactHashSet(); for (int ix = 0; ix < params.length; ix++) { int col = extmatches.getIndex(extarguments[ix]); if (extmatches.bound(col)) bound.add(params[ix]); } return bound; } /** * INTERNAL: Finds the variables inside the rule that were bound to * literals outside the rule. * @param params The parameters to the rule given in its declaration * (an array of Variable objects) * @param extarguments The parameters passed to this invocation */ private static Set getLiteralVariables(Object[] params, Object[] extarguments) { Set litvars = new CompactHashSet(); for (int ix = 0; ix < params.length; ix++) if (!(extarguments[ix] instanceof Variable)) litvars.add(params[ix]); return litvars; } // --- Various public methods public List getClauses() { return rule.getClauses(); } public List getParameters() { return rule.getParameters(); } public boolean equals(Object obj) { if (this == obj) return true; if (obj instanceof RulePredicate) { RulePredicate other = (RulePredicate)obj; return rule.equals(other.rule); } return false; } /** * INTERNAL: Checks to see if this rule is simply an alias. If it is * the optimizer can do an inline replacement of it to optimize other * rules as well as queries that use it. */ public boolean replaceable() { if (rule.getClauses().size() != 1) return false; AbstractClause clause = (AbstractClause) rule.getClauses().get(0); if (!(clause instanceof PredicateClause)) return false; Collection variables = findClauseVariables(getClauses()); List parameters = getParameters(); if (variables.size() != parameters.size()) return false; for (int ix = 0; ix < parameters.size(); ix++) if (!variables.contains(parameters.get(ix))) return false; return true; } /** * INTERNAL: Creates a new PredicateClause representing the content * of this rule inlined in an environment where the arguments in the * args parameter have been passed to the predicate. */ public PredicateClause translate(List arguments) { PredicateClause srcclause = (PredicateClause) rule.getClauses().get(0); PredicateClause clause = new PredicateClause(srcclause.getPredicate()); Map varmap = makeVariableMap(arguments); List srcargs = srcclause.getArguments(); for (int ix = 0; ix < srcargs.size(); ix++) { Object arg = srcargs.get(ix); Object newarg; if (arg instanceof Pair) { Pair pair = (Pair) arg; if (pair.getFirst() instanceof Variable) newarg = new Pair(varmap.get(pair.getFirst()), pair.getSecond()); else newarg = new Pair(pair.getFirst(), pair.getSecond()); } else if (arg instanceof Variable) newarg = varmap.get(arg); else newarg = arg; clause.addArgument(newarg); } return clause; } // maps params -> args private Map makeVariableMap(List arguments) { List params = getParameters(); Map varmap = new HashMap(); for (int ix = 0; ix < arguments.size(); ix++) varmap.put(params.get(ix), arguments.get(ix)); return varmap; } /** * INTERNAL: Finds pairs of equal variables in the arguments * received by the rule. Returns an array where items 2n and 2n+1 * (for all n) are references to internal columns that externally * are bound to the same variable. That is, let's say a rule is * invoked as rule($A, $B, $A, $C, $B), then the array returned will * be (0, 2, 1, 4), which means that columns 0 and 2 must be equal, * and columns 1 and 4 must be. * @return array with indexes referring to argument number */ private int[] getEqualPairs(Object[] extarguments) { List<Integer> l = new ArrayList<Integer>(); for (int ix = 0; ix+1 < extarguments.length; ix++) for (int i = ix+1; i < extarguments.length; i++) if (extarguments[ix] instanceof Variable && extarguments[i] instanceof Variable && extarguments[ix].equals(extarguments[i])) { l.add(new Integer(ix)); l.add(new Integer(i)); } int[] pairs = new int[l.size()]; for (int ix = 0; ix < l.size(); ix++) pairs[ix] = l.get(ix).intValue(); return pairs; } }