[Mulgara-svn] r1223 - in trunk/src/jar/querylang/java/org/mulgara: . protocol

pag at mulgara.org pag at mulgara.org
Wed Sep 3 14:56:29 UTC 2008


Author: pag
Date: 2008-09-03 07:56:28 -0700 (Wed, 03 Sep 2008)
New Revision: 1223

Added:
   trunk/src/jar/querylang/java/org/mulgara/protocol/
   trunk/src/jar/querylang/java/org/mulgara/protocol/AbstractStreamedAnswer.java
   trunk/src/jar/querylang/java/org/mulgara/protocol/AbstractStreamedXMLAnswer.java
   trunk/src/jar/querylang/java/org/mulgara/protocol/StreamedAnswer.java
   trunk/src/jar/querylang/java/org/mulgara/protocol/StreamedJSONAnswer.java
   trunk/src/jar/querylang/java/org/mulgara/protocol/StreamedSparqlJSONAnswer.java
   trunk/src/jar/querylang/java/org/mulgara/protocol/StreamedSparqlJSONAnswerUnitTest.java
   trunk/src/jar/querylang/java/org/mulgara/protocol/StreamedSparqlXMLAnswer.java
   trunk/src/jar/querylang/java/org/mulgara/protocol/StreamedSparqlXMLAnswerUnitTest.java
   trunk/src/jar/querylang/java/org/mulgara/protocol/StreamedTqlXMLAnswer.java
   trunk/src/jar/querylang/java/org/mulgara/protocol/StreamedTqlXMLAnswerUnitTest.java
   trunk/src/jar/querylang/java/org/mulgara/protocol/StreamedXMLAnswer.java
   trunk/src/jar/querylang/java/org/mulgara/protocol/XMLAnswer.java
Log:
Streaming results for queries. JSON not yet being tested

Added: trunk/src/jar/querylang/java/org/mulgara/protocol/AbstractStreamedAnswer.java
===================================================================
--- trunk/src/jar/querylang/java/org/mulgara/protocol/AbstractStreamedAnswer.java	                        (rev 0)
+++ trunk/src/jar/querylang/java/org/mulgara/protocol/AbstractStreamedAnswer.java	2008-09-03 14:56:28 UTC (rev 1223)
@@ -0,0 +1,108 @@
+package org.mulgara.protocol;
+
+import java.io.IOException;
+import java.io.OutputStream;
+import java.io.OutputStreamWriter;
+import java.nio.charset.Charset;
+
+import org.jrdf.graph.BlankNode;
+import org.jrdf.graph.Literal;
+import org.jrdf.graph.URIReference;
+import org.mulgara.query.Answer;
+import org.mulgara.query.TuplesException;
+import org.mulgara.query.Variable;
+
+/**
+ * Converts an answer to a stream, acccording to the protocol of the implementing class.
+ *
+ * @created Sep 1, 2008
+ * @author Paul Gearon
+ * @copyright &copy; 2008 <a href="http://www.topazproject.org/">The Topaz Project</a>
+ * @licence <a href="{@docRoot}/../../LICENCE.txt">Open Software License v3.0</a>
+ */
+public abstract class AbstractStreamedAnswer {
+
+  /** The API {@link Answer} to convert to the stream. */
+  protected final Answer answer;
+
+  /** The number of columns in the Answer. */
+  protected final int width;
+
+  /** The {@link Variable}s in the Answer. */
+  protected final Variable[] vars;
+
+  /** The writer used for creating the XML. */
+  protected OutputStreamWriter s = null;
+
+  /** The byte output stream used for creating the XML. */
+  protected OutputStream output = null;
+
+  /** The charset encoding to use when writing to the output stream. */
+  Charset charset = Charset.defaultCharset();
+
+  /** Adds a literal to the stream */
+  protected abstract void addLiteral(Literal literal) throws IOException;
+
+  /** Adds a blank node to the stream */
+  protected abstract void addBNode(BlankNode bnode) throws IOException;
+
+  /** Adds a URI reference to the stream */
+  protected abstract void addURI(URIReference uri) throws IOException;
+
+  /** Adds a variable binding to the stream */
+  protected abstract void addBinding(Variable var, Object value) throws TuplesException, IOException;
+
+  /** Adds a single result to the stream */
+  protected abstract void addResult() throws TuplesException, IOException;
+
+  /** Adds all results to the stream */
+  protected abstract void addResults() throws TuplesException, IOException;
+
+  /** Adds a variable in the header to the stream */
+  protected abstract void addHeaderVariable(Variable var) throws IOException;
+
+  /** Adds the entire header to the stream */
+  protected abstract void addHeader() throws IOException;
+
+  /** Closes the document in the stream */
+  protected abstract void addDocFooter() throws IOException;
+
+  /** Oopens the document in the stream */
+  protected abstract void addDocHeader() throws IOException;
+
+
+  /**
+   * Creates the object around the answer and output stream.
+   * @param answer The answer to encode.
+   * @param output The stream to write to.
+   */
+  public AbstractStreamedAnswer(Answer answer, OutputStream output) {
+    this.answer = answer;
+    this.output = output;
+    width = (answer != null) ? answer.getNumberOfVariables() : 0;
+    vars = (answer != null) ? answer.getVariables() : null;
+  }
+
+  /**
+   * @see org.mulgara.protocol.StreamedXMLAnswer#emit()
+   */
+  public void emit() throws TuplesException, IOException {
+    if (s == null) {
+      s = new OutputStreamWriter(output, charset);
+      generate();
+      s.flush();
+    }
+  }
+
+  /**
+   * Generates the XML document in the {@link #s} buffer.
+   * @throws TuplesException Indicates an error accessing the Answer.
+   */
+  void generate() throws TuplesException, IOException {
+    addDocHeader();
+    addHeader();
+    addResults();
+    addDocFooter();
+  }
+
+}
\ No newline at end of file

Added: trunk/src/jar/querylang/java/org/mulgara/protocol/AbstractStreamedXMLAnswer.java
===================================================================
--- trunk/src/jar/querylang/java/org/mulgara/protocol/AbstractStreamedXMLAnswer.java	                        (rev 0)
+++ trunk/src/jar/querylang/java/org/mulgara/protocol/AbstractStreamedXMLAnswer.java	2008-09-03 14:56:28 UTC (rev 1223)
@@ -0,0 +1,182 @@
+package org.mulgara.protocol;
+
+import java.io.IOException;
+import java.io.OutputStream;
+import java.net.URI;
+import java.nio.charset.Charset;
+import java.util.HashMap;
+import java.util.Map;
+
+import org.jrdf.graph.BlankNode;
+import org.jrdf.graph.Literal;
+import org.jrdf.graph.URIReference;
+import org.mulgara.query.Answer;
+import org.mulgara.query.TuplesException;
+import org.mulgara.query.Variable;
+
+/**
+ * Basic functionality for emitting Answers as XML using raw text methods.
+ *
+ * @created Jul 9, 2008
+ * @author Paul Gearon
+ * @copyright &copy; 2008 <a href="http://www.topazproject.org/">The Topaz Project</a>
+ * @licence <a href="{@docRoot}/../../LICENCE.txt">Open Software License v3.0</a>
+ */
+public abstract class AbstractStreamedXMLAnswer extends AbstractStreamedAnswer implements StreamedXMLAnswer {
+
+  /** A single indent for use when pretty printing. */
+  static final private String SINGLE_INDENT_STR = "  ";
+
+  /** The size of indents to use for pretty printing. */
+  static final private int SINGLE_INDENT_SIZE = SINGLE_INDENT_STR.length();
+
+  /** The largest indent string allowed. */
+  static final private String MAX_INDENT = "\n            ";
+
+  /**
+   * An array of indent strings used by {@link #i(int)}.
+   * This is pre-generated to avoid creating them while emiting xml.
+   */
+  static final String[] INDENT = new String[MAX_INDENT.length() / SINGLE_INDENT_SIZE];
+
+  // Build the indents using the MAX_INDENT as a raw data source
+  static {
+    for (int i = 0; i < INDENT.length; i++) INDENT[i] = MAX_INDENT.substring(0, i * SINGLE_INDENT_SIZE + 1);
+  }
+
+  /** All the namespaces explicitly requested. */
+  Map<String,URI> namespaces = new HashMap<String,URI>();
+
+  /** Indicates that pretty printing should be used. */
+  boolean prettyPrint = true;
+ 
+  /**
+   * Create an XMLAnswer based on a given {@link Answer}.
+   * @param answer The Answer with the data to convert.
+   */
+  public AbstractStreamedXMLAnswer(Answer answer, OutputStream output) {
+    super(answer, output);
+  }
+
+  /**
+   * @see org.mulgara.protocol.StreamedXMLAnswer#setCharacterEncoding(java.lang.String)
+   */
+  public void setCharacterEncoding(String encoding) {
+    charset = Charset.forName(encoding);
+  }
+
+  /**
+   * @see org.mulgara.protocol.StreamedXMLAnswer#setCharacterEncoding(java.nio.Charset)
+   */
+  public void setCharacterEncoding(Charset charset) {
+    this.charset = charset;
+  }
+
+  /**
+   * @see org.mulgara.protocol.StreamedXMLAnswer#addNamespace(java.lang.String, java.net.URI)
+   */
+  public void addNamespace(String name, URI nsValue) {
+    namespaces.put(name, nsValue);
+    s = null;
+  }
+
+  /**
+   * @see org.mulgara.protocol.StreamedXMLAnswer#clearNamespaces()
+   */
+  public void clearNamespaces() {
+    namespaces.clear();
+    s = null;
+  }
+
+  /**
+   * @see org.mulgara.protocol.StreamedXMLAnswer#setPrettyPrint(boolean)
+   */
+  public void setPrettyPrint(boolean prettyPrint) {
+    this.prettyPrint = prettyPrint;
+    s = null;
+  }
+
+  /**
+   * When {@link #setPrettyPrint(boolean)} is set, returns a string containing a new line and an indentation.
+   * When pretty printing is off, then return an empty string.
+   * @param indent The number of indentations to use.
+   * @return A String containing the appropriate indentation, or an empty string if not pretty printing.
+   */
+  String i(int indent) {
+    if (!prettyPrint) return "";
+    if (indent < INDENT.length) return INDENT[indent];
+    StringBuilder sbi = new StringBuilder(MAX_INDENT);
+    for (int i = INDENT.length; i < indent; i++) sbi.append(SINGLE_INDENT_STR);
+    return sbi.toString();
+  }
+
+  /**
+   * Adds the document header to the buffer.
+   */
+  @Override
+  protected abstract void addDocHeader() throws IOException;
+
+  /**
+   * Adds the document footer to the buffer.
+   */
+  @Override
+  protected abstract void addDocFooter() throws IOException;
+
+  /**
+   * Adds the header to the document data.
+   */
+  @Override
+  protected abstract void addHeader() throws IOException;
+
+  /**
+   * Adds the results to the document data.
+   * @throws TuplesException Indicates an error accessing the Answer.
+   */
+  @Override
+  protected abstract void addResults() throws TuplesException, IOException;
+
+  /**
+   * Adds a variable to the header.
+   * @param var The variable to add.
+   */
+  @Override
+  protected abstract void addHeaderVariable(Variable var) throws IOException;
+
+  /**
+   * Adds a single result, based on the current result in the {@link #answer}.
+   * @throws TuplesException Indicates an error accessing the Answer.
+   */
+  @Override
+  protected abstract void addResult() throws TuplesException, IOException;
+
+  /**
+   * Adds a single binding from the current result in the {@link #answer}.
+   * @param var The bound variable.
+   * @param value The value bound to the variable.
+   * @throws TuplesException Indicates an error accessing the Answer.
+   */
+  @Override
+  protected abstract void addBinding(Variable var, Object value) throws TuplesException, IOException;
+
+  /**
+   * Adds a single URI to the buffer.
+   * @param uri The URIReference for the URI to add.
+   */
+  @Override
+  protected abstract void addURI(URIReference uri) throws IOException;
+
+  /**
+   * Adds a single blank node to the buffer.
+   * @param uri The blank node to add.
+   */
+  @Override
+  protected abstract void addBNode(BlankNode bnode) throws IOException;
+
+  /**
+   * Adds a single Literal to the buffer.
+   * @param literal The Literal to add.
+   */
+  @Override
+  protected abstract void addLiteral(Literal literal) throws IOException;
+
+}
\ No newline at end of file

