/* * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF licenses this file * to you 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.apache.jena.arq.querybuilder; import java.util.HashMap; import java.util.Map; import org.apache.jena.arq.querybuilder.clauses.PrologClause; import org.apache.jena.arq.querybuilder.handlers.HandlerBlock; import org.apache.jena.arq.querybuilder.handlers.PrologHandler; import org.apache.jena.graph.FrontsNode ; import org.apache.jena.graph.Node ; import org.apache.jena.graph.NodeFactory ; import org.apache.jena.graph.impl.LiteralLabelFactory ; import org.apache.jena.query.Query ; import org.apache.jena.query.QueryParseException; import org.apache.jena.rdf.model.Resource ; import org.apache.jena.riot.RiotException; import org.apache.jena.riot.system.PrefixMapFactory; import org.apache.jena.shared.PrefixMapping; import org.apache.jena.sparql.ARQInternalErrorException ; import org.apache.jena.sparql.core.Var ; import org.apache.jena.sparql.expr.Expr; import org.apache.jena.sparql.expr.ExprVar ; import org.apache.jena.sparql.syntax.ElementGroup; import org.apache.jena.sparql.util.ExprUtils; import org.apache.jena.sparql.util.NodeFactoryExtra ; /** * Base class for all QueryBuilders. * * @param <T> * The derived class type. Used for return types. */ public abstract class AbstractQueryBuilder<T extends AbstractQueryBuilder<T>> implements Cloneable, PrologClause<T> { // the query this builder is building protected Query query; // a map of vars to nodes for replacement during build. private Map<Var, Node> values; /** * Make a Node from an object. * <ul> * <li>Will return Node.ANY if object is null.</li> * <li>Will return the enclosed Node from a FrontsNode</li> * <li>Will return the object if it is a Node.</li> * <li>Will call NodeFactoryExtra.parseNode() using the currently defined * prefixes if the object is a String</li> * <li>Will create a literal representation if the parseNode() fails or for * any other object type.</li> * </ul> * * Uses the internal query prefix mapping to resolve prefixes. * * @param o * The object to convert. (may be null) * @return The Node value. */ public Node makeNode(Object o) { return makeNode( o, query.getPrefixMapping() ); } /** * A convenience method to make an expression from a string. Evaluates the * expression with respect to the current query. * * @param expression The expression to parse. * @return the Expr object. * @throws QueryParseException on error. */ public Expr makeExpr(String expression) throws QueryParseException { return ExprUtils.parse(query, expression, true); } /** * A convenience method to quote a string. * @param q the string to quote. * * Will use single quotes if there are no single quotes in the string or if the * double quote is before the single quote in the string. * * Will use double quote otherwise. * * @return the quoted string. */ public String quote(String q) { int qt = q.indexOf('"'); int sqt = q.indexOf("'"); if (sqt == -1 || qt<sqt) { return String.format( "'%s'", q); } return String.format( "\"%s\"", q); } /** * Make a node from an object while using the associated prefix mapping. * <ul> * <li>Will return Node.ANY if object is null.</li> * <li>Will return the enclosed Node from a FrontsNode</li> * <li>Will return the object if it is a Node.</li> * <li>Will call NodeFactoryExtra.parseNode() using the currently defined * prefixes if the object is a String</li> * <li>Will create a literal representation if the parseNode() fails or for * any other object type.</li> * </ul> * @param o The object to convert (may be null). * @param pMapping The prefix mapping to use for prefix resolution. * @return The Node value. */ public static Node makeNode(Object o, PrefixMapping pMapping) { if (o == null) { return Node.ANY; } if (o instanceof FrontsNode) { return ((FrontsNode) o).asNode(); } if (o instanceof Node) { return (Node) o; } if (o instanceof String) { try { return NodeFactoryExtra.parseNode((String) o, PrefixMapFactory .createForInput(pMapping)); } catch (RiotException e) { // expected in some cases -- do nothing } } return NodeFactory.createLiteral(LiteralLabelFactory.createTypedLiteral(o)); } /** * Make a Var from an object. * <ul> * <li>Will return Var.ANON if object is null.</li> * <li>Will return null if the object is "*" or Node_RuleVariable.WILD</li> * <li>Will return the object if it is a Var</li> * <li>Will return resolve FrontsNode to Node and then resolve to Var</li> * <li>Will return resolve Node if the Node implements Node_Variable, * otherwise throws an NotAVariableException (instance of * ARQInternalErrorException)</li> * <li>Will return ?x if object is "?x"</li> * <li>Will return ?x if object is "x"</li> * <li>Will return the enclosed Var of a ExprVar</li> * <li>For all other objects will return the "?" prefixed to the toString() * value.</li> * </ul> * * @param o * The object to convert. * @return the Var value. * @throws ARQInternalErrorException */ public Var makeVar(Object o) throws ARQInternalErrorException { if (o == null) { return Var.ANON; } if (o instanceof Var) { return (Var) o; } Var retval = null; if (o instanceof FrontsNode) { retval = Var.alloc(((FrontsNode) o).asNode()); } else if (o instanceof Node) { retval = Var.alloc((Node) o); } else if (o instanceof ExprVar) { retval = Var.alloc((ExprVar) o); } else { retval = Var.alloc(Var.canonical(o.toString())); } if ("*".equals(Var.canonical(retval.toString()))) { return null; } return retval; } /** * Create a new query builder. */ protected AbstractQueryBuilder() { query = new Query(); values = new HashMap<Var, Node>(); } /** * Get the HandlerBlock for this query builder. * @return The associated handler block. */ public abstract HandlerBlock getHandlerBlock(); @Override public final PrologHandler getPrologHandler() { return getHandlerBlock().getPrologHandler(); } /** * Set a variable replacement. During build all instances of var in the * query will be replaced with value. If value is null the replacement is * cleared. * * @param var * The variable to replace * @param value * The value to replace it with or null to remove the * replacement. */ public void setVar(Var var, Node value) { if (value == null) { values.remove(var); } else { values.put(var, value); } } /** * Set a variable replacement. During build all instances of var in the * query will be replaced with value. If value is null the replacement is * cleared. * * See {@link #makeVar} for conversion of the var param. See * {@link #makeNode} for conversion of the value param. * * @param var * The variable to replace. * @param value * The value to replace it with or null to remove the * replacement. */ public void setVar(Object var, Object value) { if (value == null) { setVar(makeVar(var), null); } else { setVar(makeVar(var), makeNode(value)); } } @Override public T addPrefix(String pfx, Resource uri) { return addPrefix(pfx, uri.getURI()); } @Override public T addPrefix(String pfx, Node uri) { return addPrefix(pfx, uri.getURI()); } @SuppressWarnings("unchecked") @Override public T addPrefix(String pfx, String uri) { getPrologHandler().addPrefix(pfx, uri); return (T) this; } @SuppressWarnings("unchecked") @Override public T addPrefixes(Map<String, String> prefixes) { getPrologHandler().addPrefixes(prefixes); return (T) this; } @SuppressWarnings("unchecked") @Override public T setBase(String base) { getPrologHandler().setBase(base); return (T) this; } @SuppressWarnings("unchecked") @Override public T setBase(Object base) { setBase(makeNode(base).getURI()); return (T) this; } @Override public String toString() { return buildString(); } /** * Build the query as a string. * * @return the string representation of the query. */ public final String buildString() { return build().toString(); } /** * Build the query. Performs the var replacements as specified by * setVar(var,node) calls. * * @return The query. */ public final Query build() { Query q = new Query(); // set the query type switch (query.getQueryType()) { case Query.QueryTypeAsk: q.setQueryAskType(); break; case Query.QueryTypeConstruct: q.setQueryConstructType(); break; case Query.QueryTypeDescribe: q.setQueryDescribeType(); break; case Query.QueryTypeSelect: q.setQuerySelectType(); break; default: throw new IllegalStateException( "Internal query is not a known type: "+q.getQueryType()); } // use the HandlerBlock implementation to copy the data. HandlerBlock handlerBlock = new HandlerBlock(q); handlerBlock.addAll( getHandlerBlock() ); // set the vars handlerBlock.setVars(values); // make sure we have a query pattern before we start building. if (q.getQueryPattern() == null) { q.setQueryPattern( new ElementGroup() ); } handlerBlock.build(); return q; } /** * Close the query. * * This can be used when the query would not normally parse as is required * by the Query.clone() method. * * @param q2 * The query to clone * @return A clone of the q2 param. */ public static Query clone(Query q2) { Query retval = new Query(); // set the query type if (q2.isSelectType()) { retval.setQuerySelectType(); } else if (q2.isAskType()) { retval.setQueryAskType(); } else if (q2.isDescribeType()) { retval.setQueryDescribeType(); } else if (q2.isConstructType()) { retval.setQueryConstructType(); } // use the handler block to clone the data HandlerBlock hb = new HandlerBlock( retval ); HandlerBlock hb2 = new HandlerBlock( q2 ); hb.addAll(hb2); return retval; } /** * Rewrite a query replacing variables as specified in the values map. * * @param q2 * The query to rewrite * @param values * a Mapping of var to node for replacement. * @return The new query with the specified vars replaced. */ public static Query rewrite(Query q2, Map<Var, Node> values) { HandlerBlock hb = new HandlerBlock(q2); hb.setVars(values); return q2; } }