/*
* #!
* 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.utils;
import java.util.Collection;
import java.util.Iterator;
import java.util.List;
import java.util.Set;
import net.ontopia.topicmaps.core.TMObjectIF;
import net.ontopia.topicmaps.query.core.InvalidQueryException;
import net.ontopia.topicmaps.query.impl.basic.BasicPredicateIF;
import net.ontopia.topicmaps.query.impl.basic.QueryMatches;
import net.ontopia.topicmaps.query.impl.basic.RulePredicate;
import net.ontopia.topicmaps.query.impl.basic.QueryTracer;
import net.ontopia.topicmaps.query.parser.PredicateClause;
import net.ontopia.topicmaps.query.parser.Variable;
import net.ontopia.utils.CompactHashSet;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* INTERNAL:
*/
public class HierarchyWalkerRulePredicate implements BasicPredicateIF {
private static Logger log =
LoggerFactory.getLogger(HierarchyWalkerRulePredicate.class.getName());
protected RulePredicate rule;
protected Variable firstvar;
protected Variable secondvar;
protected Variable midvar;
protected PredicateClause wrapped; // mid; the predicate being recursed over
public HierarchyWalkerRulePredicate(RulePredicate rule,
Variable firstvar,
Variable secondvar,
Variable midvar,
PredicateClause wrapped) {
this.rule = rule;
this.firstvar = firstvar;
this.secondvar = secondvar;
this.midvar = midvar;
this.wrapped = wrapped;
}
// --- PredicateIF implementation
public String getName() {
return rule.getName();
}
public String getSignature() throws InvalidQueryException {
return rule.getSignature();
}
public int getCost(boolean[] boundparam) {
return rule.getCost(boundparam);
}
// --- BasicPredicateIF implementation
public QueryMatches satisfy(QueryMatches extmatches, Object[] extarguments)
throws InvalidQueryException {
// use wrapped rule if there are many matches already
// FIXME: make this work for more matches
if (extmatches.last > 0)
return rule.satisfy(extmatches, extarguments);
QueryTracer.trace("satisfying hierarchywalker " + getName(), extarguments);
// find indexes of firstvar and secondvar
int ix1 = -1; // firstvar index
int ix2 = -1; // secondvar index
List params = rule.getParameters();
for (int ix = 0; ix < params.size(); ix++) {
Object p = params.get(ix);
if (p.equals(firstvar))
ix1 = extmatches.getIndex(extarguments[ix]);
else if (p.equals(secondvar))
ix2 = extmatches.getIndex(extarguments[ix]);
}
// $A $B -> run using wrapped rule (may be slower; need to test)
if (!extmatches.bound(ix1) && !extmatches.bound(ix2))
return rule.satisfy(extmatches, extarguments);
// $A b -> use optimization
// a $B -> same
// a b -> same
log.debug("hierarchy-walker runs");
// 1) get results of open query on predicate
QueryMatches result = runPredicate(extmatches, extarguments);
log.debug("table size: " + (result.last + 1));
// 2) find transitive closure from starting object
int startcol = ix1;
int goalcol = ix2;
if (!extmatches.bound(ix1)) {
startcol = ix2;
goalcol = ix1;
}
Object startval = extmatches.data[0][startcol];
// figure out columns in result
int resstartcol = result.getIndex(firstvar);
int resgoalcol = result.getIndex(secondvar);
if (!extmatches.bound(ix1)) {
resstartcol = result.getIndex(secondvar);
resgoalcol = result.getIndex(firstvar);
}
Set closure = findTransitiveClosure(result, resstartcol, resgoalcol, startval);
log.debug("closure found, size: " + closure.size());
// 3) produce situation-specific response
QueryMatches ownresult = new QueryMatches(extmatches);
if (!extmatches.bound(goalcol)) {
// output all goal values
ownresult.ensureCapacity(closure.size());
Iterator it = closure.iterator();
for (int ix = 0; ix < closure.size(); ix++) {
ownresult.data[++ownresult.last] = (Object[]) extmatches.data[0].clone();
ownresult.data[ownresult.last][goalcol] = it.next();
}
} else {
// goal column is bound, so just check the result, and output
// if correct
Object goalval = extmatches.data[0][goalcol];
if (closure.contains(goalval)) {
ownresult.data[++ownresult.last] = (Object[]) extmatches.data[0].clone();
ownresult.data[ownresult.last][goalcol] = goalval;
}
}
QueryTracer.trace("finished hierarchywalker " + getName());
return ownresult;
}
// --- Internal methods
// orgargs: arguments to rule as a whole
private QueryMatches runPredicate(QueryMatches extmatches, Object[] extargs)
throws InvalidQueryException {
// get ready to run wrapped predicate
net.ontopia.topicmaps.query.impl.basic.QueryContext context =
extmatches.getQueryContext();
Collection items = rule.findClauseItems(rule.getClauses(),
context.getParameters());
QueryMatches matches = new QueryMatches(items, context);
matches.last++; // make an empty row
matches.insertConstants();
// bind internal variables to external arguments
for (int ix = 0; ix < extargs.length; ix++) {
Variable intvar = (Variable) rule.getParameters().get(ix);
if (intvar.equals(firstvar) ||
intvar.equals(secondvar) ||
intvar.equals(midvar))
continue;
if (extargs[ix] instanceof TMObjectIF || extargs[ix] instanceof String) {
int col = matches.getIndex(intvar);
matches.data[0][col] = extargs[ix];
}
}
// run wrapped predicate
BasicPredicateIF pred = (BasicPredicateIF) wrapped.getPredicate();
return pred.satisfy(matches, wrapped.getArguments().toArray());
}
private Set findTransitiveClosure(QueryMatches result, int startcol,
int goalcol, Object startval) {
Set closure = new CompactHashSet(100);
closure.add(startval);
int before;
do {
before = closure.size();
addTransitively(result, startcol, goalcol, closure);
} while (before < closure.size());
closure.remove(startval); // FIXME: this isn't a proper fix!
return closure;
}
private void addTransitively(QueryMatches result, int startcol, int goalcol,
Set closure) {
for (int ix = 0; ix <= result.last; ix++) {
if (closure.contains(result.data[ix][startcol]))
closure.add(result.data[ix][goalcol]);
}
}
}