/*
* #!
* 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.ArrayList;
import java.util.Collection;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import net.ontopia.topicmaps.query.core.InvalidQueryException;
import net.ontopia.topicmaps.query.parser.AbstractClause;
import net.ontopia.topicmaps.query.parser.NotClause;
import net.ontopia.topicmaps.query.parser.OrClause;
import net.ontopia.topicmaps.query.parser.Pair;
import net.ontopia.topicmaps.query.parser.Parameter;
import net.ontopia.topicmaps.query.parser.PredicateClause;
import net.ontopia.topicmaps.query.parser.Variable;
import net.ontopia.utils.OntopiaRuntimeException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* INTERNAL: A collection of utility methods used by classes which need
* to evaluate queries. Simply a code-sharing class for QueryProcessor
* and RulePredicate.
*/
public abstract class AbstractQueryProcessor {
// initialization of logging facility
private static Logger log = LoggerFactory.getLogger(AbstractQueryProcessor.class.getName());
private static BasicPredicateIF NOT_PREDICATE = new NotPredicate();
/**
* INTERNAL: Finds all the values (constants and variables) used in
* the set of clauses and returns them in a collection with no
* duplicates.
*/
public Collection findClauseItems(List clauses, Map parameters) {
// find the set of constants and variables used in the clauses
Collection items = new ArrayList();
Iterator it = clauses.iterator();
while (it.hasNext()) {
AbstractClause clause = (AbstractClause) it.next();
Iterator it2 = clause.getArguments().iterator();
while (it2.hasNext()) {
Object argument = it2.next();
if (argument instanceof Pair)
// WARN: this means that second item cannot be bound.
argument = ((Pair) argument).getFirst();
if (argument instanceof Parameter) {
String pname = ((Parameter) argument).getName();
argument = parameters.get(pname);
}
if (!items.contains(argument))
items.add(argument);
}
}
return items;
}
/**
* INTERNAL: Finds all the variables used in the set of clauses
* and returns them in a collection with no duplicates.
*/
public Collection findClauseVariables(List clauses) {
// find the set of variables used in the clauses
Collection items = new ArrayList();
Iterator it = clauses.iterator();
while (it.hasNext()) {
AbstractClause clause = (AbstractClause) it.next();
Iterator it2 = clause.getArguments().iterator();
while (it2.hasNext()) {
Object argument = it2.next();
if (argument instanceof Pair)
// WARN: this means that second item cannot be bound.
argument = ((Pair) argument).getFirst();
if (argument instanceof Variable && !items.contains(argument))
items.add(argument);
}
}
return items;
}
/**
* INTERNAL: Takes the query parameters and produces the complete
* list of matches. It's static because it's not inherited, it uses
* no instance variables, and this makes it easier to access from
* outside when needed.
*/
public static QueryMatches satisfy(List clauses, QueryMatches result)
throws InvalidQueryException {
// WARNING: method used by rdbms tolog
for (int ix = 0; ix < clauses.size(); ix++) {
AbstractClause theClause = (AbstractClause) clauses.get(ix);
if (theClause instanceof PredicateClause) {
// check to see if thread has been interrupted
if(Thread.currentThread().isInterrupted())
throw new OntopiaRuntimeException(new InterruptedException());
// execute predicate
PredicateClause clause = (PredicateClause) theClause;
BasicPredicateIF predicate = (BasicPredicateIF) clause.getPredicate();
QueryTracer.enter(predicate, clause, result);
Object[] argarr = makeArgumentArray(clause, result.getQueryContext());
result = predicate.satisfy(result, argarr);
QueryTracer.leave(result);
} else if (theClause instanceof OrClause) {
OrClause clause = (OrClause) theClause;
QueryMatches matches = new QueryMatches(result);
QueryTracer.enter(clause, result);
if (clause.getShortCircuit()) {
// shortcircuting OR
Iterator it = clause.getAlternatives().iterator();
while (it.hasNext()) {
List branch = (List) it.next();
QueryTracer.enter(branch);
QueryMatches _matches = satisfy(branch, result);
if (!_matches.isEmpty()) {
matches.add(_matches);
break;
}
QueryTracer.leave(branch);
}
} else {
if (clause.getAlternatives().size() == 1) {
// optional clause
List branch = (List) clause.getAlternatives().get(0);
QueryTracer.enter(branch);
matches = satisfy(branch, result);
QueryTracer.leave(branch);
matches.addNonRedundant(result);
} else {
// ordinary OR
Iterator it = clause.getAlternatives().iterator();
while (it.hasNext()) {
List branch = (List) it.next();
QueryTracer.enter(branch);
matches.add(satisfy(branch, result));
QueryTracer.leave(branch);
}
}
}
result = matches;
QueryTracer.leave(result);
} else if (theClause instanceof NotClause) {
NotClause clause = (NotClause) theClause;
QueryTracer.enter(NOT_PREDICATE, clause, result);
QueryMatches notmatches = satisfy(clause.getClauses(), result);
QueryMatches matches = new QueryMatches(result);
matches.add(result); // making a copy
matches.remove(notmatches);
result = matches;
QueryTracer.leave(result);
} else
throw new OntopiaRuntimeException("Unknown clause type:" + theClause);
// if there are no matches, there's no need to continue, as later
// clauses can't generate any from nothing
if (result.last == -1)
return result;
}
return result;
}
private static Object[] makeArgumentArray(AbstractClause clause, QueryContext context) {
Object[] args = clause.getArguments().toArray();
for (int ix = 0; ix < args.length; ix++)
if (args[ix] instanceof Parameter)
args[ix] = context.getParameterValue(((Parameter) args[ix]).getName());
return args;
}
// --- Internal helper class
// only used to produce debugging traces
static class NotPredicate implements BasicPredicateIF {
public String getName() {
throw new OntopiaRuntimeException("INTERNAL ERROR");
}
public String getSignature() {
throw new OntopiaRuntimeException("INTERNAL ERROR");
}
public int getCost(boolean[] boundparam) {
throw new OntopiaRuntimeException("INTERNAL ERROR");
}
public QueryMatches satisfy(QueryMatches matches, Object[] arguments)
throws InvalidQueryException {
throw new OntopiaRuntimeException("INTERNAL ERROR");
}
}
}