You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@jena.apache.org by an...@apache.org on 2020/04/10 13:13:23 UTC

[jena] branch master updated: JENA-1880: ResultSetWriterJSON using Nodes

This is an automated email from the ASF dual-hosted git repository.

andy pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/jena.git


The following commit(s) were added to refs/heads/master by this push:
     new 8399c6b  JENA-1880: ResultSetWriterJSON using Nodes
     new cbdba5e  Merge pull request #724 from afs/jena1880-resultset-writer-json
8399c6b is described below

commit 8399c6b1b83892a43c6e0ed04393dae684c1b7e9
Author: Andy Seaborne <an...@apache.org>
AuthorDate: Wed Apr 8 13:46:55 2020 +0100

    JENA-1880: ResultSetWriterJSON using Nodes
---
 .../riot/resultset/rw/ResultSetWriterJSON.java     | 376 ++++++++++++---------
 ...etWriterJSON.java => ResultSetWriterJSON1.java} |   6 +-
 2 files changed, 216 insertions(+), 166 deletions(-)

diff --git a/jena-arq/src/main/java/org/apache/jena/riot/resultset/rw/ResultSetWriterJSON.java b/jena-arq/src/main/java/org/apache/jena/riot/resultset/rw/ResultSetWriterJSON.java
index 8291b03..985fc8c 100644
--- a/jena-arq/src/main/java/org/apache/jena/riot/resultset/rw/ResultSetWriterJSON.java
+++ b/jena-arq/src/main/java/org/apache/jena/riot/resultset/rw/ResultSetWriterJSON.java
@@ -29,33 +29,34 @@ import org.apache.jena.atlas.io.IO;
 import org.apache.jena.atlas.io.IndentedWriter;
 import org.apache.jena.atlas.json.io.JSWriter;
 import org.apache.jena.atlas.logging.Log;
+import org.apache.jena.graph.Node;
 import org.apache.jena.query.ARQ;
-import org.apache.jena.query.QuerySolution;
 import org.apache.jena.query.ResultSet;
-import org.apache.jena.rdf.model.Literal;
-import org.apache.jena.rdf.model.RDFNode;
-import org.apache.jena.rdf.model.Resource;
 import org.apache.jena.rdf.model.impl.Util;
 import org.apache.jena.riot.out.NodeToLabel;
 import org.apache.jena.riot.resultset.ResultSetLang;
 import org.apache.jena.riot.resultset.ResultSetWriter;
 import org.apache.jena.riot.resultset.ResultSetWriterFactory;
 import org.apache.jena.riot.system.SyntaxLabels;
-import org.apache.jena.sparql.resultset.ResultSetApply;
+import org.apache.jena.sparql.core.Var;
+import org.apache.jena.sparql.engine.binding.Binding;
 import org.apache.jena.sparql.resultset.ResultSetException;
-import org.apache.jena.sparql.resultset.ResultSetProcessor;
 import org.apache.jena.sparql.util.Context;
 
