/*
* Licensed to Aduna under one or more contributor license agreements.
* See the NOTICE.txt file distributed with this work for additional
* information regarding copyright ownership.
*
* Aduna licenses this file to you under the terms of the Aduna BSD
* License (the "License"); you may not use this file except in compliance
* with the License. See the LICENSE.txt file distributed with this work
* for the full License.
*
* 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 com.bigdata.rdf.sail.sparql;
import java.io.IOException;
import java.io.StringReader;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import org.openrdf.model.Statement;
import org.openrdf.model.Value;
import org.openrdf.model.ValueFactory;
import org.openrdf.model.impl.ValueFactoryImpl;
import org.openrdf.repository.sail.helpers.SPARQLUpdateDataBlockParser;
import org.openrdf.rio.RDFHandlerException;
import org.openrdf.rio.RDFParseException;
import org.openrdf.rio.helpers.StatementCollector;
import com.bigdata.rdf.model.BigdataStatement;
import com.bigdata.rdf.model.BigdataValueFactory;
/**
* An extension of {@link SPARQLUpdateDataBlockParser} that processes data in the format
* specified in the SPARQL* grammar. This format is almost completely compatible with
* SPARQLUpdateDataBlockParser, except:
* <ul>
* <li>it allows statement to be used as subject or object in another statement
* </ul>
*
* @author Igor Kim (igor.kim@ms2w.com)
* @openrdf
*/
public class SPARQLStarUpdateDataBlockParser extends SPARQLUpdateDataBlockParser {
private String baseURI;
/**
* Namespaces mapping, collected from PREFIX statements, should be passed in to recursive SPARQL* parsing.
* Original namespaceTable variable is private to {@link RDFParserBase} and could not be accessed from this class.
*/
private Map<String, String> namespaceTable;
/*--------------*
* Constructors *
*--------------*/
/**
* Creates a new parser that will use the supplied ValueFactory to create RDF
* model objects.
*
* @param valueFactory
* A ValueFactory.
*/
public SPARQLStarUpdateDataBlockParser(ValueFactory valueFactory) {
super(valueFactory);
this.namespaceTable = new HashMap<>();
}
/**
* Creates a new parser that will use the supplied ValueFactory and prefix mapping to create RDF
* model objects.
*
* @param valueFactory
* A ValueFactory.
* @param namespaces
* Namespaces prefix mapping.
*/
public SPARQLStarUpdateDataBlockParser(ValueFactory valueFactory, Map<String, String> namespaces) {
super(valueFactory);
this.namespaceTable = namespaces;
// fill in original {@link RDFParserBase}.namespaceTable to be used while parsing
for (Entry<String, String> entry: namespaceTable.entrySet()) {
super.setNamespace(entry.getKey(), entry.getValue());
}
}
/*---------*
* Methods *
*---------*/
@Override
protected Value parseValue() throws IOException, RDFParseException {
if (checkSparqlStarSyntax()) {
return parseStmtValue();
}
return super.parseValue();
}
private boolean checkSparqlStarSyntax() throws IOException {
int c1 = read();
int c2;
try {
c2 = read();
unread(c2);
} finally {
unread(c1);
}
return (c1 == '<' && c2 == '<');
}
private Value parseStmtValue() throws IOException, RDFParseException
{
StringBuilder stmtBuf = new StringBuilder(100);
// First 2 characters should be '<'
int c = read();
verifyCharacterOrFail(c, "<");
c = read();
verifyCharacterOrFail(c, "<");
int recursiveCounter = 1;
// Read up to the next ">>" characters combination
while (true) {
c = read();
int c2 = peek();
if (c == '<' && c2 =='<' ) {
recursiveCounter++;
} else if (c == '>' && c2 == '>') {
if (--recursiveCounter == 0) {
c = read();
break;
}
} else if (c == -1) {
throwEOFException();
}
stmtBuf.append((char)c);
if (c == '\\') {
// This escapes the next character, which might be a '>'
c = read();
if (c == -1) {
throwEOFException();
}
if (c != 'u' && c != 'U') {
reportFatalError("IRI includes string escapes: '\\" + c + "'");
}
stmtBuf.append((char)c);
}
}
// Use our own class in recursion.
SPARQLStarUpdateDataBlockParser p = new SPARQLStarUpdateDataBlockParser(valueFactory, namespaceTable);
final List<Statement> stmts = new LinkedList<Statement>();
final StatementCollector sc = new StatementCollector(stmts);
p.setRDFHandler(sc);
p.setParserConfig(getParserConfig());
try {
p.parse(new StringReader(stmtBuf.toString()), baseURI);
} catch (RDFHandlerException e) {
throw new RDFParseException("Error parsing SPARQL* value", e);
}
if (stmts.size() != 1) {
throw new RDFParseException("Error parsing SPARQL* value, invalid number of statements");
}
if (valueFactory instanceof BigdataValueFactory && stmts.get(0) instanceof BigdataStatement) {
return ((BigdataValueFactory)valueFactory).createBNode((BigdataStatement)stmts.get(0));
} else {
throw new RDFParseException("Error parsing SPARQL* value, incompatible valueFactory");
}
}
/**
* Overriding setBaseURI to keep base URI value for SPARQL* parsing as
* org.openrdf.rio.helpers.RDFParserBase.baseURI is a private field
*/
@Override
protected void setBaseURI(String uri) {
baseURI = uri;
super.setBaseURI(uri);
}
@Override
public void setNamespace(String prefix, String namespace) {
namespaceTable.put(prefix, namespace);
super.setNamespace(prefix, namespace);
}
}