Added: trunk/src/jar/querylang/java/org/mulgara/protocol/StreamedAnswer.java
===================================================================
--- trunk/src/jar/querylang/java/org/mulgara/protocol/StreamedAnswer.java	                        (rev 0)
+++ trunk/src/jar/querylang/java/org/mulgara/protocol/StreamedAnswer.java	2008-09-03 14:56:28 UTC (rev 1223)
@@ -0,0 +1,23 @@
+package org.mulgara.protocol;
+
+import java.io.IOException;
+
+import org.mulgara.query.TuplesException;
+
+/**
+ * Represents an Answer that can be emitted to a stream.
+ *
+ * @created Sep 8, 2008
+ * @author Paul Gearon
+ * @copyright &copy; 2008 <a href="http://www.topazproject.org/">The Topaz Project</a>
+ * @licence <a href="{@docRoot}/../../LICENCE.txt">Open Software License v3.0</a>
+ */
+public interface StreamedAnswer {
+
+  /**
+   * Converts the Answer to a String and send to output.
+   * @throws TuplesException Indicates an error accessing the Answer.
+   */
+  public void emit() throws TuplesException, IOException;
+
+}
\ No newline at end of file

Added: trunk/src/jar/querylang/java/org/mulgara/protocol/StreamedJSONAnswer.java
===================================================================
--- trunk/src/jar/querylang/java/org/mulgara/protocol/StreamedJSONAnswer.java	                        (rev 0)
+++ trunk/src/jar/querylang/java/org/mulgara/protocol/StreamedJSONAnswer.java	2008-09-03 14:56:28 UTC (rev 1223)
@@ -0,0 +1,23 @@
+package org.mulgara.protocol;
+
+import java.io.IOException;
+
+import org.mulgara.query.TuplesException;
+
+/**
+ * Represents an Answer that can be emitted as JSON to a stream.
+ *
+ * @created Sept 1, 2008
+ * @author Paul Gearon
+ * @copyright &copy; 2008 <a href="http://www.topazproject.org/">The Topaz Project</a>
+ * @licence <a href="{@docRoot}/../../LICENCE.txt">Open Software License v3.0</a>
+ */
+public interface StreamedJSONAnswer extends StreamedAnswer {
+
+  /**
+   * Converts the Answer to a JSON String and send to output.
+   * @throws TuplesException Indicates an error accessing the Answer.
+   */
+  public void emit() throws TuplesException, IOException;
+
+}
\ No newline at end of file

Added: trunk/src/jar/querylang/java/org/mulgara/protocol/StreamedSparqlJSONAnswer.java
===================================================================
--- trunk/src/jar/querylang/java/org/mulgara/protocol/StreamedSparqlJSONAnswer.java	                        (rev 0)
+++ trunk/src/jar/querylang/java/org/mulgara/protocol/StreamedSparqlJSONAnswer.java	2008-09-03 14:56:28 UTC (rev 1223)
@@ -0,0 +1,188 @@
+/*
+ * The contents of this file are subject to the Open Software License
+ * Version 3.0 (the "License"); you may not use this file except in
+ * compliance with the License. You may obtain a copy of the License at
+ * http://www.opensource.org/licenses/osl-3.0.txt
+ *
+ * Software distributed under the License is distributed on an "AS IS"
+ * basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
+ * the License for the specific language governing rights and limitations
+ * under the License.
+ */
+
+package org.mulgara.protocol;
+
+import java.io.IOException;
+import java.io.OutputStream;
+import java.io.OutputStreamWriter;
+import java.net.URI;
+
+import org.jrdf.graph.BlankNode;
+import org.jrdf.graph.Literal;
+import org.jrdf.graph.URIReference;
+import org.mulgara.query.Answer;
+import org.mulgara.query.BooleanAnswer;
+import org.mulgara.query.TuplesException;
+import org.mulgara.query.Variable;
+
+/**
+ * Represents an Answer as JSON.
+ * The format is specified at: {@link http://www.w3.org/TR/rdf-sparql-json-res/}
+ *
+ * @created Sep 1, 2008
+ * @author Paul Gearon
+ * @copyright &copy; 2008 <a href="http://www.topazproject.org/">The Topaz Project</a>
+ * @licence <a href="{@docRoot}/../../LICENCE.txt">Open Software License v3.0</a>
+ */
+public class StreamedSparqlJSONAnswer extends AbstractStreamedAnswer implements StreamedJSONAnswer {
+
+  /** Additional metadata about the results. */
+  URI additionalMetadata = null;
+  
+  /** Boolean answer. */
+  boolean booleanResult = false;
+
+  /** Internal flag to indicate that a comma may be needed. */
+  boolean prependComma = false;
+
+  /**
+   * Creates an XML Answer conforming to SPARQL XML results.
+   * @param answer The Answer to wrap.
+   */
+  public StreamedSparqlJSONAnswer(Answer answer, OutputStream output) {
+    super((answer instanceof BooleanAnswer) ? null : answer, output);
+    if (answer instanceof BooleanAnswer) booleanResult = ((BooleanAnswer)answer).getResult();
+  }
+
+  /**
+   * Creates an XML Answer with additional metadata.
+   * @param answer The Answer to wrap.
+   * @param metadata Additional metadata for the answer.
+   */
+  public StreamedSparqlJSONAnswer(Answer answer, URI metadata, OutputStream output) {
+    this(answer, output);
+    additionalMetadata = metadata;
+  }
+
+  /**
+   * Creates an XML Answer conforming to SPARQL XML results.
+   * @param result The boolean result to encode.
+   */
+  public StreamedSparqlJSONAnswer(boolean result, OutputStream output) {
+    super(null, output);
+    booleanResult = result;
+  }
+
+  /**
+   * Creates an XML Answer with additional metadata.
+   * @param result The boolean result to encode.
+   * @param metadata Additional metadata for the answer.
+   */
+  public StreamedSparqlJSONAnswer(boolean result, URI metadata, OutputStream output) {
+    super(null, output);
+    booleanResult = result;
+    additionalMetadata = metadata;
+  }
+
+  /** {@inheritDoc} */
+  protected void addDocHeader() throws IOException {
+    s.append("{ ");
+  }
+
+  /** {@inheritDoc} */
+  protected void addDocFooter() throws IOException {
+    s.append(" }");
+  }
+
+  /** {@inheritDoc} */
+  protected void addHeader() throws IOException {
+    s.append("\"head\": {");
+    boolean wroteVars = false;
+    if (answer != null && answer.getVariables() != null) {
+      s.append("\"vars\": [");
+      prependComma = false;
+      for (Variable v: answer.getVariables()) addHeaderVariable(v);
+      s.append("]");
+      wroteVars = true;
+    }
+    if (additionalMetadata != null) {
+      if (wroteVars) s.append(", ");
+      s.append("\"link\": [\"").append(additionalMetadata.toString()).append("\"]");
+    }
+    s.append("}, ");
+  }
+
+  /** {@inheritDoc} */
+  protected void addHeaderVariable(Variable var) throws IOException {
+    comma().append("\"").append(var.getName()).append("\"");
+  }
+
+  /** {@inheritDoc} */
+  protected void addResults() throws TuplesException, IOException {
+    if (answer != null) {
+      comma().append("\"results\": { ");
+      s.append("\"bindings\": [ ");
+      answer.beforeFirst();
+      prependComma = false;
+      while (answer.next()) addResult();
+      s.append(" ] }");
+    } else {
+      comma().append("\"boolean\": ").append(Boolean.toString(booleanResult));
+    }
+  }
+
+  /** {@inheritDoc} */
+  protected void addResult() throws TuplesException, IOException {
+    comma().append("{ ");
+    prependComma = false;
+    for (int c = 0; c < width; c++) addBinding(vars[c], answer.getObject(c));
+    s.append(" }");
+  }
+
+  /**
+   * {@inheritDoc}
+   * No binding will be emitted if the value is null (unbound).
+   */
+  protected void addBinding(Variable var, Object value) throws IOException {
+    if (value != null) {
+      comma().append("\"").append(var.getName()).append("\": { ");
+      // no dynamic dispatch, so use if/then
+      if (value instanceof URIReference) addURI((URIReference)value);
+      else if (value instanceof Literal) addLiteral((Literal)value);
+      else if (value instanceof BlankNode) addBNode((BlankNode)value);
+      else throw new IllegalArgumentException("Unable to create a SPARQL response with an answer containing: " + value.getClass().getSimpleName());
+      s.append(" }");
+    }
+  }
+
+  /** {@inheritDoc} */
+  protected void addURI(URIReference uri) throws IOException {
+    s.append("\"type\": \"uri\", \"value\": \"").append(uri.getURI().toString()).append("\"");
+  }
+
+  /** {@inheritDoc} */
+  protected void addBNode(BlankNode bnode) throws IOException {
+    s.append("\"type\": \"bnode\", \"value\": \"").append(bnode.toString()).append("\"");
+  }
+
+  /** {@inheritDoc} */
+  protected void addLiteral(Literal literal) throws IOException {
+    if (literal.getDatatype() != null) {
+      s.append("\"type\": \"typed-literal\", \"datatype\": \"").append(literal.getDatatype().toString()).append("\", ");
+    } else {
+      s.append("\"type\": \"literal\", ");
+      if (literal.getLanguage() != null) s.append(" \"xml:lang\": \"").append(literal.getLanguage()).append("\", ");
+    }
+    s.append("\"value\": \"").append(literal.getLexicalForm()).append("\"");
+  }
+
+  /**
+   * Adds a comma if needed at this point. Commas are usually needed.
+   * @throws IOException An error writing to the stream.
+   */
+  protected OutputStreamWriter comma() throws IOException {
+    if (prependComma) s.append(", ");
+    prependComma = true;
+    return s;
+  }
+}

