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 2017/06/20 10:44:25 UTC

[5/6] jena git commit: JENA-369: Output pretty lists in basic graph patterns.

JENA-369: Output pretty lists in basic graph patterns.

This includes property functions.
Conversion is cautious - it looks for the triples as the parser
might output them.

Nested bnodes structures in lists are not converted back.


Project: http://git-wip-us.apache.org/repos/asf/jena/repo
Commit: http://git-wip-us.apache.org/repos/asf/jena/commit/f19d8b26
Tree: http://git-wip-us.apache.org/repos/asf/jena/tree/f19d8b26
Diff: http://git-wip-us.apache.org/repos/asf/jena/diff/f19d8b26

Branch: refs/heads/master
Commit: f19d8b267e1976f655e2c7509e6c7ce67c0c9496
Parents: 9a3a072
Author: Andy Seaborne <an...@apache.org>
Authored: Sat Jun 17 15:48:12 2017 +0100
Committer: Andy Seaborne <an...@apache.org>
Committed: Sun Jun 18 09:26:55 2017 +0100

----------------------------------------------------------------------
 .../jena/sparql/serializer/FmtEltLib.java       | 195 ++++++
 .../sparql/serializer/FormatterElement.java     | 688 ++++++++++---------
 .../sparql/serializer/TriplesListBlock.java     |  43 ++
 3 files changed, 612 insertions(+), 314 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/jena/blob/f19d8b26/jena-arq/src/main/java/org/apache/jena/sparql/serializer/FmtEltLib.java
----------------------------------------------------------------------
diff --git a/jena-arq/src/main/java/org/apache/jena/sparql/serializer/FmtEltLib.java b/jena-arq/src/main/java/org/apache/jena/sparql/serializer/FmtEltLib.java
new file mode 100644
index 0000000..d14f3da
--- /dev/null
+++ b/jena-arq/src/main/java/org/apache/jena/sparql/serializer/FmtEltLib.java
@@ -0,0 +1,195 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.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.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.jena.sparql.serializer;
+
+import static org.apache.jena.graph.Node.ANY;
+
+import java.util.*;
+
+import org.apache.jena.graph.Node;
+import org.apache.jena.graph.Triple;
+import org.apache.jena.sparql.core.BasicPattern;
+import org.apache.jena.vocabulary.RDF;
+
+/** Place to move some of the code from FormatterElement */ 
+class FmtEltLib {
+    
+    /*package*/ static Node rdfFirst = RDF.Nodes.first;
+    /*package*/ static Node rdfRest  = RDF.Nodes.rest;
+    /*package*/ static Node rdfNil   = RDF.Nodes.nil;
+
+    // Cautious list finder.  
+    //  The list must be like the parser creates them:
+    //   Adjacent triples, in first, rest order.
+
+    // It does support embedded lists.
+    // It does not support bnode structures i.e. [:prop :obj]
+    
+    // Ths code simply finds lists - it does not perform checks as to their suitablity to print in ()-form.
+    
+    /*package*/ static TriplesListBlock createTriplesListBlock(BasicPattern bgp) {
+        TriplesListBlock tlb = new TriplesListBlock();
+        List<Triple> triples = bgp.getList();
+        for ( int idx = 0 ; idx < triples.size() ; idx++ ) {
+            Triple t = triples.get(idx);
+            if ( idx == triples.size() - 1 )
+                // Can't be a following triple.
+                break;
+            // null -> ANY
+            if ( matches(t, ANY, rdfFirst, ANY) ) {
+                Node consCell = t.getSubject();
+                int numTriples = collectList(consCell, idx, triples, tlb);
+                if ( numTriples > 0 ) {
+                    // Skip triples.
+                    idx = idx + numTriples - 1 ;
+                } else {
+                    // Play safe
+                    // skip to (? rdf:rest rdf:nil) (if any).
+                    for ( idx = idx + 1 ; idx < triples.size() ; idx++ ) {
+                        Triple t2 = triples.get(idx);
+                        if ( matches(t2, ANY, rdfRest, rdfNil) ) 
+                            break;
+                    }
+                }
+            }
+        }
+        return tlb;
+    }
+    
+    private static Node nullAsAny(Node n) {
+        return n == null ? ANY : n ; 
+    }
+
+    /*package*/ static boolean matches(Triple t, Node s, Node p, Node o) {
+        s = nullAsAny(s) ;
+        p = nullAsAny(p) ;
+        o = nullAsAny(o) ;
+        if ( s != ANY && ! Objects.equals(s, t.getSubject()) )
+            return false ;
+        if ( p != ANY && ! Objects.equals(p, t.getPredicate()) )
+            return false ;
+        if ( o != ANY && ! Objects.equals(o, t.getObject()) )
+            return false ;
+        return true ;
+    }
+
+    /*package*/ static int collectList(Node consCell, int idx, List<Triple> triples, TriplesListBlock tlb) {
+        Set<Triple> listTriples = new LinkedHashSet<>();
+        TriplesListBlock block1 = collectList1(consCell, idx, triples, listTriples, tlb);
+        if ( block1 == null )
+            // Failed.
+            return -1;
+        if ( ! FormatterElement.FMT_FREE_STANDING_LISTS ) {
+            // Reject free standiang lists.
+            int inCount = count(triples, ANY, ANY, consCell);
+            int outCount = count(triples, consCell, ANY, ANY);
+            if ( inCount == 0 && outCount == 2 )
+                return -1;
+        }
+
+        int numTriples = block1.triplesInLists.size();
+        tlb.merge(block1);
+        return numTriples;
+    }
+
+    /**
+     * Spot parser pattern of adjacent "first-rest" pairs.
+     * Collect elements of a well-formed list else null.
+     * {@code triplesInList}.
+     */
+    /*package*/ static TriplesListBlock collectList1(Node consCell, int idx, List<Triple> triples, Set<Triple> triplesInList, TriplesListBlock tlb) {
+        // This list - accumulate separately because we aren't sure it is well-formed yet.
+        TriplesListBlock thisList = new TriplesListBlock();
+        List<Node> elts = new ArrayList<>();
+        thisList.listElementsMap.put(consCell, elts);
+        
+        for ( ;; ) {
+            if ( idx + 1 >= triples.size() )
+                // Last triple - can't be an rdf:first, rdf:rest pair.
+                return null;
+            Triple t1 = triples.get(idx);
+            consCell = t1.getSubject();
+            
+            Triple t2 = triples.get(idx + 1);
+            
+            // -- Checks on t1
+            // t1 : (consCell rdf:first element) 
+            if ( ! matches(t1, consCell, rdfFirst, ANY) )
+                return null;
+            
+            // ---- Possible compound value. 
+            // Second triple is rdf:rest, or rdf:first for a list in a list.
+            // or arbitrary triples for [:p :q] in a list.  
+            // We don't handle the latter case because programatic can make this anything.  
+
+            final boolean ListsInLists = true ;
+            if ( ListsInLists ) {
+                if ( rdfFirst.equals(t2.getPredicate()) && t1.getObject().equals(t2.getSubject()) ) {
+                    // Recursion.
+                    int numProcessed = collectList(t2.getSubject(), idx + 1, triples, thisList); // -1
+                    if ( numProcessed < 0 )
+                        return null;
+                    // Not "-1" - this loop does not have autoincrement.  
+                    idx = idx + numProcessed ;
+                    // idx: Posn of the rdf:nil. Probe to see if t2 is an "rdf:rest" to consider.
+                    t2 = triples.get(idx + 1);
+                }
+            }
+            
+            // -- Checks on t2
+            // t2 : (consCell rdf:rest element)
+            if ( ! matches(t2, consCell, rdfRest, ANY) )
+                return null;
+            // -- Check consCell - no other triples or one if a subject list.
+            int outCount = count(triples, consCell, ANY, ANY) ;
+            if ( outCount != 2 ) {
+                // Head cell also be a subject list. in which case the first cell of the list can have a count of 3. 
+                if ( outCount == 3 && ! elts.isEmpty() ) 
+                    return null;
+            }
+                
+            
+            int inCount = count(triples, ANY, ANY, consCell) ;
+            if ( inCount != 1 ) {
+                // Head cell can also be zero : subject or free standing list head.
+                if ( outCount == 0 && ! elts.isEmpty() ) 
+                    return null;
+            }
+                
+            
+            Node elt = t1.getObject();
+            thisList.triplesInLists.add(t1);
+            thisList.triplesInLists.add(t2);
+            elts.add(elt);
+            if ( matches(t2, ANY, ANY, rdfNil) ) {
+                return thisList;
+            }
+            idx += 2;
+        }
+    }
+    
+    /*package*/ static int count(List<Triple> triples, Node s, Node p, Node o) {
+        int count = 0 ;
+        for ( Triple t : triples ) {
+            if ( matches(t, s, p, o) )
+                count++;
+        }
+        return count;
+    }
+}

