/*
* Copyright Aduna (http://www.aduna-software.com/) (c) 1997-2009.
*
* Licensed under the Aduna BSD-style license.
*/
package org.openrdf.query.resultio.binary;
import static org.openrdf.query.resultio.binary.BinaryQueryResultConstants.BNODE_RECORD_MARKER;
import static org.openrdf.query.resultio.binary.BinaryQueryResultConstants.DATATYPE_LITERAL_RECORD_MARKER;
import static org.openrdf.query.resultio.binary.BinaryQueryResultConstants.EMPTY_TUPLE_RECORD_MARKER;
import static org.openrdf.query.resultio.binary.BinaryQueryResultConstants.ERROR_RECORD_MARKER;
import static org.openrdf.query.resultio.binary.BinaryQueryResultConstants.FORMAT_VERSION;
import static org.openrdf.query.resultio.binary.BinaryQueryResultConstants.LANG_LITERAL_RECORD_MARKER;
import static org.openrdf.query.resultio.binary.BinaryQueryResultConstants.MAGIC_NUMBER;
import static org.openrdf.query.resultio.binary.BinaryQueryResultConstants.MALFORMED_QUERY_ERROR;
import static org.openrdf.query.resultio.binary.BinaryQueryResultConstants.NAMESPACE_RECORD_MARKER;
import static org.openrdf.query.resultio.binary.BinaryQueryResultConstants.NULL_RECORD_MARKER;
import static org.openrdf.query.resultio.binary.BinaryQueryResultConstants.PLAIN_LITERAL_RECORD_MARKER;
import static org.openrdf.query.resultio.binary.BinaryQueryResultConstants.QNAME_RECORD_MARKER;
import static org.openrdf.query.resultio.binary.BinaryQueryResultConstants.QUERY_EVALUATION_ERROR;
import static org.openrdf.query.resultio.binary.BinaryQueryResultConstants.REPEAT_RECORD_MARKER;
import static org.openrdf.query.resultio.binary.BinaryQueryResultConstants.TABLE_END_RECORD_MARKER;
import java.io.DataOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.nio.ByteBuffer;
import java.nio.CharBuffer;
import java.nio.charset.Charset;
import java.nio.charset.CharsetEncoder;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.openrdf.model.BNode;
import org.openrdf.model.Literal;
import org.openrdf.model.URI;
import org.openrdf.model.Value;
import org.openrdf.query.BindingSet;
import org.openrdf.query.TupleQueryResultHandlerException;
import org.openrdf.query.impl.ListBindingSet;
import org.openrdf.query.resultio.TupleQueryResultFormat;
import org.openrdf.query.resultio.TupleQueryResultWriter;
/**
* Writer for the binary tuple result format. The format is explained in
* {@link BinaryQueryResultConstants}.
*
* @author Arjohn Kampman
*/
public class BinaryQueryResultWriter implements TupleQueryResultWriter {
/*-----------*
* Variables *
*-----------*/
/**
* The output stream to write the results table to.
*/
private final DataOutputStream out;
private final CharsetEncoder charsetEncoder = Charset.forName("UTF-8").newEncoder();
/**
* Map containing the namespace IDs (Integer objects) that have been defined
* in the document, stored using the concerning namespace (Strings).
*/
private final Map<String, Integer> namespaceTable = new HashMap<String, Integer>(32);
private int nextNamespaceID;
private BindingSet previousBindings;
private List<String> bindingNames;
/*--------------*
* Constructors *
*--------------*/
public BinaryQueryResultWriter(OutputStream out) {
assert out != null : "out must not be null";
this.out = new DataOutputStream(out);
}
/*---------*
* Methods *
*---------*/
public final TupleQueryResultFormat getTupleQueryResultFormat() {
return TupleQueryResultFormat.BINARY;
}
public void startQueryResult(List<String> bindingNames)
throws TupleQueryResultHandlerException
{
// Copy supplied column headers list and make it unmodifiable
bindingNames = new ArrayList<String>(bindingNames);
this.bindingNames = Collections.unmodifiableList(bindingNames);
try {
out.write(MAGIC_NUMBER);
out.writeInt(FORMAT_VERSION);
out.writeInt(this.bindingNames.size());
for (String bindingName : this.bindingNames) {
writeString(bindingName);
}
List<Value> nullTuple = Collections.nCopies(this.bindingNames.size(), (Value)null);
previousBindings = new ListBindingSet(this.bindingNames, nullTuple);
nextNamespaceID = 0;
}
catch (IOException e) {
throw new TupleQueryResultHandlerException(e);
}
}
public void endQueryResult()
throws TupleQueryResultHandlerException
{
try {
out.writeByte(TABLE_END_RECORD_MARKER);
out.flush();
}
catch (IOException e) {
throw new TupleQueryResultHandlerException(e);
}
}
public void handleSolution(BindingSet bindingSet)
throws TupleQueryResultHandlerException
{
try {
if (bindingNames.isEmpty()) {
writeEmptyTuple();
}
else {
for (String bindingName : bindingNames) {
Value value = bindingSet.getValue(bindingName);
if (value == null) {
writeNull();
}
else if (value.equals(previousBindings.getValue(bindingName))) {
writeRepeat();
}
else if (value instanceof URI) {
writeQName((URI)value);
}
else if (value instanceof BNode) {
writeBNode((BNode)value);
}
else if (value instanceof Literal) {
writeLiteral((Literal)value);
}
else {
throw new TupleQueryResultHandlerException("Unknown Value object type: " + value.getClass());
}
}
previousBindings = bindingSet;
}
}
catch (IOException e) {
throw new TupleQueryResultHandlerException(e);
}
}
private void writeNull()
throws IOException
{
out.writeByte(NULL_RECORD_MARKER);
}
private void writeRepeat()
throws IOException
{
out.writeByte(REPEAT_RECORD_MARKER);
}
private void writeQName(URI uri)
throws IOException
{
// Check if the URI has a new namespace
String namespace = uri.getNamespace();
Integer nsID = namespaceTable.get(namespace);
if (nsID == null) {
// Generate a ID for this new namespace
nsID = writeNamespace(namespace);
}
out.writeByte(QNAME_RECORD_MARKER);
out.writeInt(nsID.intValue());
writeString(uri.getLocalName());
}
private void writeBNode(BNode bnode)
throws IOException
{
out.writeByte(BNODE_RECORD_MARKER);
writeString(bnode.getID());
}
private void writeLiteral(Literal literal)
throws IOException
{
String label = literal.getLabel();
String language = literal.getLanguage();
URI datatype = literal.getDatatype();
int marker = PLAIN_LITERAL_RECORD_MARKER;
if (datatype != null) {
String namespace = datatype.getNamespace();
if (!namespaceTable.containsKey(namespace)) {
// Assign an ID to this new namespace
writeNamespace(namespace);
}
marker = DATATYPE_LITERAL_RECORD_MARKER;
}
else if (language != null) {
marker = LANG_LITERAL_RECORD_MARKER;
}
out.writeByte(marker);
writeString(label);
if (datatype != null) {
writeQName(datatype);
}
else if (language != null) {
writeString(language);
}
}
private void writeEmptyTuple()
throws IOException
{
out.writeByte(EMPTY_TUPLE_RECORD_MARKER);
}
/**
* Writes an error msg to the stream.
*
* @param errType
* The error type.
* @param msg
* The error message.
* @throws IOException
* When the error could not be written to the stream.
*/
public void error(QueryErrorType errType, String msg)
throws IOException
{
out.writeByte(ERROR_RECORD_MARKER);
if (errType == QueryErrorType.MALFORMED_QUERY_ERROR) {
out.writeByte(MALFORMED_QUERY_ERROR);
}
else {
out.writeByte(QUERY_EVALUATION_ERROR);
}
writeString(msg);
}
private Integer writeNamespace(String namespace)
throws IOException
{
out.writeByte(NAMESPACE_RECORD_MARKER);
out.writeInt(nextNamespaceID);
writeString(namespace);
Integer result = Integer.valueOf(nextNamespaceID);
namespaceTable.put(namespace, result);
nextNamespaceID++;
return result;
}
private void writeString(String s)
throws IOException
{
ByteBuffer byteBuf = charsetEncoder.encode(CharBuffer.wrap(s));
out.writeInt(byteBuf.remaining());
out.write(byteBuf.array(), 0, byteBuf.remaining());
}
}