Added: trunk/src/jar/querylang/java/org/mulgara/protocol/StreamedSparqlJSONAnswerUnitTest.java
===================================================================
--- trunk/src/jar/querylang/java/org/mulgara/protocol/StreamedSparqlJSONAnswerUnitTest.java	                        (rev 0)
+++ trunk/src/jar/querylang/java/org/mulgara/protocol/StreamedSparqlJSONAnswerUnitTest.java	2008-09-03 14:56:28 UTC (rev 1223)
@@ -0,0 +1,592 @@
+/*
+ * The contents of this file are subject to the Open Software License
+ * Version 3.0 (the "License"); you may not use this file except in
+ * compliance with the License. You may obtain a copy of the License at
+ * http://www.opensource.org/licenses/osl-3.0.txt
+ *
+ * Software distributed under the License is distributed on an "AS IS"
+ * basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
+ * the License for the specific language governing rights and limitations
+ * under the License.
+ */
+
+package org.mulgara.protocol;
+
+
+// JUnit
+import junit.framework.*;
+
+// Java 2 standard packages
+import java.io.ByteArrayOutputStream;
+import java.net.URI;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+// Locally written packages
+import org.mulgara.query.AnswerImpl;
+import org.mulgara.query.BooleanAnswer;
+import org.mulgara.query.Variable;
+import org.mulgara.query.rdf.BlankNodeImpl;
+import org.mulgara.query.rdf.LiteralImpl;
+import org.mulgara.query.rdf.URIReferenceImpl;
+import org.mulgara.util.ResultSetRow;
+import org.mulgara.util.TestResultSet;
+
+/**
+ * Test case for {@link StreamedSparqlJSONAnswer}.
+ *
+ * @created Sep 1, 2008
+ * @author Paul Gearon
+ * @copyright &copy; 2008 <a href="http://www.topazproject.org/">The Topaz Project</a>
+ * @licence <a href="{@docRoot}/../../LICENCE.txt">Open Software License v3.0</a>
+ */
+public class StreamedSparqlJSONAnswerUnitTest extends TestCase {
+
+  /**
+   * Test instance.
+   * <table>
+   *   <thead>
+   *     <tr><th>x</th> <th>y</th></tr>
+   *   </thead>
+   *   <tbody>
+   *     <tr><td>X1</td><td>Y1</td></tr>
+   *     <tr><td>X2</td><td>Y2</td></tr>
+   *   </tbody>
+   * </table>
+   */
+  private AnswerImpl answer;
+
+  private AnswerImpl empty;
+
+  private ByteArrayOutputStream output;
+
+  private ByteArrayOutputStream outputb;
+
+  private ByteArrayOutputStream outputa;
+
+  private static final URI REL_URI = URI.create("rel/uri");
+  
+  /**
+   * Constructs a new answer test with the given name.
+   * @param name the name of the test
+   */
+  public StreamedSparqlJSONAnswerUnitTest(String name) {
+    super(name);
+  }
+
+  /**
+   * Hook for test runner to obtain a test suite from.
+   * @return The test suite
+   */
+  public static Test suite() {
+    TestSuite suite = new TestSuite();
+    // TODO: tests not yet written
+//    suite.addTest(new StreamedSparqlJSONAnswerUnitTest("testEmptyConstructor"));
+//    suite.addTest(new StreamedSparqlJSONAnswerUnitTest("testEmptyConstructorPretty"));
+//    suite.addTest(new StreamedSparqlJSONAnswerUnitTest("testBooleanAnswer"));
+//    suite.addTest(new StreamedSparqlJSONAnswerUnitTest("testBooleanAnswerPretty"));
+//    suite.addTest(new StreamedSparqlJSONAnswerUnitTest("testPrettyPrint"));
+//    suite.addTest(new StreamedSparqlJSONAnswerUnitTest("testCompactPrint"));
+//    suite.addTest(new StreamedSparqlJSONAnswerUnitTest("testPrettyPrintVariations"));
+//    suite.addTest(new StreamedSparqlJSONAnswerUnitTest("testCompactPrintVariations"));
+    return suite;
+  }
+
+  /**
+   * Default text runner.
+   * @param args The command line arguments
+   */
+  public static void main(String[] args) {
+    junit.textui.TestRunner.run(suite());
+  }
+
+
+  /**
+   * Tests an empty answer.
+   * @throws Exception On error
+   */
+  public void testEmptyConstructor() throws Exception {
+    StreamedSparqlJSONAnswer a = new StreamedSparqlJSONAnswer(empty, output);
+    a.emit();
+    assertEquals(getEmpty(), output.toString());
+  }
+
+
+  /**
+   * Tests an empty answer.
+   * @throws Exception On error
+   */
+  public void testEmptyConstructorPretty() throws Exception {
+    StreamedSparqlJSONAnswer a = new StreamedSparqlJSONAnswer(empty, output);
+    a.emit();
+    assertEquals(getEmptyP(), output.toString());
+
+    output.reset();
+    a.emit();
+    assertEquals(getEmpty(), output.toString());
+  }
+
+
+  /**
+   * Tests a boolean answer.
+   * @throws Exception On error
+   */
+  public void testBooleanAnswer() throws Exception {
+    StreamedSparqlJSONAnswer a = new StreamedSparqlJSONAnswer(true, output);
+    a.emit();
+    assertEquals(getTrue(), output.toString());
+
+    a = new StreamedSparqlJSONAnswer(new BooleanAnswer(true), output);
+    output.reset();
+    a.emit();
+    assertEquals(getTrue(), output.toString());
+  }
+
+
+  /**
+   * Tests a boolean answer.
+   * @throws Exception On error
+   */
+  public void testBooleanAnswerPretty() throws Exception {
+    StreamedSparqlJSONAnswer a = new StreamedSparqlJSONAnswer(true, output);
+    a.emit();
+    assertEquals(getTrueP(), output.toString());
+    output.reset();
+    a.emit();
+    assertEquals(getTrue(), output.toString());
+  }
+
+
+  /**
+   * Test main structure.
+   */
+  public void testPrettyPrint() throws Exception {
+    StreamedSparqlJSONAnswer a = new StreamedSparqlJSONAnswer(answer, output);
+    a.emit();
+    assertEquals(getAnswerP(), output.toString());
+  }
+
+
+  /**
+   * Test main structure.
+   */
+  public void testCompactPrint() throws Exception {
+    StreamedSparqlJSONAnswer a = new StreamedSparqlJSONAnswer(answer, output);
+    a.emit();
+    assertEquals(getAnswer(), output.toString());
+  }
+
+
+  /**
+   * Tests the variations of answers.
+   * @throws Exception On error
+   */
+  public void testCompactPrintVariations() throws Exception {
+    StreamedSparqlJSONAnswer e = new StreamedSparqlJSONAnswer(empty, REL_URI, output);
+    StreamedSparqlJSONAnswer b = new StreamedSparqlJSONAnswer(true, REL_URI, outputb);
+    StreamedSparqlJSONAnswer a = new StreamedSparqlJSONAnswer(answer, REL_URI, outputa);
+
+    // No namespaces, no schema, meta set
+    e.emit();
+    b.emit();
+    a.emit();
+    assertEquals(getEmpty(false, true), output.toString());
+    assertEquals(getTrue(false, true), outputb.toString());
+    assertEquals(getAnswer(false, true), outputa.toString());
+
+    // No namespaces, schema set, meta set
+    output.reset();
+    outputb.reset();
+    outputa.reset();
+    e.emit();
+    b.emit();
+    a.emit();
+    assertEquals(getEmpty(true, true), output.toString());
+    assertEquals(getTrue(true, true), outputb.toString());
+    assertEquals(getAnswer(true, true), outputa.toString());
+
+    // Namespaces set, schema set, meta set
+    output.reset();
+    outputb.reset();
+    outputa.reset();
+    e.emit();
+    b.emit();
+    a.emit();
+    assertEquals(getEmpty(true, true), output.toString());
+    assertEquals(getTrue(true, true), outputb.toString());
+    assertEquals(getAnswer(true, true), outputa.toString());
+
+    // Namespaces set, no schema, meta set
+    output.reset();
+    outputb.reset();
+    outputa.reset();
+    e.emit();
+    b.emit();
+    a.emit();
+    assertEquals(getEmpty(false, true), output.toString());
+    assertEquals(getTrue(false, true), outputb.toString());
+    assertEquals(getAnswer(false, true), outputa.toString());
+    
+    e = new StreamedSparqlJSONAnswer(empty, output);
+    b = new StreamedSparqlJSONAnswer(true, outputb);
+    a = new StreamedSparqlJSONAnswer(answer, outputa);
+
+    // No namespaces, schema set, no meta
+    output.reset();
+    outputb.reset();
+    outputa.reset();
+    e.emit();
+    b.emit();
+    a.emit();
+    assertEquals(getEmpty(true, false), output.toString());
+    assertEquals(getTrue(true, false), outputb.toString());
+    assertEquals(getAnswer(true, false), outputa.toString());
+
+    // Namespaces set, schema set, no meta
+    output.reset();
+    outputb.reset();
+    outputa.reset();
+    e.emit();
+    b.emit();
+    a.emit();
+    assertEquals(getEmpty(true, false), output.toString());
+    assertEquals(getTrue(true, false), outputb.toString());
+    assertEquals(getAnswer(true, false), outputa.toString());
+
+    // Namespaces set, no schema, no meta
+    output.reset();
+    outputb.reset();
+    outputa.reset();
+    e.emit();
+    b.emit();
+    a.emit();
+    assertEquals(getEmpty(false, false), output.toString());
+    assertEquals(getTrue(false, false), outputb.toString());
+    assertEquals(getAnswer(false, false), outputa.toString());
+
+    // No Namespaces, no schema, no meta
+    output.reset();
+    outputb.reset();
+    outputa.reset();
+    e.emit();
+    b.emit();
+    a.emit();
+    assertEquals(getEmpty(false, false), output.toString());
+    assertEquals(getTrue(false, false), outputb.toString());
+    assertEquals(getAnswer(false, false), outputa.toString());
+
+    // No namespaces, schema set, no meta
+    output.reset();
+    outputb.reset();
+    outputa.reset();
+    e.emit();
+    b.emit();
+    a.emit();
+    assertEquals(getEmpty(true, false), output.toString());
+    assertEquals(getTrue(true, false), outputb.toString());
+    assertEquals(getAnswer(true, false), outputa.toString());
+  }
+
+
+  /**
+   * Tests the variations of answers.
+   * @throws Exception On error
+   */
+  public void testPrettyPrintVariations() throws Exception {
+    StreamedSparqlJSONAnswer e = new StreamedSparqlJSONAnswer(empty, REL_URI, output);
+    StreamedSparqlJSONAnswer b = new StreamedSparqlJSONAnswer(true, REL_URI, outputb);
+    StreamedSparqlJSONAnswer a = new StreamedSparqlJSONAnswer(answer, REL_URI, outputa);
+
+    // No namespaces, no schema, meta set
+    e.emit();
+    b.emit();
+    a.emit();
+    assertEquals(getEmptyP(false, true), output.toString());
+    assertEquals(getTrueP(false, true), outputb.toString());
+    assertEquals(getAnswerP(false, true), outputa.toString());
+
+    // No namespaces, schema set, meta set
+    output.reset();
+    outputb.reset();
+    outputa.reset();
+    e.emit();
+    b.emit();
+    a.emit();
+    assertEquals(getEmptyP(true, true), output.toString());
+    assertEquals(getTrueP(true, true), outputb.toString());
+    assertEquals(getAnswerP(true, true), outputa.toString());
+
+    // Namespaces set, schema set, meta set
+    output.reset();
+    outputb.reset();
+    outputa.reset();
+    e.emit();
+    b.emit();
+    a.emit();
+    assertEquals(getEmptyP(true, true), output.toString());
+    assertEquals(getTrueP(true, true), outputb.toString());
+    assertEquals(getAnswerP(true, true), outputa.toString());
+
+    // Namespaces set, no schema, meta set
+    output.reset();
+    outputb.reset();
+    outputa.reset();
+    e.emit();
+    b.emit();
+    a.emit();
+    assertEquals(getEmptyP(false, true), output.toString());
+    assertEquals(getTrueP(false, true), outputb.toString());
+    assertEquals(getAnswerP(false, true), outputa.toString());
+    
+    e = new StreamedSparqlJSONAnswer(empty, output);
+    b = new StreamedSparqlJSONAnswer(true, outputb);
+    a = new StreamedSparqlJSONAnswer(answer, outputa);
+
+    // No namespaces, schema set, no meta
+    output.reset();
+    outputb.reset();
+    outputa.reset();
+    e.emit();
+    b.emit();
+    a.emit();
+    assertEquals(getEmptyP(true, false), output.toString());
+    assertEquals(getTrueP(true, false), outputb.toString());
+    assertEquals(getAnswerP(true, false), outputa.toString());
+
+    // Namespaces set, schema set, no meta
+    output.reset();
+    outputb.reset();
+    outputa.reset();
+    e.emit();
+    b.emit();
+    a.emit();
+    assertEquals(getEmptyP(true, false), output.toString());
+    assertEquals(getTrueP(true, false), outputb.toString());
+    assertEquals(getAnswerP(true, false), outputa.toString());
+
+    // Namespaces set, no schema, no meta
+    output.reset();
+    outputb.reset();
+    outputa.reset();
+    e.emit();
+    b.emit();
+    a.emit();
+    assertEquals(getEmptyP(false, false), output.toString());
+    assertEquals(getTrueP(false, false), outputb.toString());
+    assertEquals(getAnswerP(false, false), outputa.toString());
+
+    // No Namespaces, no schema, no meta
+    output.reset();
+    outputb.reset();
+    outputa.reset();
+    e.emit();
+    b.emit();
+    a.emit();
+    assertEquals(getEmptyP(false, false), output.toString());
+    assertEquals(getTrueP(false, false), outputb.toString());
+    assertEquals(getAnswerP(false, false), outputa.toString());
+
+    // No namespaces, schema set, no meta
+    output.reset();
+    outputb.reset();
+    outputa.reset();
+    e.emit();
+    b.emit();
+    a.emit();
+    assertEquals(getEmptyP(true, false), output.toString());
+    assertEquals(getTrueP(true, false), outputb.toString());
+    assertEquals(getAnswerP(true, false), outputa.toString());
+  }
+
+
+  /**
+   * Populate the test answer.
+   * @throws Exception Error setting up the ResultSet
+   */
+  protected void setUp() throws Exception {
+    TestResultSet trs1 = new TestResultSet(new String[] { "x", "y" });
+    ResultSetRow row;
+    row = new ResultSetRow(trs1);
+    row.setObject("x", new LiteralImpl("X1"));
+    row.setObject("y", new URIReferenceImpl(URI.create("urn:y1")));
+    trs1.addRow(row);
+    row = new ResultSetRow(trs1);
+    row.setObject("x", new LiteralImpl("X2", "en"));
+    row.setObject("y", new BlankNodeImpl(42));
+    trs1.addRow(row);
+    answer = new AnswerImpl(trs1);
+
+    List<Variable> variables = Arrays.asList(new Variable[] { new Variable("x") });
+    empty = new AnswerImpl(variables);
+    
+    output = new ByteArrayOutputStream();
+    outputb = new ByteArrayOutputStream();
+    outputa = new ByteArrayOutputStream();
+  }
+
+  /**
+   * Clean up the test answer.
+   */
+  public void tearDown() {
+    answer.close();
+    empty.close();
+  }
+
+  /////////////////////////////
+  // start of getEmpty variants
+  /////////////////////////////
+
+  private static String getEmpty() {
+    return getEmpty(false, false);
+  }
+
+  private static String getEmptyP() {
+    return getEmptyP(false, false);
+  }
+
+  private static String getEmpty(boolean schema, boolean meta) {
+    return getCommonStart(schema, meta) + EMPTY_BODY;
+  }
+
+  private static String getEmptyP(boolean schema, boolean meta) {
+    return getCommonStartP(schema, meta) + EMPTY_BODY_P;
+  }
+
+  ////////////////////////////
+  // start of getTrue variants
+  ////////////////////////////
+
+  private static String getTrue() {
+    return getTrue(false, false);
+  }
+
+  private static String getTrueP() {
+    return getTrueP(false, false);
+  }
+
+  private static String getTrue(boolean schema, boolean meta) {
+    return getCommonStart(schema, meta) + TRUE_BODY;
+  }
+
+  private static String getTrueP(boolean schema, boolean meta) {
+    return getCommonStartP(schema, meta) + TRUE_BODY_P;
+  }
+
+  //////////////////////////////
+  // start of getAnswer variants
+  //////////////////////////////
+
+  private static String getAnswer() {
+    return getAnswer(false, false);
+  }
+
+  private static String getAnswerP() {
+    return getAnswerP(false, false);
+  }
+
+  private static String getAnswer(boolean schema, boolean meta) {
+    return getShortCommonStart(schema) + ANSWER_VARS + getMeta(meta) + ANSWER_BODY;
+  }
+
+  private static String getAnswerP(boolean schema, boolean meta) {
+    return getShortCommonStartP(schema) + ANSWER_VARS_P + getMetaP(meta) + ANSWER_BODY_P;
+  }
+
+  ////////////////
+  // common header
+  ////////////////
+
+  private static String getCommonStart(boolean schema, boolean meta) {
+    return getShortCommonStart(schema) + getMeta(meta);
+  }
+
+  private static String getCommonStartP(boolean schema, boolean meta) {
+    return getShortCommonStartP(schema) + getMetaP(meta);
+  }
+
+  private static String getShortCommonStart(boolean schema) {
+    String result = DOC_HEAD + SPARQL_HEAD;
+    if (schema) result += " " + SPARQL_HEAD_ATTR;
+    result += ">" + EMPTY_HEAD;
+    return result;
+  }
+
+  private static String getShortCommonStartP(boolean schema) {
+    String result = DOC_HEAD + SPARQL_HEAD;
+    if (schema) result += "\n" + SPARQL_HEAD_INDENT + SPARQL_HEAD_ATTR;
+    result += ">\n  " + EMPTY_HEAD + "\n";
+    return result;
+  }
+
+  private static String getMeta(boolean meta) {
+    return meta ? HEAD_META : "";
+  }
+
+  private static String getMetaP(boolean meta) {
+    return meta ? HEAD_META_INDENT + HEAD_META + "\n" : "";
+  }
+
+  static final String DOC_HEAD = "<?xml version=\"1.0\"?>\n";
+
+  static final String SPARQL_HEAD = "<sparql xmlns=\"http://www.w3.org/2005/sparql-results#\"";
+
+  static final String SPARQL_HEAD_ATTR = "xsi:schemaLocation=\"http://www.w3.org/2007/SPARQL/result.xsd\"";
+
+  static final String SPARQL_HEAD_INDENT = "        ";
+  
+  static final String HEAD_META = "<link href=\"" + REL_URI + "\"/>";
+
+  static final String HEAD_META_INDENT = "    ";
+
+  static final String EMPTY_HEAD = "<head>";
+
+  static final String EMPTY_BODY = "</head><results></results></sparql>";
+
+  static final String EMPTY_BODY_P = "  </head>\n" +
+      "  <results>\n" +
+      "  </results>\n" +
+      "</sparql>";
+
+  static final String TRUE_BODY = "</head><boolean>true</boolean></sparql>";
+
+  static final String TRUE_BODY_P = "  </head>\n" +
+      "  <boolean>true</boolean>\n" +
+      "</sparql>";
+
+  static final String ANSWER_VARS = "<variable name=\"x\"/><variable name=\"y\"/>";
+
+  static final String ANSWER_BODY = "</head>" +
+      "<results>" +
+      "<result><binding name=\"x\"><literal>X1</literal></binding>" +
+      "<binding name=\"y\"><uri>urn:y1</uri></binding></result>" +
+      "<result><binding name=\"x\"><literal xml:lang=\"en\">X2</literal></binding>" +
+      "<binding name=\"y\"><bnode>_node42</bnode></binding></result>" +
+      "</results>" +
+      "</sparql>";
+
+  static final String ANSWER_VARS_P = "    <variable name=\"x\"/>\n" +
+      "    <variable name=\"y\"/>\n";
+
+  static final String ANSWER_BODY_P = "  </head>\n" +
+      "  <results>\n" +
+      "    <result>\n" +
+      "      <binding name=\"x\">\n" +
+      "        <literal>X1</literal>\n" +
+      "      </binding>\n" +
+      "      <binding name=\"y\">\n" +
+      "        <uri>urn:y1</uri>\n" +
+      "      </binding>\n" +
+      "    </result>\n" +
+      "    <result>\n" +
+      "      <binding name=\"x\">\n" +
+      "        <literal xml:lang=\"en\">X2</literal>\n" +
+      "      </binding>\n" +
+      "      <binding name=\"y\">\n" +
+      "        <bnode>_node42</bnode>\n" +
+      "      </binding>\n" +
+      "    </result>\n" +
+      "  </results>\n" +
+      "</sparql>";
+}