http://git-wip-us.apache.org/repos/asf/jena/blob/f19d8b26/jena-arq/src/main/java/org/apache/jena/sparql/serializer/FormatterElement.java
----------------------------------------------------------------------
diff --git a/jena-arq/src/main/java/org/apache/jena/sparql/serializer/FormatterElement.java b/jena-arq/src/main/java/org/apache/jena/sparql/serializer/FormatterElement.java
index 2d1f6cc..4770fce 100644
--- a/jena-arq/src/main/java/org/apache/jena/sparql/serializer/FormatterElement.java
+++ b/jena-arq/src/main/java/org/apache/jena/sparql/serializer/FormatterElement.java
@@ -1,5 +1,5 @@
 /*
- * Licensed to the Apache Software Foundation (ASF) under one
+f * Licensed to the Apache Software Foundation (ASF) under one
  * or more contributor license agreements.  See the NOTICE file
  * distributed with this work for additional information
  * regarding copyright ownership.  The ASF licenses this file
@@ -18,283 +18,286 @@
 
 package org.apache.jena.sparql.serializer;
 
-import java.util.ArrayList ;
-import java.util.Iterator ;
-import java.util.List ;
+import static org.apache.jena.graph.Node.ANY;
+import static org.apache.jena.sparql.serializer.FmtEltLib.count;
+import static org.apache.jena.sparql.serializer.FmtEltLib.createTriplesListBlock;
+import static org.apache.jena.sparql.serializer.FmtEltLib.rdfFirst;
 
-import org.apache.jena.atlas.io.IndentedLineBuffer ;
-import org.apache.jena.atlas.io.IndentedWriter ;
-import org.apache.jena.graph.Node ;
-import org.apache.jena.graph.Triple ;
-import org.apache.jena.query.Query ;
+import java.util.*;
+
+import org.apache.jena.atlas.io.IndentedLineBuffer;
+import org.apache.jena.atlas.io.IndentedWriter;
+import org.apache.jena.graph.Node;
+import org.apache.jena.graph.Triple;
+import org.apache.jena.query.Query;
 import org.apache.jena.query.QueryVisitor;
 import org.apache.jena.query.Syntax;
-import org.apache.jena.sparql.core.BasicPattern ;
-import org.apache.jena.sparql.core.PathBlock ;
-import org.apache.jena.sparql.core.TriplePath ;
-import org.apache.jena.sparql.expr.Expr ;
-import org.apache.jena.sparql.path.PathWriter ;
-import org.apache.jena.sparql.syntax.* ;
-import org.apache.jena.sparql.util.FmtUtils ;
-import org.apache.jena.vocabulary.RDF ;
-
-
-public class FormatterElement extends FormatterBase
-    implements ElementVisitor
-{
-    public static final int INDENT = 2 ; 
-    
+import org.apache.jena.sparql.core.BasicPattern;
+import org.apache.jena.sparql.core.PathBlock;
+import org.apache.jena.sparql.core.TriplePath;
+import org.apache.jena.sparql.expr.Expr;
+import org.apache.jena.sparql.path.PathWriter;
+import org.apache.jena.sparql.syntax.*;
+import org.apache.jena.sparql.util.FmtUtils;
+import org.apache.jena.vocabulary.RDF;
+
+public class FormatterElement extends FormatterBase implements ElementVisitor {
+    public static final int     INDENT                   = 2;
+
     /** Control whether to show triple pattern boundaries - creates extra nesting */
