/* * 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.net.URI; import java.util.ArrayList; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; import org.mulgara.sparql.parser.cst.BlankNode; 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.EmptyGraphPattern; import org.mulgara.sparql.parser.cst.Expression; import org.mulgara.sparql.parser.cst.GraphPatternConjunction; import org.mulgara.sparql.parser.cst.GraphPatternDisjunction; import org.mulgara.sparql.parser.cst.GraphPatternOptional; import org.mulgara.sparql.parser.cst.GroupGraphPattern; import org.mulgara.sparql.parser.cst.IRIReference; import org.mulgara.sparql.parser.cst.IntegerLiteral; import org.mulgara.sparql.parser.cst.Modifier; import org.mulgara.sparql.parser.cst.Node; import org.mulgara.sparql.parser.cst.RDFLiteral; import org.mulgara.sparql.parser.cst.Triple; import org.mulgara.sparql.parser.cst.TripleList; import org.mulgara.sparql.parser.cst.VarAssign; import org.mulgara.parser.MulgaraParserException; import org.mulgara.query.Constraint; import org.mulgara.query.ConstraintAssignment; import org.mulgara.query.ConstraintConjunction; import org.mulgara.query.ConstraintDisjunction; import org.mulgara.query.ConstraintElement; import org.mulgara.query.ConstraintExpression; import org.mulgara.query.ConstraintFalse; import org.mulgara.query.ConstraintFilter; import org.mulgara.query.ConstraintImpl; import org.mulgara.query.ConstraintIn; import org.mulgara.query.ConstraintOptionalJoin; import org.mulgara.query.SingleTransitiveConstraint; import org.mulgara.query.Variable; import org.mulgara.query.rdf.LiteralImpl; import org.mulgara.query.rdf.URIReferenceImpl; import org.mulgara.query.rdf.XSD; /** * This object maps a {@link GroupGraphPattern} into a {@link ConstraintExpression}. * * @created Apr 21, 2008 * @author Paula Gearon * @copyright © 2008 <a href="http://www.fedora-commons.org/">Fedora Commons</a> */ public class PatternMapper { /** The pattern to start the mapping on. */ GroupGraphPattern startPattern; /** An accumulating list of variables that are used in GRAPH patterns. */ Set<Variable> graphVars = new HashSet<Variable>(); /** An accumulating list of URIs that are used in GRAPH patterns. */ Set<URI> graphUris = new HashSet<URI>(); /** * Create a new mapper for a given graph pattern. * @param pattern The graph pattern to be mapped. */ PatternMapper(GroupGraphPattern pattern) { startPattern = pattern; } /** * Get the set of variables that were employed as graphs in GRAPH patterns. * @return The set of variables used in GRAPH expressions. */ Set<Variable> getGraphVars() { return graphVars; } /** * Perform the mapping of the graph pattern and return the results as a {@link ConstraintExpression}. * @return The mapped constraint expression. */ ConstraintExpression mapToConstraints() throws MulgaraParserException { return mapPattern(startPattern); } /** * Converts a pattern to the matching constraint type. * @param pattern The pattern to convert. * @return The new ConstraintExpression which matches the pattern. */ private ConstraintExpression mapPattern(GroupGraphPattern pattern) throws MulgaraParserException { PatternToConstraintMapper<? extends GroupGraphPattern> cons = constructors.get(pattern.getClass()); if (cons == null) throw new UnsupportedOperationException("Unknown SPARQL pattern: " + pattern.getClass().getSimpleName()); ConstraintExpression result = cons.map(pattern, this); result = applyFilter(result, pattern.getFilter()); result = applyGraph(result, pattern.getGraph()); return result; } /** * Apply a FILTER to a constraint expression. * @param constraint The expression to be filtered. * @param filter The filter to be wrapped around this constraint. * @return The filtered version of the constraint. */ private ConstraintExpression applyFilter(ConstraintExpression constraint, Expression filter) throws MulgaraParserException { if (filter == null) return constraint; FilterMapper filterMapper = new FilterMapper(filter); return new ConstraintFilter(constraint, filterMapper.getFilter()); } /** * Apply the parameter of a GRAPH modifier to a constraint expression. * @param constraint The expression to be updated. * @param graph The parameter of the GRAPH expression that is to be propagated through the constraint. * @return The modified version of the constraint. */ private ConstraintExpression applyGraph(ConstraintExpression constraint, Expression graph) { if (graph == null) return constraint; // graph is a Variable or IRIReference if (graph instanceof org.mulgara.sparql.parser.cst.Variable) { org.mulgara.sparql.parser.cst.Variable v = (org.mulgara.sparql.parser.cst.Variable)graph; if (v.getName().equals("_from")); Variable var = new Variable(v.getName()); // remember this is variable to be bound to the FROM NAMED values graphVars.add(var); constraint = new ConstraintIn(constraint, var); } else if (graph instanceof IRIReference) { // store this reference as a value that should be in the FROM NAMED list URI ref = ((IRIReference)graph).getUri(); graphUris.add(ref); constraint = new ConstraintIn(constraint, new URIReferenceImpl(ref, false)); } else { throw new IllegalArgumentException("Illegal argument in a GRAPH expression: " + graph.getClass().getSimpleName()); } return constraint; } /** * A case analysis to convert simple types into {@link ConstraintElement}s. * @param n The {@link Node} to convert to a ConstraintElement. * @return A new constraint element that matches the node n. */ private static ConstraintElement convertElement(Node n) { if (n instanceof org.mulgara.sparql.parser.cst.Variable) { return new Variable(((org.mulgara.sparql.parser.cst.Variable)n).getName()); } if (n instanceof IRIReference) return new URIReferenceImpl(((IRIReference)n).getUri(), false); if (n instanceof BlankNode) return new Variable(((BlankNode)n).getLabel()); if (n instanceof RDFLiteral) { RDFLiteral lit = (RDFLiteral)n; if (lit.isTyped()) return new LiteralImpl(lit.getValue(), lit.getDatatype().getUri()); if (lit.isLanguageCoded()) return new LiteralImpl(lit.getValue(), lit.getLanguage()); return new LiteralImpl(lit.getValue()); } // decimal, double, boolean if (n instanceof DecimalLiteral) return new LiteralImpl(((IntegerLiteral)n).getValue().toString(), XSD.DECIMAL_URI); if (n instanceof DoubleLiteral) return new LiteralImpl(((IntegerLiteral)n).getValue().toString(), XSD.DOUBLE_URI); if (n instanceof BooleanLiteral) return new LiteralImpl(((IntegerLiteral)n).getValue().toString(), XSD.BOOLEAN_URI); // don't know what to make of this throw new UnsupportedOperationException("Unhandled data type in triple: " + n.getClass().getSimpleName()); } /** * Converts a Triple from the CST into a ConstraintImpl in the AST. * @param t The triple to convert. * @return The new constraint. */ private static ConstraintExpression newConstraintImpl(Triple t) { ConstraintElement s = convertElement(t.getSubject()); ConstraintElement p = convertElement(t.getPredicate()); Modifier m = t.getPredicateModifier(); // if the object it a literal, then it may need to be expanded into various equivalent types Node n = t.getObject(); if (n instanceof IntegerLiteral) { ConstraintElement[] numbers = createIntLiterals((IntegerLiteral)n); List<ConstraintExpression> options = new ArrayList<ConstraintExpression>(); for (ConstraintElement obj: numbers) { options.add(newConstraint(s, p, obj, m)); } return new ConstraintDisjunction(options); } ConstraintElement o = convertElement(n); return newConstraint(s, p, o, m); } private static ConstraintExpression newConstraint(ConstraintElement s, ConstraintElement p, ConstraintElement o, Modifier m) { Constraint c = new ConstraintImpl(s, p, o); if (m == Modifier.none) return c; boolean zeroStep = m == Modifier.star; return new ConstraintDisjunction(c, new SingleTransitiveConstraint(c, zeroStep)); } /** * Create an array of the literals represented by a number. * @param nrText The text of a number. * @return All the literals that this number can represent. */ private static ConstraintElement[] createIntLiterals(IntegerLiteral n) { List<ConstraintElement> elts = new ArrayList<ConstraintElement>(); String nrText = n.getValue().toString(); long nr = n.getValue().longValue(); elts.add(new LiteralImpl(nrText, XSD.DECIMAL_URI)); elts.add(new LiteralImpl(nrText, XSD.INTEGER_URI)); elts.add(new LiteralImpl(nrText, XSD.LONG_URI)); if (nr <= Integer.MAX_VALUE && nr >= Integer.MIN_VALUE) { elts.add(new LiteralImpl(nrText, XSD.INT_URI)); if (nr <= Short.MAX_VALUE && nr >= Short.MIN_VALUE) { elts.add(new LiteralImpl(nrText, XSD.SHORT_URI)); if (nr <= Byte.MAX_VALUE && nr >= Byte.MIN_VALUE) { elts.add(new LiteralImpl(nrText, XSD.BYTE_URI)); } } } if (nr < 0) { elts.add(new LiteralImpl(nrText, XSD.NON_POSITIVE_INTEGER_URI)); elts.add(new LiteralImpl(nrText, XSD.NEGATIVE_INTEGER_URI)); } else if (nr > 0) { elts.add(new LiteralImpl(nrText, XSD.NON_NEGATIVE_INTEGER_URI)); elts.add(new LiteralImpl(nrText, XSD.POSITIVE_INTEGER_URI)); elts.add(new LiteralImpl(nrText, XSD.UNSIGNED_LONG_URI)); if (nr <= UInt.MAX_VALUE) { elts.add(new LiteralImpl(nrText, XSD.UNSIGNED_INT_URI)); if (nr <= UShort.MAX_VALUE) { elts.add(new LiteralImpl(nrText, XSD.UNSIGNED_SHORT_URI)); if (nr <= UByte.MAX_VALUE) { elts.add(new LiteralImpl(nrText, XSD.UNSIGNED_BYTE_URI)); } } } } else { // nr == 0 elts.add(new LiteralImpl(nrText, XSD.NON_POSITIVE_INTEGER_URI)); elts.add(new LiteralImpl(nrText, XSD.NON_NEGATIVE_INTEGER_URI)); elts.add(new LiteralImpl(nrText, XSD.UNSIGNED_LONG_URI)); elts.add(new LiteralImpl(nrText, XSD.UNSIGNED_INT_URI)); elts.add(new LiteralImpl(nrText, XSD.UNSIGNED_SHORT_URI)); elts.add(new LiteralImpl(nrText, XSD.UNSIGNED_BYTE_URI)); } return elts.toArray(new ConstraintElement[elts.size()]); } /** A mapping of pattern types to constructors for the objects they map to. */ private static Map<Class<? extends GroupGraphPattern>,PatternToConstraintMapper<? extends GroupGraphPattern>> constructors = new HashMap<Class<? extends GroupGraphPattern>,PatternToConstraintMapper<? extends GroupGraphPattern>>(); /** * The class for the mapping of {@link GroupGraphPattern}s to {@link ConstraintExpression}s. * This class and extending classes are not static, as the classes will be call back into the * outer class to recurse down the CST. * The reason this is an abstract class instead of an interface is so map(T) can be set * to accept the general GroupGraphPattern and do the cast. This was not possible outside * of the context of the generic type T. */ private static abstract class PatternToConstraintMapper<T extends GroupGraphPattern> { /** An entry point for the map operation. This method handles casting to be compatible with the generic template. */ @SuppressWarnings("unchecked") public ConstraintExpression map(GroupGraphPattern pattern, PatternMapper mapper) throws MulgaraParserException { return typedMap((T)pattern, mapper); } /** * Convert a GroupGraphPattern to a ConstraintExpression for Mulgara. * @param pattern The pattern to convert. Should be specific to the mapper. * @return The constraint expression relevant to the mapper. */ abstract ConstraintExpression typedMap(T pattern, PatternMapper mapper) throws MulgaraParserException; /** Identify the class to be mapped by the extension. */ public abstract Class<T> getMapType(); } /** * Utility method to add a pattern mapper to the map, keyed on the class it maps. * @param mapper The mapper to add to the map. */ static void addToMap(PatternToConstraintMapper<? extends GroupGraphPattern> mapper) { constructors.put(mapper.getMapType(), mapper); } /** * Initialize the mapping of patterns to the constraint builders. * This is not static in order to avoid passing "this" through to the methods * on each of the implementing mapper classes. */ static { addToMap(new EmptyGraphPatternToConstraint()); addToMap(new GraphPatternConjunctionToConstraint()); addToMap(new GraphPatternDisjunctionToConstraint()); addToMap(new GraphPatternOptionalToConstraint()); addToMap(new VarAssignToConstraint()); addToMap(new TripleToConstraint()); addToMap(new TripleListToConstraint()); } /** Map the empty graph pattern to a constraint that always resolves to nothing. */ private static class EmptyGraphPatternToConstraint extends PatternToConstraintMapper<EmptyGraphPattern> { public Class<EmptyGraphPattern> getMapType() { return EmptyGraphPattern.class; } ConstraintExpression typedMap(EmptyGraphPattern pattern, PatternMapper mapper) { return ConstraintFalse.INSTANCE; } } /** Map the conjunctions to ConstraintConjunction. */ private static class GraphPatternConjunctionToConstraint extends PatternToConstraintMapper<GraphPatternConjunction> { public Class<GraphPatternConjunction> getMapType() { return GraphPatternConjunction.class; } ConstraintExpression typedMap(GraphPatternConjunction pattern, PatternMapper mapper) throws MulgaraParserException { List<GroupGraphPattern> list = pattern.getElements(); List<ConstraintExpression> newList = new ArrayList<ConstraintExpression>(list.size()); for (GroupGraphPattern p: list) { if (p instanceof EmptyGraphPattern) { if (p.getFilter() != null) throw new MulgaraParserException("Unexpected filter on empty pattern"); } else newList.add(mapper.mapPattern(p)); } if (newList.size() == 1) return newList.get(0); return new ConstraintConjunction(newList); } } /** Map the disjunctions to ConstraintConjunction. */ private static class GraphPatternDisjunctionToConstraint extends PatternToConstraintMapper<GraphPatternDisjunction> { public Class<GraphPatternDisjunction> getMapType() { return GraphPatternDisjunction.class; } ConstraintExpression typedMap(GraphPatternDisjunction pattern, PatternMapper mapper) throws MulgaraParserException { List<GroupGraphPattern> list = pattern.getElements(); List<ConstraintExpression> newList = new ArrayList<ConstraintExpression>(list.size()); for (GroupGraphPattern p: list) { if (p instanceof EmptyGraphPattern) { // if (p.getFilter() != null) throw new MulgaraParserException("Unexpected filter on empty pattern"); } else newList.add(mapper.mapPattern(p)); } if (newList.size() == 1) return newList.get(0); return new ConstraintDisjunction(newList); } } /** Map the optional patterns to ConstraintOptional. */ private static class GraphPatternOptionalToConstraint extends PatternToConstraintMapper<GraphPatternOptional> { public Class<GraphPatternOptional> getMapType() { return GraphPatternOptional.class; } ConstraintExpression typedMap(GraphPatternOptional pattern, PatternMapper mapper) throws MulgaraParserException { ConstraintExpression opt = mapper.mapPattern(pattern.getOptional()); if (opt instanceof ConstraintFilter) { ConstraintFilter f = (ConstraintFilter)opt; return new ConstraintOptionalJoin(mapper.mapPattern(pattern.getMain()), f.getUnfilteredConstraint(), f.getFilter()); } else { return new ConstraintOptionalJoin(mapper.mapPattern(pattern.getMain()), opt); } } } /** Map the assignment patterns to ConstraintAssignment. */ private static class VarAssignToConstraint extends PatternToConstraintMapper<VarAssign> { public Class<VarAssign> getMapType() { return VarAssign.class; } ConstraintExpression typedMap(VarAssign pattern, PatternMapper mapper) throws MulgaraParserException { ConstraintExpression main = mapper.mapPattern(pattern.getMain()); org.mulgara.query.Variable v = new org.mulgara.query.Variable(pattern.getVar().getName()); FilterMapper filterMapper = new FilterMapper(pattern.getExpression()); return new ConstraintAssignment(main, v, filterMapper.getFilter()); } } /** Map the triple patterns to ConstraintImpl. */ private static class TripleToConstraint extends PatternToConstraintMapper<Triple> { public Class<Triple> getMapType() { return Triple.class; } ConstraintExpression typedMap(Triple pattern, PatternMapper mapper) { return newConstraintImpl(pattern); } } /** Map the lists of triple patterns to ConstraintConjunctions on ConstraintImpl. */ private static class TripleListToConstraint extends PatternToConstraintMapper<TripleList> { public Class<TripleList> getMapType() { return TripleList.class; } @SuppressWarnings("unchecked") ConstraintExpression typedMap(TripleList pattern, PatternMapper mapper) { List<Triple> triples = (List<Triple>)pattern.getElements(); List<ConstraintExpression> constraints = new ArrayList<ConstraintExpression>(triples.size()); for (Triple t: triples) constraints.add(newConstraintImpl(t)); return new ConstraintConjunction(constraints); } } /** Describes the range of unsigned ints */ private static class UInt { public static final long MAX_VALUE = 0xFFFFFFFFL; } /** Describes the range of unsigned shorts */ private static class UShort { public static final long MAX_VALUE = 0xFFFFL; } /** Describes the range of unsigned bytes */ private static class UByte { public static final long MAX_VALUE = 0xFFL; } }