/* * 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); } }