-    public static final boolean PATTERN_MARKERS = false ;
-    
-//    /** Control whether triple patterns always have a final dot - it can be dropped in some cases */
-//    public static boolean PATTERN_FINAL_DOT = false ;
+    public static final boolean PATTERN_MARKERS          = false;
+
+// /** Control whether triple patterns always have a final dot - it can be dropped in some cases */
+// public static boolean PATTERN_FINAL_DOT = false ;
 
     /** Control whether (non-triple) patterns have a final dot - it can be dropped */
-    public static final boolean GROUP_SEP_DOT = false ;
-    
+    public static final boolean GROUP_SEP_DOT            = false;
+
     /** Control whether the first item of a group is on the same line as the { */
-    public static final boolean GROUP_FIRST_ON_SAME_LINE = true ;
+    public static final boolean GROUP_FIRST_ON_SAME_LINE = true;
 
     /** Control pretty printing */
-    public static final boolean PRETTY_PRINT = true  ;
+    public static final boolean PRETTY_PRINT             = true;
 
-    /** Control whether disjunction has set of delimiters - as it's a group usually, these aren't needed */
-    public static final boolean UNION_MARKERS = false ;
+    /** Control pretty printing of RDF lists */
+    public static final boolean FMT_LISTS                = true;
     
+    /** Control pretty printing of free standing RDF lists */
+    // Do *not* set "true" - argument of property paths can be lists and these spane
+    // PathBlocks and Basic Graph Patterns and this is not handled prop.
+    public static final boolean FMT_FREE_STANDING_LISTS  = false;
+
+    /**
+     * Control whether disjunction has set of delimiters - as it's a group usually, these
+     * aren't needed
+     */
+    public static final boolean UNION_MARKERS            = false;
+
     /** Control whether GRAPH indents in a fixed way or based on the layout size */
-    public static final boolean GRAPH_FIXED_INDENT = true ;
-    
-    /** Control whether NOT EXIST/EXISTS indents in a fixed way or based on the layout size */
-    public static final boolean ELEMENT1_FIXED_INDENT = true ;
-    
+    public static final boolean GRAPH_FIXED_INDENT       = true;
+
+    /**
+     * Control whether NOT EXIST/EXISTS indents in a fixed way or based on the layout size
+     */
+    public static final boolean ELEMENT1_FIXED_INDENT    = true;
+
     /** Control triples pretty printing */
-    public static final int TRIPLES_SUBJECT_COLUMN = 8 ;
-    
+    public static final int     TRIPLES_SUBJECT_COLUMN   = 8;
+
     // Less than this => rest of triple on the same line
-    // Could be smart and make it depend on the property length as well.  Later.
-    public static final int TRIPLES_SUBJECT_LONG = 12 ;     
+    // Could be smart and make it depend on the property length as well. Later.
+    public static final int     TRIPLES_SUBJECT_LONG     = 12;
 
