[Mulgara-svn] r1077 - in trunk: src/jar/query/java/org/mulgara/query tools/src/org/mulgara/tools

alexhall at mulgara.org alexhall at mulgara.org
Thu Jul 10 22:07:01 UTC 2008


Author: alexhall
Date: 2008-07-10 15:07:00 -0700 (Thu, 10 Jul 2008)
New Revision: 1077

Added:
   trunk/src/jar/query/java/org/mulgara/query/RdfXmlEmitter.java
Modified:
   trunk/tools/src/org/mulgara/tools/Sparql.java
Log:
Added RdfXmlEmitter, a utility for formatting the results of a SPARQL CONSTRUCT or DESCRIBE query as RDF/XML.

Added: trunk/src/jar/query/java/org/mulgara/query/RdfXmlEmitter.java
===================================================================
--- trunk/src/jar/query/java/org/mulgara/query/RdfXmlEmitter.java	                        (rev 0)
+++ trunk/src/jar/query/java/org/mulgara/query/RdfXmlEmitter.java	2008-07-10 22:07:00 UTC (rev 1077)
@@ -0,0 +1,548 @@
+/*
+ * 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.query;
+
+import java.io.BufferedOutputStream;
+import java.io.OutputStream;
+import java.io.PrintWriter;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import org.jrdf.graph.BlankNode;
+import org.jrdf.graph.Literal;
+import org.jrdf.graph.Node;
+import org.jrdf.graph.ObjectNode;
+import org.jrdf.graph.PredicateNode;
+import org.jrdf.graph.SubjectNode;
+import org.jrdf.graph.Triple;
+import org.jrdf.graph.URIReference;
+import org.mulgara.query.rdf.TripleImpl;
+import org.mulgara.util.StringUtil;
+import org.openrdf.model.URI;
+
+/**
+ * <p>
+ * Static utility class for emitting RDF/XML that represents the results of a
+ * SPARQL CONSTRUCT or DESCRIBE query.
+ * </p>
+ * <p>
+ * <b>Note:</b> This utility builds up the entire set of statements in-memory to sort
+ * them, so is not suitable for very large graphs.
+ * </p>
+ * <p>
+ * This class borrows heavily from the org.mulgara.content.rdfxml.writer.RDFXMLWriter class.
+ * </p>
+ * 
+ * @created Jul 10, 2008
+ * @author Alex Hall
+ * @copyright &copy; 2008 <a href="http://www.revelytix.com">Revelytix, Inc.</a>
+ * @licence <a href="{@docRoot}/../../LICENCE.txt">Open Software License v3.0</a>
+ */
+public class RdfXmlEmitter {
+  
+  /** Comparator for sorting statements in the graph. */
+  private static final TripleComparator TRIPLE_COMPARATOR = new TripleComparator();
+  
+  /** Index used to access the subject column of the GraphAnswer */
+  private static final int SUBJECT_INDEX = 0;
+  /** Index used to access the predicate column of the GraphAnswer */
+  private static final int PREDICATE_INDEX = 1;
+  /** Index used to access the object column of the GraphAnswer */
+  private static final int OBJECT_INDEX = 2;
+  
+  /** Prefix to use for the RDF namespace. */
+  private static final String RDF_PREFIX = "rdf";
+  /** The RDF namespace. */
+  private static final String RDF_NAMESPACE = "http://www.w3.org/1999/02/22-rdf-syntax-ns#";
+  
+  /** Convenience reference to the new line character(s) */
+  private static final String NEWLINE = System.getProperty("line.separator");
+  
+  /**
+   * Compares RDF triples in subject, predicate, object order.  The order for individual
+   * nodes is determined as follows:
+   * <ol>
+   * <li>URI References (ordered based on the natural ordering of the component URI's)</li>
+   * <li>Blank nodes (ordered based on the natural ordering of the blank node labels)</li>
+   * <li>
+   * Literals, ordered as follows:
+   *   <ul>
+   *   <li>Datatype URI (untyped literals precede typed literals)</li>
+   *   <li>Language tag (literals without a language tag precede those with a tag)</li>
+   *   <li>Lexical form</li>
+   *   </ul>
+   * </li>
+   * </ol>
+   */
+  private static class TripleComparator implements Comparator<Triple> {
+
+    public int compare(Triple triple1, Triple triple2) {
+      int c = compare(triple1.getSubject(), triple2.getSubject());
+      if (c != 0) return c;
+      
+      c = compare(triple1.getPredicate(), triple2.getPredicate());
+      if (c != 0) return c;
+      
+      return compare(triple1.getObject(), triple2.getObject());
+    }
+    
+    private int compare(Node n1, Node n2) {
+      if (n1 instanceof URIReference) {
+        return compareUri((URIReference)n1, n2);
+      } else if (n1 instanceof BlankNode) {
+        return compareBlankNode((BlankNode)n1, n2);
+      } else if (n1 instanceof Literal) {
+        return compareLiteral((Literal)n1, n2);
+      }
+      return 0;
+    }
+    
+    private int compareUri(URIReference n1, Node n2) {
+      return (n2 instanceof URIReference) ? n1.getURI().compareTo(((URIReference)n2).getURI()) : -1;
+    }
+    
+    private int compareBlankNode(BlankNode n1, Node n2) {
+      int c = 0;
+      if (n2 instanceof URIReference) {
+        c = 1;
+      } else if (n2 instanceof BlankNode) {
+        c = n1.getID().compareTo(((BlankNode)n2).getID());
+      } else {
+        c = -1;
+      }
+      return c;
+    }
+    
+    private int compareLiteral(Literal n1, Node n2) {
+      if (n2 instanceof Literal) {
+        Literal n2lit = (Literal)n2;
+        int c = compareComponent(n1.getDatatypeURI(), n2lit.getDatatypeURI());
+        if (c != 0) return c;
+        
+        c = compareComponent(n1.getLanguage(), n2lit.getLanguage());
+        if (c != 0) return c;
+
+        return n1.getLexicalForm().compareTo(n2lit.getLexicalForm());
+      } else {
+        return 1;
+      }
+    }
+    
+    private <T extends Comparable<T>> int compareComponent(T t1, T t2) {
+      if (t1 == null) {
+        return (t2 == null) ? 0 : -1;
+      } else {
+        return (t2 == null) ? 1 : t1.compareTo(t2);
+      }
+    }
+    
+  }
+  
+  /**
+   * Writes the RDF graph described by the specified GraphAnswer to an output stream.
+   * This method consumes and closes the supplied Answer, so calling code should pass in
+   * a clone if it needs to access the Answer after this method finishes.  This method will
+   * include the XML document headers and is equivalent to calling:
+   * <pre>
+   * RdfXmlEmitter.writeRdfXml(answer, output, true);
+   * </pre>
+   * @see #writeRdfXml(GraphAnswer, OutputStream, boolean)
+   * @param answer The answer object that contains the RDF graph statements.
+   * @param output The output stream to write to.
+   * @throws QueryException If there was an error accessing statements from the answer.
+   */
+  public static void writeRdfXml(GraphAnswer answer, OutputStream output) throws QueryException {
+    writeRdfXml(answer, output, true);
+  }
+  
+  /**
+   * Writes the RDF graph described by the specified GraphAnswer to an output stream.
+   * This method consumes and closes the supplied Answer, so calling code should pass in
+   * a clone if it needs to access the Answer after this method finishes.
+   * @param answer The answer object that contains the RDF graph statements.
+   * @param output The output stream to write to.
+   * @param includeDocHeader If <code>true</code>, the RDF/XML output will contain an XML
+   *        header with entity definitions, and the body will use the entities to abbreviate
+   *        RDF resource URI's.
+   * @throws QueryException If there was an error accessing statements from the answer.
+   */
+  public static void writeRdfXml(GraphAnswer answer, OutputStream output, boolean includeDocHeader) throws QueryException {
+    PrintWriter writer = new PrintWriter(new BufferedOutputStream(output));
+    
+    Map<String,String> nsMap = createInitialNsMap();
+    List<Triple> statements = getStatementList(answer, nsMap);
+    Collections.sort(statements, TRIPLE_COMPARATOR);
+    
+    if (includeDocHeader) {
+      writeDocHeader(nsMap, writer);
+    }
+    
+    writeRdfHeader(nsMap, writer, includeDocHeader);
+    writeRdfBody(statements, nsMap, writer, includeDocHeader);
+    writeRdfFooter(nsMap, writer);
+    
+    writer.flush();
+  }
+  
+  /**
+   * Extracts a list of RDF statements from the GraphAnswer, consuming and closing the answer.
+   * This method also builds up a mapping of namespace URI's to prefixes for all URIReferences
+   * that it encounters.
+   * @param answer The graph to extract statements from.
+   * @param nsMap The mapping of namespace URI to prefix.
+   * @return An unsorted list of RDF statements.
+   * @throws QueryException if there was an error accessing the statements.
+   */
+  private static List<Triple> getStatementList(GraphAnswer answer, Map<String,String> nsMap) throws QueryException {
+    assert answer != null;
+    
+    List<Triple> statements = new ArrayList<Triple>();
+    int nsCounter = 0;
+    
+    try {
+      answer.beforeFirst();
+      while (answer.next()) {
+        Object subject = answer.getObject(SUBJECT_INDEX);
+        if (!(subject instanceof SubjectNode)) {
+          throw new QueryException("Illegal value in subject position: " + subject);
+        }
+        
+        Object predicate = answer.getObject(PREDICATE_INDEX);
+        if (!(predicate instanceof PredicateNode)) {
+          throw new QueryException("Illegal value in predicate position: " + predicate);
+        }
+        
+        Object object = answer.getObject(OBJECT_INDEX);
+        if (!(object instanceof ObjectNode)) {
+          throw new QueryException("Illegal value in object position: " + object);
+        }
+        
+        nsCounter = addNamespaceToMap(subject, nsMap, nsCounter);
+        nsCounter = addNamespaceToMap(predicate, nsMap, nsCounter);
+        nsCounter = addNamespaceToMap(object, nsMap, nsCounter);
+        
+        statements.add(new TripleImpl((SubjectNode)subject, (PredicateNode)predicate, (ObjectNode)object));
+      }
+    } catch (TuplesException te) {
+      throw new QueryException("Error accessing statements from GraphAnswer", te);
+    } finally {
+      try {
+        answer.close();
+      } catch (TuplesException te) {
+        throw new QueryException("Error closing GraphAnswer", te);
+      }
+    }
+    
+    return statements;
+  }
+  
+  /**
+   * Writes the XML document header to the specified writer.
+   * @param nsMap The namespace prefix mappings.
+   * @param out The writer.
+   */
+  private static void writeDocHeader(Map<String,String> nsMap, PrintWriter out) {
+    assert out != null;
+    String rdf = getRdfPrefix(nsMap);
+    
+    out.println("<?xml version=\"1.0\"?>");
+    
+    //print opening DOCTYPE DECLARATION tag
+    out.print(NEWLINE + "<!DOCTYPE " + rdf + ":RDF [");
+    
+    for (Map.Entry<String,String> entry : nsMap.entrySet()) {
+      String ns = entry.getKey();
+      String prefix = entry.getValue();
+      if (ns != null && prefix != null) {
+        //write as: <!ENTITY ns 'http://example.org/abc#'>
+        out.print(NEWLINE + "  <!ENTITY " + prefix + " '" + ns + "'>");        
+      }
+    }
+    
+    //close the opening tag (add a space for readability)
+    out.println("]>" + NEWLINE);
+  }
+  
+  /**
+   * Writes the opening tag for the root element of the RDF/XML document.  The root element
+   * will define XML namespaces for entries in the namespace prefix mapping.
+   * @param nsMap The namespace prefix mapping.
+   * @param out The writer.
+   * @param useEntities Determines whether to abbreviate namespaces using XML entities.
+   */
+  private static void writeRdfHeader(Map<String,String> nsMap, PrintWriter out, boolean useEntities) {
+    //print opening RDF tag (including namespaces)
+    out.print("<" + getRdfPrefix(nsMap) + ":RDF");
+    
+    //print namespaces
+    for (Map.Entry<String,String> entry : nsMap.entrySet()) {
+      String prefix = entry.getValue();
+      String ns = useEntities ? "&" + prefix + ";" : entry.getKey();
+      if (prefix != null && ns != null) {
+        out.print(NEWLINE + "  xmlns:" + prefix + "=\"" + ns + "\"");
+      }
+    }
+    
+    //close the opening tag (add a space for readability)
+    out.println(">" + NEWLINE);
+  }
+  
+  /**
+   * Writes the closing tag for the root element of the RDF/XML document.
+   * @param nsMap The namespace prefix mapping.
+   * @param out The writer.
+   */
+  private static void writeRdfFooter(Map<String,String> nsMap, PrintWriter out) {
+    out.println("</" + getRdfPrefix(nsMap) + ":RDF>");
+  }
+  
+  /**
+   * Writes the given statements as RDF/XML, using the supplied namespace prefix mappings.
+   * @param statements The statements to write.
+   * @param nsMap The namespace prefix mappings.
+   * @param out The writer.
+   * @param useEntities Determines whether to abbreviate URIReferences using XML entities.
+   */
+  private static void writeRdfBody(List<Triple> statements, Map<String,String> nsMap, PrintWriter out, boolean useEntities) {
+    SubjectNode subject = null;
+    SubjectNode newSubject = null;
+    
+    for (Triple statement : statements) {
+      newSubject = statement.getSubject();
+      assert newSubject != null;
+      
+      if (!newSubject.equals(subject)) {
+        if (subject != null) {
+          writeClosingSubjectTag(nsMap, out);
+        }
+
+        subject = newSubject;
+        writeOpeningSubjectTag(subject, nsMap, out, useEntities);
+      }
+      
+      ObjectNode obj = statement.getObject();
+      if (obj instanceof URIReference) {
+        writeUriStatement(statement, nsMap, out, useEntities);
+      } else if (obj instanceof BlankNode) {
+        writeBlankNodeStatement(statement, nsMap, out);
+      } else if (obj instanceof Literal) {
+        writeLiteralStatement(statement, nsMap, out, useEntities);
+      }
+    }
+    
+    if (subject != null) {
+      writeClosingSubjectTag(nsMap, out);
+    }
+  }
+  
+  /**
+   * Writes an opening <code>rdf:Description</code> tag for the given resource.
+   * @param subject The subject of an RDF statement.
+   * @param nsMap The namespace prefix mappings for the document.
+   * @param out The writer.
+   * @param useEntities Determines whether to abbreviate URIReferences using XML entities.
+   */
+  private static void writeOpeningSubjectTag(SubjectNode subject, Map<String,String> nsMap, PrintWriter out, boolean useEntities) {
+    String idAttr = null;
+    String rdf = getRdfPrefix(nsMap);
+    
+    if (subject instanceof URIReference) {
+      URIReference subjUri = (URIReference)subject;
+      idAttr = rdf + ":about=\"" + (useEntities ? abbreviateUriWithEntity(subjUri, nsMap) : subjUri.getURI().toString()) + "\"";
+    } else if (subject instanceof BlankNode) {
+      idAttr = rdf + ":nodeID=\"" + StringUtil.quoteAV(((BlankNode)subject).getID()) + "\"";
+    } else {
+      throw new IllegalArgumentException("Unrecognized SubjectNode type: " + subject.getClass());
+    }
+    
+    out.println("  <" + rdf + ":Description " + idAttr + ">");
+  }
+  
+  /**
+   * Writes the closing <code>rdf:Description</code> tag for a resource.
+   * @param nsMap The namespace prefix mappings for the document.
+   * @param out The writer.
+   */
+  private static void writeClosingSubjectTag(Map<String,String> nsMap, PrintWriter out) {
+    out.println("  </" + getRdfPrefix(nsMap) + ":Description>" + NEWLINE);
+  }
+  
+  /**
+   * Writes the predicate/object for an RDF statement whose object is a URIReference.
+   * Does not write the subject, as it is written in the containing <code>rdf:Description</code> element.
+   * @param statement An RDF statement.
+   * @param nsMap The namespace prefix mappings for the document.
+   * @param out The writer.
+   * @param useEntities Determines whether to abbreviate URIReferences using XML entities.
+   */
+  private static void writeUriStatement(Triple statement, Map<String,String> nsMap, PrintWriter out, boolean useEntities) {
+    assert statement.getObject() instanceof URIReference;
+    
+    URIReference objUri = (URIReference)statement.getObject();
+    String resource = useEntities ? abbreviateUriWithEntity(objUri, nsMap) : objUri.getURI().toString();
+    String predicate = prefixPredicateUri(statement.getPredicate(), nsMap);
+    
+    out.println("    <" + predicate + " " + getRdfPrefix(nsMap) + ":resource=\"" + resource + "\"/>");
+  }
+  
+  /**
+   * Writes the predicate/object for an RDF statement whose object is a BlankNode.
+   * Does not write the subject, as it is written in the containing <code>rdf:Description</code> element.
+   * @param statement An RDF statement.
+   * @param nsMap The namespace prefix mappings for the document.
+   * @param out The writer.
+   */
+  private static void writeBlankNodeStatement(Triple statement, Map<String,String> nsMap, PrintWriter out) {
+    assert statement.getObject() instanceof BlankNode;
+    
+    String nodeId = StringUtil.quoteAV(((BlankNode)statement.getObject()).getID());
+    String predicate = prefixPredicateUri(statement.getPredicate(), nsMap);
+    
+    out.println("    <" + predicate + " " + getRdfPrefix(nsMap) + ":nodeID=\"" + nodeId + "\"/>");
+  }
+  
+  /**
+   * Writes the predicate/object for an RDF statement whose object is a Literal.
+   * Does not write the subject, as it is written in the containing <code>rdf:Description</code> element.
+   * @param statement An RDF statement.
+   * @param nsMap The namespace prefix mappings for the document.
+   * @param out The writer.
+   * @param useEntities Determines whether to abbreviate URIReferences using XML entities.
+   */
+  private static void writeLiteralStatement(Triple statement, Map<String,String> nsMap, PrintWriter out, boolean useEntities) {
+    assert statement.getObject() instanceof Literal;
+    
+    Literal objLiteral = (Literal)statement.getObject();
+    String predicate = prefixPredicateUri(statement.getPredicate(), nsMap);
+    out.print("    <" + predicate);
+    
+    URI datatype = objLiteral.getDatatype();
+    if (datatype != null) {
+      out.print(" " + getRdfPrefix(nsMap) + ":datatype=\"" + 
+          (useEntities ? abbreviateUriWithEntity(datatype, nsMap) : datatype.toString()) + "\"");
+    }
+    
+    String lang = objLiteral.getLanguage();
+    if (lang != null) {
+      out.print(" xml:lang=\"" + lang + "\"");
+    }
+    
+    out.println(">" + StringUtil.quoteAV(objLiteral.getLexicalForm()) + "</" + predicate + ">");
+  }
+  
+  /**
+   * Gets a string representation of an RDF predicate suitable for use in RDF/XML, substituting
+   * a namespace prefix where appropriate.
+   * @param predicate The RDF predicate node.
+   * @param nsMap The namespace prefix mappings for the document.
+   * @return The prefixed predicate URI.
+   */
+  private static String prefixPredicateUri(PredicateNode predicate, Map<String,String> nsMap) {
+    if (!(predicate instanceof URIReference)) {
+      throw new IllegalArgumentException("Invalid predicate type: " + predicate.getClass());
+    }
+    
+    URIReference predUri = (URIReference)predicate;
+    String value = predUri.getURI().toString();
+    assert value != null;
+    
+    String ns = predUri.getNamespace();
+    String prefix = nsMap.get(ns);
+    
+    // Substitute the namespace prefix
+    if (prefix != null) {
+      assert value.startsWith(ns);
+      value = value.replaceFirst(ns, prefix + ":");
+    }
+    
+    // Handle the RDF container predicates.
+    if (value.startsWith(getRdfPrefix(nsMap))) {
+      value = value.replaceAll("_[0-9]+", "li");
+    }
+    
+    return value;
+  }
+  
+  /**
+   * Abbreviate an RDF resource URI using an XML entity taken from the specified prefix mappings,
+   * for use in an <code>rdf:about</code> or <code>rdf:resource</code> RDF/XML attribute.
+   * @param uriRef The resource URI.
+   * @param nsMap The namespace prefix mappings for the document.
+   * @return The abbreviated URI reference.
+   */
+  private static String abbreviateUriWithEntity(URI uriRef, Map<String,String> nsMap) {
+    String value = uriRef.toString();
+    assert value != null;
+    String ns = uriRef.getNamespace();
+    String prefix = nsMap.get(ns);
+    return (prefix != null) ? value.replaceFirst(ns, "&" + prefix + ";") : value;
+  }
+  
+  /**
+   * If the given node is a URIReference or a literal with a datatype URI, then get the namespace URI 
+   * and check if it in the supplied namespace mappings.  If not, allocate a new namespace prefix 
+   * and increment the counter.
+   * @param node The RDF node to check.
+   * @param nsMap The mapping from namespace URI to namespace prefix.
+   * @param nsCounter The counter, used to allocate new namespaces.
+   * @return The supplied counter value, incremented if a new entry was added to the mapping.
+   */
+  private static int addNamespaceToMap(Object node, Map<String,String> nsMap, int nsCounter) {
+    URI uriToAdd = null;
+    if (node instanceof URIReference) {
+      uriToAdd = (URIReference)node;
+    } else if (node instanceof Literal) {
+      uriToAdd = ((Literal)node).getDatatype();
+    }
+    
+    if (uriToAdd != null) {
+      String ns = uriToAdd.getNamespace();
+      if (!nsMap.containsKey(ns)) {
+        String nsPrefix = "ns" + nsCounter++;
+        nsMap.put(ns, nsPrefix);
+      }
+    }
+    
+    return nsCounter;
+  }
+  
+  /**
+   * Gets the RDF namespace prefix from the mappings.  Does basic error checking to verify
+   * that the prefix was not reassigned.
+   * @param nsMap The prefix mappings.
+   * @return The RDF namespace prefix.
+   */
+  private static String getRdfPrefix(Map<String,String> nsMap) {
+    String prefix = nsMap.get(RDF_NAMESPACE);
+    assert prefix != null && prefix.equals(RDF_PREFIX) : "RDF prefix was reassigned";
+    return prefix;
+  }
+  
+  /**
+   * Creates a new mapping of namespace URI to prefix string, pre-populated with mappings for the
+   * RDF, RDFS, OWL, and DC namespaces.
+   * @return An initial namespace prefix mapping.
+   */
+  private static Map<String,String> createInitialNsMap() {
+    Map<String,String> nsMap = new HashMap<String,String>();
+    nsMap.put(RDF_NAMESPACE, RDF_PREFIX);
+    nsMap.put("http://www.w3.org/2000/01/rdf-schema#", "rdfs");
+    nsMap.put("http://www.w3.org/2002/07/owl#", "owl");
+    nsMap.put("http://purl.org/dc/elements/1.1/", "dc");
+    nsMap.put("http://www.w3.org/2001/XMLSchema#", "xsd");
+    return nsMap;
+  }
+}


