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