-    public static final int TRIPLES_PROPERTY_COLUMN = 20;
-    
-    public static final int TRIPLES_COLUMN_GAP = 2 ;
+    public static final int     TRIPLES_PROPERTY_COLUMN  = 20;
+
+    public static final int     TRIPLES_COLUMN_GAP       = 2;
 
     public FormatterElement(IndentedWriter out, SerializationContext context) {
-        super(out, context) ;
+        super(out, context);
     }
 
     public static void format(IndentedWriter out, SerializationContext cxt, Element el) {
-        FormatterElement fmt = new FormatterElement(out, cxt) ;
-        fmt.startVisit() ;
-        el.visit(fmt) ;
-        fmt.finishVisit() ;
+        FormatterElement fmt = new FormatterElement(out, cxt);
+        fmt.startVisit();
+        el.visit(fmt);
+        fmt.finishVisit();
     }
 
     public static String asString(Element el) {
-        SerializationContext cxt = new SerializationContext() ;
-        IndentedLineBuffer b = new IndentedLineBuffer() ;
-        FormatterElement.format(b, cxt, el) ;
-        return b.toString() ;
+        SerializationContext cxt = new SerializationContext();
+        IndentedLineBuffer b = new IndentedLineBuffer();
+        FormatterElement.format(b, cxt, el);
+        return b.toString();
     }
 
     public boolean topMustBeGroup() {
-        return false ;
+        return false;
     }
 
     @Override
     public void visit(ElementTriplesBlock el) {
         if ( el.isEmpty() ) {
-            out.println("# Empty BGP") ;
-            return ;
+            out.println("# Empty BGP");
+            return;
         }
-        formatTriples(el.getPattern()) ;
+        formatTriples(el.getPattern());
     }
 
     @Override
     public void visit(ElementPathBlock el) {
-        // Write path block - don't put in a final trailing "." 
+        // Write path block - don't put in a final trailing "."
         if ( el.isEmpty() ) {
-            out.println("# Empty BGP") ;
-            return ;
+            out.println("# Empty BGP");
+            return;
         }
 
         // Split into BGP-path-BGP-...
         // where the BGPs may be empty.
-        PathBlock pBlk = el.getPattern() ;
-        BasicPattern bgp = new BasicPattern() ;
-        boolean first = true ;      // Has anything been output?
+        PathBlock pBlk = el.getPattern();
+        BasicPattern bgp = new BasicPattern();
+        boolean first = true;      // Has anything been output?
         for ( TriplePath tp : pBlk ) {
             if ( tp.isTriple() ) {
-                bgp.add(tp.asTriple()) ;
-                continue ;
+                bgp.add(tp.asTriple());
+                continue;
             }
-            
+
             if ( !bgp.isEmpty() ) {
                 if ( !first )
-                    out.println(" .") ;
-                flush(bgp) ;
-                first = false ;
+                    out.println(" .");
+                flush(bgp);
+                first = false;
             }
             if ( !first )
-                out.println(" .") ;
-            // Path
-            printSubject(tp.getSubject()) ;
-            out.print(" ") ;
-            PathWriter.write(out, tp.getPath(), context.getPrologue()) ;
-            out.print(" ") ;
-            printObject(tp.getObject()) ;
-            first = false ;
+                out.println(" .");
+            // Path (no RDF list output).
+            printSubject(tp.getSubject());
+            out.print(" ");
+            PathWriter.write(out, tp.getPath(), context.getPrologue());
+            out.print(" ");
+            printObject(tp.getObject());
+            first = false;
         }
         // Flush any stored triple patterns.
         if ( !bgp.isEmpty() ) {
             if ( !first )
-                out.println(" .") ;
-            flush(bgp) ;
-            first = false ;
+                out.println(" .");
+            flush(bgp);
+            first = false;
         }
     }
 
-    private void flush(BasicPattern bgp) {
-        formatTriples(bgp) ;
-        bgp.getList().clear(); 
-    }
-
     @Override