Property changes on: trunk/src/jar/query/java/org/mulgara/query/RdfXmlEmitter.java
___________________________________________________________________
Name: svn:eol-style
   + native

Modified: trunk/tools/src/org/mulgara/tools/Sparql.java
===================================================================
--- trunk/tools/src/org/mulgara/tools/Sparql.java	2008-07-10 14:46:24 UTC (rev 1076)
+++ trunk/tools/src/org/mulgara/tools/Sparql.java	2008-07-10 22:07:00 UTC (rev 1077)
@@ -83,6 +83,11 @@
    * @param a The answer to print.
    */
   private static void printAnswer(Answer a) throws Exception {
+    GraphAnswer graphAnswer = null;
+    if (a instanceof GraphAnswer) {
+      graphAnswer = (GraphAnswer)a.clone();
+    }
+    
     System.out.println("Showing " + a.getRowCount() + " results");
     int width = a.getNumberOfVariables();
     a.beforeFirst();
@@ -90,6 +95,12 @@
       for (int c = 0; c < width; c++) System.out.print(toString(a.getObject(c)) + "    ");
       System.out.println();
     }
+    
+    if (graphAnswer != null) {
+      System.out.println("RDF/XML for GraphAnswer:");
+      System.out.println("------------------------");
+      RdfXmlEmitter.writeRdfXml(graphAnswer, System.out);
+    }
   }
 
   /**




More information about the Mulgara-svn mailing list