Added: trunk/src/jar/querylang/java/org/mulgara/protocol/StreamedSparqlXMLAnswer.java
===================================================================
--- trunk/src/jar/querylang/java/org/mulgara/protocol/StreamedSparqlXMLAnswer.java	                        (rev 0)
+++ trunk/src/jar/querylang/java/org/mulgara/protocol/StreamedSparqlXMLAnswer.java	2008-09-03 14:56:28 UTC (rev 1223)
@@ -0,0 +1,187 @@
+/*
+ * The contents of this file are subject to the Open Software License
+ * Version 3.0 (the "License"); you may not use this file except in
+ * compliance with the License. You may obtain a copy of the License at
+ * http://www.opensource.org/licenses/osl-3.0.txt
+ *
+ * Software distributed under the License is distributed on an "AS IS"
+ * basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
+ * the License for the specific language governing rights and limitations
+ * under the License.
+ */
+
+package org.mulgara.protocol;
+
+import java.io.IOException;
+import java.io.OutputStream;
+import java.net.URI;
+import java.util.Map;
+
+import org.jrdf.graph.BlankNode;
+import org.jrdf.graph.Literal;
+import org.jrdf.graph.URIReference;
+import org.mulgara.query.Answer;
+import org.mulgara.query.BooleanAnswer;
+import org.mulgara.query.TuplesException;
+import org.mulgara.query.Variable;
+
+/**
+ * Represents an Answer as XML.
+ * The format is specified at: {@link http://www.w3.org/TR/rdf-sparql-XMLres/}
+ *
+ * @created Jul 8, 2008
+ * @author Paul Gearon
+ * @copyright &copy; 2008 <a href="http://www.topazproject.org/">The Topaz Project</a>
+ * @licence <a href="{@docRoot}/../../LICENCE.txt">Open Software License v3.0</a>
+ */
+public class StreamedSparqlXMLAnswer extends AbstractStreamedXMLAnswer {
+
+  /** Indent to use for namespaces in the document header. */
+  private static final String HEADER_INDENT = "\n        ";
+
+  /** Indicates that the W3C Schema should be used. */
+  boolean useW3CSchema = false;
+
+  /** Additional metadata about the results. */
+  URI additionalMetadata = null;
+  
+  /** Boolean answer. */
+  boolean booleanResult = false;
+
+  /**
+   * Creates an XML Answer conforming to SPARQL XML results.
+   * @param answer The Answer to wrap.
+   */
+  public StreamedSparqlXMLAnswer(Answer answer, OutputStream output) {
+    super((answer instanceof BooleanAnswer) ? null : answer, output);
+    if (answer instanceof BooleanAnswer) booleanResult = ((BooleanAnswer)answer).getResult();
+  }
+
+  /**
+   * Creates an XML Answer with additional metadata.
+   * @param answer The Answer to wrap.
+   * @param metadata Additional metadata for the answer.
+   */
+  public StreamedSparqlXMLAnswer(Answer answer, URI metadata, OutputStream output) {
+    this(answer, output);
+    additionalMetadata = metadata;
+  }
+
+  /**
+   * Creates an XML Answer conforming to SPARQL XML results.
+   * @param result The boolean result to encode.
+   */
+  public StreamedSparqlXMLAnswer(boolean result, OutputStream output) {
+    super(null, output);
+    booleanResult = result;
+  }
+
+  /**
+   * Creates an XML Answer with additional metadata.
+   * @param result The boolean result to encode.
+   * @param metadata Additional metadata for the answer.
+   */
+  public StreamedSparqlXMLAnswer(boolean result, URI metadata, OutputStream output) {
+    super(null, output);
+    booleanResult = result;
+    additionalMetadata = metadata;
+  }
+
+  /**
+   * Set this XMLAnswer to use the W3C Schema for SPARQL results.
+   * @param use Set to <code>true</code> if the W3C schema should be used.
+   */
+  public void useW3CSchema(boolean use) {
+    useW3CSchema = use;
+    s = null;
+  }
+
+  /** {@inheritDoc} */
+  protected void addDocHeader() throws IOException {
+    s.append("<?xml version=\"1.0\"?>\n");
+    s.append("<sparql xmlns=\"http://www.w3.org/2005/sparql-results#\"");
+    for (Map.Entry<String,URI> ns: namespaces.entrySet()) {
+      s.append(prettyPrint ? HEADER_INDENT : " ");
+      s.append(ns.getKey()).append("=\"").append(ns.getValue().toString()).append("\"");
+    }
+    if (useW3CSchema) s.append(prettyPrint ? HEADER_INDENT : " ").append("xsi:schemaLocation=\"http://www.w3.org/2007/SPARQL/result.xsd\"");
+    s.append(">");
+  }
+
+  /** {@inheritDoc} */
+  protected void addDocFooter() throws IOException {
+    s.append(i(0)).append("</sparql>");
+  }
+
+  /** {@inheritDoc} */
+  protected void addHeader() throws IOException {
+    s.append(i(1)).append("<head>");
+    if (answer != null && answer.getVariables() != null) {
+      for (Variable v: answer.getVariables()) addHeaderVariable(v);
+    }
+    if (additionalMetadata != null) {
+      s.append(i(2)).append("<link href=\"").append(additionalMetadata.toString()).append("\"/>");
+    }
+    s.append(i(1)).append("</head>");
+  }
+
+  /** {@inheritDoc} */
+  protected void addHeaderVariable(Variable var) throws IOException {
+    s.append(i(2)).append("<variable name=\"");
+    s.append(var.getName()).append("\"/>");
+  }
+
+  /** {@inheritDoc} */
+  protected void addResults() throws TuplesException, IOException {
+    if (answer != null) {
+      s.append(i(1)).append("<results>");
+      answer.beforeFirst();
+      while (answer.next()) addResult();
+      s.append(i(1)).append("</results>");
+    } else {
+      s.append(i(1)).append("<boolean>").append(Boolean.toString(booleanResult)).append("</boolean>");
+    }
+  }
+
+  /** {@inheritDoc} */
+  protected void addResult() throws TuplesException, IOException {
+    s.append(i(2)).append("<result>");
+    for (int c = 0; c < width; c++) addBinding(vars[c], answer.getObject(c));
+    s.append(i(2)).append("</result>");
+  }
+
+  /**
+   * {@inheritDoc}
+   * No binding will be emitted if the value is null (unbound).
+   */
+  protected void addBinding(Variable var, Object value) throws IOException {
+    if (value != null) {
+      s.append(i(3)).append("<binding name=\"").append(var.getName()).append("\">");
+      // no dynamic dispatch, so use if/then
+      if (value instanceof URIReference) addURI((URIReference)value);
+      else if (value instanceof Literal) addLiteral((Literal)value);
+      else if (value instanceof BlankNode) addBNode((BlankNode)value);
+      else throw new IllegalArgumentException("Unable to create a SPARQL response with an answer containing: " + value.getClass().getSimpleName());
+      s.append(i(3)).append("</binding>");
+    }
+  }
+
+  /** {@inheritDoc} */
+  protected void addURI(URIReference uri) throws IOException {
+    s.append(i(4)).append("<uri>").append(uri.getURI().toString()).append("</uri>");
+  }
+
+  /** {@inheritDoc} */
+  protected void addBNode(BlankNode bnode) throws IOException {
+    s.append(i(4)).append("<bnode>").append(bnode.toString()).append("</bnode>");
+  }
+
+  /** {@inheritDoc} */
+  protected void addLiteral(Literal literal) throws IOException {
+    s.append(i(4)).append("<literal");
+    if (literal.getLanguage() != null) s.append(" xml:lang=\"").append(literal.getLanguage()).append("\"");
+    else if (literal.getDatatype() != null) s.append(" datatype=\"").append(literal.getDatatype().toString()).append("\"");
+    s.append(">").append(literal.getLexicalForm()).append("</literal>");
+  }
+
+}

