/*
* Copyright 2008 Fedora Commons
*
* 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.parser;
import java.net.URI;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.mulgara.sparql.parser.cst.BlankNode;
import org.mulgara.sparql.parser.cst.Expression;
import org.mulgara.sparql.parser.cst.GraphList;
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.TripleList;
import org.mulgara.sparql.parser.cst.Variable;
/**
* <p>CST data for SPARQL query parsing.</p>
* <p>This class contains the entire structure for a query, and implements the main
* external interface for this library. All elements of a query are found in here after
* a query has been parsed.</p>
* <p>This class is not public, as only the {@link org.mulgara.sparql.parser.SparqlParser}
* class generated by JavaCC is expected to create instances it. Instances of this
* class are returned by calling the static {@link SparqlParser#parse(String)}
* method on {@link SparqlParser}.</p>
*
* @created January 15, 2008
* @author Paula Gearon
* @copyright © 2008 <a href="http://www.fedora-commons.org/">Fedora Commons</a>
* @licence <a href="{@docRoot}/../LICENCE.txt">Apache License, Version 2.0</a>
*/
class QueryStructureImpl implements QueryStructure {
/** The base namespace */
private URI base = URI.create("http://www.w3.org/1999/02/22-rdf-syntax-ns#");
/** The prefixes to use in the query */
private Map<String,URI> prefixes = new HashMap<String,URI>();
/** The type of query */
private QueryType type;
/** Distinct results in the query */
private boolean distinct = false;
/** Reduced results from the query */
private boolean reduced = false;
/** The query selection */
private List<Node> selection = new ArrayList<Node>();
/** Indicates if all variables should be selected */
private boolean selectAll = false;
/** A template used for building triples in a CONSTRUCT query */
private TripleList constructTemplate = null;
/** The graphs to get data from by default */
private List<IRIReference> defaultFroms = new ArrayList<IRIReference>();
/** The names graphs to get data from by use of the GRAPH keyword */
private List<IRIReference> namedFroms = new ArrayList<IRIReference>();
/** The WHERE clause */
private GroupGraphPattern whereClause = null;
/** Order info on the results */
private List<Ordering> orderings = new ArrayList<Ordering>();
/** Result offset. 0 means no offset. */
private int offset = 0;
/** Result limit. -1 means no limit. */
private int limit = -1;
/** Remembers all used variables by name */
private Map<String,Variable> variables = new LinkedHashMap<String,Variable>();
/**
* Blank node allocator. Blank nodes are like variables,
* but with generated names and they cannot be selected.
*/
private VarNameAllocator bNodeAllocator = new VarNameAllocator();
/** A RegEx pattern for separating out a namespace from a prefixed name. */
private static final Pattern pnamePattern = Pattern.compile("([^:]*):(.*)");
/**
* Creates a string representation of the prefix map. Used for debugging.
* @return A list of namespace/uri pairs.
*/
private String prologToString() {
StringBuffer s = new StringBuffer();
if (base != null) s.append("BASE <").append(base).append(">\n");
for (Map.Entry<String,URI> p: prefixes.entrySet()) s.append("PREFIX ").append(p.getKey()).append(": <").append(p.getValue()).append(">\n");
return s.toString();
}
///////////////////////////////////////////////////////////////
// Package scope methods. Used to build the structure.
///////////////////////////////////////////////////////////////
/**
* Sets the base namespace for the query.
* @param base The URI for the base namespace.
*/
void setBase(URI base) { this.base = base; }
/**
* Add a prefix to the list of prefix mappings.
* WARNING: Don't use a prefix with a common URL protocol, as this will result in
* unexpected substitutions!
* @param prefix The prefix to map
* @param namespace The namespace the prefix refers to
*/
void addPrefix(String prefix, URI namespace) { prefixes.put(prefix.substring(0, prefix.length() - 1), namespace); }
/**
* Set the type of query.
* @param type The type of this query:
*/
void setType(QueryType type) { this.type = type; }
/**
* Sets this query to have Distinct results
*/
void setDistinct() { distinct = true; }
/**
* Sets this query to have Reduced results
*/
void setReduced() { reduced = true; }
/**
* Set this query to select all variables
*/
void setSelectAll() {
// no variables should have been encountered yet
assert variables.isEmpty();
selectAll = true;
}
/**
* Adds a variable (or selectable expression) to be returned by the query.
* @param var The variable to add.
*/
void addSelection(Node var) { selection.add(var); }
/**
* Set the template for triples to be created in a Construct query.
* @param template A template in the form of a list of triples.
*/
void setConstructTemplate(TripleList template) {
assert type == QueryType.construct;
constructTemplate = template;
}
/**
* Adds a default graph to select from.
* @param g A graph to select from when not using a named graph.
*/
void addDefaultFrom(IRIReference g) { defaultFroms.add(g); }
/**
* Adds a graph to the list of named graphs, making it available to be resolved
* against on patterns with a named graph.
* @param g The graph to add to the named graphs list.
*/
void addNamedFrom(IRIReference g) { namedFroms.add(g); }
/**
* Sets the WHERE clause clause for this query.
* @param g The graph pattern to match against graphs to obtain results.
*/
void setWhereClause(GroupGraphPattern g) { whereClause = g; }
/**
* Appends an ordering of results, based on an expression and a direction.
* NOTE: while the specification allows for arbitrary expressions here, we do not
* expect to order by anything more complex than a variable.
* @param expr The expression (variable) to order by.
* @param ascending <code>true</code> if ordering is ascending, <code>false</code>
* if ordering is descending.
*/
void addOrdering(Expression expr, boolean ascending) { orderings.add(new Ordering(expr, ascending)); }
/**
* Sets the offset for the first returned result. Accepts a string, but this should be
* fine as the pattern matcher has already established that it is a valid number.
* @param o The numeric offset as a string. A value of "0" has no effect on the query.
*/
void setOffset(String o) { offset = Integer.parseInt(o); }
/**
* Set the upper limit to the number of returned results. Accepts a string, but this
* should be fine as the pattern matcher has already established that it is a valid
* number.
* @param l The numeric limit as a string. A value of "0" will give an empty result from
* the query, while a value of "-1" will allow all the results to be returned.
*/
void setLimit(String l) { limit = Integer.parseInt(l); }
/**
* Factory method for {@link org.mulgara.sparql.parser.cst.Variable}s, returning only
* one variable for each unique name. After a variable is created for a name, any
* requests for a variable of that name will return the existing Variable instead of
* creating a new one.
* @param name The name of the variable, with either '?' or '$' prepended.
* @return A new variable with the requested name, or an existing variable if one
* already existed with that name.
*/
Variable newVariable(String name) {
// remove the variable identifier: ? or $
name = name.substring(1);
// look for an existing variable
if (variables.containsKey(name)) return variables.get(name);
// create and remember a new variable
Variable v = new Variable(name);
variables.put(name, v);
// selectAll should have been set by now if it is going to be
if (selectAll) selection.add(v);
return v;
}
/**
* Allocate a new anonymous blank node.
* @return A blank node with a random unique label.
*/
BlankNode newBlankNode() {
return bNodeAllocator.allocate();
}
/**
* Create a new {@link org.mulgara.sparql.parser.cst.IRIReference}.
* Tests to see if the IRI starts with a known namespace and performs a
* substitution if it does. Note that this implementation only accepts URIs,
* and not the more complete IRI.
* @param r The string containing the image of the IRI
* @return A new {@link org.mulgara.sparql.parser.cst.IRIReference} for the string image.
* @throws ParseException The r parameter was not a syntactically valid URI.
*/
IRIReference newIRIRef(String r) throws ParseException {
return newPrefixedName(r);
}
/**
* Create a new IRI based on a prefixed name. Note that this implementation
* only accepts URIs, and not the more complete IRI.
* @param r The string containing the image of the prefixed name.
* @return A new {@link org.mulgara.sparql.parser.cst.IRIReference} for the string image.
* @throws ParseException The r parameter was not a syntactically valid URI.
*/
IRIReference newPrefixedName(String r) throws ParseException {
// Create an IRI directly if the string does not start with a prefix
Matcher m = pnamePattern.matcher(r);
if (!m.matches()) {
// either a normal IRI, or one with a BASE
return isRelative(r) ? new IRIReference(base, null, r) : new IRIReference(r, prefixes);
}
String prefix = m.group(1);
String localPart = m.group(2);
URI namespace = prefixes.get(prefix);
return namespace == null ? new IRIReference(r, prefixes) : new IRIReference(namespace, prefix, localPart);
}
/**
* Gets a new RDF list for the current graph pattern.
* @return A new list object for generating triples for an RDF Collection.
*/
GraphList newList() { return new GraphList(bNodeAllocator); }
/**
* Tests if the string for a URI is relative or absolute. The test is based on a scheme existing
* in the string, which in turn expects a : character to follow it. If there is no colon, then
* it is presumed to be relative. Otherwise, if there are special characters preceding the first
* colon these are presumed to not be in a scheme.
* @param u A string for a URI.
* @return <code>true</code> if the URI appears to be relative, <code>false</code> otherwise.
*/
boolean isRelative(String u) {
int colon = u.indexOf(':');
if (colon < 0) return true;
for (int c = 0; c < colon; c++) {
// if there a non-alphanum characters then this is not a scheme, so the URI is relative
if (!Character.isLetterOrDigit(u.charAt(c))) return true;
}
// found a (probably) valid scheme, so the URI is absolute
return false;
}
///////////////////////////////////////////////////////////////////////////////////
// Public interfaces for accessing this structure.
///////////////////////////////////////////////////////////////////////////////////
/**
* @see org.mulgara.sparql.parser.QueryStructure#getBase()
*/
public URI getBase() { return base; }
/**
* @see org.mulgara.sparql.parser.QueryStructure#getPrefixes()
*/
public Map<String,URI> getPrefixes() { return prefixes; }
/**
* @see org.mulgara.sparql.parser.QueryStructure#getType()
*/
public QueryType getType() { return type; }
/**
* @see org.mulgara.sparql.parser.QueryStructure#isDistinct()
*/
public boolean isDistinct() { return distinct; }
/**
* @see org.mulgara.sparql.parser.QueryStructure#isReduced()
*/
public boolean isReduced() { return reduced; }
/**
* @see org.mulgara.sparql.parser.QueryStructure#isSelectAll()
*/
public boolean isSelectAll() { return selectAll; }
/**
* @see org.mulgara.sparql.parser.QueryStructure#getSelection()
*/
public List<? extends Node> getSelection() { return selection; }
/**
* @see org.mulgara.sparql.parser.QueryStructure#getConstructTemplate()
*/
public TripleList getConstructTemplate() { return constructTemplate; }
/**
* @see org.mulgara.sparql.parser.QueryStructure#getDefaultFroms()
*/
public List<IRIReference> getDefaultFroms() { return defaultFroms; }
/**
* @see org.mulgara.sparql.parser.QueryStructure#getNamedFroms()
*/
public List<IRIReference> getNamedFroms() { return namedFroms; }
/**
* @see org.mulgara.sparql.parser.QueryStructure#getWhereClause()
*/
public GroupGraphPattern getWhereClause() { return whereClause; }
/**
* @see org.mulgara.sparql.parser.QueryStructure#getOrderings()
*/
public List<Ordering> getOrderings() { return orderings; }
/**
* @see org.mulgara.sparql.parser.QueryStructure#getOffset()
*/
public int getOffset() { return offset; }
/**
* @see org.mulgara.sparql.parser.QueryStructure#getLimit()
*/
public int getLimit() { return limit; }
/**
* @see org.mulgara.sparql.parser.QueryStructure#getAllVariables()
*/
public Collection<Variable> getAllVariables() { return variables.values(); }
/**
* @see org.mulgara.sparql.parser.QueryStructure#toString()
*/
public String toString() {
return prologToString() + type.toString(this);
}
}