+/** Write results in {@code application/sparql-results+json} format. */
 public class ResultSetWriterJSON implements ResultSetWriter {
 
     public static ResultSetWriterFactory factory = lang->{
         if (!Objects.equals(lang, ResultSetLang.SPARQLResultSetJSON ) )
-            throw new ResultSetException("ResultSetWriter for JSON asked for a "+lang); 
-        return new ResultSetWriterJSON(); 
+            throw new ResultSetException("ResultSetWriter for JSON asked for a "+lang);
+        return new ResultSetWriterJSON();
     };
-    
-    private ResultSetWriterJSON() {}
-    
+
+    private ResultSetWriterJSON() { }
+
+    // We use an inner object for writing of one result set so that the
+    // ResultSetWriter has no per-write state variables.
+
     @Override
     public void write(Writer out, ResultSet resultSet, Context context) {
         throw new UnsupportedOperationException("Writing JSON results to a java.io.Writer. Use an OutputStream.") ;
@@ -63,226 +64,255 @@ public class ResultSetWriterJSON implements ResultSetWriter {
 
     @Override
     public void write(OutputStream outStream, boolean result, Context context) {
-        JSWriter out = new JSWriter(outStream);
-        out.startOutput();
-        out.startObject();
-        out.key(kHead);
-        out.startObject();
-        out.finishObject();
-        out.pair(kBoolean, result);
-        out.finishObject();
-        out.finishOutput();
-        IO.flush(outStream);
+        try {
+            JSWriter out = new JSWriter(outStream);
+            out.startOutput();
+            out.startObject();
+            out.key(kHead);
+            out.startObject();
+            out.finishObject();
+            out.pair(kBoolean, result);
+            out.finishObject();
+            out.finishOutput();
+        } finally {
+            IO.flush(outStream);
+        }
     }
-    
+
     @Override
-    public void write(OutputStream out, ResultSet resultSet, Context context) {
-        JSONOutputResultSet jsonOut = new JSONOutputResultSet(out, context);
-        ResultSetApply a = new ResultSetApply(resultSet, jsonOut);
-        a.apply();
+    public void write(OutputStream outStream, ResultSet resultSet, Context context) {
+        IndentedWriter out = new IndentedWriter(outStream);
+        try {
+            ResultSetWriterTableJSON x = new ResultSetWriterTableJSON(out, context);
+            x.write(resultSet);
+        }
+        finally {
+            IO.flush(out);
+        }
     }
 
-    private static class JSONOutputResultSet implements ResultSetProcessor {
+    // Create once per write call.
+    // This holds the state of the writing of one ResultSet.
+    static class ResultSetWriterTableJSON {
+        private final NodeToLabel    labels;
+        private final  IndentedWriter out;
+        /** Control whether the type/literal/fileds all go on one line. */
         private static final boolean multiLineValues   = false;
+        /** Control whether variables in header are one per line (minor). */
         private static final boolean multiLineVarNames = false;
 
-        private final IndentedWriter out;
-        private final NodeToLabel    labels;
-
-        private JSONOutputResultSet(OutputStream outStream, Context context) {
+        private ResultSetWriterTableJSON(OutputStream outStream, Context context) {
             this(new IndentedWriter(outStream), context);
         }
 
-        private JSONOutputResultSet(IndentedWriter indentedOut, Context context) {
+        private ResultSetWriterTableJSON(IndentedWriter indentedOut, Context context) {
             out = indentedOut;
             boolean outputGraphBNodeLabels = (context != null) && context.isTrue(ARQ.outputGraphBNodeLabels);
             labels = outputGraphBNodeLabels
                 ? SyntaxLabels.createNodeToLabelAsGiven()
-                : SyntaxLabels.createNodeToLabel();
-        }
-
-        @Override
-        public void start(ResultSet rs) {
-            println("{");
-            out.incIndent();
-            doHead(rs);
-            println(quoteName(kResults), ": {");
-            out.incIndent();
-            println(quoteName(kBindings), ": [");
-            out.incIndent();
-            firstSolution = true;
-        }
-
-        @Override
-        public void finish(ResultSet rs) {
-            // Close last binding.
-            out.println();
-
-            out.decIndent();    // bindings
-            println("]");
-            out.decIndent();
-            println("}");       // results
-            out.decIndent();
-            println("}");       // top level {}
-            out.flush();
+                    : SyntaxLabels.createNodeToLabel();
         }
 
-        private void doHead(ResultSet rs) {
-            println(quoteName(kHead),": {");
-            out.incIndent();
-            doLink(rs);
-            doVars(rs);
-            out.decIndent();
-            println("} ,");
+        private void writeHeader(ResultSet rs) {
+            println(out, quoteName(kHead),": {");
+            incIndent(out);
+            writeHeaderLink(out, rs);
+            writeHeaderVars(out, rs);
+            decIndent(out);
+            println(out, "} ,");
         }
 
-        private void doLink(ResultSet rs) {
+        private static void writeHeaderLink(IndentedWriter out, ResultSet rs) {
             // ---- link
             // out.println("\"link\": []") ;
         }
 
-        private void doVars(ResultSet rs) {
+        //  "var": [  ... ]
+        private static void writeHeaderVars(IndentedWriter out, ResultSet rs) {
             // On one line.
-            print(quoteName(kVars), ": [ ");
+            print(out, quoteName(kVars), ": [ ");
             if ( multiLineVarNames )
-                out.println();
-            out.incIndent();
+                println(out);
+            incIndent(out);
             for ( Iterator<String> iter = rs.getResultVars().iterator() ; iter.hasNext() ; ) {
                 String varname = iter.next();
-                print("\"", varname, "\"");
-                if ( multiLineVarNames )
-                    println();
+                print(out, "\"", varname, "\"");
                 if ( iter.hasNext() )
-                    print(" , ");
+                    print(out, " , ");
+                if ( multiLineVarNames )
+                    println(out);
             }
-            println(" ]");
-            out.decIndent();
+            decIndent(out);
+            println(out, " ]");
         }
 
-        boolean firstSolution          = true;
-        boolean firstBindingInSolution = true;
-
-        // NB assumes are on end of previous line.
-        @Override
-        public void start(QuerySolution qs) {
-            if ( !firstSolution )
-                println(" ,");
-            firstSolution = false;
-            println("{");
-            out.incIndent();
-            firstBindingInSolution = true;
+        private void write(ResultSet resultSet) {
+            print(out, "{");
+            incIndent(out);
+
+            writeHeader(resultSet);
+            println(out, quoteName(kResults), ": {");
+            incIndent(out);
+            println(out, quoteName(kBindings), ": [");
+            incIndent(out);
+
+            boolean firstRow = true;
+            for ( ; resultSet.hasNext() ; ) {
+                Binding binding = resultSet.nextBinding();
+                writeRow(out, resultSet, binding, firstRow);
+                firstRow = false;
+            }
+            println(out);
+            decIndent(out);        // bindings
+            println(out, "]");
+            decIndent(out);
+            println(out, "}");      // results
+            decIndent(out);
+            println(out, "}");      // top level {}
         }
 
-        @Override
-        public void finish(QuerySolution qs) {
-            println(); // Finish last binding
-            out.decIndent();
-            print("}"); // NB No newline
+        private void writeRow(IndentedWriter out, ResultSet resultSet, Binding binding, boolean firstRow) {
+            if ( !firstRow )
+                println(out, " ,");
+            println(out, "{");
+            incIndent(out);
+            boolean firstInRow = true;
+            // Print in the order seen in the header.
+            for ( Iterator<String> iter = resultSet.getResultVars().iterator() ; iter.hasNext() ; ) {
+                Var var = Var.alloc(iter.next());
+                Node value = binding.get(var);
+                if ( value == null )
+                    continue;
+                if ( ! firstInRow )
+                    println(out, " ,");
+                writeVarValue(out, var, value, firstInRow );
+                firstInRow = false;
+            }
+
+            if ( false ) {
+                // Print "missing" variables.
+                // If the header is right, this should not be necessary.
+                for ( Iterator<Var> vars = binding.vars() ; vars.hasNext() ; ) {
+                    Var var = vars.next();
+                    if ( ! resultSet.getResultVars().contains(var.getName()) ) {
+                        Node value = binding.get(var);
+                        if ( ! firstInRow )
+                            println(out, " ,");
+                        writeVarValue(out, var, value, firstInRow );
+                        firstInRow = false;
+                    }
+                }
+            }
+            println(out);
+            decIndent(out);
+            print(out, "}"); // NB No newline
         }
 
-        @Override
-        public void binding(String varName, RDFNode value) {
+        /** Write one   { "var": { ... term ... } } */
+        private void writeVarValue(IndentedWriter out, Var var, Node value, boolean firstInRow) {
             if ( value == null )
+                // Skip if no value.
                 return;
-
-            if ( !firstBindingInSolution )
-                println(" ,");
-            firstBindingInSolution = false;
-
             // Do not use quoteName - varName may not be JSON-safe as a bare name.
-            print(quote(varName), ": { ");
+            print(out, quote(var.getVarName()), ": { ");
             if ( multiLineValues )
-                out.println();
+                println(out);
 
-            out.incIndent();
-            // Old, explicit unbound
+            incIndent(out);
+            writeValue(out, value);
+            decIndent(out);
+
+            if ( !multiLineValues )
+                print(out, " ");
+            print(out, "}");
+            // No newline - allow for " ,"
+        }
+
+        private void writeValue(IndentedWriter out, Node value) {
+            // Explicit unbound
             // if ( value == null )
-            //     printUnbound() ;
+            //     writeValueUnbound(out) ;
             // else
+
             if ( value.isLiteral() )
-                printLiteral((Literal)value);
-            else if ( value.isResource() )
-                printResource((Resource)value);
+                writeValueLiteral(out, value);
+            else if ( value.isURI() )
+                writeValueURI(out, value);
+            else if ( value.isBlank() )
+                writeValueBlankNode(out, value);
+            // For RDF*
+//            else if ( value.isNodeTriple() )
+//                writeValueNodeTriple(out, value);
             else
-                Log.warn(this, "Unknown RDFNode type in result set: " + value.getClass());
-            out.decIndent();
-
-            if ( !multiLineValues )
-                print(" ");
-            print("}"); // NB No newline
+                Log.warn(ResultSetWriterJSON.class, "Unknown RDFNode type in result set: " + value.getClass());
         }
 
-         private void printUnbound() {
-             print(quoteName(kType), ": ", quote(kUnbound), " , ") ;
-             if ( multiLineValues ) 
-                 println() ;
-             print(quoteName(kValue), ": null") ;
-             if ( multiLineValues )
-                 println() ;
-         }
+        private void writeValueUnbound(IndentedWriter out) {
+            print(out, quoteName(kType), ": ", quote(kUnbound), " , ") ;
+            if ( multiLineValues )
+                println(out) ;
+            print(out, quoteName(kValue), ": null") ;
+            if ( multiLineValues )
+                println(out) ;
+        }
 
-        private void printLiteral(Literal literal) {
-            String datatype = literal.getDatatypeURI();
-            String lang = literal.getLanguage();
+        private void writeValueLiteral(IndentedWriter out, Node literal) {
+            String datatype = literal.getLiteralDatatypeURI();
+            String lang = literal.getLiteralLanguage();
 
             if ( Util.isSimpleString(literal) || Util.isLangString(literal) ) {
-                print(quoteName(kType), ": ", quote(kLiteral), " , ");
+                print(out, quoteName(kType), ": ", quote(kLiteral), " , ");
                 if ( multiLineValues )
-                    println();
+                    println(out);
 
                 if ( lang != null && !lang.equals("") ) {
-                    print(quoteName(kXmlLang), ": ", quote(lang), " , ");
+                    print(out, quoteName(kXmlLang), ": ", quote(lang), " , ");
                     if ( multiLineValues )
-                        println();
+                        println(out);
                 }
             } else {
-                print(quoteName(kType), ": ", quote(kLiteral), " , ");
+                print(out, quoteName(kType), ": ", quote(kLiteral), " , ");
                 if ( multiLineValues )
-                    println();
+                    println(out);
 
-                print(quoteName(kDatatype), ": ", quote(datatype), " , ");
+                print(out, quoteName(kDatatype), ": ", quote(datatype), " , ");
                 if ( multiLineValues )
-                    println();
+                    println(out);
             }
 
-            print(quoteName(kValue), ": ", quote(literal.getLexicalForm()));
+            print(out, quoteName(kValue), ": ", quote(literal.getLiteralLexicalForm()));
             if ( multiLineValues )
-                println();
+                println(out);
         }
 
-        private void printResource(Resource resource) {
-            if ( resource.isAnon() ) {
-                String label = labels.get(null, resource.asNode());
-                // Comes with leading "_:"
-                label = label.substring(2);
+        private void writeValueBlankNode(IndentedWriter out, Node resource) {
+            String label = labels.get(null, resource);
+            // Comes with leading "_:"
+            label = label.substring(2);
 
-                print(quoteName(kType), ": ", quote(kBnode), " , ");
-                if ( multiLineValues )
-                    out.println();
+            print(out, quoteName(kType), ": ", quote(kBnode), " , ");
+            if ( multiLineValues )
+                println(out);
 
-                print(quoteName(kValue), ": ", quote(label));
+            print(out, quoteName(kValue), ": ", quote(label));
+
+            if ( multiLineValues )
+                println(out);
 
-                if ( multiLineValues )
-                    println();
-            } else {
-                print(quoteName(kType), ": ", quote(kUri), " , ");
-                if ( multiLineValues )
-                    println();
-                print(quoteName(kValue), ": ", quote(resource.getURI()));
-                if ( multiLineValues )
-                    println();
-                return;
-            }
         }
-        
-        private void print(String... strings) {
-            for ( String s : strings )
-                out.print(s);
+
+        private void writeValueURI(IndentedWriter out, Node resource) {
+            print(out, quoteName(kType), ": ", quote(kUri), " , ");
+            if ( multiLineValues )
+                println(out);
+            print(out, quoteName(kValue), ": ", quote(resource.getURI()));
+            if ( multiLineValues )
+                println(out);
+            return;
         }
 
-        private void println(String... strings) {
-            print(strings);
-            out.println();
+        private void writeValueNodeTriple(IndentedWriter out, Node value) {
+            print(out, "** NodeTriple **");
         }
 
         private static String quote(String string) {
@@ -301,5 +331,25 @@ public class ResultSetWriterJSON implements ResultSetWriter {
             //return "\""+string+"\"";
             return quote(string);
         }
+
+        // Intercept all operations - development assistance.
+
+        private static void incIndent(IndentedWriter out) {
+            out.incIndent();
+        }
+
+        private static void decIndent(IndentedWriter out) {
+            out.decIndent();
+        }
+
+        private static void print(IndentedWriter out, String... strings) {
+            for ( String s : strings )
+                out.print(s);
+        }
+
+        private static void println(IndentedWriter out, String... strings) {
+            print(out, strings);
+            out.println();
+        }
     }
 }
diff --git a/jena-arq/src/main/java/org/apache/jena/riot/resultset/rw/ResultSetWriterJSON.java b/jena-arq/src/main/java/org/apache/jena/riot/resultset/rw/ResultSetWriterJSON1.java
similarity index 98%
copy from jena-arq/src/main/java/org/apache/jena/riot/resultset/rw/ResultSetWriterJSON.java
copy to jena-arq/src/main/java/org/apache/jena/riot/resultset/rw/ResultSetWriterJSON1.java
index 8291b03..4ed13c0 100644
--- a/jena-arq/src/main/java/org/apache/jena/riot/resultset/rw/ResultSetWriterJSON.java
+++ b/jena-arq/src/main/java/org/apache/jena/riot/resultset/rw/ResultSetWriterJSON1.java
@@ -46,15 +46,15 @@ import org.apache.jena.sparql.resultset.ResultSetException;
 import org.apache.jena.sparql.resultset.ResultSetProcessor;
 import org.apache.jena.sparql.util.Context;
 
-public class ResultSetWriterJSON implements ResultSetWriter {
+public class ResultSetWriterJSON1 implements ResultSetWriter {
 
     public static ResultSetWriterFactory factory = lang->{
         if (!Objects.equals(lang, ResultSetLang.SPARQLResultSetJSON ) )
             throw new ResultSetException("ResultSetWriter for JSON asked for a "+lang); 
-        return new ResultSetWriterJSON(); 
+        return new ResultSetWriterJSON1(); 
     };
     
-    private ResultSetWriterJSON() {}
+    private ResultSetWriterJSON1() {}
     
     @Override
     public void write(Writer out, ResultSet resultSet, Context context) {