Added: trunk/src/jar/querylang/java/org/mulgara/protocol/StreamedSparqlXMLAnswerUnitTest.java
===================================================================
--- trunk/src/jar/querylang/java/org/mulgara/protocol/StreamedSparqlXMLAnswerUnitTest.java	                        (rev 0)
+++ trunk/src/jar/querylang/java/org/mulgara/protocol/StreamedSparqlXMLAnswerUnitTest.java	2008-09-03 14:56:28 UTC (rev 1223)
@@ -0,0 +1,702 @@
+/*
+ * The contents of this file are subject to the Open Software License
+ * Version 3.0 (the "License"); you may not use this file except in
+ * compliance with the License. You may obtain a copy of the License at
+ * http://www.opensource.org/licenses/osl-3.0.txt
+ *
+ * Software distributed under the License is distributed on an "AS IS"
+ * basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
+ * the License for the specific language governing rights and limitations
+ * under the License.
+ */
+
+package org.mulgara.protocol;
+
+
+// JUnit
+import junit.framework.*;
+
+// Java 2 standard packages
+import java.io.ByteArrayOutputStream;
+import java.net.URI;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+// Locally written packages
+import org.mulgara.query.AnswerImpl;
+import org.mulgara.query.BooleanAnswer;
+import org.mulgara.query.Variable;
+import org.mulgara.query.rdf.BlankNodeImpl;
+import org.mulgara.query.rdf.LiteralImpl;
+import org.mulgara.query.rdf.URIReferenceImpl;
+import org.mulgara.util.ResultSetRow;
+import org.mulgara.util.TestResultSet;
+
+/**
+ * Test case for {@link StreamedSparqlXMLAnswer}.
+ *
+ * @created Jul 8, 2008
+ * @author Paul Gearon
+ * @copyright &copy; 2008 <a href="http://www.topazproject.org/">The Topaz Project</a>
+ * @licence <a href="{@docRoot}/../../LICENCE.txt">Open Software License v3.0</a>
+ */
+public class StreamedSparqlXMLAnswerUnitTest extends TestCase {
+
+  /**
+   * Test instance.
+   * <table>
+   *   <thead>
+   *     <tr><th>x</th> <th>y</th></tr>
+   *   </thead>
+   *   <tbody>
+   *     <tr><td>X1</td><td>Y1</td></tr>
+   *     <tr><td>X2</td><td>Y2</td></tr>
+   *   </tbody>
+   * </table>
+   */
+  private AnswerImpl answer;
+
+  private AnswerImpl empty;
+
+  private Map<String,String> namesp = new HashMap<String,String>();
+
+  private ByteArrayOutputStream output;
+
+  private ByteArrayOutputStream outputb;
+
+  private ByteArrayOutputStream outputa;
+
+  private static final URI REL_URI = URI.create("rel/uri");
+  
+  private static final String NAME1 = "ns1";
+
+  private static final String NAME2 = "ns2";
+
+  private static final URI NS1 = URI.create("http://mulgara.org/test/ns1");
+
+  private static final URI NS2 = URI.create("http://mulgara.org/test/ns2");
+
+  /**
+   * Constructs a new answer test with the given name.
+   * @param name the name of the test
+   */
+  public StreamedSparqlXMLAnswerUnitTest(String name) {
+    super(name);
+  }
+
+  /**
+   * Hook for test runner to obtain a test suite from.
+   * @return The test suite
+   */
+  public static Test suite() {
+    TestSuite suite = new TestSuite();
+    suite.addTest(new StreamedSparqlXMLAnswerUnitTest("testEmptyConstructor"));
+    suite.addTest(new StreamedSparqlXMLAnswerUnitTest("testEmptyConstructorPretty"));
+    suite.addTest(new StreamedSparqlXMLAnswerUnitTest("testBooleanAnswer"));
+    suite.addTest(new StreamedSparqlXMLAnswerUnitTest("testBooleanAnswerPretty"));
+    suite.addTest(new StreamedSparqlXMLAnswerUnitTest("testPrettyPrint"));
+    suite.addTest(new StreamedSparqlXMLAnswerUnitTest("testCompactPrint"));
+    suite.addTest(new StreamedSparqlXMLAnswerUnitTest("testPrettyPrintVariations"));
+    suite.addTest(new StreamedSparqlXMLAnswerUnitTest("testCompactPrintVariations"));
+    return suite;
+  }
+
+  /**
+   * Default text runner.
+   * @param args The command line arguments
+   */
+  public static void main(String[] args) {
+    junit.textui.TestRunner.run(suite());
+  }
+
+
+  /**
+   * Tests an empty answer.
+   * @throws Exception On error
+   */
+  public void testEmptyConstructor() throws Exception {
+    StreamedSparqlXMLAnswer a = new StreamedSparqlXMLAnswer(empty, output);
+    a.setPrettyPrint(false);
+    a.emit();
+    assertEquals(getEmpty(), output.toString());
+  }
+
+
+  /**
+   * Tests an empty answer.
+   * @throws Exception On error
+   */
+  public void testEmptyConstructorPretty() throws Exception {
+    StreamedSparqlXMLAnswer a = new StreamedSparqlXMLAnswer(empty, output);
+    a.emit();
+    assertEquals(getEmptyP(), output.toString());
+
+    a.setPrettyPrint(false);
+    output.reset();
+    a.emit();
+    assertEquals(getEmpty(), output.toString());
+  }
+
+
+  /**
+   * Tests a boolean answer.
+   * @throws Exception On error
+   */
+  public void testBooleanAnswer() throws Exception {
+    StreamedSparqlXMLAnswer a = new StreamedSparqlXMLAnswer(true, output);
+    a.setPrettyPrint(false);
+    a.emit();
+    assertEquals(getTrue(), output.toString());
+
+    a = new StreamedSparqlXMLAnswer(new BooleanAnswer(true), output);
+    a.setPrettyPrint(false);
+    output.reset();
+    a.emit();
+    assertEquals(getTrue(), output.toString());
+  }
+
+
+  /**
+   * Tests a boolean answer.
+   * @throws Exception On error
+   */
+  public void testBooleanAnswerPretty() throws Exception {
+    StreamedSparqlXMLAnswer a = new StreamedSparqlXMLAnswer(true, output);
+    a.emit();
+    assertEquals(getTrueP(), output.toString());
+    a.setPrettyPrint(false);
+    output.reset();
+    a.emit();
+    assertEquals(getTrue(), output.toString());
+  }
+
+
+  /**
+   * Test main structure.
+   */
+  public void testPrettyPrint() throws Exception {
+    StreamedSparqlXMLAnswer a = new StreamedSparqlXMLAnswer(answer, output);
+    a.emit();
+    assertEquals(getAnswerP(), output.toString());
+  }
+
+
+  /**
+   * Test main structure.
+   */
+  public void testCompactPrint() throws Exception {
+    StreamedSparqlXMLAnswer a = new StreamedSparqlXMLAnswer(answer, output);
+    a.setPrettyPrint(false);
+    a.emit();
+    assertEquals(getAnswer(), output.toString());
+  }
+
+
+  /**
+   * Tests the variations of answers.
+   * @throws Exception On error
+   */
+  public void testCompactPrintVariations() throws Exception {
+    StreamedSparqlXMLAnswer e = new StreamedSparqlXMLAnswer(empty, REL_URI, output);
+    StreamedSparqlXMLAnswer b = new StreamedSparqlXMLAnswer(true, REL_URI, outputb);
+    StreamedSparqlXMLAnswer a = new StreamedSparqlXMLAnswer(answer, REL_URI, outputa);
+    e.setPrettyPrint(false);
+    b.setPrettyPrint(false);
+    a.setPrettyPrint(false);
+
+    // No namespaces, no schema, meta set
+    e.emit();
+    b.emit();
+    a.emit();
+    assertEquals(getEmpty(null, false, true), output.toString());
+    assertEquals(getTrue(null, false, true), outputb.toString());
+    assertEquals(getAnswer(null, false, true), outputa.toString());
+
+    e.useW3CSchema(true);
+    b.useW3CSchema(true);
+    a.useW3CSchema(true);
+
+    // No namespaces, schema set, meta set
+    output.reset();
+    outputb.reset();
+    outputa.reset();
+    e.emit();
+    b.emit();
+    a.emit();
+    assertEquals(getEmpty(null, true, true), output.toString());
+    assertEquals(getTrue(null, true, true), outputb.toString());
+    assertEquals(getAnswer(null, true, true), outputa.toString());
+
+    e.addNamespace(NAME1, NS1);
+    e.addNamespace(NAME2, NS2);
+    b.addNamespace(NAME1, NS1);
+    b.addNamespace(NAME2, NS2);
+    a.addNamespace(NAME1, NS1);
+    a.addNamespace(NAME2, NS2);
+
+    // Namespaces set, schema set, meta set
+    output.reset();
+    outputb.reset();
+    outputa.reset();
+    e.emit();
+    b.emit();
+    a.emit();
+    assertEquals(getEmpty(namesp, true, true), output.toString());
+    assertEquals(getTrue(namesp, true, true), outputb.toString());
+    assertEquals(getAnswer(namesp, true, true), outputa.toString());
+
+    e.useW3CSchema(false);
+    b.useW3CSchema(false);
+    a.useW3CSchema(false);
+
+    // Namespaces set, no schema, meta set
+    output.reset();
+    outputb.reset();
+    outputa.reset();
+    e.emit();
+    b.emit();
+    a.emit();
+    assertEquals(getEmpty(namesp, false, true), output.toString());
+    assertEquals(getTrue(namesp, false, true), outputb.toString());
+    assertEquals(getAnswer(namesp, false, true), outputa.toString());
+    
+    e = new StreamedSparqlXMLAnswer(empty, output);
+    b = new StreamedSparqlXMLAnswer(true, outputb);
+    a = new StreamedSparqlXMLAnswer(answer, outputa);
+
+    e.setPrettyPrint(false);
+    b.setPrettyPrint(false);
+    a.setPrettyPrint(false);
+
+    e.useW3CSchema(true);
+    b.useW3CSchema(true);
+    a.useW3CSchema(true);
+
+    // No namespaces, schema set, no meta
+    output.reset();
+    outputb.reset();
+    outputa.reset();
+    e.emit();
+    b.emit();
+    a.emit();
+    assertEquals(getEmpty(null, true, false), output.toString());
+    assertEquals(getTrue(null, true, false), outputb.toString());
+    assertEquals(getAnswer(null, true, false), outputa.toString());
+
+    e.addNamespace(NAME1, NS1);
+    e.addNamespace(NAME2, NS2);
+    b.addNamespace(NAME1, NS1);
+    b.addNamespace(NAME2, NS2);
+    a.addNamespace(NAME1, NS1);
+    a.addNamespace(NAME2, NS2);
+
+    // Namespaces set, schema set, no meta
+    output.reset();
+    outputb.reset();
+    outputa.reset();
+    e.emit();
+    b.emit();
+    a.emit();
+    assertEquals(getEmpty(namesp, true, false), output.toString());
+    assertEquals(getTrue(namesp, true, false), outputb.toString());
+    assertEquals(getAnswer(namesp, true, false), outputa.toString());
+
+    e.useW3CSchema(false);
+    b.useW3CSchema(false);
+    a.useW3CSchema(false);
+
+    // Namespaces set, no schema, no meta
+    output.reset();
+    outputb.reset();
+    outputa.reset();
+    e.emit();
+    b.emit();
+    a.emit();
+    assertEquals(getEmpty(namesp, false, false), output.toString());
+    assertEquals(getTrue(namesp, false, false), outputb.toString());
+    assertEquals(getAnswer(namesp, false, false), outputa.toString());
+
+    e.clearNamespaces();
+    b.clearNamespaces();
+    a.clearNamespaces();
+
+    // No Namespaces, no schema, no meta
+    output.reset();
+    outputb.reset();
+    outputa.reset();
+    e.emit();
+    b.emit();
+    a.emit();
+    assertEquals(getEmpty(null, false, false), output.toString());
+    assertEquals(getTrue(null, false, false), outputb.toString());
+    assertEquals(getAnswer(null, false, false), outputa.toString());
+
+    e.useW3CSchema(true);
+    b.useW3CSchema(true);
+    a.useW3CSchema(true);
+
+    // No namespaces, schema set, no meta
+    output.reset();
+    outputb.reset();
+    outputa.reset();
+    e.emit();
+    b.emit();
+    a.emit();
+    assertEquals(getEmpty(null, true, false), output.toString());
+    assertEquals(getTrue(null, true, false), outputb.toString());
+    assertEquals(getAnswer(null, true, false), outputa.toString());
+  }
+
+
+  /**
+   * Tests the variations of answers.
+   * @throws Exception On error
+   */
+  public void testPrettyPrintVariations() throws Exception {
+    StreamedSparqlXMLAnswer e = new StreamedSparqlXMLAnswer(empty, REL_URI, output);
+    StreamedSparqlXMLAnswer b = new StreamedSparqlXMLAnswer(true, REL_URI, outputb);
+    StreamedSparqlXMLAnswer a = new StreamedSparqlXMLAnswer(answer, REL_URI, outputa);
+    e.setPrettyPrint(true);
+    b.setPrettyPrint(true);
+    a.setPrettyPrint(true);
+
+    // No namespaces, no schema, meta set
+    e.emit();
+    b.emit();
+    a.emit();
+    assertEquals(getEmptyP(null, false, true), output.toString());
+    assertEquals(getTrueP(null, false, true), outputb.toString());
+    assertEquals(getAnswerP(null, false, true), outputa.toString());
+
+    e.useW3CSchema(true);
+    b.useW3CSchema(true);
+    a.useW3CSchema(true);
+
+    // No namespaces, schema set, meta set
+    output.reset();
+    outputb.reset();
+    outputa.reset();
+    e.emit();
+    b.emit();
+    a.emit();
+    assertEquals(getEmptyP(null, true, true), output.toString());
+    assertEquals(getTrueP(null, true, true), outputb.toString());
+    assertEquals(getAnswerP(null, true, true), outputa.toString());
+
+    e.addNamespace(NAME1, NS1);
+    e.addNamespace(NAME2, NS2);
+    b.addNamespace(NAME1, NS1);
+    b.addNamespace(NAME2, NS2);
+    a.addNamespace(NAME1, NS1);
+    a.addNamespace(NAME2, NS2);
+
+    // Namespaces set, schema set, meta set
+    output.reset();
+    outputb.reset();
+    outputa.reset();
+    e.emit();
+    b.emit();
+    a.emit();
+    assertEquals(getEmptyP(namesp, true, true), output.toString());
+    assertEquals(getTrueP(namesp, true, true), outputb.toString());
+    assertEquals(getAnswerP(namesp, true, true), outputa.toString());
+
+    e.useW3CSchema(false);
+    b.useW3CSchema(false);
+    a.useW3CSchema(false);
+
+    // Namespaces set, no schema, meta set
+    output.reset();
+    outputb.reset();
+    outputa.reset();
+    e.emit();
+    b.emit();
+    a.emit();
+    assertEquals(getEmptyP(namesp, false, true), output.toString());
+    assertEquals(getTrueP(namesp, false, true), outputb.toString());
+    assertEquals(getAnswerP(namesp, false, true), outputa.toString());
+    
+    e = new StreamedSparqlXMLAnswer(empty, output);
+    b = new StreamedSparqlXMLAnswer(true, outputb);
+    a = new StreamedSparqlXMLAnswer(answer, outputa);
+
+    e.useW3CSchema(true);
+    b.useW3CSchema(true);
+    a.useW3CSchema(true);
+
+    // No namespaces, schema set, no meta
+    output.reset();
+    outputb.reset();
+    outputa.reset();
+    e.emit();
+    b.emit();
+    a.emit();
+    assertEquals(getEmptyP(null, true, false), output.toString());
+    assertEquals(getTrueP(null, true, false), outputb.toString());
+    assertEquals(getAnswerP(null, true, false), outputa.toString());
+
+    e.addNamespace(NAME1, NS1);
+    e.addNamespace(NAME2, NS2);
+    b.addNamespace(NAME1, NS1);
+    b.addNamespace(NAME2, NS2);
+    a.addNamespace(NAME1, NS1);
+    a.addNamespace(NAME2, NS2);
+
+    // Namespaces set, schema set, no meta
+    output.reset();
+    outputb.reset();
+    outputa.reset();
+    e.emit();
+    b.emit();
+    a.emit();
+    assertEquals(getEmptyP(namesp, true, false), output.toString());
+    assertEquals(getTrueP(namesp, true, false), outputb.toString());
+    assertEquals(getAnswerP(namesp, true, false), outputa.toString());
+
+    e.useW3CSchema(false);
+    b.useW3CSchema(false);
+    a.useW3CSchema(false);
+
+    // Namespaces set, no schema, no meta
+    output.reset();
+    outputb.reset();
+    outputa.reset();
+    e.emit();
+    b.emit();
+    a.emit();
+    assertEquals(getEmptyP(namesp, false, false), output.toString());
+    assertEquals(getTrueP(namesp, false, false), outputb.toString());
+    assertEquals(getAnswerP(namesp, false, false), outputa.toString());
+
+    e.clearNamespaces();
+    b.clearNamespaces();
+    a.clearNamespaces();
+
+    // No Namespaces, no schema, no meta
+    output.reset();
+    outputb.reset();
+    outputa.reset();
+    e.emit();
+    b.emit();
+    a.emit();
+    assertEquals(getEmptyP(null, false, false), output.toString());
+    assertEquals(getTrueP(null, false, false), outputb.toString());
+    assertEquals(getAnswerP(null, false, false), outputa.toString());
+
+    e.useW3CSchema(true);
+    b.useW3CSchema(true);
+    a.useW3CSchema(true);
+
+    // No namespaces, schema set, no meta
+    output.reset();
+    outputb.reset();
+    outputa.reset();
+    e.emit();
+    b.emit();
+    a.emit();
+    assertEquals(getEmptyP(null, true, false), output.toString());
+    assertEquals(getTrueP(null, true, false), outputb.toString());
+    assertEquals(getAnswerP(null, true, false), outputa.toString());
+  }
+
+
+  /**
+   * Populate the test answer.
+   * @throws Exception Error setting up the ResultSet
+   */
+  protected void setUp() throws Exception {
+    TestResultSet trs1 = new TestResultSet(new String[] { "x", "y" });
+    ResultSetRow row;
+    row = new ResultSetRow(trs1);
+    row.setObject("x", new LiteralImpl("X1"));
+    row.setObject("y", new URIReferenceImpl(URI.create("urn:y1")));
+    trs1.addRow(row);
+    row = new ResultSetRow(trs1);
+    row.setObject("x", new LiteralImpl("X2", "en"));
+    row.setObject("y", new BlankNodeImpl(42));
+    trs1.addRow(row);
+    answer = new AnswerImpl(trs1);
+
+    List<Variable> variables = Arrays.asList(new Variable[] { new Variable("x") });
+    empty = new AnswerImpl(variables);
+    
+    namesp.put(NAME1, NS1.toString());
+    namesp.put(NAME2, NS2.toString());
+
+    output = new ByteArrayOutputStream();
+    outputb = new ByteArrayOutputStream();
+    outputa = new ByteArrayOutputStream();
+  }
+
+  /**
+   * Clean up the test answer.
+   */
+  public void tearDown() {
+    answer.close();
+    empty.close();
+  }
+
+  /////////////////////////////
+  // start of getEmpty variants
+  /////////////////////////////
+
+  private static String getEmpty() {
+    return getEmpty(null, false, false);
+  }
+
+  private static String getEmptyP() {
+    return getEmptyP(null, false, false);
+  }
+
+  private static String getEmpty(Map<String,String> ns, boolean schema, boolean meta) {
+    return getCommonStart(ns, schema, meta) + EMPTY_BODY;
+  }
+
+  private static String getEmptyP(Map<String,String> ns, boolean schema, boolean meta) {
+    return getCommonStartP(ns, schema, meta) + EMPTY_BODY_P;
+  }
+
+  ////////////////////////////
+  // start of getTrue variants
+  ////////////////////////////
+
+  private static String getTrue() {
+    return getTrue(null, false, false);
+  }
+
+  private static String getTrueP() {
+    return getTrueP(null, false, false);
+  }
+
+  private static String getTrue(Map<String,String> ns, boolean schema, boolean meta) {
+    return getCommonStart(ns, schema, meta) + TRUE_BODY;
+  }
+
+  private static String getTrueP(Map<String,String> ns, boolean schema, boolean meta) {
+    return getCommonStartP(ns, schema, meta) + TRUE_BODY_P;
+  }
+
+  //////////////////////////////
+  // start of getAnswer variants
+  //////////////////////////////
+
+  private static String getAnswer() {
+    return getAnswer(null, false, false);
+  }
+
+  private static String getAnswerP() {
+    return getAnswerP(null, false, false);
+  }
+
+  private static String getAnswer(Map<String,String> ns, boolean schema, boolean meta) {
+    return getShortCommonStart(ns, schema) + ANSWER_VARS + getMeta(meta) + ANSWER_BODY;
+  }
+
+  private static String getAnswerP(Map<String,String> ns, boolean schema, boolean meta) {
+    return getShortCommonStartP(ns, schema) + ANSWER_VARS_P + getMetaP(meta) + ANSWER_BODY_P;
+  }
+
+  ////////////////
+  // common header
+  ////////////////
+
+  private static String getCommonStart(Map<String,String> ns, boolean schema, boolean meta) {
+    return getShortCommonStart(ns, schema) + getMeta(meta);
+  }
+
+  private static String getCommonStartP(Map<String,String> ns, boolean schema, boolean meta) {
+    return getShortCommonStartP(ns, schema) + getMetaP(meta);
+  }
+
+  private static String getShortCommonStart(Map<String,String> ns, boolean schema) {
+    String result = DOC_HEAD + SPARQL_HEAD;
+    if (ns != null) {
+      for (String k: ns.keySet()) result += " " + k + "=\"" + ns.get(k) + "\"";
+    }
+    if (schema) result += " " + SPARQL_HEAD_ATTR;
+    result += ">" + EMPTY_HEAD;
+    return result;
+  }
+
+  private static String getShortCommonStartP(Map<String,String> ns, boolean schema) {
+    String result = DOC_HEAD + SPARQL_HEAD;
+    if (ns != null) {
+      for (String k: ns.keySet()) result += "\n" + SPARQL_HEAD_INDENT + k + "=\"" + ns.get(k) + "\"";
+    }
+    if (schema) result += "\n" + SPARQL_HEAD_INDENT + SPARQL_HEAD_ATTR;
+    result += ">\n  " + EMPTY_HEAD + "\n";
+    return result;
+  }
+
+  private static String getMeta(boolean meta) {
+    return meta ? HEAD_META : "";
+  }
+
+  private static String getMetaP(boolean meta) {
+    return meta ? HEAD_META_INDENT + HEAD_META + "\n" : "";
+  }
+
+  static final String DOC_HEAD = "<?xml version=\"1.0\"?>\n";
+
+  static final String SPARQL_HEAD = "<sparql xmlns=\"http://www.w3.org/2005/sparql-results#\"";
+
+  static final String SPARQL_HEAD_ATTR = "xsi:schemaLocation=\"http://www.w3.org/2007/SPARQL/result.xsd\"";
+
+  static final String SPARQL_HEAD_INDENT = "        ";
+  
+  static final String HEAD_META = "<link href=\"" + REL_URI + "\"/>";
+
+  static final String HEAD_META_INDENT = "    ";
+
+  static final String EMPTY_HEAD = "<head>";
+
+  static final String EMPTY_BODY = "</head><results></results></sparql>";
+
+  static final String EMPTY_BODY_P = "  </head>\n" +
+      "  <results>\n" +
+      "  </results>\n" +
+      "</sparql>";
+
+  static final String TRUE_BODY = "</head><boolean>true</boolean></sparql>";
+
+  static final String TRUE_BODY_P = "  </head>\n" +
+      "  <boolean>true</boolean>\n" +
+      "</sparql>";
+
+  static final String ANSWER_VARS = "<variable name=\"x\"/><variable name=\"y\"/>";
+
+  static final String ANSWER_BODY = "</head>" +
+      "<results>" +
+      "<result><binding name=\"x\"><literal>X1</literal></binding>" +
+      "<binding name=\"y\"><uri>urn:y1</uri></binding></result>" +
+      "<result><binding name=\"x\"><literal xml:lang=\"en\">X2</literal></binding>" +
+      "<binding name=\"y\"><bnode>_node42</bnode></binding></result>" +
+      "</results>" +
+      "</sparql>";
+
+  static final String ANSWER_VARS_P = "    <variable name=\"x\"/>\n" +
+      "    <variable name=\"y\"/>\n";
+
+  static final String ANSWER_BODY_P = "  </head>\n" +
+      "  <results>\n" +
+      "    <result>\n" +
+      "      <binding name=\"x\">\n" +
+      "        <literal>X1</literal>\n" +
+      "      </binding>\n" +
+      "      <binding name=\"y\">\n" +
+      "        <uri>urn:y1</uri>\n" +
+      "      </binding>\n" +
+      "    </result>\n" +
+      "    <result>\n" +
+      "      <binding name=\"x\">\n" +
+      "        <literal xml:lang=\"en\">X2</literal>\n" +
+      "      </binding>\n" +
+      "      <binding name=\"y\">\n" +
+      "        <bnode>_node42</bnode>\n" +
+      "      </binding>\n" +
+      "    </result>\n" +
+      "  </results>\n" +
+      "</sparql>";
+}