-    public void visit(ElementDataset el)
-    {
-//        if ( el.getDataset() != null)
-//        {
-//            DatasetGraph dsNamed = el.getDataset() ;
-//            out.print("DATASET ") ;
-//            out.incIndent(INDENT) ;
-//            Iterator iter = dsNamed.listNames() ;
-//            if ( iter.hasNext() )
-//            {
-//                boolean first = false ;
-//                for ( ; iter.hasNext() ; )
-//                {
-//                    if ( ! first )
-//                        out.newline() ;
-//                    out.print("FROM <") ;
-//                    String s = (String)iter.next() ; 
-//                    out.print(s) ;
-//                    out.print(">") ;
+    public void visit(ElementDataset el) {
+        // Not implemented.
+//        if ( el.getDataset() != null ) {
+//            DatasetGraph dsNamed = el.getDataset();
+//            out.print("DATASET ");
+//            out.incIndent(INDENT);
+//            Iterator<Node> iter = dsNamed.listGraphNodes();
+//            if ( iter.hasNext() ) {
+//                boolean first = false;
+//                for ( ; iter.hasNext() ; ) {
+//                    if ( !first )
+//                        out.newline();
+//                    out.print("FROM <");
+//                    Node n = iter.next();
+//                    out.print(slotToString(n));
+//                    out.print(">");
 //                }
 //            }
-//            out.decIndent(INDENT) ;
-//            out.newline() ;
+//            out.decIndent(INDENT);
+//            out.newline();
 //        }
-        if ( el.getElement() != null )
-            visitAsGroup(el.getElement()) ;
+//        if ( el.getElement() != null )
+//            visitAsGroup(el.getElement());
     }
 
     @Override
     public void visit(ElementFilter el) {
-        out.print("FILTER ") ;
-        Expr expr = el.getExpr() ;
-        FmtExprSPARQL v = new FmtExprSPARQL(out, context) ;
+        out.print("FILTER ");
+        Expr expr = el.getExpr();
+        FmtExprSPARQL v = new FmtExprSPARQL(out, context);
 
         // This assumes that complex expressions are bracketted
         // (parens) as necessary except for some cases:
         // Plain variable or constant
 
-        boolean addParens = false ;
+        boolean addParens = false;
         if ( expr.isVariable() )
-            addParens = true ;
+            addParens = true;
         if ( expr.isConstant() )
-            addParens = true ;
+            addParens = true;
 
         if ( addParens )
-            out.print("( ") ;
-        v.format(expr) ;
+            out.print("( ");
+        v.format(expr);
         if ( addParens )
-            out.print(" )") ;
+            out.print(" )");
     }
 
     @Override
     public void visit(ElementAssign el) {
-        out.print("LET (") ;
-        out.print("?" + el.getVar().getVarName()) ;
-        out.print(" := ") ;
-        FmtExprSPARQL v = new FmtExprSPARQL(out, context) ;
-        v.format(el.getExpr()) ;
-        out.print(")") ;
+        out.print("LET (");
+        out.print("?" + el.getVar().getVarName());
+        out.print(" := ");
+        FmtExprSPARQL v = new FmtExprSPARQL(out, context);
+        v.format(el.getExpr());
+        out.print(")");
     }
 
     @Override
     public void visit(ElementBind el) {
-        out.print("BIND(") ;
-        FmtExprSPARQL v = new FmtExprSPARQL(out, context) ;
-        v.format(el.getExpr()) ;
-        out.print(" AS ") ;
-        out.print("?" + el.getVar().getVarName()) ;
-        out.print(")") ;
+        out.print("BIND(");
+        FmtExprSPARQL v = new FmtExprSPARQL(out, context);
+        v.format(el.getExpr());
+        out.print(" AS ");
+        out.print("?" + el.getVar().getVarName());
+        out.print(")");
     }
 
     @Override
     public void visit(ElementData el) {
-        QuerySerializer.outputDataBlock(out, el.getVars(), el.getRows(), context) ;
+        QuerySerializer.outputDataBlock(out, el.getVars(), el.getRows(), context);
     }
 
     @Override
     public void visit(ElementUnion el) {
         if ( el.getElements().size() == 1 ) {
             // If this is an element of just one, just do it inplace
-            // Can't happen from a parsed query.
-            // Now can :-)
-
-            // SPARQL 1.1 inline UNION.
-            // Same as OPTIONAL, MINUS
-            out.print("UNION") ;
-            out.incIndent(INDENT) ;
-            out.newline() ;
-            visitAsGroup(el.getElements().get(0)) ;
-            out.decIndent(INDENT) ;
-            return ;
+            // Can't happen from a parsed query in SPARQL.
+            visitAsGroup(el.getElements().get(0));
+//            // Same as OPTIONAL, MINUS
+//            out.print("UNION");
+//            out.incIndent(INDENT);
+//            out.newline();
+//            visitAsGroup(el.getElements().get(0));
+//            out.decIndent(INDENT);
+            return;
         }
 
         if ( UNION_MARKERS ) {
-            out.print("{") ;
-            out.newline() ;
-            out.pad() ;
+            out.print("{");
+            out.newline();
+            out.pad();
         }
 
-        out.incIndent(INDENT) ;
+        out.incIndent(INDENT);
 
-        boolean first = true ;
+        boolean first = true;
         for ( Element subElement : el.getElements() ) {
             if ( !first ) {
-                out.decIndent(INDENT) ;
-                out.newline() ;
-                out.print("UNION") ;
-                out.newline() ;
-                out.incIndent(INDENT) ;
+                out.decIndent(INDENT);
+                out.newline();
+                out.print("UNION");
+                out.newline();
+                out.incIndent(INDENT);
             }
-            visitAsGroup(subElement) ;
-            first = false ;
+            visitAsGroup(subElement);
+            first = false;
         }
 
-        out.decIndent(INDENT) ;
+        out.decIndent(INDENT);
 
         if ( UNION_MARKERS ) {
-            out.newline() ;
-            out.print("}") ;
+            out.newline();
+            out.print("}");
         }
     }
 
     @Override
     public void visit(ElementGroup el) {
-        out.print("{") ;
-        int initialRowNumber = out.getRow() ;
-        out.incIndent(INDENT) ;
+        out.print("{");
+        int initialRowNumber = out.getRow();
+        out.incIndent(INDENT);
         if ( !GROUP_FIRST_ON_SAME_LINE )
-            out.newline() ;
+            out.newline();
 
-        int row1 = out.getRow() ;
-        out.pad() ;
+        int row1 = out.getRow();
+        out.pad();
 
-        boolean first = true ;
-        Element lastElt = null ;
+        boolean first = true;
+        Element lastElt = null;
 
         for ( Element subElement : el.getElements() ) {
             // Some adjacent elements need a DOT:
@@ -303,302 +306,359 @@ public class FormatterElement extends FormatterBase
                 // Need to move on after the last thing printed.
                 // Check for necessary DOT as separator
                 if ( GROUP_SEP_DOT || needsDotSeparator(lastElt, subElement) )
-                    out.print(" . ") ;
-                out.newline() ;
+                    out.print(" . ");
+                out.newline();
             }
-            subElement.visit(this) ;
-            first = false ;
-            lastElt = subElement ;
+            subElement.visit(this);
+            first = false;
+            lastElt = subElement;
         }
-        out.decIndent(INDENT) ;
+        out.decIndent(INDENT);
 
         // Where to put the closing "}"
-        int row2 = out.getRow() ;
+        int row2 = out.getRow();
         if ( row1 != row2 )
-            out.newline() ;
+            out.newline();
 
         // Finally, close the group.
         if ( out.getRow() == initialRowNumber )
-            out.print(" ") ;
-        out.print("}") ;
+            out.print(" ");
+        out.print("}");
     }
 
     private static boolean needsDotSeparator(Element el1, Element el2) {
-        return needsDotSeparator(el1) && needsDotSeparator(el2) ;
+        return needsDotSeparator(el1) && needsDotSeparator(el2);
     }
 
     private static boolean needsDotSeparator(Element el) {
-        return (el instanceof ElementTriplesBlock) || (el instanceof ElementPathBlock) ;
+        return (el instanceof ElementTriplesBlock) || (el instanceof ElementPathBlock);
     }
 
     @Override
     public void visit(ElementOptional el) {
-        out.print("OPTIONAL") ;
-        out.incIndent(INDENT) ;
-        out.newline() ;
-        visitAsGroup(el.getOptionalElement()) ;
-        out.decIndent(INDENT) ;
+        out.print("OPTIONAL");
+        out.incIndent(INDENT);
+        out.newline();
+        visitAsGroup(el.getOptionalElement());
+        out.decIndent(INDENT);
     }
 
     @Override
     public void visit(ElementNamedGraph el) {
-        visitNodePattern("GRAPH", el.getGraphNameNode(), el.getElement()) ;
+        visitNodePattern("GRAPH", el.getGraphNameNode(), el.getElement());
     }
 
     @Override
     public void visit(ElementService el) {
-        String x = "SERVICE" ;
+        String x = "SERVICE";
         if ( el.getSilent() )
-            x = "SERVICE SILENT" ;
-        visitNodePattern(x, el.getServiceNode(), el.getElement()) ;
+            x = "SERVICE SILENT";
+        visitNodePattern(x, el.getServiceNode(), el.getElement());
     }
 
     private void visitNodePattern(String label, Node node, Element subElement) {
-        int len = label.length() ;
-        out.print(label) ;
-        out.print(" ") ;
-        String nodeStr = (node == null) ? "*" : slotToString(node) ;
-        out.print(nodeStr) ;
-        len += nodeStr.length() ;
+        int len = label.length();
+        out.print(label);
+        out.print(" ");
+        String nodeStr = (node == null) ? "*" : slotToString(node);
+        out.print(nodeStr);
+        len += nodeStr.length();
         if ( GRAPH_FIXED_INDENT ) {
-            out.incIndent(INDENT) ;
-            out.newline() ; // NB and newline
+            out.incIndent(INDENT);
+            out.newline(); // NB and newline
         } else {
-            out.print(" ") ;
-            len++ ;
-            out.incIndent(len) ;
+            out.print(" ");
+            len++;
+            out.incIndent(len);
         }
-        visitAsGroup(subElement) ;
+        visitAsGroup(subElement);
 
         if ( GRAPH_FIXED_INDENT )
-            out.decIndent(INDENT) ;
+            out.decIndent(INDENT);
         else
-            out.decIndent(len) ;
+            out.decIndent(len);
     }
 
     private void visitElement1(String label, Element1 el) {
 
-        int len = label.length() ;
-        out.print(label) ;
-        len += label.length() ;
+        int len = label.length();
+        out.print(label);
+        len += label.length();
         if ( ELEMENT1_FIXED_INDENT ) {
-            out.incIndent(INDENT) ;
-            out.newline() ; // NB and newline
+            out.incIndent(INDENT);
+            out.newline(); // NB and newline
         } else {
-            out.print(" ") ;
-            len++ ;
-            out.incIndent(len) ;
+            out.print(" ");
+            len++;
+            out.incIndent(len);
         }
-        visitAsGroup(el.getElement()) ;
+        visitAsGroup(el.getElement());
         if ( ELEMENT1_FIXED_INDENT )
-            out.decIndent(INDENT) ;
+            out.decIndent(INDENT);
         else
-            out.decIndent(len) ;
+            out.decIndent(len);
     }
 
     @Override
     public void visit(ElementExists el) {
-        visitElement1("EXISTS", el) ;
+        visitElement1("EXISTS", el);
     }
 
     @Override
     public void visit(ElementNotExists el) {
-        visitElement1("NOT EXISTS", el) ;
+        visitElement1("NOT EXISTS", el);
     }
 
     @Override
     public void visit(ElementMinus el) {
-        out.print("MINUS") ;
-        out.incIndent(INDENT) ;
-        out.newline() ;
-        visitAsGroup(el.getMinusElement()) ;
-        out.decIndent(INDENT) ;
+        out.print("MINUS");
+        out.incIndent(INDENT);
+        out.newline();
+        visitAsGroup(el.getMinusElement());
+        out.decIndent(INDENT);
     }
 
     @Override
     public void visit(ElementSubQuery el) {
-        out.print("{ ") ;
-        out.incIndent(INDENT) ;
-        Query q = el.getQuery() ;
+        out.print("{ ");
+        out.incIndent(INDENT);
+        Query q = el.getQuery();
 
         // Serialize with respect to the existing context
-        QuerySerializerFactory factory = SerializerRegistry.get().getQuerySerializerFactory(Syntax.syntaxARQ) ;
-        QueryVisitor serializer = factory.create(Syntax.syntaxARQ, context, out) ;
-        q.visit(serializer) ;
+        QuerySerializerFactory factory = SerializerRegistry.get().getQuerySerializerFactory(Syntax.syntaxARQ);
+        QueryVisitor serializer = factory.create(Syntax.syntaxARQ, context, out);
+        q.visit(serializer);
 
-        out.decIndent(INDENT) ;
-        out.print("}") ;
+        out.decIndent(INDENT);
+        out.print("}");
     }
 
     public void visitAsGroup(Element el) {
-        boolean needBraces = !((el instanceof ElementGroup) || (el instanceof ElementSubQuery)) ;
+        boolean needBraces = !((el instanceof ElementGroup) || (el instanceof ElementSubQuery));
 
         if ( needBraces ) {
-            out.print("{ ") ;
-            out.incIndent(INDENT) ;
+            out.print("{ ");
+            out.incIndent(INDENT);
         }
 
-        el.visit(this) ;
+        el.visit(this);
 
         if ( needBraces ) {
-            out.decIndent(INDENT) ;
-            out.print("}") ;
+            out.decIndent(INDENT);
+            out.print("}");
         }
     }
