001: /*
002: * Copyright Aduna (http://www.aduna-software.com/) (c) 1997-2007.
003: *
004: * Licensed under the Aduna BSD-style license.
005: */
006: package org.openrdf.query.resultio.binary;
007:
008: import static org.openrdf.query.resultio.binary.BinaryQueryResultConstants.BNODE_RECORD_MARKER;
009: import static org.openrdf.query.resultio.binary.BinaryQueryResultConstants.DATATYPE_LITERAL_RECORD_MARKER;
010: import static org.openrdf.query.resultio.binary.BinaryQueryResultConstants.ERROR_RECORD_MARKER;
011: import static org.openrdf.query.resultio.binary.BinaryQueryResultConstants.FORMAT_VERSION;
012: import static org.openrdf.query.resultio.binary.BinaryQueryResultConstants.LANG_LITERAL_RECORD_MARKER;
013: import static org.openrdf.query.resultio.binary.BinaryQueryResultConstants.MAGIC_NUMBER;
014: import static org.openrdf.query.resultio.binary.BinaryQueryResultConstants.MALFORMED_QUERY_ERROR;
015: import static org.openrdf.query.resultio.binary.BinaryQueryResultConstants.NAMESPACE_RECORD_MARKER;
016: import static org.openrdf.query.resultio.binary.BinaryQueryResultConstants.NULL_RECORD_MARKER;
017: import static org.openrdf.query.resultio.binary.BinaryQueryResultConstants.PLAIN_LITERAL_RECORD_MARKER;
018: import static org.openrdf.query.resultio.binary.BinaryQueryResultConstants.QNAME_RECORD_MARKER;
019: import static org.openrdf.query.resultio.binary.BinaryQueryResultConstants.QUERY_EVALUATION_ERROR;
020: import static org.openrdf.query.resultio.binary.BinaryQueryResultConstants.REPEAT_RECORD_MARKER;
021: import static org.openrdf.query.resultio.binary.BinaryQueryResultConstants.TABLE_END_RECORD_MARKER;
022:
023: import java.io.DataOutputStream;
024: import java.io.IOException;
025: import java.io.OutputStream;
026: import java.nio.ByteBuffer;
027: import java.nio.CharBuffer;
028: import java.nio.charset.Charset;
029: import java.nio.charset.CharsetEncoder;
030: import java.util.ArrayList;
031: import java.util.Collections;
032: import java.util.HashMap;
033: import java.util.List;
034: import java.util.Map;
035:
036: import org.openrdf.model.BNode;
037: import org.openrdf.model.Literal;
038: import org.openrdf.model.URI;
039: import org.openrdf.model.Value;
040: import org.openrdf.query.BindingSet;
041: import org.openrdf.query.TupleQueryResultHandlerException;
042: import org.openrdf.query.impl.ListBindingSet;
043: import org.openrdf.query.resultio.TupleQueryResultFormat;
044: import org.openrdf.query.resultio.TupleQueryResultWriter;
045:
046: /**
047: * Writer for the binary tuple result format. The format is explained in
048: * {@link BinaryQueryResultConstants}.
049: *
050: * @author Arjohn Kampman
051: */
052: public class BinaryQueryResultWriter implements TupleQueryResultWriter {
053:
054: /*-----------*
055: * Variables *
056: *-----------*/
057:
058: /**
059: * The output stream to write the results table to.
060: */
061: private DataOutputStream out;
062:
063: private CharsetEncoder charsetEncoder = Charset.forName("UTF-8")
064: .newEncoder();
065:
066: /**
067: * Map containing the namespace IDs (Integer objects) that have been defined
068: * in the document, stored using the concerning namespace (Strings).
069: */
070: private Map<String, Integer> namespaceTable = new HashMap<String, Integer>(
071: 32);
072:
073: private int nextNamespaceID;
074:
075: private BindingSet previousBindings;
076:
077: private List<String> bindingNames;
078:
079: /*--------------*
080: * Constructors *
081: *--------------*/
082:
083: public BinaryQueryResultWriter(OutputStream out) {
084: this .out = new DataOutputStream(out);
085: }
086:
087: /*---------*
088: * Methods *
089: *---------*/
090:
091: public final TupleQueryResultFormat getTupleQueryResultFormat() {
092: return TupleQueryResultFormat.BINARY;
093: }
094:
095: public void startQueryResult(List<String> bindingNames)
096: throws TupleQueryResultHandlerException {
097: // Copy supplied column headers list and make it unmodifiable
098: bindingNames = new ArrayList<String>(bindingNames);
099: this .bindingNames = Collections.unmodifiableList(bindingNames);
100:
101: try {
102: out.write(MAGIC_NUMBER);
103: out.writeInt(FORMAT_VERSION);
104:
105: out.writeInt(this .bindingNames.size());
106:
107: for (String bindingName : this .bindingNames) {
108: writeString(bindingName);
109: }
110:
111: List<Value> nullTuple = Collections.nCopies(
112: this .bindingNames.size(), (Value) null);
113: previousBindings = new ListBindingSet(this .bindingNames,
114: nullTuple);
115: nextNamespaceID = 0;
116: } catch (IOException e) {
117: throw new TupleQueryResultHandlerException(e);
118: }
119: }
120:
121: public void endQueryResult()
122: throws TupleQueryResultHandlerException {
123: try {
124: out.writeByte(TABLE_END_RECORD_MARKER);
125: out.flush();
126: } catch (IOException e) {
127: throw new TupleQueryResultHandlerException(e);
128: }
129: }
130:
131: public void handleSolution(BindingSet bindingSet)
132: throws TupleQueryResultHandlerException {
133: try {
134: for (String bindingName : bindingNames) {
135: Value value = bindingSet.getValue(bindingName);
136:
137: if (value == null) {
138: writeNull();
139: } else if (value.equals(previousBindings
140: .getValue(bindingName))) {
141: writeRepeat();
142: } else if (value instanceof URI) {
143: writeQName((URI) value);
144: } else if (value instanceof BNode) {
145: writeBNode((BNode) value);
146: } else if (value instanceof Literal) {
147: writeLiteral((Literal) value);
148: } else {
149: throw new TupleQueryResultHandlerException(
150: "Unknown Value object type: "
151: + value.getClass());
152: }
153: }
154:
155: previousBindings = bindingSet;
156: } catch (IOException e) {
157: throw new TupleQueryResultHandlerException(e);
158: }
159: }
160:
161: private void writeNull() throws IOException {
162: out.writeByte(NULL_RECORD_MARKER);
163: }
164:
165: private void writeRepeat() throws IOException {
166: out.writeByte(REPEAT_RECORD_MARKER);
167: }
168:
169: private void writeQName(URI uri) throws IOException {
170: // Check if the URI has a new namespace
171: String namespace = uri.getNamespace();
172:
173: Integer nsID = namespaceTable.get(namespace);
174:
175: if (nsID == null) {
176: // Generate a ID for this new namespace
177: nsID = writeNamespace(namespace);
178: }
179:
180: out.writeByte(QNAME_RECORD_MARKER);
181: out.writeInt(nsID.intValue());
182: writeString(uri.getLocalName());
183: }
184:
185: private void writeBNode(BNode bnode) throws IOException {
186: out.writeByte(BNODE_RECORD_MARKER);
187: writeString(bnode.getID());
188: }
189:
190: private void writeLiteral(Literal literal) throws IOException {
191: String label = literal.getLabel();
192: String language = literal.getLanguage();
193: URI datatype = literal.getDatatype();
194:
195: int marker = PLAIN_LITERAL_RECORD_MARKER;
196:
197: if (datatype != null) {
198: String namespace = datatype.getNamespace();
199:
200: if (!namespaceTable.containsKey(namespace)) {
201: // Assign an ID to this new namespace
202: writeNamespace(namespace);
203: }
204:
205: marker = DATATYPE_LITERAL_RECORD_MARKER;
206: } else if (language != null) {
207: marker = LANG_LITERAL_RECORD_MARKER;
208: }
209:
210: out.writeByte(marker);
211: writeString(label);
212:
213: if (datatype != null) {
214: writeQName(datatype);
215: } else if (language != null) {
216: writeString(language);
217: }
218: }
219:
220: /**
221: * Writes an error msg to the stream.
222: *
223: * @param errType
224: * The error type.
225: * @param msg
226: * The error message.
227: * @throws IOException
228: * When the error could not be written to the stream.
229: */
230: public void error(QueryErrorType errType, String msg)
231: throws IOException {
232: out.writeByte(ERROR_RECORD_MARKER);
233:
234: if (errType == QueryErrorType.MALFORMED_QUERY_ERROR) {
235: out.writeByte(MALFORMED_QUERY_ERROR);
236: } else {
237: out.writeByte(QUERY_EVALUATION_ERROR);
238: }
239:
240: writeString(msg);
241: }
242:
243: private Integer writeNamespace(String namespace) throws IOException {
244: out.writeByte(NAMESPACE_RECORD_MARKER);
245: out.writeInt(nextNamespaceID);
246: writeString(namespace);
247:
248: Integer result = new Integer(nextNamespaceID);
249: namespaceTable.put(namespace, result);
250:
251: nextNamespaceID++;
252:
253: return result;
254: }
255:
256: private void writeString(String s) throws IOException {
257: ByteBuffer byteBuf = charsetEncoder.encode(CharBuffer.wrap(s));
258: out.writeInt(byteBuf.remaining());
259: out.write(byteBuf.array(), 0, byteBuf.remaining());
260: }
261: }
|