Added: trunk/src/jar/querylang/java/org/mulgara/protocol/StreamedTqlXMLAnswer.java
===================================================================
--- trunk/src/jar/querylang/java/org/mulgara/protocol/StreamedTqlXMLAnswer.java	                        (rev 0)
+++ trunk/src/jar/querylang/java/org/mulgara/protocol/StreamedTqlXMLAnswer.java	2008-09-03 14:56:28 UTC (rev 1223)
@@ -0,0 +1,150 @@
+/*
+ * The contents of this file are subject to the Open Software License
+ * Version 3.0 (the "License"); you may not use this file except in
+ * compliance with the License. You may obtain a copy of the License at
+ * http://www.opensource.org/licenses/osl-3.0.txt
+ *
+ * Software distributed under the License is distributed on an "AS IS"
+ * basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
+ * the License for the specific language governing rights and limitations
+ * under the License.
+ */
+
+package org.mulgara.protocol;
+
+import java.io.IOException;
+import java.io.OutputStream;
+
+import org.jrdf.graph.BlankNode;
+import org.jrdf.graph.Literal;
+import org.jrdf.graph.URIReference;
+import org.mulgara.query.Answer;
+import org.mulgara.query.TuplesException;
+import org.mulgara.query.Variable;
+
+/**
+ * Represents an Answer as TQL XML.
+ *
+ * @created Jul 8, 2008
+ * @author Paul Gearon
+ * @copyright &copy; 2008 <a href="http://www.topazproject.org/">The Topaz Project</a>
+ * @licence <a href="{@docRoot}/../../LICENCE.txt">Open Software License v3.0</a>
+ */
+public class StreamedTqlXMLAnswer extends AbstractStreamedXMLAnswer {
+
+  /**
+   * Creates an XML Answer for XML results. Pretty printing is off by default.
+   * @param answer The Answer to wrap.
+   */
+  public StreamedTqlXMLAnswer(Answer answer, OutputStream output) {
+    super(answer, output);
+    setPrettyPrint(false);
+  }
+
+  /** {@inheritDoc} */
+  protected void addDocHeader() throws IOException {
+    s.append("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n");
+    s.append("<answer xmlns=\"http://mulgara.org/tql#\">");
+    s.append(i(1)).append("<query>");
+  }
+
+  /** {@inheritDoc} */
+  protected void addDocFooter() throws IOException {
+    s.append(i(1)).append("</query>");
+    s.append(i(0)).append("</answer>");
+  }
+
+  /** {@inheritDoc} */
+  protected void addHeader() throws IOException {
+    addHeader(answer, 0);
+  }
+
+  void addHeader(Answer a, int indent) throws IOException {
+    s.append(i(indent + 2)).append("<variables>");
+    if (a != null && a.getVariables() != null) for (Variable v: a.getVariables()) addHeaderVariable(v, indent);
+    s.append(i(indent + 2)).append("</variables>");
+  }
+
+  /** {@inheritDoc} */
+  protected void addHeaderVariable(Variable var) throws IOException {
+    addHeaderVariable(var, 0);
+  }
+
+  /** {@inheritDoc} */
+  protected void addHeaderVariable(Variable var, int indent) throws IOException {
+    s.append(i(indent + 3)).append("<").append(var.getName()).append("/>");
+  }
+
+  /** {@inheritDoc} */
+  protected void addResults() throws TuplesException, IOException {
+    answer.beforeFirst();
+    while (answer.next()) addResult();
+  }
+
+  /** {@inheritDoc} */
+  protected void addResults(Answer a, int indent) throws TuplesException, IOException {
+    a.beforeFirst();
+    while (a.next()) addResult(a, indent);
+  }
+
+  /** {@inheritDoc} */
+  protected void addResult() throws TuplesException, IOException {
+    addResult(answer, 0);
+  }
+
+  /** {@inheritDoc} */
+  protected void addResult(Answer a, int indent) throws TuplesException, IOException {
+    s.append(i(indent + 2)).append("<solution>");
+    for (int c = 0; c < width; c++) addBinding(vars[c], answer.getObject(c), indent);
+    s.append(i(indent + 2)).append("</solution>");
+  }
+
+  /**
+   * {@inheritDoc}
+   * No binding will be emitted if the value is null (unbound).
+   */
+  protected void addBinding(Variable var, Object value) throws TuplesException, IOException {
+    addBinding(var, value, 0);
+  }
+
+  /**
+   * {@inheritDoc}
+   * No binding will be emitted if the value is null (unbound).
+   * @throws TuplesException Indicates an error accessing the Answer.
+   */
+  protected void addBinding(Variable var, Object value, int indent) throws TuplesException, IOException {
+    if (value != null) {
+      s.append(i(indent + 3)).append("<").append(var.getName());
+      // no dynamic dispatch, so use if/then
+      if (value instanceof URIReference) {
+        addURI((URIReference)value);
+      } else if (value instanceof BlankNode) {
+        addBNode((BlankNode)value);
+      } else if (value instanceof Literal) {
+        addLiteral((Literal)value);
+        s.append("</").append(var.getName()).append(">");
+      } else if (value instanceof Answer) {
+        addHeader((Answer)value, indent + 4);
+        addResults((Answer)value, indent + 4);
+      } else throw new IllegalArgumentException("Unable to create a SPARQL response with an answer containing: " + value.getClass().getSimpleName());
+    }
+  }
+
+  /** {@inheritDoc} */
+  protected void addURI(URIReference uri) throws IOException {
+    s.append(" resource=\"").append(uri.getURI().toString()).append("\"/>");
+  }
+
+  /** {@inheritDoc} */
+  protected void addBNode(BlankNode bnode) throws IOException {
+    s.append(" blank-node=\"").append(bnode.toString()).append("\"/>");
+  }
+
+  /** {@inheritDoc} */
+  protected void addLiteral(Literal literal) throws IOException {
+    if (literal.getLanguage() != null) s.append(" language=\"").append(literal.getLanguage()).append("\"");
+    else if (literal.getDatatype() != null) s.append(" datatype=\"").append(literal.getDatatype().toString()).append("\"");
+    s.append(">").append(literal.getLexicalForm());
+  }
+
+}

