/*
* 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 static org.jrdf.vocabulary.RDF.TYPE;
import static org.jrdf.vocabulary.RDFS.LITERAL;
import static org.mulgara.query.rdf.Mulgara.NODE_TYPE_GRAPH;
import static org.mulgara.query.rdf.Mulgara.DEFAULT_GRAPH;
import java.io.IOException;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.LinkedList;
import java.util.List;
import java.util.Set;
import org.mulgara.parser.Interpreter;
import org.mulgara.parser.MulgaraLexerException;
import org.mulgara.parser.MulgaraParserException;
import org.mulgara.query.AskQuery;
import org.mulgara.query.ConstantValue;
import org.mulgara.query.Constraint;
import org.mulgara.query.ConstraintConjunction;
import org.mulgara.query.ConstraintDisjunction;
import org.mulgara.query.ConstraintExpression;
import org.mulgara.query.ConstraintImpl;
import org.mulgara.query.ConstraintIs;
import org.mulgara.query.ConstructQuery;
import org.mulgara.query.GraphAnswer;
import org.mulgara.query.GraphExpression;
import org.mulgara.query.GraphResource;
import org.mulgara.query.GraphUnion;
import org.mulgara.query.Order;
import org.mulgara.query.Query;
import org.mulgara.query.SelectElement;
import org.mulgara.query.UnconstrainedAnswer;
import org.mulgara.query.Variable;
import org.mulgara.query.operation.Command;
import org.mulgara.query.rdf.LiteralImpl;
import org.mulgara.query.rdf.Mulgara;
import org.mulgara.query.rdf.URIReferenceImpl;
import org.mulgara.sparql.parser.ParseException;
import org.mulgara.sparql.parser.QueryStructure;
import org.mulgara.sparql.parser.QueryType;
import org.mulgara.sparql.parser.SparqlParser;
import org.mulgara.sparql.parser.cst.BlankNode;
import org.mulgara.sparql.parser.cst.Expression;
import org.mulgara.sparql.parser.cst.GroupGraphPattern;
import org.mulgara.sparql.parser.cst.IRIReference;
import org.mulgara.sparql.parser.cst.Node;
import org.mulgara.sparql.parser.cst.Ordering;
import org.mulgara.sparql.parser.cst.RDFLiteral;
import org.mulgara.sparql.parser.cst.Triple;
import org.mulgara.util.ServerInfoRef;
import org.mulgara.util.functional.C;
import org.mulgara.util.functional.Fn1;
/**
* Converts a parsed SPARQL query into a Command for execution.
*
* @created Apr 18, 2008
* @author Paula Gearon
* @copyright © 2008 <a href="http://www.fedora-commons.org/">Fedora Commons</a>
*/
public class SparqlInterpreter implements Interpreter {
/** The default graph to use if none has been set. */
private static final List<URI> INTERNAL_DEFAULT_GRAPH_URIS = Collections.singletonList(URI.create(DEFAULT_GRAPH));
/** The column variables used to build a graph. */
private static final Variable[] GRAPH_VARS = GraphAnswer.getGraphVariables();
/** A constraint for referring to an entire constructed graph. */
private static final Constraint GRAPH_CONSTRAINT = new ConstraintImpl(GRAPH_VARS[0], GRAPH_VARS[1], GRAPH_VARS[2]);
/** Reference for URI references. */
private static final URIReferenceImpl URI_REF = new URIReferenceImpl(URI.create(Mulgara.NAMESPACE + "UriReference"));
/** Reference for rdf:type. */
private static final URIReferenceImpl TYPE_REF = new URIReferenceImpl(TYPE);
/** Reference for rdfs:Literal. */
private static final URIReferenceImpl LITERAL_REF = new URIReferenceImpl(LITERAL);
/** Reference for the graph of sys:type. */
private static final URIReferenceImpl NODE_TYPE_GRAPH_REF = new URIReferenceImpl(URI.create(NODE_TYPE_GRAPH));
/** A constraint for limiting the graph predicate variable to literals. */
private static final ConstraintImpl LITERAL_ONLY_CONSTRAINT = new ConstraintImpl(GRAPH_VARS[2], TYPE_REF, LITERAL_REF, NODE_TYPE_GRAPH_REF);
/** A constraint for limiting the graph predicate variable to URIs. */
private static final ConstraintImpl URI_ONLY_CONSTRAINT = new ConstraintImpl(GRAPH_VARS[2], TYPE_REF, URI_REF, NODE_TYPE_GRAPH_REF);
/** The default graphs to use as set by the protocol. */
private List<IRIReference> defaultGraphIris = Collections.emptyList();
/** The named graphs to use as set by the protocol. */
private List<IRIReference> namedGraphIris = Collections.emptyList();
/**
* Sets the graphs to use in parsed queries, overriding what is found in the query.
* @param graphUris The graph URIs to use as the default graphs. A <code>null</code> value
* is treated the same as an empty list, and is used to unset this override.
*/
public SparqlInterpreter setDefaultGraphUris(List<URI> graphUris) {
defaultGraphIris = toIRIs(graphUris);
return this;
}
/**
* Sets the single default graph to use in parsed queries, overriding what is found in the query.
* @param graphUri The graph URI to use as the default graph, or <code>null</code> if the
* override graph is to be unset.
*/
public SparqlInterpreter setDefaultGraphUri(URI graphUri) {
if (graphUri == null) defaultGraphIris = Collections.emptyList();
else defaultGraphIris = Collections.singletonList(new IRIReference(graphUri));
return this;
}
/**
* Sets the single default graph to use in parsed queries.
* @param graph The graph URI to use as the default graph, or <code>null</code> if the
* default graph is to be unset.
* @throws URISyntaxException The graph was not a valid URI.
*/
public SparqlInterpreter setDefaultGraphUri(String graph) throws URISyntaxException {
if (graph == null || graph.trim().length() == 0) return setDefaultGraphUri((URI)null);
return setDefaultGraphUri(new URI(graph));
}
/**
* Sets the graphs to use as named graphs in parsed queries, overriding what is found in the query.
* @param graphUris The graph URIs to use as the named graphs. A <code>null</code> value
* is treated the same as an empty list, and is used to unset this override.
*/
public SparqlInterpreter setNamedGraphUris(List<URI> graphUris) {
namedGraphIris = toIRIs(graphUris);
return this;
}
/**
* Gets the default graph to use when none has been parsed from the query.
* @return The graph that parsed queries will default to when no FROM graph is supplied.
*/
public List<URI> getDefaultGraphUris() {
return defaultGraphIris.isEmpty() ? INTERNAL_DEFAULT_GRAPH_URIS : toURIs(defaultGraphIris);
}
/**
* Gets the default graph to use when none has been parsed from the query.
* @return The graph that parsed queries will default to when no FROM graph is supplied.
*/
public List<IRIReference> getDefaultGraphIris() {
return defaultGraphIris.isEmpty() ? toIRIs(INTERNAL_DEFAULT_GRAPH_URIS) : defaultGraphIris;
}
/**
* Gets the default graph to use when none has been parsed from the query.
* @return The graph that parsed queries will default to when no FROM graph is supplied.
*/
public List<IRIReference> getNamedGraphIris() {
return namedGraphIris;
}
/**
* @see org.mulgara.parser.Interpreter#parseCommand(java.lang.String)
* The only commands that SPARQL current handles are queries.
*/
public Command parseCommand(String command) throws MulgaraParserException, MulgaraLexerException, IllegalArgumentException, IOException {
return parseQuery(command);
}
/**
* @see org.mulgara.parser.Interpreter#parseCommands(java.lang.String)
* Since SPARQL has no separator character, there can only be one command per string.
*/
public List<Command> parseCommands(String command) throws MulgaraParserException, MulgaraLexerException, IOException, IllegalArgumentException {
return Collections.singletonList(parseCommand(command));
}
/**
* @see org.mulgara.parser.Interpreter#parseQuery(java.lang.String)
*/
public Query parseQuery(String queryString) throws IOException, MulgaraLexerException, MulgaraParserException {
QueryStructure struct;
try {
struct = SparqlParser.parse(queryString);
} catch (ParseException pe) {
throw new MulgaraParserException(pe);
}
Query result = null;
switch (struct.getType()) {
case select:
result = buildSelectQuery(struct);
break;
case construct:
result = buildConstructQuery(struct);
break;
case describe:
result = buildDescribeQuery(struct);
break;
case ask:
result = buildAskQuery(struct);
break;
default:
throw new MulgaraParserException("Unknown query type: " + struct.getType().name());
}
result.setText(queryString);
return result;
}
/**
* Respond to an unhandled query type.
* @param struct The structure representing the query
* @return Nothing. An exception is always thrown.
* @throws UnsupportedOperationException An exception explaining that this query type is not handled.
*/
@SuppressWarnings("unused")
private Query unhandledType(QueryStructure struct) throws UnsupportedOperationException {
throw new UnsupportedOperationException("Query type not yet supported: " + struct.getType().name());
}
/**
* Converts the elements of a {@link QueryStructure} into a Mulgara {@link Query}.
* @param queryStruct The structure to analyze and convert.
* @return A new query that can be run as a {@link org.mulgara.query.operation.Command} on a connection.
* @throws MulgaraParserException If the query structure contains elements that are not supported by Mulgara.
*/
Query buildSelectQuery(QueryStructure queryStruct) throws MulgaraParserException {
List<? extends SelectElement> selection = getSelection(queryStruct);
GraphExpression defaultGraphs = getFrom(queryStruct);
ConstraintExpression whereClause = getWhere(queryStruct);
if (whereClause == null) throw new MulgaraParserException("SELECT query must have a WHERE clause");
List<Order> orderBy = getOrdering(queryStruct);
Integer limit = getLimit(queryStruct);
int offset = queryStruct.getOffset();
boolean distinct = queryStruct.isDistinct();
// null having, unconstrained answer
return new Query(selection, defaultGraphs, whereClause, null, orderBy, limit, offset, distinct, new UnconstrainedAnswer());
}
/**
* Converts the elements of a {@link QueryStructure} into a Mulgara {@link AskQuery}.
* @param queryStruct The structure to analyze and convert.
* @return A new query that can be run as a {@link org.mulgara.query.operation.Command} on a connection.
* @throws MulgaraParserException If the query structure contains elements that are not supported by Mulgara.
*/
AskQuery buildAskQuery(QueryStructure queryStruct) throws MulgaraParserException {
List<Variable> selection = new ArrayList<Variable>();
Collection<org.mulgara.sparql.parser.cst.Variable> allVars = queryStruct.getAllVariables();
for (org.mulgara.sparql.parser.cst.Variable v: allVars) selection.add(new Variable(v.getName()));
GraphExpression defaultGraphs = getFrom(queryStruct);
ConstraintExpression whereClause = getWhere(queryStruct);
if (whereClause == null) throw new MulgaraParserException("ASK query must have a WHERE clause");
return new AskQuery(selection, defaultGraphs, whereClause);
}
/**
* Converts the elements of a {@link QueryStructure} into a Mulgara {@link ConstructQuery}.
* @param queryStruct The structure to analyze and convert.
* @return A new query that can be run as a {@link org.mulgara.query.operation.Command} on a connection.
* @throws MulgaraParserException If the query structure contains elements that are not supported by Mulgara.
*/
ConstructQuery buildConstructQuery(QueryStructure queryStruct) throws MulgaraParserException {
List<? extends SelectElement> selection = getConstructTemplate(queryStruct);
if (selection.size() % 3 != 0) {
throw new MulgaraParserException("CONSTRUCT queries require a multiple of 3 nodes in the template.");
}
GraphExpression defaultGraphs = getFrom(queryStruct);
ConstraintExpression whereClause = getWhere(queryStruct);
if (whereClause == null) throw new MulgaraParserException("CONSTRUCT query must have a WHERE clause");
List<Order> orderBy = getOrdering(queryStruct);
Integer limit = getLimit(queryStruct);
int offset = queryStruct.getOffset();
// null having, unconstrained answer
return new ConstructQuery(selection, defaultGraphs, whereClause, orderBy, limit, offset);
}
/**
* Converts the elements of a {@link QueryStructure} into a Mulgara {@link ConstructQuery}
* appropriate for a DESCRIBE query.
* @param queryStruct The structure to analyze and convert.
* @return A new query that can be run as a {@link org.mulgara.query.operation.Command} on a connection.
* @throws MulgaraParserException If the query structure contains elements that are not supported by Mulgara.
*/
ConstructQuery buildDescribeQuery(QueryStructure queryStruct) throws MulgaraParserException {
List<? extends SelectElement> described = getSelection(queryStruct);
ConstraintExpression whereClause = distributeIntoWhereClause(described, getWhere(queryStruct));
whereClause = constraintToNonBlank(whereClause);
GraphExpression defaultGraphs = getFrom(queryStruct);
// Ignore the order since its behavior is unspecified for DESCRIBE.
//List<Order> orderBy = getOrdering(queryStruct);
List<Order> orderBy = new ArrayList<Order>(0);
Integer limit = getLimit(queryStruct);
int offset = queryStruct.getOffset();
return new ConstructQuery(graphVariables(), defaultGraphs, whereClause, orderBy, limit, offset);
}
/**
* Extract the requested variables from this query into a list.
* @param queryStruct The query to get the selected variables from.
* @return A new list containing Mulgara {@link Variable}s.
* @throws MulgaraParserException If and selected elements are not variables.
*/
List<? extends SelectElement> getSelection(QueryStructure queryStruct) throws MulgaraParserException {
List<SelectElement> result = new ArrayList<SelectElement>();
if (queryStruct.isSelectAll()) {
Collection<org.mulgara.sparql.parser.cst.Variable> allVars = queryStruct.getAllVariables();
for (org.mulgara.sparql.parser.cst.Variable v: allVars) result.add(new Variable(v.getName()));
} else {
ConstantVarFactory constantVars = new ConstantVarFactory();
List<? extends Node> selection = queryStruct.getSelection();
for (Node n: selection) {
if (n instanceof org.mulgara.sparql.parser.cst.Variable) {
org.mulgara.sparql.parser.cst.Variable cv = (org.mulgara.sparql.parser.cst.Variable)n;
result.add(new Variable(cv.getName()));
} else if (n instanceof IRIReference) {
// SPARQL SELECT only permits variables; DESCRIBE permits IRI's
if (queryStruct.getType() == QueryType.select) throw new MulgaraParserException("Unexpected non-variable in the SELECT clause");
result.add(new ConstantValue(constantVars.newVar(), new URIReferenceImpl(((IRIReference)n).getUri(), false)));
} else {
throw new MulgaraParserException("Unexpected select expression");
}
}
}
return result;
}
/**
* Extract the requested elements from this construction template into a List.
* @param queryStruct The query to get the selected variables from.
* @return A new list containing Mulgara {@link Variable}s.
* @throws MulgaraParserException If and selected elements are not variables.
*/
@SuppressWarnings("unchecked")
List<? extends SelectElement> getConstructTemplate(QueryStructure queryStruct) throws MulgaraParserException {
List<SelectElement> result = new ArrayList<SelectElement>();
List<Triple> template = (List<Triple>)queryStruct.getConstructTemplate().getElements();
ConstantVarFactory factory = new ConstantVarFactory();
for (Triple triple: template) {
result.add(convertForTemplate(triple.getSubject(), factory));
result.add(convertForTemplate(triple.getPredicate(), factory));
result.add(convertForTemplate(triple.getObject(), factory));
}
return result;
}
/**
* Converts a node to a "selectable" element for use in a CONSTRUCT template.
* @param node The query node to convert.
* @param constantVars A variable factory for labelling constants.
* @return A new element that can be used in a CONSTRUCT template.
* @throws MulgaraParserException The template element was not valid.
*/
private SelectElement convertForTemplate(Node node, ConstantVarFactory constantVars) throws MulgaraParserException {
// variables are simply converted
if (node instanceof org.mulgara.sparql.parser.cst.Variable) {
return new Variable(((org.mulgara.sparql.parser.cst.Variable)node).getName());
}
// blank nodes are converted to unused variables
if (node instanceof BlankNode) {
// blank node labels are not used elsewhere as variables as they are not parsed as valid variables
return new Variable(((BlankNode)node).getLabel());
}
// RDFLiterals are converted to a literal, and wrapped in a ConstantValue
if (node instanceof RDFLiteral) {
RDFLiteral rl = (RDFLiteral)node;
LiteralImpl literal;
if (rl.isLanguageCoded()) literal = new LiteralImpl(rl.getValue(), rl.getLanguage());
else if (rl.isTyped()) literal = new LiteralImpl(rl.getValue(), rl.getDatatype().getUri());
else literal = new LiteralImpl(rl.getValue());
return new ConstantValue(constantVars.newVar(), literal);
}
// IRIReferences are converted to a URI and wrapped in a ConstantValue
if (node instanceof IRIReference) {
return new ConstantValue(constantVars.newVar(), new URIReferenceImpl(((IRIReference)node).getUri(), false));
}
// nothing else is valid. We probably have a compound expression.
throw new MulgaraParserException("Unexpected element in CONSTRUCT template: " + node);
}
/**
* Gets the graph expression ({@link GraphExpression}) the represents the FROM clause, or the default
* graph if none was provided.
* @param queryStruct The structure to query for the FROM clause.
* @return A GraphExpression containing all the required graphs as a union. TODO: this should be a merge.
*/
GraphExpression getFrom(QueryStructure queryStruct) {
// get the overriding defaults
List<IRIReference> graphs = defaultGraphIris;
if (graphs == null || graphs.isEmpty()) {
// no override set, so get the data from the query
graphs = queryStruct.getDefaultFroms();
// nothing in the query, so fall back to the system default
if (graphs == null || graphs.isEmpty()) graphs = getSystemDefaultGraph();
}
// accumulate the graphs as a union
return graphUnion(graphs);
}
/**
* Convert a list of IRIs into a model resource union of minimal depth. This recurses through construction
* of a tree of binary unions, rather than creating a linear linked list of unions.
* @param iris The list to convert.
* @return A GraphExpression which is a union of all the elements in the list,
* or a {@link GraphResource} if the list contains only one element.
*/
private GraphExpression graphUnion(List<IRIReference> iris) {
int listSize = iris.size();
// terminate on singleton lists
if (listSize == 1) return new GraphResource(iris.get(0).getUri());
// short circuit for 2 element lists - optimization
if (listSize == 2) return new GraphUnion(new GraphResource(iris.get(0).getUri()), new GraphResource(iris.get(1).getUri()));
// general case
return new GraphUnion(graphUnion(iris.subList(0, listSize / 2)), graphUnion(iris.subList(listSize / 2, listSize)));
}
/**
* Creates a list of the ordering to apply to the results. While SPARQL permits ordering by complex
* expressions, this is not supported.
* @param queryStruct The query structure.
* @return A list of {@link Order}, which are each ordered ascending or descending by variable.
* @throws MulgaraParserException If the ORDER BY expression was more complex than a simple variable.
*/
List<Order> getOrdering(QueryStructure queryStruct) throws MulgaraParserException {
List<Ordering> orderings = queryStruct.getOrderings();
List<Order> result = new ArrayList<Order>(orderings.size());
for (Ordering order: orderings) {
Expression v = order.getExpr();
if (!(v instanceof org.mulgara.sparql.parser.cst.Variable)) throw new MulgaraParserException("Unable to support arbitrarily complex ORDER BY clauses.");
org.mulgara.sparql.parser.cst.Variable var = (org.mulgara.sparql.parser.cst.Variable)v;
result.add(new Order(new Variable(var.getName()), order.isAscending()));
}
return result;
}
/**
* Get the limit described by the query.
* @param queryStruct The structure of the query.
* @return A {@link java.lang.Integer} containing the limit, or <code>null</code> if there is no limit.
*/
Integer getLimit(QueryStructure queryStruct) {
int limit = queryStruct.getLimit();
return limit == -1 ? null : limit;
}
/**
* Build a WHERE clause for a Mulgara query out of a SPARQL WHERE clause.
* @param queryStruct The SPARQL query structure to analyze for the WHERE clause.
* @return A Mulgara WHERE clause, as a {@link ConstraintExpression}.
* @throws MulgaraParserException The structure of the pattern was incorrect.
*/
ConstraintExpression getWhere(QueryStructure queryStruct) throws MulgaraParserException {
// get the basic pattern
GroupGraphPattern pattern = queryStruct.getWhereClause();
// short-circuit, since DESCRIBE may omit the WHERE clause
if (pattern == null) return null;
PatternMapper patternMapper = new PatternMapper(pattern);
ConstraintExpression result = patternMapper.mapToConstraints();
// apply the FROM NAMED expression
// TODO: This needs to become a Constraint that wraps LiteralTuples.
List<IRIReference> namedFroms = getNamedFroms(queryStruct);
if (!namedFroms.isEmpty()) result = addNamedFroms(result, namedFroms, patternMapper.getGraphVars());
// possible to ask for non-variables that were employed in GRAPH statements as a parser check.
// grammar-level optimizations
result = PatternTransformer.transform(result);
return result;
}
/**
* Get the NAMED FROM clauses to be used in this query.
* @param queryStruct the parse structure for the query.
* @return The NAMED FROM IRIs to be used, using the protocol values if these are set.
*/
List<IRIReference> getNamedFroms(QueryStructure queryStruct) {
if (namedGraphIris != null && !namedGraphIris.isEmpty()) return namedGraphIris;
return queryStruct.getNamedFroms();
}
/**
* Add in the FROM NAMED values to provide a binding list for each variable used in GRAPH statements.
* @param expr The total expression to be modified. This is the WHERE clause.
* @param graphs The list of graphs given in the FROM NAMED clauses.
* @param graphVars The variables that are used in GRAPH statements.
* @return A modified form of expr, with all the graph variables pre-bound.
*/
ConstraintExpression addNamedFroms(ConstraintExpression expr, List<IRIReference> graphs, Set<Variable> graphVars) {
List<ConstraintExpression> params = new ArrayList<ConstraintExpression>(graphVars.size() + 1);
params.add(expr);
for (Variable var: graphVars) params.add(newListConstraint(var, graphs));
return new ConstraintConjunction(params);
}
/**
* Construct a constraint expression that binds a variable to a list of values.
* TODO: This needs to be represented by a new Constraint that gets converted to a LiteralTuples.
* @param var The variable to bind.
* @param bindingList The list of values to bind the variable to.
* @return A new {@link org.mulgara.query.Constraint} that represents the variable binding.
*/
ConstraintExpression newListConstraint(Variable var, List<IRIReference> bindingList) {
List<ConstraintExpression> isConstraints = new ArrayList<ConstraintExpression>(bindingList.size());
for (IRIReference iri: bindingList) {
// does this need a graph node that isn't variable?
isConstraints.add(new ConstraintIs(var, new URIReferenceImpl(iri.getUri(), false)));
}
return new ConstraintDisjunction(isConstraints);
}
/**
* Converts a WHERE clause into using "subject" instead of the variables from described.
* Then creates a union of this WHERE clause converted for each element in described.
* If the base WHERE clause is empty, then ($subject $predicate $object) is presumed.
* @param described The elements to distribute into the unioned WHERE clause.
* @param baseWhere The WHERE clause to convert and duplicate
* @return The UNIONed WHERE clauses with the variables adjusted.
*/
ConstraintExpression distributeIntoWhereClause(List<? extends SelectElement> described, ConstraintExpression baseWhere) throws MulgaraParserException {
if (described == null || described.size() == 0) throw new MulgaraParserException("DESCRIBE clause must have at least one entry");
baseWhere = (baseWhere == null) ? GRAPH_CONSTRAINT : new ConstraintConjunction(baseWhere, GRAPH_CONSTRAINT);
List<ConstraintExpression> constraints = new LinkedList<ConstraintExpression>();
for (SelectElement e: described) {
ConstraintExpression expr;
if (e instanceof ConstantValue) {
expr = new ConstraintConjunction(GRAPH_CONSTRAINT, new ConstraintIs(GRAPH_VARS[0], ((ConstantValue)e).getValue()));
} else if (e instanceof Variable) {
expr = mapVariable(baseWhere, (Variable)e, GRAPH_VARS[0]);
} else throw new MulgaraParserException("Illegal type in DESCRIBE clause: " + e);
constraints.add(expr);
}
assert constraints.size() == described.size();
return constraints.size() > 1 ? new ConstraintDisjunction(constraints) : constraints.get(0);
}
/**
* Maps a ConstraintExpression to a new ConstraintExpression with all instance of one
* {@link Variable} changed to another Variable.
* @param base The ConstraintExpression to map.
* @param from The Variable to find and replace.
* @param to The new Variable to replace <var>from</var> with.
* @return A new ConstraintExpression with all instances of <var>from</var> replaced
* with <var>to</var>.
* @throws MulgaraParserException There was an error with the transformation.
*/
ConstraintExpression mapVariable(ConstraintExpression base, Variable from, Variable to) throws MulgaraParserException {
VariableRenameTransformer tx = new VariableRenameTransformer(from, to);
try {
return tx.transform(base);
} catch (SymbolicTransformationException e) {
throw new MulgaraParserException("Unable to convert WHERE clause for use in query.", e);
}
}
ConstraintExpression constraintToNonBlank(ConstraintExpression expr) {
// TODO: Consider using ConstraintConjunction(expr, ConstraintDisjunction(LITERAL_ONLY, URI_ONLY) when disjunction performance is fixed.
ConstraintExpression literals = new ConstraintConjunction(expr, LITERAL_ONLY_CONSTRAINT);
ConstraintExpression uris = new ConstraintConjunction(expr, URI_ONLY_CONSTRAINT);
return new ConstraintDisjunction(literals, uris);
}
/**
* Get the default graph to use in this system.
* Without configuration, this value will be <code>sys:default</code>.
* @return A list of the graphs to be used by default when no graphs are specified.
*/
List<IRIReference> getSystemDefaultGraph() {
URI d = ServerInfoRef.getDefaultURI();
if (d != null) return Collections.singletonList(new IRIReference(d));
// can't find a configured default, use the hard-coded default
return toIRIs(INTERNAL_DEFAULT_GRAPH_URIS);
}
/**
* Get the column names used in a Graph.
* @return A list of Variables used when defining a graph.
*/
static List<? extends SelectElement> graphVariables() {
return Arrays.asList(GRAPH_VARS);
}
/**
* Converts a collection of URIs to a collection of IRIReferences. If IRIReference becomes
* more compliant then this may throw an exception.
* @param uris The URI collection. If <code>null</code> then convert to an empty list.
* @return A new List of IRIReferences.
*/
static List<IRIReference> toIRIs(Collection<URI> uris) {
if (uris == null) return Collections.emptyList();
return C.map(uris, new Fn1<URI,IRIReference>(){public IRIReference fn(URI u) {return new IRIReference(u);}});
}
/**
* Converts a collection of IRIReferences to a collections of URIs.
* @param iris The IRIReference collection. If <code>null</code> then convert to an empty list.
* @return A new List of URIs. These are extracted from the IRIs.
*/
static List<URI> toURIs(Collection<IRIReference> iris) {
if (iris == null) return Collections.emptyList();
return C.map(iris, new Fn1<IRIReference,URI>(){public URI fn(IRIReference i) {return i.getUri();}});
}
}