/* * Copyright Aduna (http://www.aduna-software.com/) (c) 1997-2007. * * Licensed under the Aduna BSD-style license. */ package org.openrdf.query.algebra.evaluation.impl; import java.math.BigDecimal; import java.math.BigInteger; import java.math.RoundingMode; import java.util.HashSet; import java.util.List; import java.util.Set; import java.util.regex.Pattern; import info.aduna.iteration.CloseableIteration; import info.aduna.iteration.ConvertingIteration; import info.aduna.iteration.DelayedIteration; import info.aduna.iteration.DistinctIteration; import info.aduna.iteration.EmptyIteration; import info.aduna.iteration.FilterIteration; import info.aduna.iteration.IntersectIteration; import info.aduna.iteration.Iteration; import info.aduna.iteration.LimitIteration; import info.aduna.iteration.MinusIteration; import info.aduna.iteration.OffsetIteration; import info.aduna.iteration.SingletonIteration; import info.aduna.iteration.UnionIteration; import org.openrdf.model.BNode; import org.openrdf.model.Literal; import org.openrdf.model.Resource; import org.openrdf.model.Statement; import org.openrdf.model.URI; import org.openrdf.model.Value; import org.openrdf.model.datatypes.XMLDatatypeUtil; import org.openrdf.model.impl.BooleanLiteralImpl; import org.openrdf.model.impl.DecimalLiteralImpl; import org.openrdf.model.impl.IntegerLiteralImpl; import org.openrdf.model.impl.NumericLiteralImpl; import org.openrdf.model.vocabulary.XMLSchema; import org.openrdf.query.BindingSet; import org.openrdf.query.Dataset; import org.openrdf.query.QueryEvaluationException; import org.openrdf.query.algebra.And; import org.openrdf.query.algebra.BNodeGenerator; import org.openrdf.query.algebra.BinaryTupleOperator; import org.openrdf.query.algebra.Bound; import org.openrdf.query.algebra.Compare; import org.openrdf.query.algebra.CompareAll; import org.openrdf.query.algebra.CompareAny; import org.openrdf.query.algebra.Datatype; import org.openrdf.query.algebra.Difference; import org.openrdf.query.algebra.Distinct; import org.openrdf.query.algebra.EmptySet; import org.openrdf.query.algebra.Exists; import org.openrdf.query.algebra.Extension; import org.openrdf.query.algebra.Filter; import org.openrdf.query.algebra.FunctionCall; import org.openrdf.query.algebra.Group; import org.openrdf.query.algebra.In; import org.openrdf.query.algebra.Intersection; import org.openrdf.query.algebra.IsBNode; import org.openrdf.query.algebra.IsLiteral; import org.openrdf.query.algebra.IsResource; import org.openrdf.query.algebra.IsURI; import org.openrdf.query.algebra.Join; import org.openrdf.query.algebra.Label; import org.openrdf.query.algebra.Lang; import org.openrdf.query.algebra.LangMatches; import org.openrdf.query.algebra.LeftJoin; import org.openrdf.query.algebra.Like; import org.openrdf.query.algebra.LocalName; import org.openrdf.query.algebra.MathExpr; import org.openrdf.query.algebra.MultiProjection; import org.openrdf.query.algebra.Namespace; import org.openrdf.query.algebra.Not; import org.openrdf.query.algebra.Or; import org.openrdf.query.algebra.Order; import org.openrdf.query.algebra.Projection; import org.openrdf.query.algebra.QueryRoot; import org.openrdf.query.algebra.Regex; import org.openrdf.query.algebra.SameTerm; import org.openrdf.query.algebra.SingletonSet; import org.openrdf.query.algebra.Slice; import org.openrdf.query.algebra.StatementPattern; import org.openrdf.query.algebra.Str; import org.openrdf.query.algebra.TupleExpr; import org.openrdf.query.algebra.UnaryTupleOperator; import org.openrdf.query.algebra.Union; import org.openrdf.query.algebra.ValueConstant; import org.openrdf.query.algebra.ValueExpr; import org.openrdf.query.algebra.Var; import org.openrdf.query.algebra.MathExpr.MathOp; import org.openrdf.query.algebra.StatementPattern.Scope; import org.openrdf.query.algebra.evaluation.EvaluationStrategy; import org.openrdf.query.algebra.evaluation.QueryBindingSet; import org.openrdf.query.algebra.evaluation.TripleSource; import org.openrdf.query.algebra.evaluation.ValueExprEvaluationException; import org.openrdf.query.algebra.evaluation.function.Function; import org.openrdf.query.algebra.evaluation.function.FunctionRegistry; import org.openrdf.query.algebra.evaluation.iterator.CompatibleBindingSetFilter; import org.openrdf.query.algebra.evaluation.iterator.ExtensionIterator; import org.openrdf.query.algebra.evaluation.iterator.FilterIterator; import org.openrdf.query.algebra.evaluation.iterator.GroupIterator; import org.openrdf.query.algebra.evaluation.iterator.JoinIterator; import org.openrdf.query.algebra.evaluation.iterator.LeftJoinIterator; import org.openrdf.query.algebra.evaluation.iterator.MultiProjectionIterator; import org.openrdf.query.algebra.evaluation.iterator.OrderIterator; import org.openrdf.query.algebra.evaluation.iterator.ProjectionIterator; import org.openrdf.query.algebra.evaluation.util.OrderComparator; import org.openrdf.query.algebra.evaluation.util.QueryEvaluationUtil; import org.openrdf.query.algebra.evaluation.util.ValueComparator; /** * Evaluates the TupleExpr and ValueExpr using Iterators and common tripleSource * API. * * @author James Leigh * @author Arjohn Kampman * @author David Huynh */ public class EvaluationStrategyImpl implements EvaluationStrategy { /*-----------* * Constants * *-----------*/ private final TripleSource tripleSource; private final Dataset dataset; /*--------------* * Constructors * *--------------*/ public EvaluationStrategyImpl(TripleSource tripleSource) { this(tripleSource, null); } public EvaluationStrategyImpl(TripleSource tripleSource, Dataset dataset) { this.tripleSource = tripleSource; this.dataset = dataset; } /*---------* * Methods * *---------*/ public CloseableIteration<BindingSet, QueryEvaluationException> evaluate(TupleExpr expr, BindingSet bindings) throws QueryEvaluationException { if (expr instanceof StatementPattern) { return evaluate((StatementPattern)expr, bindings); } else if (expr instanceof UnaryTupleOperator) { return evaluate((UnaryTupleOperator)expr, bindings); } else if (expr instanceof BinaryTupleOperator) { return evaluate((BinaryTupleOperator)expr, bindings); } else if (expr instanceof SingletonSet) { return evaluate((SingletonSet)expr, bindings); } else if (expr instanceof EmptySet) { return evaluate((EmptySet)expr, bindings); } else if (expr == null) { throw new IllegalArgumentException("expr must not be null"); } else { throw new QueryEvaluationException("Unsupported tuple expr type: " + expr.getClass()); } } public CloseableIteration<BindingSet, QueryEvaluationException> evaluate(StatementPattern sp, final BindingSet bindings) throws QueryEvaluationException { final Var subjVar = sp.getSubjectVar(); final Var predVar = sp.getPredicateVar(); final Var objVar = sp.getObjectVar(); final Var conVar = sp.getContextVar(); Value subjValue = getVarValue(subjVar, bindings); Value predValue = getVarValue(predVar, bindings); Value objValue = getVarValue(objVar, bindings); Value contextValue = getVarValue(conVar, bindings); CloseableIteration<? extends Statement, QueryEvaluationException> stIter = null; try { Resource[] contexts; if (dataset != null) { Set<URI> graphs; if (sp.getScope() == Scope.DEFAULT_CONTEXTS) { graphs = dataset.getDefaultGraphs(); } else { graphs = dataset.getNamedGraphs(); } if (graphs.isEmpty()) { // Search zero contexts return new EmptyIteration<BindingSet, QueryEvaluationException>(); } else if (contextValue != null) { if (graphs.contains(contextValue)) { contexts = new Resource[] { (Resource)contextValue }; } else { // Statement pattern specifies a context that is not part of // the dataset return new EmptyIteration<BindingSet, QueryEvaluationException>(); } } else { contexts = graphs.toArray(new Resource[graphs.size()]); } } else if (contextValue != null) { contexts = new Resource[] { (Resource)contextValue }; } else { contexts = new Resource[0]; } stIter = tripleSource.getStatements((Resource)subjValue, (URI)predValue, objValue, contexts); if (contexts.length == 0 && sp.getScope() == Scope.NAMED_CONTEXTS) { // Named contexts are matched by retrieving all statements from // the store and filtering out the statements that do not have a // context. stIter = new FilterIteration<Statement, QueryEvaluationException>(stIter) { @Override protected boolean accept(Statement st) { return st.getContext() != null; } }; // end anonymous class } } catch (ClassCastException e) { // Invalid value type for subject, predicate and/or context return new EmptyIteration<BindingSet, QueryEvaluationException>(); } // The same variable might have been used multiple times in this // StatementPattern, verify value equality in those cases. stIter = new FilterIteration<Statement, QueryEvaluationException>(stIter) { @Override protected boolean accept(Statement st) { Resource subj = st.getSubject(); URI pred = st.getPredicate(); Value obj = st.getObject(); Resource context = st.getContext(); if (subjVar != null) { if (subjVar.equals(predVar) && !subj.equals(pred)) { return false; } if (subjVar.equals(objVar) && !subj.equals(obj)) { return false; } if (subjVar.equals(conVar) && !subj.equals(context)) { return false; } } if (predVar != null) { if (predVar.equals(objVar) && !pred.equals(obj)) { return false; } if (predVar.equals(conVar) && !pred.equals(context)) { return false; } } if (objVar != null) { if (objVar.equals(conVar) && !obj.equals(context)) { return false; } } return true; } }; // Return an iterator that converts the statements to var bindings return new ConvertingIteration<Statement, BindingSet, QueryEvaluationException>(stIter) { @Override protected BindingSet convert(Statement st) { QueryBindingSet result = new QueryBindingSet(bindings); if (subjVar != null && !result.hasBinding(subjVar.getName())) { result.addBinding(subjVar.getName(), st.getSubject()); } if (predVar != null && !result.hasBinding(predVar.getName())) { result.addBinding(predVar.getName(), st.getPredicate()); } if (objVar != null && !result.hasBinding(objVar.getName())) { result.addBinding(objVar.getName(), st.getObject()); } if (conVar != null && !result.hasBinding(conVar.getName()) && st.getContext() != null) { result.addBinding(conVar.getName(), st.getContext()); } return result; } }; } private Value getVarValue(Var var, BindingSet bindings) { if (var == null) { return null; } else if (var.hasValue()) { return var.getValue(); } else { return bindings.getValue(var.getName()); } } public CloseableIteration<BindingSet, QueryEvaluationException> evaluate(UnaryTupleOperator expr, BindingSet bindings) throws QueryEvaluationException { if (expr instanceof Projection) { return evaluate((Projection)expr, bindings); } else if (expr instanceof MultiProjection) { return evaluate((MultiProjection)expr, bindings); } else if (expr instanceof Filter) { return evaluate((Filter)expr, bindings); } else if (expr instanceof Slice) { return evaluate((Slice)expr, bindings); } else if (expr instanceof Extension) { return evaluate((Extension)expr, bindings); } else if (expr instanceof Distinct) { return evaluate((Distinct)expr, bindings); } else if (expr instanceof Group) { return evaluate((Group)expr, bindings); } else if (expr instanceof Order) { return evaluate((Order)expr, bindings); } else if (expr instanceof QueryRoot) { return evaluate(((QueryRoot)expr).getArg(), bindings); } else if (expr == null) { throw new IllegalArgumentException("expr must not be null"); } else { throw new QueryEvaluationException("Unknown unary tuple operator type: " + expr.getClass()); } } public CloseableIteration<BindingSet, QueryEvaluationException> evaluate(Projection projection, BindingSet bindings) throws QueryEvaluationException { CloseableIteration<BindingSet, QueryEvaluationException> result; result = this.evaluate(projection.getArg(), bindings); result = new ProjectionIterator(projection, result, bindings); return result; } public CloseableIteration<BindingSet, QueryEvaluationException> evaluate(MultiProjection multiProjection, BindingSet bindings) throws QueryEvaluationException { CloseableIteration<BindingSet, QueryEvaluationException> result; result = this.evaluate(multiProjection.getArg(), bindings); result = new MultiProjectionIterator(multiProjection, result, bindings); return result; } public CloseableIteration<BindingSet, QueryEvaluationException> evaluate(Filter filter, BindingSet bindings) throws QueryEvaluationException { CloseableIteration<BindingSet, QueryEvaluationException> result; result = this.evaluate(filter.getArg(), bindings); result = new FilterIterator(filter, result, this); return result; } public CloseableIteration<BindingSet, QueryEvaluationException> evaluate(Slice slice, BindingSet bindings) throws QueryEvaluationException { CloseableIteration<BindingSet, QueryEvaluationException> result = evaluate(slice.getArg(), bindings); if (slice.hasOffset()) { result = new OffsetIteration<BindingSet, QueryEvaluationException>(result, slice.getOffset()); } if (slice.hasLimit()) { result = new LimitIteration<BindingSet, QueryEvaluationException>(result, slice.getLimit()); } return result; } public CloseableIteration<BindingSet, QueryEvaluationException> evaluate(Extension extension, BindingSet bindings) throws QueryEvaluationException { CloseableIteration<BindingSet, QueryEvaluationException> result; result = this.evaluate(extension.getArg(), bindings); result = new ExtensionIterator(extension, result, this); return result; } public CloseableIteration<BindingSet, QueryEvaluationException> evaluate(Distinct distinct, BindingSet bindings) throws QueryEvaluationException { return new DistinctIteration<BindingSet, QueryEvaluationException>( evaluate(distinct.getArg(), bindings)); } public CloseableIteration<BindingSet, QueryEvaluationException> evaluate(Group node, BindingSet bindings) throws QueryEvaluationException { return new GroupIterator(this, node, bindings); } public CloseableIteration<BindingSet, QueryEvaluationException> evaluate(Order node, BindingSet bindings) throws QueryEvaluationException { ValueComparator vcmp = new ValueComparator(); OrderComparator cmp = new OrderComparator(this, node, vcmp); return new OrderIterator(evaluate(node.getArg(), bindings), cmp); } public CloseableIteration<BindingSet, QueryEvaluationException> evaluate(BinaryTupleOperator expr, BindingSet bindings) throws QueryEvaluationException { if (expr instanceof Join) { return evaluate((Join)expr, bindings); } else if (expr instanceof LeftJoin) { return evaluate((LeftJoin)expr, bindings); } else if (expr instanceof Union) { return evaluate((Union)expr, bindings); } else if (expr instanceof Intersection) { return evaluate((Intersection)expr, bindings); } else if (expr instanceof Difference) { return evaluate((Difference)expr, bindings); } else if (expr == null) { throw new IllegalArgumentException("expr must not be null"); } else { throw new QueryEvaluationException("Unsupported binary tuple operator type: " + expr.getClass()); } } public CloseableIteration<BindingSet, QueryEvaluationException> evaluate(Join join, BindingSet bindings) throws QueryEvaluationException { return new JoinIterator(this, join, bindings); } public CloseableIteration<BindingSet, QueryEvaluationException> evaluate(LeftJoin leftJoin, BindingSet bindings) throws QueryEvaluationException { // Check whether optional join is "well designed" as defined in section // 4.2 of "Semantics and Complexity of SPARQL", 2006, Jorge P�rez et al. Set<String> boundVars = bindings.getBindingNames(); Set<String> leftVars = leftJoin.getLeftArg().getBindingNames(); Set<String> optionalVars = leftJoin.getRightArg().getBindingNames(); Set<String> problemVars = new HashSet<String>(boundVars); problemVars.retainAll(optionalVars); problemVars.removeAll(leftVars); if (problemVars.isEmpty()) { // left join is "well designed" return new LeftJoinIterator(this, leftJoin, bindings); } else { QueryBindingSet filteredBindings = new QueryBindingSet(bindings); filteredBindings.removeAll(problemVars); CloseableIteration<BindingSet, QueryEvaluationException> iter; iter = new LeftJoinIterator(this, leftJoin, filteredBindings); iter = new CompatibleBindingSetFilter(iter, bindings); return iter; } } @SuppressWarnings("unchecked") public CloseableIteration<BindingSet, QueryEvaluationException> evaluate(final Union union, final BindingSet bindings) throws QueryEvaluationException { Iteration<BindingSet, QueryEvaluationException> leftArg, rightArg; leftArg = new DelayedIteration<BindingSet, QueryEvaluationException>() { @Override protected Iteration<BindingSet, QueryEvaluationException> createIteration() throws QueryEvaluationException { return evaluate(union.getLeftArg(), bindings); } }; rightArg = new DelayedIteration<BindingSet, QueryEvaluationException>() { @Override protected Iteration<BindingSet, QueryEvaluationException> createIteration() throws QueryEvaluationException { return evaluate(union.getRightArg(), bindings); } }; return new UnionIteration<BindingSet, QueryEvaluationException>(leftArg, rightArg); } public CloseableIteration<BindingSet, QueryEvaluationException> evaluate(final Intersection intersection, final BindingSet bindings) throws QueryEvaluationException { Iteration<BindingSet, QueryEvaluationException> leftArg, rightArg; leftArg = new DelayedIteration<BindingSet, QueryEvaluationException>() { @Override protected Iteration<BindingSet, QueryEvaluationException> createIteration() throws QueryEvaluationException { return evaluate(intersection.getLeftArg(), bindings); } }; rightArg = new DelayedIteration<BindingSet, QueryEvaluationException>() { @Override protected Iteration<BindingSet, QueryEvaluationException> createIteration() throws QueryEvaluationException { return evaluate(intersection.getRightArg(), bindings); } }; return new IntersectIteration<BindingSet, QueryEvaluationException>(leftArg, rightArg); } public CloseableIteration<BindingSet, QueryEvaluationException> evaluate(final Difference difference, final BindingSet bindings) throws QueryEvaluationException { Iteration<BindingSet, QueryEvaluationException> leftArg, rightArg; leftArg = new DelayedIteration<BindingSet, QueryEvaluationException>() { @Override protected Iteration<BindingSet, QueryEvaluationException> createIteration() throws QueryEvaluationException { return evaluate(difference.getLeftArg(), bindings); } }; rightArg = new DelayedIteration<BindingSet, QueryEvaluationException>() { @Override protected Iteration<BindingSet, QueryEvaluationException> createIteration() throws QueryEvaluationException { return evaluate(difference.getRightArg(), bindings); } }; return new MinusIteration<BindingSet, QueryEvaluationException>(leftArg, rightArg); } public CloseableIteration<BindingSet, QueryEvaluationException> evaluate(SingletonSet singletonSet, BindingSet bindings) throws QueryEvaluationException { return new SingletonIteration<BindingSet, QueryEvaluationException>(bindings); } public CloseableIteration<BindingSet, QueryEvaluationException> evaluate(EmptySet emptySet, BindingSet bindings) throws QueryEvaluationException { return new EmptyIteration<BindingSet, QueryEvaluationException>(); } public Value evaluate(ValueExpr expr, BindingSet bindings) throws ValueExprEvaluationException, QueryEvaluationException { if (expr instanceof Var) { return evaluate((Var)expr, bindings); } else if (expr instanceof ValueConstant) { return evaluate((ValueConstant)expr, bindings); } else if (expr instanceof BNodeGenerator) { return evaluate((BNodeGenerator)expr, bindings); } else if (expr instanceof Bound) { return evaluate((Bound)expr, bindings); } else if (expr instanceof Str) { return evaluate((Str)expr, bindings); } else if (expr instanceof Label) { return evaluate((Label)expr, bindings); } else if (expr instanceof Lang) { return evaluate((Lang)expr, bindings); } else if (expr instanceof LangMatches) { return evaluate((LangMatches)expr, bindings); } else if (expr instanceof Datatype) { return evaluate((Datatype)expr, bindings); } else if (expr instanceof Namespace) { return evaluate((Namespace)expr, bindings); } else if (expr instanceof LocalName) { return evaluate((LocalName)expr, bindings); } else if (expr instanceof IsResource) { return evaluate((IsResource)expr, bindings); } else if (expr instanceof IsURI) { return evaluate((IsURI)expr, bindings); } else if (expr instanceof IsBNode) { return evaluate((IsBNode)expr, bindings); } else if (expr instanceof IsLiteral) { return evaluate((IsLiteral)expr, bindings); } else if (expr instanceof Regex) { return evaluate((Regex)expr, bindings); } else if (expr instanceof Like) { return evaluate((Like)expr, bindings); } else if (expr instanceof FunctionCall) { return evaluate((FunctionCall)expr, bindings); } else if (expr instanceof And) { return evaluate((And)expr, bindings); } else if (expr instanceof Or) { return evaluate((Or)expr, bindings); } else if (expr instanceof Not) { return evaluate((Not)expr, bindings); } else if (expr instanceof SameTerm) { return evaluate((SameTerm)expr, bindings); } else if (expr instanceof Compare) { return evaluate((Compare)expr, bindings); } else if (expr instanceof MathExpr) { return evaluate((MathExpr)expr, bindings); } else if (expr instanceof In) { return evaluate((In)expr, bindings); } else if (expr instanceof CompareAny) { return evaluate((CompareAny)expr, bindings); } else if (expr instanceof CompareAll) { return evaluate((CompareAll)expr, bindings); } else if (expr instanceof Exists) { return evaluate((Exists)expr, bindings); } else if (expr == null) { throw new IllegalArgumentException("expr must not be null"); } else { throw new QueryEvaluationException("Unsupported value expr type: " + expr.getClass()); } } public Value evaluate(Var var, BindingSet bindings) throws ValueExprEvaluationException, QueryEvaluationException { Value value = var.getValue(); if (value == null) { value = bindings.getValue(var.getName()); } if (value == null) { throw new ValueExprEvaluationException(); } return value; } public Value evaluate(ValueConstant valueConstant, BindingSet bindings) throws ValueExprEvaluationException, QueryEvaluationException { return valueConstant.getValue(); } public Value evaluate(BNodeGenerator node, BindingSet bindings) throws ValueExprEvaluationException, QueryEvaluationException { return tripleSource.getValueFactory().createBNode(); } public Value evaluate(Bound node, BindingSet bindings) throws QueryEvaluationException { try { Value argValue = evaluate(node.getArg(), bindings); return BooleanLiteralImpl.valueOf(argValue != null); } catch (ValueExprEvaluationException e) { return BooleanLiteralImpl.FALSE; } } public Value evaluate(Str node, BindingSet bindings) throws ValueExprEvaluationException, QueryEvaluationException { Value argValue = evaluate(node.getArg(), bindings); if (argValue instanceof URI) { return tripleSource.getValueFactory().createLiteral(argValue.toString()); } else if (argValue instanceof Literal) { Literal literal = (Literal)argValue; if (QueryEvaluationUtil.isSimpleLiteral(literal)) { return literal; } else { return tripleSource.getValueFactory().createLiteral(literal.getLabel()); } } else { throw new ValueExprEvaluationException(); } } public Value evaluate(Label node, BindingSet bindings) throws ValueExprEvaluationException, QueryEvaluationException { // FIXME: deprecate Label in favour of Str(?) Value argValue = evaluate(node.getArg(), bindings); if (argValue instanceof Literal) { Literal literal = (Literal)argValue; if (QueryEvaluationUtil.isSimpleLiteral(literal)) { return literal; } else { return tripleSource.getValueFactory().createLiteral(literal.getLabel()); } } else { throw new ValueExprEvaluationException(); } } public Value evaluate(Lang node, BindingSet bindings) throws ValueExprEvaluationException, QueryEvaluationException { Value argValue = evaluate(node.getArg(), bindings); if (argValue instanceof Literal) { Literal literal = (Literal)argValue; String langTag = literal.getLanguage(); if (langTag == null) { langTag = ""; } return tripleSource.getValueFactory().createLiteral(langTag); } throw new ValueExprEvaluationException(); } public Value evaluate(Datatype node, BindingSet bindings) throws ValueExprEvaluationException, QueryEvaluationException { Value v = evaluate(node.getArg(), bindings); if (v instanceof Literal) { Literal literal = (Literal)v; if (literal.getDatatype() != null) { // literal with datatype return literal.getDatatype(); } else if (literal.getLanguage() == null) { // simple literal return XMLSchema.STRING; } } throw new ValueExprEvaluationException(); } public Value evaluate(Namespace node, BindingSet bindings) throws ValueExprEvaluationException, QueryEvaluationException { Value argValue = evaluate(node.getArg(), bindings); if (argValue instanceof URI) { URI uri = (URI)argValue; return tripleSource.getValueFactory().createURI(uri.getNamespace()); } else { throw new ValueExprEvaluationException(); } } public Value evaluate(LocalName node, BindingSet bindings) throws ValueExprEvaluationException, QueryEvaluationException { Value argValue = evaluate(node.getArg(), bindings); if (argValue instanceof URI) { URI uri = (URI)argValue; return tripleSource.getValueFactory().createLiteral(uri.getLocalName()); } else { throw new ValueExprEvaluationException(); } } /** * Determines whether the operand (a variable) contains a Resource. * * @return <tt>true</tt> if the operand contains a Resource, <tt>false</tt> * otherwise. */ public Value evaluate(IsResource node, BindingSet bindings) throws ValueExprEvaluationException, QueryEvaluationException { Value argValue = evaluate(node.getArg(), bindings); return BooleanLiteralImpl.valueOf(argValue instanceof Resource); } /** * Determines whether the operand (a variable) contains a URI. * * @return <tt>true</tt> if the operand contains a URI, <tt>false</tt> * otherwise. */ public Value evaluate(IsURI node, BindingSet bindings) throws ValueExprEvaluationException, QueryEvaluationException { Value argValue = evaluate(node.getArg(), bindings); return BooleanLiteralImpl.valueOf(argValue instanceof URI); } /** * Determines whether the operand (a variable) contains a BNode. * * @return <tt>true</tt> if the operand contains a BNode, <tt>false</tt> * otherwise. */ public Value evaluate(IsBNode node, BindingSet bindings) throws ValueExprEvaluationException, QueryEvaluationException { Value argValue = evaluate(node.getArg(), bindings); return BooleanLiteralImpl.valueOf(argValue instanceof BNode); } /** * Determines whether the operand (a variable) contains a Literal. * * @return <tt>true</tt> if the operand contains a Literal, <tt>false</tt> * otherwise. */ public Value evaluate(IsLiteral node, BindingSet bindings) throws ValueExprEvaluationException, QueryEvaluationException { Value argValue = evaluate(node.getArg(), bindings); return BooleanLiteralImpl.valueOf(argValue instanceof Literal); } /** * Determines whether the two operands match according to the * <code>regex</code> operator. * * @return <tt>true</tt> if the operands match according to the * <tt>regex</tt> operator, <tt>false</tt> otherwise. */ public Value evaluate(Regex node, BindingSet bindings) throws ValueExprEvaluationException, QueryEvaluationException { Value arg = evaluate(node.getArg(), bindings); Value parg = evaluate(node.getPatternArg(), bindings); Value farg = null; ValueExpr flagsArg = node.getFlagsArg(); if (flagsArg != null) { farg = evaluate(flagsArg, bindings); } if (QueryEvaluationUtil.isSimpleLiteral(arg) && QueryEvaluationUtil.isSimpleLiteral(parg) && (farg == null || QueryEvaluationUtil.isSimpleLiteral(farg))) { String text = ((Literal)arg).getLabel(); String ptn = ((Literal)parg).getLabel(); String flags = ""; if (farg != null) { flags = ((Literal)farg).getLabel(); } // TODO should this Pattern be cached? int f = 0; for (char c : flags.toCharArray()) { switch (c) { case 's': f |= Pattern.DOTALL; break; case 'm': f |= Pattern.MULTILINE; break; case 'i': f |= Pattern.CASE_INSENSITIVE; break; case 'x': f |= Pattern.COMMENTS; break; case 'd': f |= Pattern.UNIX_LINES; break; case 'u': f |= Pattern.UNICODE_CASE; break; default: throw new ValueExprEvaluationException(flags); } } Pattern pattern = Pattern.compile(ptn, f); boolean result = pattern.matcher(text).find(); return BooleanLiteralImpl.valueOf(result); } throw new ValueExprEvaluationException(); } public Value evaluate(LangMatches node, BindingSet bindings) throws ValueExprEvaluationException, QueryEvaluationException { Value langTagValue = evaluate(node.getLeftArg(), bindings); Value langRangeValue = evaluate(node.getRightArg(), bindings); if (QueryEvaluationUtil.isSimpleLiteral(langTagValue) && QueryEvaluationUtil.isSimpleLiteral(langRangeValue)) { String langTag = ((Literal)langTagValue).getLabel(); String langRange = ((Literal)langRangeValue).getLabel(); boolean result = false; if (langRange.equals("*")) { result = langTag.length() > 0; } else if (langTag.length() == langRange.length()) { result = langTag.equalsIgnoreCase(langRange); } else if (langTag.length() > langRange.length()) { // check if the range is a prefix of the tag String prefix = langTag.substring(0, langRange.length()); result = prefix.equalsIgnoreCase(langRange) && langTag.charAt(langRange.length()) == '-'; } return BooleanLiteralImpl.valueOf(result); } throw new ValueExprEvaluationException(); } /** * Determines whether the two operands match according to the * <code>like</code> operator. The operator is defined as a string * comparison with the possible use of an asterisk (*) at the end and/or the * start of the second operand to indicate substring matching. * * @return <tt>true</tt> if the operands match according to the * <tt>like</tt> operator, <tt>false</tt> otherwise. */ public Value evaluate(Like node, BindingSet bindings) throws ValueExprEvaluationException, QueryEvaluationException { Value val = evaluate(node.getArg(), bindings); String strVal = null; if (val instanceof URI) { strVal = ((URI)val).toString(); } else if (val instanceof Literal) { strVal = ((Literal)val).getLabel(); } if (strVal == null) { throw new ValueExprEvaluationException(); } if (!node.isCaseSensitive()) { // Convert strVal to lower case, just like the pattern has been done strVal = strVal.toLowerCase(); } int valIndex = 0; int prevPatternIndex = -1; int patternIndex = node.getOpPattern().indexOf('*'); if (patternIndex == -1) { // No wildcards return BooleanLiteralImpl.valueOf(node.getOpPattern().equals(strVal)); } String snippet; if (patternIndex > 0) { // Pattern does not start with a wildcard, first part must match snippet = node.getOpPattern().substring(0, patternIndex); if (!strVal.startsWith(snippet)) { return BooleanLiteralImpl.FALSE; } valIndex += snippet.length(); prevPatternIndex = patternIndex; patternIndex = node.getOpPattern().indexOf('*', patternIndex + 1); } while (patternIndex != -1) { // Get snippet between previous wildcard and this wildcard snippet = node.getOpPattern().substring(prevPatternIndex + 1, patternIndex); // Search for the snippet in the value valIndex = strVal.indexOf(snippet, valIndex); if (valIndex == -1) { return BooleanLiteralImpl.FALSE; } valIndex += snippet.length(); prevPatternIndex = patternIndex; patternIndex = node.getOpPattern().indexOf('*', patternIndex + 1); } // Part after last wildcard snippet = node.getOpPattern().substring(prevPatternIndex + 1); if (snippet.length() > 0) { // Pattern does not end with a wildcard. // Search last occurence of the snippet. valIndex = strVal.indexOf(snippet, valIndex); int i; while ((i = strVal.indexOf(snippet, valIndex + 1)) != -1) { // A later occurence was found. valIndex = i; } if (valIndex == -1) { return BooleanLiteralImpl.FALSE; } valIndex += snippet.length(); if (valIndex < strVal.length()) { // Some characters were not matched return BooleanLiteralImpl.FALSE; } } return BooleanLiteralImpl.TRUE; } /** * Evaluates a function. */ public Value evaluate(FunctionCall node, BindingSet bindings) throws ValueExprEvaluationException, QueryEvaluationException { Function function = FunctionRegistry.getInstance().get(node.getURI()); if (function == null) { throw new QueryEvaluationException("Unknown function '" + node.getURI() + "'"); } List<ValueExpr> args = node.getArgs(); Value[] argValues = new Value[args.size()]; for (int i = 0; i < args.size(); i++) { argValues[i] = evaluate(args.get(i), bindings); } return function.evaluate(tripleSource.getValueFactory(), argValues); } public Value evaluate(And node, BindingSet bindings) throws ValueExprEvaluationException, QueryEvaluationException { try { Value leftValue = evaluate(node.getLeftArg(), bindings); if (QueryEvaluationUtil.getEffectiveBooleanValue(leftValue) == false) { // Left argument evaluates to false, we don't need to look any // further return BooleanLiteralImpl.FALSE; } } catch (ValueExprEvaluationException e) { // Failed to evaluate the left argument. Result is 'false' when // the right argument evaluates to 'false', failure otherwise. Value rightValue = evaluate(node.getRightArg(), bindings); if (QueryEvaluationUtil.getEffectiveBooleanValue(rightValue) == false) { return BooleanLiteralImpl.FALSE; } else { throw new ValueExprEvaluationException(); } } // Left argument evaluated to 'true', result is determined // by the evaluation of the right argument. Value rightValue = evaluate(node.getRightArg(), bindings); return BooleanLiteralImpl.valueOf(QueryEvaluationUtil.getEffectiveBooleanValue(rightValue)); } public Value evaluate(Or node, BindingSet bindings) throws ValueExprEvaluationException, QueryEvaluationException { try { Value leftValue = evaluate(node.getLeftArg(), bindings); if (QueryEvaluationUtil.getEffectiveBooleanValue(leftValue) == true) { // Left argument evaluates to true, we don't need to look any // further return BooleanLiteralImpl.TRUE; } } catch (ValueExprEvaluationException e) { // Failed to evaluate the left argument. Result is 'true' when // the right argument evaluates to 'true', failure otherwise. Value rightValue = evaluate(node.getRightArg(), bindings); if (QueryEvaluationUtil.getEffectiveBooleanValue(rightValue) == true) { return BooleanLiteralImpl.TRUE; } else { throw new ValueExprEvaluationException(); } } // Left argument evaluated to 'false', result is determined // by the evaluation of the right argument. Value rightValue = evaluate(node.getRightArg(), bindings); return BooleanLiteralImpl.valueOf(QueryEvaluationUtil.getEffectiveBooleanValue(rightValue)); } public Value evaluate(Not node, BindingSet bindings) throws ValueExprEvaluationException, QueryEvaluationException { Value argValue = evaluate(node.getArg(), bindings); boolean argBoolean = QueryEvaluationUtil.getEffectiveBooleanValue(argValue); return BooleanLiteralImpl.valueOf(!argBoolean); } public Value evaluate(SameTerm node, BindingSet bindings) throws ValueExprEvaluationException, QueryEvaluationException { Value leftVal = evaluate(node.getLeftArg(), bindings); Value rightVal = evaluate(node.getRightArg(), bindings); return BooleanLiteralImpl.valueOf(leftVal != null && leftVal.equals(rightVal)); } public Value evaluate(Compare node, BindingSet bindings) throws ValueExprEvaluationException, QueryEvaluationException { Value leftVal = evaluate(node.getLeftArg(), bindings); Value rightVal = evaluate(node.getRightArg(), bindings); return BooleanLiteralImpl.valueOf(QueryEvaluationUtil.compare(leftVal, rightVal, node.getOperator())); } public Value evaluate(MathExpr node, BindingSet bindings) throws ValueExprEvaluationException, QueryEvaluationException { // Do the math Value leftVal = evaluate(node.getLeftArg(), bindings); Value rightVal = evaluate(node.getRightArg(), bindings); if (leftVal instanceof Literal && rightVal instanceof Literal) { return getValue((Literal)leftVal, (Literal)rightVal, node.getOperator()); } return null; } public Value evaluate(In node, BindingSet bindings) throws ValueExprEvaluationException, QueryEvaluationException { Value leftValue = evaluate(node.getArg(), bindings); // Result is false until a match has been found boolean result = false; // Use first binding name from tuple expr to compare values String bindingName = node.getSubQuery().getBindingNames().iterator().next(); CloseableIteration<BindingSet, QueryEvaluationException> iter = evaluate(node.getSubQuery(), bindings); try { while (result == false && iter.hasNext()) { BindingSet bindingSet = iter.next(); Value rightValue = bindingSet.getValue(bindingName); result = leftValue == null && rightValue == null || leftValue != null && leftValue.equals(rightValue); } } finally { iter.close(); } return BooleanLiteralImpl.valueOf(result); } public Value evaluate(CompareAny node, BindingSet bindings) throws ValueExprEvaluationException, QueryEvaluationException { Value leftValue = evaluate(node.getArg(), bindings); // Result is false until a match has been found boolean result = false; // Use first binding name from tuple expr to compare values String bindingName = node.getSubQuery().getBindingNames().iterator().next(); CloseableIteration<BindingSet, QueryEvaluationException> iter = evaluate(node.getSubQuery(), bindings); try { while (result == false && iter.hasNext()) { BindingSet bindingSet = iter.next(); Value rightValue = bindingSet.getValue(bindingName); try { result = QueryEvaluationUtil.compare(leftValue, rightValue, node.getOperator()); } catch (ValueExprEvaluationException e) { // ignore, maybe next value will match } } } finally { iter.close(); } return BooleanLiteralImpl.valueOf(result); } public Value evaluate(CompareAll node, BindingSet bindings) throws ValueExprEvaluationException, QueryEvaluationException { Value leftValue = evaluate(node.getArg(), bindings); // Result is true until a mismatch has been found boolean result = true; // Use first binding name from tuple expr to compare values String bindingName = node.getSubQuery().getBindingNames().iterator().next(); CloseableIteration<BindingSet, QueryEvaluationException> iter = evaluate(node.getSubQuery(), bindings); try { while (result == true && iter.hasNext()) { BindingSet bindingSet = iter.next(); Value rightValue = bindingSet.getValue(bindingName); try { result = QueryEvaluationUtil.compare(leftValue, rightValue, node.getOperator()); } catch (ValueExprEvaluationException e) { // Exception thrown by ValueCompare.isTrue(...) result = false; } } } finally { iter.close(); } return BooleanLiteralImpl.valueOf(result); } public Value evaluate(Exists node, BindingSet bindings) throws ValueExprEvaluationException, QueryEvaluationException { CloseableIteration<BindingSet, QueryEvaluationException> iter = evaluate(node.getSubQuery(), bindings); try { return BooleanLiteralImpl.valueOf(iter.hasNext()); } finally { iter.close(); } } public boolean isTrue(ValueExpr expr, BindingSet bindings) throws QueryEvaluationException { try { Value value = evaluate(expr, bindings); return QueryEvaluationUtil.getEffectiveBooleanValue(value); } catch (ValueExprEvaluationException e) { return false; } } private static Literal getValue(Literal leftLit, Literal rightLit, MathOp op) { URI leftDatatype = leftLit.getDatatype(); URI rightDatatype = rightLit.getDatatype(); // Only numeric value can be used in math expressions if (leftDatatype != null && rightDatatype != null && XMLDatatypeUtil.isNumericDatatype(leftDatatype) && XMLDatatypeUtil.isNumericDatatype(rightDatatype)) { // Determine most specific datatype that the arguments have in common, // choosing from xsd:integer, xsd:decimal, xsd:float and xsd:double as // per the SPARQL/XPATH spec URI commonDatatype; if (leftDatatype.equals(XMLSchema.DOUBLE) || rightDatatype.equals(XMLSchema.DOUBLE)) { commonDatatype = XMLSchema.DOUBLE; } else if (leftDatatype.equals(XMLSchema.FLOAT) || rightDatatype.equals(XMLSchema.FLOAT)) { commonDatatype = XMLSchema.FLOAT; } else if (leftDatatype.equals(XMLSchema.DECIMAL) || rightDatatype.equals(XMLSchema.DECIMAL)) { commonDatatype = XMLSchema.DECIMAL; } else if (op == MathOp.DIVIDE) { // Result of integer divide is decimal and requires the arguments to // be handled as such, see for details: // http://www.w3.org/TR/xpath-functions/#func-numeric-divide commonDatatype = XMLSchema.DECIMAL; } else { commonDatatype = XMLSchema.INTEGER; } // Note: Java already handles cases like divide-by-zero appropriately // for floats and doubles, see: // http://www.particle.kth.se/~lindsey/JavaCourse/Book/Part1/Tech/Chapter02/floatingPt2.html try { if (commonDatatype.equals(XMLSchema.DOUBLE)) { double left = leftLit.doubleValue(); double right = rightLit.doubleValue(); switch (op) { case PLUS: return new NumericLiteralImpl(left + right); case MINUS: return new NumericLiteralImpl(left - right); case MULTIPLY: return new NumericLiteralImpl(left * right); case DIVIDE: return new NumericLiteralImpl(left / right); default: throw new IllegalArgumentException("Unknown operator: " + op); } } else if (commonDatatype.equals(XMLSchema.FLOAT)) { float left = leftLit.floatValue(); float right = rightLit.floatValue(); switch (op) { case PLUS: return new NumericLiteralImpl(left + right); case MINUS: return new NumericLiteralImpl(left - right); case MULTIPLY: return new NumericLiteralImpl(left * right); case DIVIDE: return new NumericLiteralImpl(left / right); default: throw new IllegalArgumentException("Unknown operator: " + op); } } else if (commonDatatype.equals(XMLSchema.DECIMAL)) { BigDecimal left = leftLit.decimalValue(); BigDecimal right = rightLit.decimalValue(); switch (op) { case PLUS: return new DecimalLiteralImpl(left.add(right)); case MINUS: return new DecimalLiteralImpl(left.subtract(right)); case MULTIPLY: return new DecimalLiteralImpl(left.multiply(right)); case DIVIDE: // Divide by zero handled through NumberFormatException return new DecimalLiteralImpl(left.divide(right, RoundingMode.HALF_UP)); default: throw new IllegalArgumentException("Unknown operator: " + op); } } else { // XMLSchema.INTEGER BigInteger left = leftLit.integerValue(); BigInteger right = rightLit.integerValue(); switch (op) { case PLUS: return new IntegerLiteralImpl(left.add(right)); case MINUS: return new IntegerLiteralImpl(left.subtract(right)); case MULTIPLY: return new IntegerLiteralImpl(left.multiply(right)); case DIVIDE: throw new RuntimeException("Integer divisions should be processed as decimal divisions"); default: throw new IllegalArgumentException("Unknown operator: " + op); } } } catch (NumberFormatException e) { return null; } catch (ArithmeticException e) { return null; } } return null; } }