Added: trunk/src/jar/querylang/java/org/mulgara/protocol/StreamedTqlXMLAnswerUnitTest.java
===================================================================
--- trunk/src/jar/querylang/java/org/mulgara/protocol/StreamedTqlXMLAnswerUnitTest.java	                        (rev 0)
+++ trunk/src/jar/querylang/java/org/mulgara/protocol/StreamedTqlXMLAnswerUnitTest.java	2008-09-03 14:56:28 UTC (rev 1223)
@@ -0,0 +1,257 @@
+/*
+ * The contents of this file are subject to the Open Software License
+ * Version 3.0 (the "License"); you may not use this file except in
+ * compliance with the License. You may obtain a copy of the License at
+ * http://www.opensource.org/licenses/osl-3.0.txt
+ *
+ * Software distributed under the License is distributed on an "AS IS"
+ * basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
+ * the License for the specific language governing rights and limitations
+ * under the License.
+ */
+
+package org.mulgara.protocol;
+
+
+// JUnit
+import junit.framework.*;
+
+// Java 2 standard packages
+import java.io.ByteArrayOutputStream;
+import java.net.URI;
+import java.util.Arrays;
+import java.util.List;
+
+// Locally written packages
+import org.mulgara.query.AnswerImpl;
+import org.mulgara.query.BooleanAnswer;
+import org.mulgara.query.Variable;
+import org.mulgara.query.rdf.BlankNodeImpl;
+import org.mulgara.query.rdf.LiteralImpl;
+import org.mulgara.query.rdf.URIReferenceImpl;
+import org.mulgara.util.ResultSetRow;
+import org.mulgara.util.TestResultSet;
+
+/**
+ * Test case for {@link StreamedTqlXMLAnswer}.
+ *
+ * @created Jul 8, 2008
+ * @author Paul Gearon
+ * @copyright &copy; 2008 <a href="http://www.topazproject.org/">The Topaz Project</a>
+ * @licence <a href="{@docRoot}/../../LICENCE.txt">Open Software License v3.0</a>
+ */
+public class StreamedTqlXMLAnswerUnitTest extends TestCase {
+
+  /**
+   * Test instance.
+   * <table>
+   *   <thead>
+   *     <tr><th>x</th> <th>y</th></tr>
+   *   </thead>
+   *   <tbody>
+   *     <tr><td>X1</td><td>Y1</td></tr>
+   *     <tr><td>X2</td><td>Y2</td></tr>
+   *   </tbody>
+   * </table>
+   */
+  private AnswerImpl answer;
+
+  private AnswerImpl empty;
+
+  private ByteArrayOutputStream output;
+
+  /**
+   * Constructs a new answer test with the given name.
+   * @param name the name of the test
+   */
+  public StreamedTqlXMLAnswerUnitTest(String name) {
+    super(name);
+  }
+
+  /**
+   * Hook for test runner to obtain a test suite from.
+   * @return The test suite
+   */
+  public static Test suite() {
+    TestSuite suite = new TestSuite();
+    suite.addTest(new StreamedTqlXMLAnswerUnitTest("testEmptyConstructor"));
+    suite.addTest(new StreamedTqlXMLAnswerUnitTest("testEmptyConstructorPretty"));
+    suite.addTest(new StreamedTqlXMLAnswerUnitTest("testBooleanAnswer"));
+    suite.addTest(new StreamedTqlXMLAnswerUnitTest("testBooleanAnswerPretty"));
+    suite.addTest(new StreamedTqlXMLAnswerUnitTest("testPrettyPrint"));
+    suite.addTest(new StreamedTqlXMLAnswerUnitTest("testCompactPrint"));
+    return suite;
+  }
+
+  /**
+   * Default text runner.
+   * @param args The command line arguments
+   */
+  public static void main(String[] args) {
+    junit.textui.TestRunner.run(suite());
+  }
+
+
+  /**
+   * Tests an empty answer.
+   * @throws Exception On error
+   */
+  public void testEmptyConstructor() throws Exception {
+    StreamedTqlXMLAnswer a = new StreamedTqlXMLAnswer(empty, output);
+    a.emit();
+    assertEquals(EMPTY, output.toString());
+  }
+
+
+  /**
+   * Tests an empty answer.
+   * @throws Exception On error
+   */
+  public void testEmptyConstructorPretty() throws Exception {
+    StreamedTqlXMLAnswer a = new StreamedTqlXMLAnswer(empty, output);
+    a.setPrettyPrint(true);
+    a.emit();
+    assertEquals(EMPTY_P, output.toString());
+    a.setPrettyPrint(false);
+    output.reset();
+    a.emit();
+    assertEquals(EMPTY, output.toString());
+  }
+
+
+  /**
+   * Tests a boolean answer.
+   * @throws Exception On error
+   */
+  public void testBooleanAnswer() throws Exception {
+    StreamedTqlXMLAnswer a = new StreamedTqlXMLAnswer(new BooleanAnswer(true), output);
+    a.emit();
+    assertEquals(TRUE, output.toString());
+  }
+
+
+  /**
+   * Tests a boolean answer.
+   * @throws Exception On error
+   */
+  public void testBooleanAnswerPretty() throws Exception {
+    StreamedTqlXMLAnswer a = new StreamedTqlXMLAnswer(new BooleanAnswer(true), output);
+    a.emit();
+    assertEquals(TRUE, output.toString());
+    a.setPrettyPrint(true);
+    output.reset();
+    a.emit();
+    assertEquals(TRUE_P, output.toString());
+    a.setPrettyPrint(false);
+    output.reset();
+    a.emit();
+    assertEquals(TRUE, output.toString());
+  }
+
+
+  /**
+   * Test main structure.
+   */
+  public void testPrettyPrint() throws Exception {
+    StreamedTqlXMLAnswer a = new StreamedTqlXMLAnswer(answer, output);
+    a.setPrettyPrint(true);
+    a.emit();
+    assertEquals(ANSWER_P, output.toString());
+  }
+
+
+  /**
+   * Test main structure.
+   */
+  public void testCompactPrint() throws Exception {
+    StreamedTqlXMLAnswer a = new StreamedTqlXMLAnswer(answer, output);
+    a.emit();
+    assertEquals(ANSWER, output.toString());
+  }
+
+
+  /**
+   * Populate the test answer.
+   * @throws Exception Error setting up the ResultSet
+   */
+  protected void setUp() throws Exception {
+    TestResultSet trs1 = new TestResultSet(new String[] { "x", "y" });
+    ResultSetRow row;
+    row = new ResultSetRow(trs1);
+    row.setObject("x", new LiteralImpl("X1"));
+    row.setObject("y", new URIReferenceImpl(URI.create("urn:y1")));
+    trs1.addRow(row);
+    row = new ResultSetRow(trs1);
+    row.setObject("x", new LiteralImpl("X2", "en"));
+    row.setObject("y", new BlankNodeImpl(42));
+    trs1.addRow(row);
+    answer = new AnswerImpl(trs1);
+
+    List<Variable> variables = Arrays.asList(new Variable[] { new Variable("x") });
+    empty = new AnswerImpl(variables);
+    output = new ByteArrayOutputStream();
+  }
+
+  /**
+   * Clean up the test answer.
+   */
+  public void tearDown() {
+    answer.close();
+    empty.close();
+  }
+
+  static final String EMPTY = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n" +
+      "<answer xmlns=\"http://mulgara.org/tql#\"><query><variables></variables></query></answer>";
+
+  static final String EMPTY_P = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n" +
+      "<answer xmlns=\"http://mulgara.org/tql#\">\n" +
+      "  <query>\n" +
+      "    <variables>\n" +
+      "    </variables>\n" +
+      "  </query>\n" +
+      "</answer>";
+
+  static final String TRUE = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n" +
+      "<answer xmlns=\"http://mulgara.org/tql#\">" +
+      "<query><variables><k0/></variables>" +
+      "<solution><k0 datatype=\"http://www.w3.org/2001/XMLSchema#boolean\">true</k0></solution>" +
+      "</query></answer>";
+
+  static final String TRUE_P = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n" +
+      "<answer xmlns=\"http://mulgara.org/tql#\">\n" +
+      "  <query>\n" +
+      "    <variables>\n" +
+      "      <k0/>\n" +
+      "    </variables>\n" +
+      "    <solution>\n" +
+      "      <k0 datatype=\"http://www.w3.org/2001/XMLSchema#boolean\">true</k0>\n" +
+      "    </solution>\n" +
+      "  </query>\n" +
+      "</answer>";
+
+  static final String ANSWER = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n" +
+      "<answer xmlns=\"http://mulgara.org/tql#\">" +
+      "<query><variables><x/><y/></variables>" +
+      "<solution><x>X1</x><y resource=\"urn:y1\"/></solution>" +
+      "<solution><x language=\"en\">X2</x><y blank-node=\"_node42\"/></solution>" +
+      "</query>" +
+      "</answer>";
+
+  static final String ANSWER_P = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n" +
+      "<answer xmlns=\"http://mulgara.org/tql#\">\n" +
+      "  <query>\n" +
+      "    <variables>\n" +
+      "      <x/>\n" +
+      "      <y/>\n" +
+      "    </variables>\n" +
+      "    <solution>\n" +
+      "      <x>X1</x>\n" +
+      "      <y resource=\"urn:y1\"/>\n" +
+      "    </solution>\n" +
+      "    <solution>\n" +
+      "      <x language=\"en\">X2</x>\n" +
+      "      <y blank-node=\"_node42\"/>\n" +
+      "    </solution>\n" +
+      "  </query>\n" +
+      "</answer>";
+}

