/* * Copyright 2008 Fedora Commons, Inc. * * 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 org.mulgara.sparql; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import org.mulgara.sparql.parser.cst.AndExpression; import org.mulgara.sparql.parser.cst.BicBound; import org.mulgara.sparql.parser.cst.BicDatatype; import org.mulgara.sparql.parser.cst.BicIsBlank; import org.mulgara.sparql.parser.cst.BicIsIri; import org.mulgara.sparql.parser.cst.BicIsLiteral; import org.mulgara.sparql.parser.cst.BicIsUri; import org.mulgara.sparql.parser.cst.BicLang; import org.mulgara.sparql.parser.cst.BicLangMatches; import org.mulgara.sparql.parser.cst.BicRegEx; import org.mulgara.sparql.parser.cst.BicSameTerm; import org.mulgara.sparql.parser.cst.BicStr; import org.mulgara.sparql.parser.cst.BooleanLiteral; import org.mulgara.sparql.parser.cst.DecimalLiteral; import org.mulgara.sparql.parser.cst.DoubleLiteral; import org.mulgara.sparql.parser.cst.Equals; import org.mulgara.sparql.parser.cst.Expression; import org.mulgara.sparql.parser.cst.FunctionCall; import org.mulgara.sparql.parser.cst.GreaterThan; import org.mulgara.sparql.parser.cst.GreaterThanEqual; import org.mulgara.sparql.parser.cst.IRIReference; import org.mulgara.sparql.parser.cst.IntegerLiteral; import org.mulgara.sparql.parser.cst.Divide; import org.mulgara.sparql.parser.cst.LessThan; import org.mulgara.sparql.parser.cst.LessThanEqual; import org.mulgara.sparql.parser.cst.LogicExpression; import org.mulgara.sparql.parser.cst.Minus; import org.mulgara.sparql.parser.cst.Multiply; import org.mulgara.sparql.parser.cst.Not; import org.mulgara.sparql.parser.cst.NotEquals; import org.mulgara.sparql.parser.cst.OrExpression; import org.mulgara.sparql.parser.cst.Plus; import org.mulgara.sparql.parser.cst.RDFLiteral; import org.mulgara.sparql.parser.cst.UnaryMinus; import org.mulgara.sparql.parser.cst.UnaryPlus; import org.mulgara.sparql.parser.cst.Variable; import org.mulgara.parser.MulgaraParserException; import org.mulgara.query.QueryException; import org.mulgara.query.filter.And; import org.mulgara.query.filter.BoundFn; import org.mulgara.query.filter.Filter; import org.mulgara.query.filter.GreaterThanEqualTo; import org.mulgara.query.filter.IsBlankFn; import org.mulgara.query.filter.IsIriFn; import org.mulgara.query.filter.IsLiteralFn; import org.mulgara.query.filter.IsUriFn; import org.mulgara.query.filter.LangMatches; import org.mulgara.query.filter.LessThanEqualTo; import org.mulgara.query.filter.Or; import org.mulgara.query.filter.RDFTerm; import org.mulgara.query.filter.RegexFn; import org.mulgara.query.filter.SameTerm; import org.mulgara.query.filter.arithmetic.AddOperation; import org.mulgara.query.filter.arithmetic.DivideOperation; import org.mulgara.query.filter.arithmetic.MinusOperation; import org.mulgara.query.filter.arithmetic.MultiplyOperation; import org.mulgara.query.filter.value.Bool; import org.mulgara.query.filter.value.ComparableExpression; import org.mulgara.query.filter.value.DataTypeFn; import org.mulgara.query.filter.value.ExternalFn; import org.mulgara.query.filter.value.IRI; import org.mulgara.query.filter.value.LangFn; import org.mulgara.query.filter.value.NumericExpression; import org.mulgara.query.filter.value.NumericLiteral; import org.mulgara.query.filter.value.SimpleLiteral; import org.mulgara.query.filter.value.StrFn; import org.mulgara.query.filter.value.TypedLiteral; import org.mulgara.query.filter.value.ValueLiteral; import org.mulgara.query.filter.value.Var; /** * This object maps a constraint from an {@link org.mulgara.sparql.parser.cst.Expression} into a * {@link org.mulgara.query.filter.Filter}. * * @created Apr 22, 2008 * @author Paula Gearon * @copyright © 2008 <a href="http://www.fedora-commons.org/">Fedora Commons</a> */ public class FilterMapper { /** Represents the CST Filter expression that is to be mapped. */ private Expression cstFilter; public FilterMapper(Expression cstFilter) { this.cstFilter = cstFilter; } /** * Get the Filter object described by the expression. * @return A new Filter for use in a query. * @throws MulgaraParserException If the mapping could not be performed due to structural errors in the CST. */ public Filter getFilter() throws MulgaraParserException { RDFTerm term = mapExpression(cstFilter); if (!(term instanceof Filter)) throw new MulgaraParserException("Bad structure in the FILTER"); return (Filter)term; } /** * The internal method for mapping a CST expression into an AST term. * @param expr The expression to be mapped. * @return A new AST term. * @throws MulgaraParserException If the mapping could not be performed due to structural errors in the CST. */ static private RDFTerm mapExpression(Expression expr) throws MulgaraParserException { ExpressionToTerm<? extends Expression> mapper = mappers.get(expr.getClass()); if (mapper == null) throw new UnsupportedOperationException("Unable to handle expression: \"" + expr.getImage() + "\" (" + expr.getClass().getSimpleName() + ")"); return mapper.map(expr); } /** * Converts a CST NumericExpression into an AST NumericExpression. * @param operand The CST numeric expression. * @return An AST numeric expression. * @throws MulgaraParserException If the value was not mapped to an AST numeric expression. */ static private NumericExpression mapNumber(Expression operand) throws MulgaraParserException { RDFTerm op = mapExpression(operand); if (!(op instanceof NumericExpression)) throw new MulgaraParserException("Non-numeric value in arithmetic operation: " + op.getClass().getSimpleName()); return (NumericExpression)op; } /** * Converts a list of CST NumericExpressions into a list of AST NumericExpressions. * @param operands The list of CST numeric expressions. * @return A list of AST numeric expressions. * @throws MulgaraParserException If one of the values was not mapped to an AST numeric expression. */ static private List<NumericExpression> mapNumbers(List<org.mulgara.sparql.parser.cst.NumericExpression> operands) throws MulgaraParserException { List<NumericExpression> numbers = new ArrayList<NumericExpression>(operands.size()); for (org.mulgara.sparql.parser.cst.NumericExpression e: operands) numbers.add(mapNumber(e)); return numbers; } /** * Converts a CST NumericExpression into an AST NumericExpression. * @param operand The CST numeric expression. * @return An AST numeric expression. * @throws MulgaraParserException If the value was not mapped to an AST numeric expression. */ static private Filter mapLogic(Expression operand) throws MulgaraParserException { RDFTerm op = mapExpression(operand); if (!(op instanceof Filter)) throw new MulgaraParserException("Value without an Effective Boolean Value in a logic expression: " + op.getClass().getSimpleName()); return (Filter)op; } /** * Converts a list of CST LogicExpressions into a list of AST Filters. * @param operands The list of CST logic expressions. * @return A list of AST filter expressions. * @throws MulgaraParserException If one of the values was not mapped to an AST expression with an EBV. */ static private Filter[] mapLogicListArr(List<LogicExpression> operands) throws MulgaraParserException { List<Filter> logicOps = new ArrayList<Filter>(operands.size()); for (LogicExpression e: operands) logicOps.add(mapLogic(e)); return logicOps.toArray(new Filter[logicOps.size()]); } /** * Converts a CST ComparableExpression into an AST ComparableExpression. * @param operand The CST comparable expression. * @return An AST comparable expression. * @throws MulgaraParserException If the value was not mapped to an AST numeric expression. */ static private ComparableExpression mapComparable(Expression operand) throws MulgaraParserException { RDFTerm op = mapExpression(operand); if (!(op instanceof ComparableExpression)) throw new MulgaraParserException("Non-comparable value in comparison operation: " + op.getClass().getSimpleName()); return (ComparableExpression)op; } /** * Converts a CST Expression into an AST value that resolves to a literal ({@link ValueLiteral}). * @param operand The CST expression. * @return An AST value expression. * @throws MulgaraParserException If the value was not mapped to an AST ValueLiteral. */ static private ValueLiteral mapValue(Expression operand) throws MulgaraParserException { if (operand == null) return null; RDFTerm op = mapExpression(operand); try { if (!op.isLiteral()) throw new MulgaraParserException("Non-literal resolution when a value is required from: " + op.getClass().getSimpleName()); } catch (Exception qe) { // this is a QueryException throw new MulgaraParserException("Unexpected error getting value type from: " + op.getClass().getSimpleName() + ". " + qe.getMessage()); } return (ValueLiteral)op; } ////////////////////////////////// // The internal mapping structures ////////////////////////////////// /** Defines an interface for mapping expressions into an equivalent Filter operation. */ private static interface ExpressionToTerm<T extends Expression> { /** * A main entry point for the mapper, which will do the casting into {@link #typedMap(Expression)}. * @param expr The expression to map to an RDFTerm. * @return A {@link RDFTerm} that is the equivalent to the given expression. */ public RDFTerm map(Expression expr) throws MulgaraParserException; /** * Convert an Expression to a RDFTerm for Mulgara. * @param pattern The Expression to convert. Should be specific to the mapper. * @return The RDFTerm relevant to the mapper. */ RDFTerm typedMap(T pattern) throws MulgaraParserException; /** Identify the class to be mapped by the extension. */ public Class<T> getMapType(); } /** * An abstract root for all the mappers, which provides a {@link #map(Expression)} implementation * that handles the casting to the generics type. */ private static abstract class AbstractExprToFilter<T extends Expression> implements ExpressionToTerm<T> { /** Call the {@link ExpressionToTerm#typedMap(Expression)} method with an appropriate cast for the parameter. */ @SuppressWarnings("unchecked") public RDFTerm map(Expression expr) throws MulgaraParserException { return typedMap((T)expr); } } /** A mapping of Expression types to constructors for the Filters they map to. */ private static Map<Class<? extends Expression>,ExpressionToTerm<? extends Expression>> mappers = new HashMap<Class<? extends Expression>,ExpressionToTerm<? extends Expression>>(); /** * A utility method to add ExpressionToFilter mappers to the {@link #mappers} map. * @param mapper The mapper to add. */ private static void addToMap(ExpressionToTerm<? extends Expression> mapper) { mappers.put(mapper.getMapType(), mapper); } // initialize the mappers static { addToMap(new BooleanMap()); addToMap(new IntegerMap()); addToMap(new DecimalMap()); addToMap(new DoubleMap()); addToMap(new VariableMap()); addToMap(new RDFLiteralMap()); addToMap(new IRIReferenceMap()); addToMap(new FunctionCallMap()); addToMap(new UnaryPlusMap()); addToMap(new UnaryMinusMap()); addToMap(new PlusMap()); addToMap(new MinusMap()); addToMap(new MultiplyMap()); addToMap(new DivideMap()); addToMap(new EqualsMap()); addToMap(new NotEqualsMap()); addToMap(new GreaterThanMap()); addToMap(new GreaterThanEqualMap()); addToMap(new LessThanMap()); addToMap(new LessThanEqualMap()); addToMap(new NotMap()); addToMap(new AndMap()); addToMap(new OrMap()); addToMap(new BoundFnMap()); addToMap(new DataTypeFnMap()); addToMap(new IsBlankFnMap()); addToMap(new IsIriFnMap()); addToMap(new IsLiteralFnMap()); addToMap(new IsUriFnMap()); addToMap(new LangFnMap()); addToMap(new LangMatchesFnMap()); addToMap(new RegexFnMap()); addToMap(new SameTermMap()); addToMap(new StrFnMap()); } private static class BooleanMap extends AbstractExprToFilter<BooleanLiteral> { public Class<BooleanLiteral> getMapType() { return BooleanLiteral.class; } public RDFTerm typedMap(BooleanLiteral expr) { return expr == BooleanLiteral.TRUE ? Bool.TRUE : Bool.FALSE; } } private static class IntegerMap extends AbstractExprToFilter<IntegerLiteral> { public Class<IntegerLiteral> getMapType() { return IntegerLiteral.class; } public RDFTerm typedMap(IntegerLiteral expr) { return new NumericLiteral(expr.getInteger()); } } private static class DecimalMap extends AbstractExprToFilter<DecimalLiteral> { public Class<DecimalLiteral> getMapType() { return DecimalLiteral.class; } public RDFTerm typedMap(DecimalLiteral expr) { return new NumericLiteral(expr.getValue()); } } private static class DoubleMap extends AbstractExprToFilter<DoubleLiteral> { public Class<DoubleLiteral> getMapType() { return DoubleLiteral.class; } public RDFTerm typedMap(DoubleLiteral expr) { return new NumericLiteral(expr.getDouble()); } } private static class VariableMap extends AbstractExprToFilter<Variable> { public Class<Variable> getMapType() { return Variable.class; } public RDFTerm typedMap(Variable expr) { return new Var(expr.getName()); } } private static class RDFLiteralMap extends AbstractExprToFilter<RDFLiteral> { public Class<RDFLiteral> getMapType() { return RDFLiteral.class; } public RDFTerm typedMap(RDFLiteral expr) throws MulgaraParserException { try { if (expr.isTyped()) return TypedLiteral.newLiteral(expr.getValue(), expr.getDatatype().getUri(), null); } catch (QueryException qe) { throw new MulgaraParserException(qe.getMessage()); } if (expr.isLanguageCoded()) return new SimpleLiteral(expr.getValue(), expr.getLanguage()); return new SimpleLiteral(expr.getValue()); } } private static class IRIReferenceMap extends AbstractExprToFilter<IRIReference> { public Class<IRIReference> getMapType() { return IRIReference.class; } public RDFTerm typedMap(IRIReference expr) { return new IRI(expr.getUri()); } } private static class FunctionCallMap extends AbstractExprToFilter<FunctionCall> { public Class<FunctionCall> getMapType() { return FunctionCall.class; } public RDFTerm typedMap(FunctionCall expr) throws MulgaraParserException { List<Expression> exprArgs = expr.getArgs(); RDFTerm[] operands = new RDFTerm[exprArgs.size()]; for (int i = 0; i < operands.length; i++) operands[i] = mapExpression(exprArgs.get(i)); IRIReference fnName = expr.getName(); return new ExternalFn(new IRI(fnName.getUri(), fnName.getQName()), operands); } } private static class UnaryPlusMap extends AbstractExprToFilter<UnaryPlus> { public Class<UnaryPlus> getMapType() { return UnaryPlus.class; } public RDFTerm typedMap(UnaryPlus expr) throws MulgaraParserException { return mapExpression(expr.getOperand()); } } private static class UnaryMinusMap extends AbstractExprToFilter<UnaryMinus> { public Class<UnaryMinus> getMapType() { return UnaryMinus.class; } public RDFTerm typedMap(UnaryMinus expr) throws MulgaraParserException { RDFTerm param = mapExpression(expr.getOperand()); if (!(param instanceof NumericLiteral)) throw new MulgaraParserException("Cannot negate a non-number: " + param.getClass().getSimpleName()); return new org.mulgara.query.filter.arithmetic.UnaryMinus((NumericLiteral)param); } } private static class PlusMap extends AbstractExprToFilter<Plus> { public Class<Plus> getMapType() { return Plus.class; } public RDFTerm typedMap(Plus expr) throws MulgaraParserException { return AddOperation.newAddOperation(mapNumbers(expr.getOperands())); } } private static class MinusMap extends AbstractExprToFilter<Minus> { public Class<Minus> getMapType() { return Minus.class; } public RDFTerm typedMap(Minus expr) throws MulgaraParserException { return MinusOperation.newMinusOperation(mapNumbers(expr.getOperands())); } } private static class MultiplyMap extends AbstractExprToFilter<Multiply> { public Class<Multiply> getMapType() { return Multiply.class; } public RDFTerm typedMap(Multiply expr) throws MulgaraParserException { return MultiplyOperation.newMultiplyOperation(mapNumbers(expr.getOperands())); } } private static class DivideMap extends AbstractExprToFilter<Divide> { public Class<Divide> getMapType() { return Divide.class; } public RDFTerm typedMap(Divide expr) throws MulgaraParserException { return DivideOperation.newDivideOperation(mapNumbers(expr.getOperands())); } } private static class EqualsMap extends AbstractExprToFilter<Equals> { public Class<Equals> getMapType() { return Equals.class; } public RDFTerm typedMap(Equals expr) throws MulgaraParserException { return new org.mulgara.query.filter.Equals(mapExpression(expr.getLhs()), mapExpression(expr.getRhs())); } } private static class NotEqualsMap extends AbstractExprToFilter<NotEquals> { public Class<NotEquals> getMapType() { return NotEquals.class; } public RDFTerm typedMap(NotEquals expr) throws MulgaraParserException { return new org.mulgara.query.filter.NotEquals(mapExpression(expr.getLhs()), mapExpression(expr.getRhs())); } } private static class GreaterThanMap extends AbstractExprToFilter<GreaterThan> { public Class<GreaterThan> getMapType() { return GreaterThan.class; } public RDFTerm typedMap(GreaterThan expr) throws MulgaraParserException { return new org.mulgara.query.filter.GreaterThan(mapComparable(expr.getLhs()), mapComparable(expr.getRhs())); } } private static class GreaterThanEqualMap extends AbstractExprToFilter<GreaterThanEqual> { public Class<GreaterThanEqual> getMapType() { return GreaterThanEqual.class; } public RDFTerm typedMap(GreaterThanEqual expr) throws MulgaraParserException { return new GreaterThanEqualTo(mapComparable(expr.getLhs()), mapComparable(expr.getRhs())); } } private static class LessThanMap extends AbstractExprToFilter<LessThan> { public Class<LessThan> getMapType() { return LessThan.class; } public RDFTerm typedMap(LessThan expr) throws MulgaraParserException { return new org.mulgara.query.filter.LessThan(mapComparable(expr.getLhs()), mapComparable(expr.getRhs())); } } private static class LessThanEqualMap extends AbstractExprToFilter<LessThanEqual> { public Class<LessThanEqual> getMapType() { return LessThanEqual.class; } public RDFTerm typedMap(LessThanEqual expr) throws MulgaraParserException { return new LessThanEqualTo(mapComparable(expr.getLhs()), mapComparable(expr.getRhs())); } } private static class NotMap extends AbstractExprToFilter<Not> { public Class<Not> getMapType() { return Not.class; } public RDFTerm typedMap(Not expr) throws MulgaraParserException { return new org.mulgara.query.filter.Not(mapLogic(expr.getOperand())); } } private static class AndMap extends AbstractExprToFilter<AndExpression> { public Class<AndExpression> getMapType() { return AndExpression.class; } public RDFTerm typedMap(AndExpression expr) throws MulgaraParserException { return new And(mapLogicListArr(expr.getOperands())); } } private static class OrMap extends AbstractExprToFilter<OrExpression> { public Class<OrExpression> getMapType() { return OrExpression.class; } public RDFTerm typedMap(OrExpression expr) throws MulgaraParserException { return new Or(mapLogicListArr(expr.getOperands())); } } private static class BoundFnMap extends AbstractExprToFilter<BicBound> { public Class<BicBound> getMapType() { return BicBound.class; } public RDFTerm typedMap(BicBound expr) throws MulgaraParserException { return new BoundFn((Var)mapExpression(expr.getOperand())); } } private static class DataTypeFnMap extends AbstractExprToFilter<BicDatatype> { public Class<BicDatatype> getMapType() { return BicDatatype.class; } public RDFTerm typedMap(BicDatatype expr) throws MulgaraParserException { return new DataTypeFn(mapExpression(expr.getOperand())); } } private static class IsBlankFnMap extends AbstractExprToFilter<BicIsBlank> { public Class<BicIsBlank> getMapType() { return BicIsBlank.class; } public RDFTerm typedMap(BicIsBlank expr) throws MulgaraParserException { return new IsBlankFn(mapExpression(expr.getOperand())); } } private static class IsIriFnMap extends AbstractExprToFilter<BicIsIri> { public Class<BicIsIri> getMapType() { return BicIsIri.class; } public RDFTerm typedMap(BicIsIri expr) throws MulgaraParserException { return new IsIriFn(mapExpression(expr.getOperand())); } } private static class IsLiteralFnMap extends AbstractExprToFilter<BicIsLiteral> { public Class<BicIsLiteral> getMapType() { return BicIsLiteral.class; } public RDFTerm typedMap(BicIsLiteral expr) throws MulgaraParserException { return new IsLiteralFn(mapExpression(expr.getOperand())); } } private static class IsUriFnMap extends AbstractExprToFilter<BicIsUri> { public Class<BicIsUri> getMapType() { return BicIsUri.class; } public RDFTerm typedMap(BicIsUri expr) throws MulgaraParserException { return new IsUriFn(mapExpression(expr.getOperand())); } } private static class LangFnMap extends AbstractExprToFilter<BicLang> { public Class<BicLang> getMapType() { return BicLang.class; } public RDFTerm typedMap(BicLang expr) throws MulgaraParserException { return new LangFn(mapExpression(expr.getOperand())); } } private static class LangMatchesFnMap extends AbstractExprToFilter<BicLangMatches> { public Class<BicLangMatches> getMapType() { return BicLangMatches.class; } public RDFTerm typedMap(BicLangMatches expr) throws MulgaraParserException { return new LangMatches(mapValue(expr.getFirstOperand()), mapValue(expr.getSecondOperand())); } } private static class RegexFnMap extends AbstractExprToFilter<BicRegEx> { public Class<BicRegEx> getMapType() { return BicRegEx.class; } public RDFTerm typedMap(BicRegEx expr) throws MulgaraParserException { return new RegexFn(mapValue(expr.getExpr()), mapValue(expr.getPattern()), mapValue(expr.getFlags())); } } private static class SameTermMap extends AbstractExprToFilter<BicSameTerm> { public Class<BicSameTerm> getMapType() { return BicSameTerm.class; } public RDFTerm typedMap(BicSameTerm expr) throws MulgaraParserException { return new SameTerm(mapExpression(expr.getFirstOperand()), mapExpression(expr.getSecondOperand())); } } private static class StrFnMap extends AbstractExprToFilter<BicStr> { public Class<BicStr> getMapType() { return BicStr.class; } public RDFTerm typedMap(BicStr expr) throws MulgaraParserException { return new StrFn(mapExpression(expr.getOperand())); } } }