-
-    int subjectWidth = -1 ;
-    int predicateWidth = -1 ;
     
+    // -------- Formatting a basic graph pattern
+    // Triple order is preserved.
+
+    int subjectWidth   = -1;
+    int predicateWidth = -1;
+
     @Override
     protected void formatTriples(BasicPattern triples) {
         if ( !PRETTY_PRINT ) {
-            super.formatTriples(triples) ;
-            return ;
+            super.formatTriples(triples);
+            return;
         }
 
-        // TODO RDF Collections - spot the parsers pattern
         if ( triples.isEmpty() )
-            return ;
+            return;
+
+        // Lists in this BasicPattern.
+        // TriplesListBlock is a record of lists in this BGP.
+        // Formatting is off for list if there is an empty TriplesListBlock.
+        // This is cautionsly spotting the triples from RDF lists as generated by the
+        // parser.
 
-        setWidths(triples) ;
+        TriplesListBlock block = FMT_LISTS 
+            ? createTriplesListBlock(triples) 
+            : new TriplesListBlock();
+        
+        Set<Node> freeStanding = new HashSet<>();
+        for ( Node head : block.listElementsMap.keySet() ) {
+            // Check for suitablity to print.
+            // See also FmtEltLib#collectList
+            //
+            // Subject-list : inCount = 0, outCount = 3
+            // Object-list :  inCount = 1, outCount = 2
+            // Free-standing list :  inCount = 0, outCount = 2
+            //     Free-standing list is handled as a special case. 
+            int inCount = count(triples.getList(), ANY, ANY, head);
+            int outCount = count(triples.getList(), head, ANY, ANY);
+            if ( inCount == 0 && outCount == 2 )
+                // Free standing.
+                freeStanding.add(head);
+        }
+
+        setWidths(triples);
         if ( subjectWidth > TRIPLES_SUBJECT_COLUMN )
-            subjectWidth = TRIPLES_SUBJECT_COLUMN ;
+            subjectWidth = TRIPLES_SUBJECT_COLUMN;
         if ( predicateWidth > TRIPLES_PROPERTY_COLUMN )
-            predicateWidth = TRIPLES_PROPERTY_COLUMN ;
+            predicateWidth = TRIPLES_PROPERTY_COLUMN;
 
-        // Loops:
-        List<Triple> subjAcc = new ArrayList<>() ; // Accumulate all triples
-                                                   // with the same subject.
-        Node subj = null ; // Subject being accumulated
+        // Accumulate all triples with the same subject.
+        List<Triple> subjAcc = new ArrayList<>();
+        // Subject being accumulated
+        Node subj = null;
+        // Print newlines between blocks.
+        boolean first = true;
 
-        boolean first = true ; // Print newlines between blocks.
-
-        int indent = -1 ;
+        int indent = -1;
         for ( Triple t : triples ) {
+            if ( block.triplesInLists.contains(t) ) {
+                if ( rdfFirst.equals(t.getPredicate()) ) {
+                    if ( freeStanding.contains(t.getSubject()) )
+                        printNodeOrList(t.getSubject(), block.listElementsMap);
+                }
+                continue;
+            }
+
             if ( subj != null && t.getSubject().equals(subj) ) {
-                subjAcc.add(t) ;
-                continue ;
+                subjAcc.add(t);
+                continue;
             }
 
             if ( subj != null ) {
                 if ( !first )
-                    out.println(" .") ;
-                formatSameSubject(subj, subjAcc) ;
-                first = false ;
+                    out.println(" .");
+                formatSameSubject(subj, subjAcc, block.listElementsMap);
+                first = false;
                 // At end of line of a block of triples with same subject.
                 // Drop through and start new block of same subject triples.
             }
 
             // New subject
-            subj = t.getSubject() ;
-            subjAcc.clear() ;
-            subjAcc.add(t) ;
+            subj = t.getSubject();
+            subjAcc.clear();
+            subjAcc.add(t);
         }
 
         // Flush accumulator
         if ( subj != null && subjAcc.size() != 0 ) {
             if ( !first )
-                out.println(" .") ;
-            first = false ;
-            formatSameSubject(subj, subjAcc) ;
+                out.println(" .");
+            first = false;
+            formatSameSubject(subj, subjAcc, block.listElementsMap);
         }
     }
 
