/* * Copyright Aduna (http://www.aduna-software.com/) (c) 1997-2006. * * Licensed under the Aduna BSD-style license. */ package com.bigdata.rdf.sail.sparql; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.Map; import java.util.Set; import org.openrdf.query.MalformedQueryException; import com.bigdata.rdf.sail.sparql.ast.ASTBasicGraphPattern; import com.bigdata.rdf.sail.sparql.ast.ASTBlankNode; import com.bigdata.rdf.sail.sparql.ast.ASTBlankNodePropertyList; import com.bigdata.rdf.sail.sparql.ast.ASTCollection; import com.bigdata.rdf.sail.sparql.ast.ASTOperationContainer; import com.bigdata.rdf.sail.sparql.ast.ASTVar; import com.bigdata.rdf.sail.sparql.ast.SyntaxTreeBuilderTreeConstants; import com.bigdata.rdf.sail.sparql.ast.VisitorException; /** * Processes blank nodes in the query body, replacing them with variables while * retaining scope. * * @author Arjohn Kampman * @openrdf */ public class BlankNodeVarProcessor extends ASTVisitorBase { public static Set<String> process(ASTOperationContainer qc) throws MalformedQueryException { try { BlankNodeToVarConverter converter = new BlankNodeToVarConverter(); qc.jjtAccept(converter, null); return converter.getUsedBNodeIDs(); } catch (VisitorException e) { throw new MalformedQueryException(e); } } /*-------------------------------------* * Inner class BlankNodeToVarConverter * *-------------------------------------*/ private static class BlankNodeToVarConverter extends ASTVisitorBase { private int anonVarNo = 1; private Map<String, String> conversionMap = new HashMap<String, String>(); private Set<String> usedBNodeIDs = new HashSet<String>(); private String createAnonVarName() { /* * Note: This is an illegal name for a SPARQL variable. That is * deliberate. It prevents conflicts with variable names in user * queries, even when they are generated by a query generator. * * Note: This is also important in Federated Query, where we are * doing some query generation of our own. However, it does mean * that the Federated Query logic needs to manage the mapping * between the anonymous variable names for blank nodes in the local * query and the legal SPARQL variable name used for that blank node * in the generated query. * * @see https://sourceforge.net/apps/trac/bigdata/ticket/510 (Blank * nodes in SERVICE graph patterns) */ return "-anon-" + anonVarNo++; } public Set<String> getUsedBNodeIDs() { usedBNodeIDs.addAll(conversionMap.keySet()); return Collections.unmodifiableSet(usedBNodeIDs); } @Override public Object visit(ASTBasicGraphPattern node, Object data) throws VisitorException { // The same Blank node ID cannot be used across Graph Patterns usedBNodeIDs.addAll(conversionMap.keySet()); // Blank nodes are scoped to Basic Graph Patterns conversionMap.clear(); return super.visit(node, data); } @Override public Object visit(ASTBlankNode node, Object data) throws VisitorException { String bnodeID = node.getID(); String varName = findVarName(bnodeID); if (varName == null) { varName = createAnonVarName(); if (bnodeID != null) { conversionMap.put(bnodeID, varName); } } ASTVar varNode = new ASTVar(SyntaxTreeBuilderTreeConstants.JJTVAR); varNode.setName(varName); varNode.setAnonymous(true); node.jjtReplaceWith(varNode); return super.visit(node, data); } private String findVarName(String bnodeID) throws VisitorException { if (bnodeID == null) return null; String varName = conversionMap.get(bnodeID); if (varName == null && usedBNodeIDs.contains(bnodeID)) throw new VisitorException( "BNodeID already used in another scope: " + bnodeID); return varName; } @Override public Object visit(ASTBlankNodePropertyList node, Object data) throws VisitorException { node.setVarName(createAnonVarName()); return super.visit(node, data); } @Override public Object visit(ASTCollection node, Object data) throws VisitorException { node.setVarName(createAnonVarName()); return super.visit(node, data); } } }