/******************************************************************************* * Copyright (c) 2009 Fraunhofer IWU and others. * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html * * Contributors: * Fraunhofer IWU - initial API and implementation *******************************************************************************/ package net.enilink.komma.query; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.LinkedHashMap; import java.util.LinkedHashSet; import java.util.List; import java.util.Map; import java.util.Set; import java.util.Stack; import net.enilink.komma.parser.sparql.Sparql11Parser; import net.enilink.komma.parser.sparql.SparqlParser; import net.enilink.komma.parser.sparql.tree.AbstractGraphNode; import net.enilink.komma.parser.sparql.tree.BNode; import net.enilink.komma.parser.sparql.tree.ConstructQuery; import net.enilink.komma.parser.sparql.tree.Graph; import net.enilink.komma.parser.sparql.tree.GraphNode; import net.enilink.komma.parser.sparql.tree.GraphPattern; import net.enilink.komma.parser.sparql.tree.IriRef; import net.enilink.komma.parser.sparql.tree.OptionalGraph; import net.enilink.komma.parser.sparql.tree.OrderCondition; import net.enilink.komma.parser.sparql.tree.OrderModifier; import net.enilink.komma.parser.sparql.tree.PrefixDecl; import net.enilink.komma.parser.sparql.tree.PropertyList; import net.enilink.komma.parser.sparql.tree.PropertyPattern; import net.enilink.komma.parser.sparql.tree.Query; import net.enilink.komma.parser.sparql.tree.QueryWithSolutionModifier; import net.enilink.komma.parser.sparql.tree.SelectQuery; import net.enilink.komma.parser.sparql.tree.SolutionModifier; import net.enilink.komma.parser.sparql.tree.Variable; import net.enilink.komma.parser.sparql.tree.expr.Expression; import net.enilink.komma.parser.sparql.tree.visitor.ToStringVisitor; import net.enilink.komma.parser.sparql.tree.visitor.TreeWalker; import net.enilink.komma.parser.sparql.tree.visitor.Visitable; import org.parboiled.Parboiled; import org.parboiled.Rule; import org.parboiled.errors.ErrorUtils; import org.parboiled.parserunners.ReportingParseRunner; import org.parboiled.support.ParsingResult; import org.parboiled.support.Var; public class SparqlBuilder { private static final IriRef RESULT_NODE = new IriRef("komma:Result"); private static final PropertyPattern PATTERN_TYPE_RESULT_NODE = new PropertyPattern( new IriRef(SparqlParser.RDF_TYPE), RESULT_NODE); protected Query query; protected List<GraphNode> resultNodes; protected Set<String> usedVarNames; protected SparqlParser parser; protected ToStringVisitor toStringVisitor; static class VarRenamer extends TreeWalker<Object> { private List<Variable> queryVars = new ArrayList<Variable>(); private Set<String> queryVarNames = new HashSet<String>(); private Map<String, String> queryVarNameMap = new LinkedHashMap<String, String>(); public Collection<String> process(Query query, Map<String, String> varNameMap, Set<String> usedVarNames) { query.accept(this, null); if (varNameMap != null) { queryVarNameMap.putAll(varNameMap); } // rename variables for (Variable variable : queryVars) { String varName = variable.getName(); String newVarName = mapVarName(varName); if (newVarName == null) { int i = 1; newVarName = varName; if (usedVarNames.contains(newVarName)) { while (usedVarNames.contains(newVarName) || queryVarNames.contains(newVarName)) { newVarName = varName + i++; } } queryVarNameMap.put(varName, newVarName); } if (!varName.equals(newVarName)) { variable.setName(newVarName); } } return queryVarNameMap.values(); } public String mapVarName(String varName) { return queryVarNameMap.get(varName); } @Override public Boolean variable(Variable variable, Object value) { queryVars.add(variable); queryVarNames.add(variable.getName()); return variable.getPropertyList().accept(this, value); } } public SparqlBuilder(String sparql) { query = parseQuery(sparql); } protected Object parse(Rule rule, String sparql) { ParsingResult<Object> result = new ReportingParseRunner<Object>(rule) .run(sparql); if (result.hasErrors()) { throw new IllegalArgumentException( ErrorUtils.printParseErrors(result.parseErrors)); } return result.resultValue; } protected SparqlParser getParser() { if (parser == null) { parser = Parboiled.createParser(Sparql11Parser.class); } return parser; } protected Query parseQuery(String sparql) { return (Query) parse(getParser().Query(), sparql); } protected void toConstructQuery() { List<GraphNode> template = new ArrayList<GraphNode>(); if (query instanceof SelectQuery) { template.addAll(((SelectQuery) query).getProjection()); } if (template.isEmpty()) { for (String varName : getUsedVarNames()) { template.add(new Variable(varName)); } } prepareTemplate(template); Collection<? extends SolutionModifier> modifiers = Collections .emptyList(); // preserve order by modifier of original query if (query instanceof QueryWithSolutionModifier && ((QueryWithSolutionModifier) query).getOrderModifier() != null) { modifiers = Collections .singleton(((QueryWithSolutionModifier) query) .getOrderModifier()); } ConstructQuery constructQuery = new ConstructQuery(template, query.getDataset(), query.getGraph(), modifiers); constructQuery.setPrologue(query.getPrologue()); query = constructQuery; } protected void prepareTemplate(List<GraphNode> template) { for (Iterator<GraphNode> it = template.iterator(); it.hasNext();) { GraphNode node = it.next(); if (!(node instanceof Variable)) { it.remove(); continue; } PropertyList propertyList = new PropertyList(); propertyList.add(PATTERN_TYPE_RESULT_NODE.copy()); ((AbstractGraphNode) node).setPropertyList(propertyList); } } protected void addPropertyToTemplate(String property, GraphNode object) { for (GraphNode resultNode : resultNodes) { resultNode.getPropertyList().add( new PropertyPattern(new IriRef(property), object .copy(false))); resultNode.getPropertyList().add( new PropertyPattern(new IriRef(property), RESULT_NODE .copy(false))); } if (!object.getPropertyList().isEmpty()) { ((ConstructQuery) query).getTemplate().add(object.copy(true)); } } public SparqlBuilder optional(String property, String param, String sparql) { return optional(property, param, null, sparql); } public SparqlBuilder optional(String property, String variable, String param, String sparql) { return optional(property, variable, param, parseQuery(sparql)); } public SparqlBuilder optional(String property, String variable, String param, SparqlBuilder other) { return optional(property, variable, param, other.query); } protected Set<String> getUsedVarNames() { if (usedVarNames == null) { usedVarNames = new LinkedHashSet<String>(); usedVarNames.addAll(new VarRenamer().process(query, null, usedVarNames)); } return usedVarNames; } protected SparqlBuilder optional(String property, String variable, String param, Query other) { if (!(query instanceof ConstructQuery)) { toConstructQuery(); } if (resultNodes == null) { resultNodes = new ArrayList<GraphNode>(); for (GraphNode node : ((ConstructQuery) query).getTemplate()) { for (PropertyPattern pattern : node.getPropertyList()) { if (RESULT_NODE.equals(pattern.getObject())) { resultNodes.add(node); break; } } } } VarRenamer otherVarRenamer = new VarRenamer(); Map<String, String> nodeNameMap = null; if (param != null && !resultNodes.isEmpty()) { nodeNameMap = new HashMap<String, String>(); nodeNameMap.put(param, ((Variable) resultNodes.get(0)).getName()); } Collection<String> otherVarNames = otherVarRenamer.process(other, nodeNameMap, getUsedVarNames()); getUsedVarNames().addAll(otherVarNames); if (property != null) { if (variable != null) { addPropertyToTemplate(property, new Variable(otherVarRenamer.mapVarName(variable))); if (other instanceof ConstructQuery) { for (GraphNode node : ((ConstructQuery) other) .getTemplate()) { node = node.copy(true); node.getPropertyList().remove(PATTERN_TYPE_RESULT_NODE); ((ConstructQuery) query).getTemplate().add(node); } } } else { if (other instanceof ConstructQuery) { if (((ConstructQuery) other).getTemplate().isEmpty()) { for (String varName : otherVarNames) { addPropertyToTemplate(property, new Variable( varName)); } } else { for (GraphNode node : ((ConstructQuery) other) .getTemplate()) { addPropertyToTemplate(property, node); } } } else if (((SelectQuery) other).getProjection().isEmpty()) { for (String varName : otherVarNames) { addPropertyToTemplate(property, new Variable(varName)); } } else { for (GraphNode node : ((SelectQuery) other).getProjection()) { addPropertyToTemplate(property, node); } } } } else if (other instanceof ConstructQuery) { // add template from other query ((ConstructQuery) query).getTemplate().addAll( ((ConstructQuery) other).getTemplate()); } query.getPrologue().getPrefixDecls() .addAll(other.getPrologue().getPrefixDecls()); removeDuplicates(query.getPrologue().getPrefixDecls()); query.getDataset().add(other.getDataset()); // query.setGraph(new UnionGraph(Arrays.asList(query.getGraph(), other // .getGraph()))); // merge order by clauses of both queries OrderModifier orderModifier = ((ConstructQuery) query) .getOrderModifier(); if (other instanceof QueryWithSolutionModifier && ((QueryWithSolutionModifier) other).getOrderModifier() != null) { if (orderModifier == null) { orderModifier = ((QueryWithSolutionModifier) other) .getOrderModifier(); } else { List<OrderCondition> conditions = new ArrayList<OrderCondition>( orderModifier.getOrderConditions()); conditions.addAll(((QueryWithSolutionModifier) other) .getOrderModifier().getOrderConditions()); orderModifier = new OrderModifier(conditions); } } OptionalGraph optionalGraph = other.getGraph() instanceof OptionalGraph ? (OptionalGraph) other .getGraph() : new OptionalGraph(other.getGraph()); ConstructQuery combinedQuery = new ConstructQuery( ((ConstructQuery) query).getTemplate(), query.getDataset(), new GraphPattern(new ArrayList<Graph>(Arrays.asList( query.getGraph(), optionalGraph)), Collections .<Expression> emptyList()), orderModifier == null ? Collections .<SolutionModifier> emptyList() : Collections .singleton(orderModifier)); combinedQuery.setPrologue(query.getPrologue()); query = combinedQuery; return this; } public SparqlBuilder fetchTypes() { if (!(query instanceof ConstructQuery)) { toConstructQuery(); } final Map<GraphNode, GraphPattern> nodesWithoutType = new HashMap<GraphNode, GraphPattern>(); TreeWalker<GraphNode> nodeFinder = new TreeWalker<GraphNode>() { public Boolean propertyList(PropertyList propertyList, GraphNode subject) { nodesWithoutType.put(subject, null); for (PropertyPattern pattern : propertyList) { if (pattern.getPredicate() instanceof IriRef && ((IriRef) pattern.getPredicate()).getIri() .equals(SparqlParser.RDF_TYPE)) { if (RESULT_NODE.equals(pattern.getObject())) { continue; } nodesWithoutType.remove(subject); } nodesWithoutType.put(pattern.getObject(), null); pattern.getObject().accept(this, pattern.getObject()); } return true; } }; for (GraphNode node : ((ConstructQuery) query).getTemplate()) { node.accept(nodeFinder, node); } TreeWalker<GraphNode> patternFinder = new TreeWalker<GraphNode>() { class PatternInfo { int depth; int optionals; PatternInfo(int depth, int optionals) { this.depth = depth; this.optionals = optionals; } } Stack<GraphPattern> patterns = new Stack<GraphPattern>(); Map<GraphPattern, PatternInfo> patternInfos = new HashMap<GraphPattern, PatternInfo>(); int depth; int optionals; @Override public Boolean bNode(BNode bNode, GraphNode data) { return super.bNode(bNode, bNode); } @Override public Boolean iriRef(IriRef iriRef, GraphNode data) { return super.iriRef(iriRef, iriRef); } @Override public Boolean variable(Variable variable, GraphNode data) { return super.variable(variable, variable); } @Override public Boolean optionalGraph(OptionalGraph optionalGraph, GraphNode data) { try { optionals++; return super.optionalGraph(optionalGraph, data); } finally { optionals--; } } @Override public Boolean graphPattern(GraphPattern graphPattern, GraphNode data) { try { depth++; patterns.push(graphPattern); patternInfos.put(graphPattern, new PatternInfo(depth, optionals)); return super.graphPattern(graphPattern, data); } finally { patterns.pop(); depth--; } } public Boolean propertyList(PropertyList propertyList, GraphNode subject) { if (nodesWithoutType.containsKey(subject)) { GraphPattern other = nodesWithoutType.get(subject); if (other != null) { // take best pattern for insertion PatternInfo info = patternInfos.get(other); if (info.optionals > optionals || info.depth > depth) { nodesWithoutType.put(subject, patterns.peek()); } } else { nodesWithoutType.put(subject, patterns.peek()); } } for (PropertyPattern pattern : propertyList) { pattern.getObject().accept(this, pattern.getObject()); } return true; } }; query.getGraph().accept(patternFinder, null); int i = 1; for (Map.Entry<GraphNode, GraphPattern> entry : nodesWithoutType .entrySet()) { if (entry.getKey().equals(RESULT_NODE)) { continue; } GraphNode copy = entry.getKey().copy(false); String nodeName = toString(copy); String typeVar; do { typeVar = "preloadedType_" + i++; } while (getUsedVarNames().contains(typeVar)); getUsedVarNames().add(typeVar); parse(parser.PropertyListNotEmpty(new Var<>(copy)), "a ?" + typeVar); ((ConstructQuery) query).getTemplate().add(copy); ((GraphPattern) entry.getValue()).getPatterns().add( (Graph) parse(parser.OptionalGraphPattern(), "OPTIONAL {" + nodeName + " a ?" + typeVar + " . FILTER isIRI(?" + typeVar + ")}}")); } // if (query.getGraph() instanceof GraphPattern) { // ((GraphPattern) query.getGraph()).getPatterns().addAll(newPatterns); // } else { // newPatterns.add(0, query.getGraph()); // query.setGraph(new GraphPattern(newPatterns, Collections // .<Expression> emptyList())); // } return this; } protected void removeDuplicates(List<PrefixDecl> prefixDecls) { Set<String> prefixes = new HashSet<String>(); for (Iterator<PrefixDecl> it = prefixDecls.iterator(); it.hasNext();) { PrefixDecl prefixDecl = it.next(); if (!prefixes.add(prefixDecl.getPrefix() + ":<" + prefixDecl.getIri().getIri() + ">")) { it.remove(); } } } protected String toString(Visitable value) { return value.accept(new ToStringVisitor(), new StringBuilder()) .toString(); } @Override public String toString() { if (query == null) { return ""; } return toString(query); } }