-    private void formatSameSubject(Node subject, List<Triple> triples) {
+    // ----
+
+    private void flush(BasicPattern bgp) {
+        formatTriples(bgp);
+        bgp.getList().clear();
+    }
+
+    private void formatSameSubject(Node subject, List<Triple> triples, Map<Node, List<Node>> lists) {
+
         if ( triples == null || triples.size() == 0 )
-            return ;
-        
-        // Do the first triple.
-        Iterator<Triple> iter = triples.iterator() ;
-        Triple t1 = iter.next() ; 
+            return;
 
-//        int indent = TRIPLES_SUBJECT_COLUMN+TRIPLES_COLUMN_GAP ;
-//        // Long subject => same line.  Works for single triple as well.
-//        int s1_len = printSubject(t1.getSubject()) ;
-//        //int x = out.getCol() ;
+        // Do the first triple.
+        Iterator<Triple> iter = triples.iterator();
+        Triple t1 = iter.next();
 
-        int indent = subjectWidth + TRIPLES_COLUMN_GAP ;
-        int s1_len = printSubject(t1.getSubject()) ;
+        int indent = subjectWidth + TRIPLES_COLUMN_GAP;
 
+        int s1_len = printNodeOrList(subject, lists);
         if ( s1_len > TRIPLES_SUBJECT_LONG ) {
-            // Too long - start a new line.
-            out.incIndent(indent) ;
-            out.println() ;
+            out.incIndent(indent);
+            out.println();
         } else {
-            printGap() ;
-            out.incIndent(indent) ;
+            printGap();
+            out.incIndent(indent);
         }
 
         // Remainder of first triple
-        printProperty(t1.getPredicate()) ;
-        printGap() ;
-        printObject(t1.getObject()) ;
+        printProperty(t1.getPredicate());
+        printGap();
 
-        // Do the rest
+        printNodeOrList(t1.getObject(), lists);
 
+        // Do the rest
         for ( ; iter.hasNext() ; ) {
-            Triple t = iter.next() ;
-            out.println(" ;") ;
-            printProperty(t.getPredicate()) ;
-            printGap() ;
-            printObject(t.getObject()) ;
-            continue ;
-            // print property list
+            Triple t = iter.next();
+            out.println(" ;");
+            printProperty(t.getPredicate());
+            printGap();
+            printNodeOrList(t.getObject(), lists);
+            continue;
         }
 
         // Finish off the block.
-        out.decIndent(indent) ;
+        out.decIndent(indent);
         // out.print(" .") ;
     }
 
+    private int printNodeOrList(Node node, Map<Node, List<Node>> lists) {
+        if ( lists.containsKey(node) )
+            return printList(lists.get(node), lists);
+        else
+            return printNoCol(node);
+    }
+
     private void setWidths(BasicPattern triples) {
-        subjectWidth = -1 ;
-        predicateWidth = -1 ;
+        subjectWidth = -1;
+        predicateWidth = -1;
 
         for ( Triple t : triples ) {
-            String s = slotToString(t.getSubject()) ;
+            String s = slotToString(t.getSubject());
             if ( s.length() > subjectWidth )
-                subjectWidth = s.length() ;
+                subjectWidth = s.length();
 
-            String p = slotToString(t.getPredicate()) ;
+            String p = slotToString(t.getPredicate());
             if ( p.length() > predicateWidth )
-                predicateWidth = p.length() ;
+                predicateWidth = p.length();
         }
     }
 
     private void printGap() {
-        out.print(' ', TRIPLES_COLUMN_GAP) ;
+        out.print(' ', TRIPLES_COLUMN_GAP);
     }
 
-    // Indent must be set first.
+    // printSubject, printObject - used in ElementPathBlock.
     private int printSubject(Node s) {
-        String str = slotToString(s) ;
-        out.print(str) ;
-        out.pad(subjectWidth) ;
-        return str.length() ;
+        return printNoCol(s);
     }
 
     // Assumes the indent is TRIPLES_SUBJECT_COLUMN+GAP
-    private static String RDFTYPE = FmtUtils.stringForNode(RDF.Nodes.type, new SerializationContext()) ;
-    
+    private static String RDFTYPE = FmtUtils.stringForNode(RDF.Nodes.type, new SerializationContext());
+
     private int printProperty(Node p) {
-        String str = slotToString(p) ;
+        String str = slotToString(p);
         if ( p.equals(RDF.Nodes.type) && str.equals(RDFTYPE) )
-            out.print("a") ;
+            out.print("a");
         else
-            out.print(str) ;
-        out.pad(predicateWidth) ;
-        return str.length() ;
+            out.print(str);
+        out.pad(predicateWidth);
+        return str.length();
     }
 
     private int printObject(Node obj) {
-        return printNoCol(obj) ;
+        return printNoCol(obj);
     }
 
-    private int printNoCol(Node node) {
-        String str = slotToString(node) ;
-        out.print(str) ;
-        return str.length() ;
+    private int printList(List<Node> list, Map<Node, List<Node>> lists) {
+        if ( list.isEmpty() ) {
+            out.print("()");
+            return 2;
+        }
+            
+        int col0 = out.getCol();
+        out.print("( ");
+        for ( Node n : list ) {
+            printNodeOrList(n, lists);
+            out.print(" ");
+        }
+        out.print(")");
+        int col1 = out.getCol();
+        return col1 - col0;
+    }
 
+    private int printNoCol(Node node) {
+        String str = slotToString(node);
+        out.print(str);
+        return str.length();
     }
 }

http://git-wip-us.apache.org/repos/asf/jena/blob/f19d8b26/jena-arq/src/main/java/org/apache/jena/sparql/serializer/TriplesListBlock.java
----------------------------------------------------------------------
diff --git a/jena-arq/src/main/java/org/apache/jena/sparql/serializer/TriplesListBlock.java b/jena-arq/src/main/java/org/apache/jena/sparql/serializer/TriplesListBlock.java
new file mode 100644
index 0000000..37671b8
--- /dev/null
+++ b/jena-arq/src/main/java/org/apache/jena/sparql/serializer/TriplesListBlock.java
@@ -0,0 +1,43 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.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.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.jena.sparql.serializer;
+
+import java.util.*;
+
+import org.apache.jena.atlas.iterator.Iter;
+import org.apache.jena.graph.Node;
+import org.apache.jena.graph.Triple;
+
+/** Internal record of list details. */
+/*package*/ class TriplesListBlock {
+    Map<Node, List<Node>> listElementsMap = new HashMap<>();
+    // Triples in lists.
+    Set<Triple>           triplesInLists  = new LinkedHashSet<>();
+
+    /*package*/ void merge(TriplesListBlock other) {
+        listElementsMap.putAll(other.listElementsMap);
+        triplesInLists.addAll(other.triplesInLists);
+    }
+        
+    @Override
+    public String toString() {
+        return Iter.asString(listElementsMap.keySet(), ", ") + "\n" + "{"+ Iter.asString(triplesInLists.iterator(), "\n")+"}";
+            
+    }
+}
\ No newline at end of file