Added: trunk/src/jar/querylang/java/org/mulgara/protocol/StreamedXMLAnswer.java
===================================================================
--- trunk/src/jar/querylang/java/org/mulgara/protocol/StreamedXMLAnswer.java	                        (rev 0)
+++ trunk/src/jar/querylang/java/org/mulgara/protocol/StreamedXMLAnswer.java	2008-09-03 14:56:28 UTC (rev 1223)
@@ -0,0 +1,35 @@
+package org.mulgara.protocol;
+
+import java.io.IOException;
+import java.nio.charset.Charset;
+
+import org.mulgara.query.TuplesException;
+
+/**
+ * Represents an Answer that can be emitted as XML to a stream.
+ *
+ * @created Jul 8, 2008
+ * @author Paul Gearon
+ * @copyright &copy; 2008 <a href="http://www.topazproject.org/">The Topaz Project</a>
+ * @licence <a href="{@docRoot}/../../LICENCE.txt">Open Software License v3.0</a>
+ */
+public interface StreamedXMLAnswer extends XMLAnswer, StreamedAnswer {
+
+  /**
+   * Converts the Answer to an XML String and send to output.
+   * @throws TuplesException Indicates an error accessing the Answer.
+   */
+  public void emit() throws TuplesException, IOException;
+
+  /**
+   * Sets the character encoding when writing XML text to a byte stream.
+   * @param encoding The encoding to use.
+   */
+  public void setCharacterEncoding(String encoding);
+
+  /**
+   * Sets the character encoding when writing XML text to a byte stream.
+   * @param encoding The charset encoding to use.
+   */
+  public void setCharacterEncoding(Charset charset);
+}
\ No newline at end of file

Added: trunk/src/jar/querylang/java/org/mulgara/protocol/XMLAnswer.java
===================================================================
--- trunk/src/jar/querylang/java/org/mulgara/protocol/XMLAnswer.java	                        (rev 0)
+++ trunk/src/jar/querylang/java/org/mulgara/protocol/XMLAnswer.java	2008-09-03 14:56:28 UTC (rev 1223)
@@ -0,0 +1,33 @@
+package org.mulgara.protocol;
+
+import java.net.URI;
+
+/**
+ * Represents an Answer that can be emitted as XML.
+ *
+ * @created Jul 9, 2008
+ * @author Paul Gearon
+ * @copyright &copy; 2008 <a href="http://www.topazproject.org/">The Topaz Project</a>
+ * @licence <a href="{@docRoot}/../../LICENCE.txt">Open Software License v3.0</a>
+ */
+public interface XMLAnswer {
+
+  /**
+   * Explicitly adds a namespace to be used in the document.
+   * @param name The name of the namespace to use.
+   * @param nsValue The URI of the namespace.
+   */
+  public abstract void addNamespace(String name, URI nsValue);
+
+  /**
+   * Remove all previously added namespaces.
+   */
+  public abstract void clearNamespaces();
+
+  /**
+   * Sets whether or not to used pretty printing when creating the XML. On by default.
+   * @param prettyPrint <code>true</code> to turn pretty printing on. <code>false</code> to turn it off.
+   */
+  public abstract void setPrettyPrint(boolean prettyPrint);
+
+}
\ No newline at end of file




More information about the Mulgara-svn mailing list