001/**
002 * Licensed to the Apache Software Foundation (ASF) under one
003 * or more contributor license agreements.  See the NOTICE file
004 * distributed with this work for ad
005 * ditional information
006 * regarding copyright ownership.  The ASF licenses this file
007 * to you under the Apache License, Version 2.0 (the
008 * "License"); you may not use this file except in compliance
009 * with the License.  You may obtain a copy of the License at
010 *
011 *     http://www.apache.org/licenses/LICENSE-2.0
012 *
013 * Unless required by applicable law or agreed to in writing, software
014 * distributed under the License is distributed on an "AS IS" BASIS,
015 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
016 * See the License for the specific language governing permissions and
017 * limitations under the License.
018 */
019
020package org.fcrepo.jena;
021
022import static org.apache.jena.riot.writer.WriterConst.GAP_P_O ;
023import static org.apache.jena.riot.writer.WriterConst.GAP_S_P ;
024import static org.apache.jena.riot.writer.WriterConst.INDENT_OBJECT ;
025import static org.apache.jena.riot.writer.WriterConst.INDENT_PREDICATE ;
026import static org.apache.jena.riot.writer.WriterConst.LONG_PREDICATE ;
027import static org.apache.jena.riot.writer.WriterConst.LONG_SUBJECT ;
028import static org.apache.jena.riot.writer.WriterConst.MIN_PREDICATE ;
029import static org.apache.jena.riot.writer.WriterConst.OBJECT_LISTS ;
030import static org.apache.jena.riot.writer.WriterConst.RDF_First ;
031import static org.apache.jena.riot.writer.WriterConst.RDF_Nil ;
032import static org.apache.jena.riot.writer.WriterConst.RDF_Rest ;
033import static org.apache.jena.riot.writer.WriterConst.RDF_type ;
034import static org.apache.jena.riot.writer.WriterConst.rdfNS ;
035
036import java.util.* ;
037
038import org.apache.jena.atlas.io.IndentedWriter ;
039import org.apache.jena.atlas.iterator.Iter ;
040import org.apache.jena.atlas.lib.InternalErrorException ;
041import org.apache.jena.atlas.lib.Pair ;
042import org.apache.jena.atlas.lib.SetUtils ;
043import org.apache.jena.graph.Graph ;
044import org.apache.jena.graph.Node ;
045import org.apache.jena.graph.Triple ;
046import org.apache.jena.riot.RIOT ;
047import org.apache.jena.riot.other.GLib ;
048import org.apache.jena.riot.out.NodeFormatter ;
049import org.apache.jena.riot.out.NodeFormatterTTL ;
050import org.apache.jena.riot.out.NodeFormatterTTL_MultiLine ;
051import org.apache.jena.riot.out.NodeToLabel ;
052import org.apache.jena.riot.system.PrefixMap ;
053import org.apache.jena.riot.system.PrefixMapFactory ;
054import org.apache.jena.riot.system.RiotLib ;
055import org.apache.jena.sparql.core.DatasetGraph ;
056import org.apache.jena.sparql.core.Quad ;
057import org.apache.jena.sparql.util.Context ;
058import org.apache.jena.util.iterator.ExtendedIterator ;
059import org.apache.jena.vocabulary.RDF ;
060import org.apache.jena.vocabulary.RDFS ;
061
062/**
063 * Base class to support the pretty forms of Turtle-related languages (Turtle, TriG)
064 */
065public abstract class CopyOfTurtleShell {
066    protected final IndentedWriter out ;
067    protected final NodeFormatter  nodeFmt ;
068    protected final PrefixMap      prefixMap ;
069    protected final String         baseURI ;
070
071    protected CopyOfTurtleShell(IndentedWriter out, PrefixMap pmap, String baseURI, Context context) {
072        this.out = out ;
073        if ( pmap == null )
074            pmap = PrefixMapFactory.emptyPrefixMap() ;
075        this.prefixMap = pmap ;
076        this.baseURI = baseURI ;
077        if ( context != null && context.isTrue(RIOT.multilineLiterals) )
078            this.nodeFmt = new NodeFormatterTTL_MultiLine(baseURI, pmap, NodeToLabel.createScopeByDocument()) ;
079        else {
080            // NOTE, Fedora change: NodeFormatterTTL -> FedoraNodeFormatterTTL
081            this.nodeFmt = new FedoraNodeFormatterTTL(baseURI, pmap, NodeToLabel.createScopeByDocument()) ;
082        }
083    }
084
085    protected void writeBase(String base) {
086        RiotLib.writeBase(out, base) ;
087    }
088
089    protected void writePrefixes(PrefixMap prefixMap) {
090        RiotLib.writePrefixes(out, prefixMap) ;
091    }
092
093    /** Write graph in Turtle syntax (or part of TriG) */
094    protected void writeGraphTTL(Graph graph) {
095        ShellGraph x = new ShellGraph(graph, null, null) ;
096        x.writeGraph() ;
097    }
098
099    /** Write graph in Turtle syntax (or part of TriG). graphName is null for default graph. */
100    protected void writeGraphTTL(DatasetGraph dsg, Node graphName) {
101        Graph g = (graphName == null || Quad.isDefaultGraph(graphName))
102                ? dsg.getDefaultGraph()
103                : dsg.getGraph(graphName) ;
104        ShellGraph x = new ShellGraph(g, graphName, dsg) ;
105        x.writeGraph() ;
106    }
107
108    // Write one graph - using an inner object class to isolate
109    // the state variables for writing a single graph.
110    private final class ShellGraph {
111        // Dataset (for writing graphs indatasets) -- may be null
112        private final DatasetGraph          dsg ;
113        private final Collection<Node>      graphNames ;
114        private final Node                  graphName ;
115        private final Graph                 graph ;
116
117        // Blank nodes that have one incoming triple
118        private /*final*/ Set<Node>             nestedObjects ;
119        private final Set<Node>             nestedObjectsWritten ;
120
121        // Blank node subjects that are not referenced as objects or graph names
122        // excluding unlnked lists.
123        private final Set<Node>             freeBnodes ;
124
125        // The head node in each well-formed list -> list elements
126        private /*final*/ Map<Node, List<Node>> lists ;
127
128        // List that do not have any incoming triples
129        private final Map<Node, List<Node>> freeLists ;
130
131        // Lists that have more than one incoming triple
132        private final Map<Node, List<Node>> nLinkedLists ;
133
134        // All nodes that are part of list structures.
135        private final Collection<Node>      listElts ;
136
137        // Allow lists and nest bnode objects.
138        // This is true for the main pretty printing then
139        // false when we are clearing up unwritten triples.
140        private boolean allowDeepPretty = true ;
141
142        private ShellGraph(Graph graph, Node graphName, DatasetGraph dsg) {
143            this.dsg = dsg ;
144            this.graphName = graphName ;
145
146            this.graphNames = (dsg != null) ? Iter.toSet(dsg.listGraphNodes()) : null ;
147
148            this.graph = graph ;
149            this.nestedObjects = new HashSet<>() ;
150            this.nestedObjectsWritten = new HashSet<>() ;
151            this.freeBnodes = new HashSet<>() ;
152
153            this.lists = new HashMap<>() ;
154            this.freeLists = new HashMap<>() ;
155            this.nLinkedLists = new HashMap<>() ;
156            this.listElts = new HashSet<>() ;
157            this.allowDeepPretty = true ;
158
159            // Must be in this order.
160            findLists() ;
161            findBNodesSyntax1() ;
162            // Stop head of lists printed as triples going all the way to the
163            // good part.
164            nestedObjects.removeAll(listElts) ;
165
166            //printDetails() ;
167        }
168
169        // Debug
170        private void printDetails() {
171            printDetails("nestedObjects", nestedObjects) ;
172            //printDetails("nestedObjectsWritten", nestedObjectsWritten) ;
173            printDetails("freeBnodes", freeBnodes) ;
174
175            printDetails("lists", lists) ;
176            printDetails("freeLists", freeLists) ;
177            printDetails("nLinkedLists", nLinkedLists) ;
178            printDetails("listElts", listElts) ;
179        }
180
181        private void printDetails(String label, Map<Node, List<Node>> map) {
182            System.err.print("## ") ;
183            System.err.print(label) ;
184            System.err.print(" = ") ;
185            System.err.println(map) ;
186        }
187
188        private void printDetails(String label, Collection<Node> nodes) {
189            System.err.print("## ") ;
190            System.err.print(label) ;
191            System.err.print(" = ") ;
192            System.err.println(nodes) ;
193        }
194        // Debug
195
196        private ShellGraph(Graph graph) {
197            this(graph, null, null) ;
198        }
199
200        // ---- Data access
201        /** Get all the triples for the graph.find */
202        private List<Triple> triples(Node s, Node p, Node o) {
203            List<Triple> acc = new ArrayList<>() ;
204            RiotLib.accTriples(acc, graph, s, p, o) ;
205            return acc ;
206        }
207
208        /** Get exactly one triple or null for none or more than one. */
209        private Triple triple1(Node s, Node p, Node o) {
210            if ( dsg != null )
211                return RiotLib.triple1(dsg, s, p, o) ;
212            else
213                return RiotLib.triple1(graph, s, p, o) ;
214        }
215
216        /** Get exactly one triple, or null for none or more than one. */
217        private Triple triple1(DatasetGraph dsg, Node s, Node p, Node o) {
218            Iterator<Quad> iter = dsg.find(Node.ANY, s, p, o) ;
219            if ( !iter.hasNext() )
220                return null ;
221            Quad q = iter.next() ;
222            if ( iter.hasNext() )
223                return null ;
224            return q.asTriple() ;
225        }
226
227        private long countTriples(Node s, Node p, Node o) {
228            if ( dsg != null )
229                return RiotLib.countTriples(dsg, s, p, o) ;
230            else
231                return RiotLib.countTriples(graph, s, p, o) ;
232        }
233
234        private ExtendedIterator<Triple> find(Node s, Node p, Node o) {
235            return graph.find(s, p, o) ;
236        }
237
238        /** returns 0,1,2 (where 2 really means "more than 1") */
239        private int inLinks(Node obj) {
240            if ( dsg != null ) {
241                Iterator<Quad> iter = dsg.find(Node.ANY, Node.ANY, Node.ANY, obj) ;
242                return count012(iter) ;
243            } else {
244                ExtendedIterator<Triple> iter = graph.find(Node.ANY, Node.ANY, obj) ;
245                try { return count012(iter) ; }
246                finally { iter.close() ; }
247            }
248        }
249
250        /** returns 0,1,2 (where 2 really means "more than 1") */
251        private int occursAsSubject(Node subj) {
252            if ( dsg != null ) {
253                Iterator<Quad> iter = dsg.find(Node.ANY, subj, Node.ANY, Node.ANY) ;
254                return count012(iter) ;
255            } else {
256                ExtendedIterator<Triple> iter = graph.find(subj, Node.ANY, Node.ANY) ;
257                try { return count012(iter) ; }
258                finally { iter.close() ; }
259            }
260        }
261
262        private int count012(Iterator<? > iter) {
263            if ( !iter.hasNext() )
264                return 0 ;
265            iter.next() ;
266            if ( !iter.hasNext() )
267                return 1 ;
268            return 2 ;
269        }
270
271        /** Check whether a node is used only in the graph we're working on */
272        private boolean containedInOneGraph(Node node) {
273            if ( dsg == null )
274                // Single graph
275                return true ;
276
277            if ( graphNames.contains(node) )
278                // Used as a graph name.
279                return false ;
280
281            Iterator<Quad> iter = dsg.find(Node.ANY, node, Node.ANY, Node.ANY) ;
282            if ( ! quadsThisGraph(iter) )
283                return false ;
284
285            iter = dsg.find(Node.ANY, Node.ANY, node, Node.ANY) ;
286            if ( ! quadsThisGraph(iter) )
287                return false ;
288
289            iter = dsg.find(Node.ANY, Node.ANY, Node.ANY, node) ;
290            if ( ! quadsThisGraph(iter) )
291                return false ;
292            return true ;
293        }
294
295        /** Check whether an iterator of quads is all in the same graph (dataset assumed) */
296        private boolean quadsThisGraph(Iterator<Quad> iter) {
297            if ( ! iter.hasNext() )
298                // Empty iterator
299                return true ;
300            Quad q = iter.next() ;
301            Node gn = q.getGraph() ;
302
303            // Test first quad - both default graph (various forms) or same named graph
304            if ( isDefaultGraph(gn) ) {
305                if ( ! isDefaultGraph(graphName) )
306                    return false ;
307            } else {
308                if ( ! Objects.equals(gn, graphName) )
309                    // Not both same named graph
310                    return false ;
311            }
312            // Check rest of iterator.
313            for ( ; iter.hasNext() ; ) {
314                Quad q2 = iter.next() ;
315                if ( ! Objects.equals(gn, q2.getGraph()) )
316                    return false ;
317            }
318            return true ;
319        }
320
321        private boolean isDefaultGraph(Node node) {
322            return node == null || Quad.isDefaultGraph(node) ;
323        }
324
325        /** Get triples with the same subject */
326        private Collection<Triple> triplesOfSubject(Node subj) {
327            return RiotLib.triplesOfSubject(graph, subj) ;
328        }
329
330        private Iterator<Node> listSubjects() {
331            return GLib.listSubjects(graph) ;
332        }
333
334        // ---- Data access
335
336        /** Find Bnodes that can written as []
337         * Subject position (top level) - only used for subject position anywhere in the dataset
338         * Object position (any level) - only used as object once anywhere in the dataset
339         */
340        private void findBNodesSyntax1() {
341            Set<Node> rejects = new HashSet<>() ; // Nodes known not to meet the requirement.
342
343            ExtendedIterator<Triple> iter = find(Node.ANY, Node.ANY, Node.ANY) ;
344            try {
345                for ( ; iter.hasNext() ; ) {
346                    Triple t = iter.next() ;
347                    Node subj = t.getSubject() ;
348                    Node obj = t.getObject() ;
349
350                    if ( subj.isBlank() )
351                    {
352                        int sConn = inLinks(subj) ;
353                        if ( sConn == 0 && containedInOneGraph(subj) )
354                            // Not used as an object in this graph.
355                            freeBnodes.add(subj) ;
356                    }
357
358                    if ( ! obj.isBlank() )
359                        continue ;
360                    if ( rejects.contains(obj) )
361                        continue ;
362
363                    int connectivity = inLinks(obj) ;
364                    if ( connectivity == 1 && containedInOneGraph(obj) ) {
365                        // If not used in another graph (or as graph name)
366                        nestedObjects.add(obj) ;
367                    }
368                    else
369                        // Uninteresting object connected multiple times.
370                        rejects.add(obj) ;
371                }
372            } finally { iter.close() ; }
373        }
374
375        // --- Lists setup
376        /*
377         * Find all list heads and all nodes in well-formed lists. Return a
378         * (list head -> Elements map), list elements)
379         */
380        private void findLists() {
381            List<Triple> tails = triples(Node.ANY, RDF_Rest, RDF_Nil) ;
382            for ( Triple t : tails ) {
383                // Returns the elements, reversed.
384                Collection<Node> listElts2 = new HashSet<>() ;
385                Pair<Node, List<Node>> p = followTailToHead(t.getSubject(), listElts2) ;
386                if ( p != null ) {
387                    Node headElt = p.getLeft() ;
388                    // Free standing/private
389                    List<Node> elts = p.getRight() ;
390                    long numLinks = countTriples(null, null, headElt) ;
391                    if ( numLinks == 1 )
392                        lists.put(headElt, elts) ;
393                    else if ( numLinks == 0 )
394                        // 0 connected lists
395                        freeLists.put(headElt, elts) ;
396                    else
397                        // Two triples to this list.
398                        nLinkedLists.put(headElt, elts) ;
399                    listElts.addAll(listElts2) ;
400                }
401            }
402        }
403
404        // return head elt node, list of elements.
405        private Pair<Node, List<Node>> followTailToHead(Node lastListElt, Collection<Node> listElts) {
406            List<Node> listCells = new ArrayList<>() ;
407            List<Node> eltsReversed = new ArrayList<>() ;
408            List<Triple> acc = new ArrayList<>() ;
409            Node x = lastListElt ;
410
411            for ( ; ; ) {
412                if ( !validListElement(x, acc) ) {
413                    if ( listCells.size() == 0 )
414                        // No earlier valid list.
415                        return null ;
416                    // Fix up to previous valid list cell.
417                    x = listCells.remove(listCells.size() - 1) ;
418                    break ;
419                }
420
421                Triple t = triple1(x, RDF_First, null) ;
422                if ( t == null )
423                    return null ;
424                eltsReversed.add(t.getObject()) ;
425                listCells.add(x) ;
426
427                // Try to move up the list.
428                List<Triple> acc2 = triples(null, null, x) ;
429                long numRest = countTriples(null, RDF_Rest, x) ;
430                if ( numRest != 1 ) {
431                    // Head of well-formed list.
432                    // Classified by 0,1,more links later.
433                    listCells.add(x) ;
434                    break ;
435                }
436                // numRest == 1
437                int numLinks = acc2.size() ;
438                if ( numLinks > 1 )
439                    // Non-list links to x
440                    break ;
441                // Valid.
442                Triple tLink = acc2.get(0) ;
443                x = tLink.getSubject() ;
444            }
445            // Success.
446            listElts.addAll(listCells) ;
447            Collections.reverse(eltsReversed) ;
448            return Pair.create(x, eltsReversed) ;
449        }
450
451        /** Return the triples of the list element, or null if invalid list */
452        private boolean validListElement(Node x, List<Triple> acc) {
453            Triple t1 = triple1(x, RDF_Rest, null) ; // Which we came up to get
454            // here :-(
455            if ( t1 == null )
456                return false ;
457            Triple t2 = triple1(x, RDF_First, null) ;
458            if ( t2 == null )
459                return false ;
460            long N = countTriples(x, null, null) ;
461            if ( N != 2 )
462                return false ;
463            acc.add(t1) ;
464            acc.add(t2) ;
465            return true ;
466        }
467
468        // ----
469
470        private void writeGraph() {
471            Iterator<Node> subjects = listSubjects() ;
472            boolean somethingWritten = writeBySubject(subjects) ;
473            // Write remainders
474            // 1 - Shared lists
475            somethingWritten = writeRemainingNLinkedLists(somethingWritten) ;
476
477            // 2 - Free standing lists
478            somethingWritten = writeRemainingFreeLists(somethingWritten) ;
479
480            // 3 - Blank nodes that are unwrittern single objects.
481            //            System.err.println("## ## ##") ;
482            //            printDetails("nestedObjects", nestedObjects) ;
483            //            printDetails("nestedObjectsWritten", nestedObjectsWritten) ;
484            Set<Node> singleNodes = SetUtils.difference(nestedObjects, nestedObjectsWritten) ;
485            somethingWritten = writeRemainingNestedObjects(singleNodes, somethingWritten) ;
486        }
487
488        private boolean writeRemainingNLinkedLists(boolean somethingWritten) {
489            // Print carefully - need a label for the first cell.
490            // So we write out the first element of the list in triples, then
491            // put the remainer as a pretty list
492            for ( Node n : nLinkedLists.keySet() ) {
493                if ( somethingWritten )
494                    out.println() ;
495                somethingWritten = true ;
496
497                List<Node> x = nLinkedLists.get(n) ;
498                writeNode(n) ;
499
500                write_S_P_Gap();
501                out.pad() ;
502
503                writeNode(RDF_First) ;
504                print(" ") ;
505                writeNode(x.get(0)) ;
506                print(" ;") ;
507                println() ;
508                writeNode(RDF_Rest) ;
509                print("  ") ;
510                x = x.subList(1, x.size()) ;
511                writeList(x) ;
512                print(" .") ;
513                out.decIndent(INDENT_PREDICATE) ;
514                println() ;
515            }
516            return somethingWritten ;
517        }
518
519        // Write free standing lists - ones where the head is not an object of
520        // some other triple. Turtle does not allow free standing (... ) .
521        // so write as a predicateObjectList for one element.
522        private boolean writeRemainingFreeLists(boolean somethingWritten) {
523            for ( Node n : freeLists.keySet() ) {
524                if ( somethingWritten )
525                    out.println() ;
526                somethingWritten = true ;
527
528                List<Node> x = freeLists.get(n) ;
529                // Print first element for the [ ... ]
530                out.print("[ ") ;
531
532                writeNode(RDF_First) ;
533                print(" ") ;
534                writeNode(x.get(0)) ;
535                print(" ; ") ;
536                writeNode(RDF_Rest) ;
537                print(" ") ;
538                x = x.subList(1, x.size()) ;
539                // Print remainder.
540                writeList(x) ;
541                out.println(" ] .") ;
542            }
543            return somethingWritten ;
544        }
545
546        // Write any left over nested objects
547        // These come from blank node cycles : _:a <p> _:b . _b: <p> _:a .
548        // Also from from blank node cycles + tail: _:a <p> _:b . _:a <p> "" .  _b: <p> _:a .
549        private boolean writeRemainingNestedObjects(Set<Node> objects, boolean somethingWritten) {
550            for ( Node n : objects ) {
551                if ( somethingWritten )
552                    out.println() ;
553                somethingWritten = true ;
554
555                Triple t = triple1(null, null, n) ;
556                if ( t == null )
557                    throw new InternalErrorException("Expected exactly one triple") ;
558
559                Node subj = t.getSubject() ;
560                boolean b = allowDeepPretty ;
561                try {
562                    allowDeepPretty = false;
563                    Collection<Triple> triples = triples(subj, null, null) ;
564                    writeCluster(subj, triples);
565                } finally { allowDeepPretty = b ; }
566            }
567
568            return somethingWritten ;
569        }
570
571        // Write triples, flat and simply.
572        // Reset the state variables so "isPretty" return false.
573        private void writeTriples(Node subj, Iterator<Triple> iter) {
574            allowDeepPretty = false;
575            writeCluster(subj, Iter.toList(iter));
576        }
577
578        // return true if did write something.
579        private boolean writeBySubject(Iterator<Node> subjects) {
580            boolean first = true ;
581            for ( ; subjects.hasNext() ; ) {
582                Node subj = subjects.next() ;
583                if ( nestedObjects.contains(subj) )
584                    continue ;
585                if ( listElts.contains(subj) )
586                    continue ;
587                if ( !first )
588                    out.println() ;
589                first = false ;
590                if ( freeBnodes.contains(subj) ) {
591                    // Top level: write in "[....]" on "[] :p" form.
592                    writeNestedObjectTopLevel(subj) ;
593                    continue ;
594                }
595
596                Collection<Triple> cluster = triplesOfSubject(subj) ;
597                writeCluster(subj, cluster) ;
598            }
599            return !first ;
600        }
601
602        // A Cluster is a collection of triples with the same subject.
603        private void writeCluster(Node subject, Collection<Triple> cluster) {
604            if ( cluster.isEmpty() )
605                return ;
606            writeNode(subject) ;
607            writeClusterPredicateObjectList(INDENT_PREDICATE, cluster) ;
608        }
609
610        // Write the PredicateObjectList fora subject already output.
611        // The subject may have been a "[]" or a URI - the indentation is passed in.
612        private void writeClusterPredicateObjectList(int indent, Collection<Triple> cluster) {
613            write_S_P_Gap() ;
614            out.incIndent(indent) ;
615            out.pad() ;
616            writePredicateObjectList(cluster) ;
617            out.decIndent(indent) ;
618            print(" .") ;
619            println() ;
620        }
621
622        // Writing predicate-object lists.
623        // We group the cluster by predicate and within each group
624        // we print:
625        //    literals, then simple objects, then pretty objects
626
627        private void writePredicateObjectList(Collection<Triple> cluster) {
628            Map<Node, List<Node>> pGroups = groupByPredicates(cluster) ;
629            Collection<Node> predicates = pGroups.keySet() ;
630
631            // Find longest predicate URI
632            int predicateMaxWidth = RiotLib.calcWidth(prefixMap, baseURI, predicates, MIN_PREDICATE, LONG_PREDICATE) ;
633
634            boolean first = true ;
635
636            if ( !OBJECT_LISTS ) {
637                for ( Node p : predicates ) {
638                    for ( Node o : pGroups.get(p) ) {
639                        writePredicateObject(p, o, predicateMaxWidth, first) ;
640                        first = false ;
641                    }
642                }
643                return ;
644            }
645
646            for ( Node p : predicates ) {
647                // Literals in the group
648                List<Node> rdfLiterals = new ArrayList<>() ;
649                // Non-literals, printed
650                List<Node> rdfSimpleNodes = new ArrayList<>() ;
651                // Non-literals, printed (), or []-embedded
652                List<Node> rdfComplexNodes = new ArrayList<>() ;
653
654                for ( Node o : pGroups.get(p) ) {
655                    if ( o.isLiteral() ) {
656                        rdfLiterals.add(o) ;
657                        continue ;
658                    }
659                    if ( isPrettyNode(o) ) {
660                        rdfComplexNodes.add(o) ;
661                        continue ;
662                    }
663                    rdfSimpleNodes.add(o) ;
664                }
665
666                if ( ! rdfLiterals.isEmpty() ) {
667                    writePredicateObjectList(p, rdfLiterals, predicateMaxWidth, first) ;
668                    first = false ;
669                }
670                if ( ! rdfSimpleNodes.isEmpty() ) {
671                    writePredicateObjectList(p, rdfSimpleNodes, predicateMaxWidth, first) ;
672                    first = false ;
673                }
674
675                for ( Node o : rdfComplexNodes ) {
676                    writePredicateObject(p, o, predicateMaxWidth, first) ;
677                    first = false ;
678                }
679            }
680        }
681
682        private void writePredicateObject(Node p, Node obj, int predicateMaxWidth, boolean first) {
683            writePredicate(p, predicateMaxWidth, first) ;
684            out.incIndent(INDENT_OBJECT) ;
685            writeNodePretty(obj) ;
686            out.decIndent(INDENT_OBJECT) ;
687        }
688
689        private void writePredicateObjectList(Node p, List<Node> objects, int predicateMaxWidth, boolean first) {
690            writePredicate(p, predicateMaxWidth, first) ;
691            out.incIndent(INDENT_OBJECT) ;
692
693            boolean lastObjectMultiLine = false ;
694            boolean firstObject = true ;
695            for ( Node o : objects ) {
696                if ( !firstObject ) {
697                    if ( out.getCurrentOffset() > 0 )
698                        out.print(" , ") ;
699                    else
700                        // Before the current indent, due to a multiline literal being written raw.
701                        // We will pad spaces to indent on output spaces.  Don't add a first " "
702                        out.print(", ") ;
703                }
704                else
705                    firstObject = false ;
706                int row1 = out.getRow() ;
707                writeNode(o) ;
708                int row2 = out.getRow();
709                lastObjectMultiLine = (row2 > row1) ;
710            }
711            out.decIndent(INDENT_OBJECT) ;
712        }
713
714        /** Write a predicate - jump to next line if deemed long */
715        private void writePredicate(Node p, int predicateMaxWidth, boolean first) {
716            if ( first )
717                first = false ;
718            else {
719                print(" ;") ;
720                println() ;
721            }
722            int colPredicateStart = out.getAbsoluteIndent() ;
723
724            if ( !prefixMap.contains(rdfNS) && RDF_type.equals(p) )
725                print("a") ;
726            else
727                writeNode(p) ;
728            int colPredicateFinish = out.getCol() ;
729            int wPredicate = (colPredicateFinish - colPredicateStart) ;
730
731            if ( wPredicate > LONG_PREDICATE )
732                println() ;
733            else {
734                out.pad(predicateMaxWidth) ;
735                // out.print(' ', predicateMaxWidth-wPredicate) ;
736                gap(GAP_P_O) ;
737            }
738        }
739
740        private Map<Node, List<Node>> groupByPredicates(Collection<Triple> cluster) {
741            SortedMap<Node, List<Node>> x = new TreeMap<>(compPredicates) ;
742            for ( Triple t : cluster ) {
743                Node p = t.getPredicate() ;
744                if ( !x.containsKey(p) )
745                    x.put(p, new ArrayList<Node>()) ;
746                x.get(p).add(t.getObject()) ;
747            }
748
749            return x ;
750        }
751
752        private int countPredicates(Collection<Triple> cluster) {
753            Set<Node> x = new HashSet<>() ;
754            for ( Triple t : cluster ) {
755                Node p = t.getPredicate() ;
756                x.add(p) ;
757            }
758            return x.size() ;
759        }
760
761        // [ :p "abc" ] .  or    [] : "abc" .
762        private void writeNestedObjectTopLevel(Node subject) {
763            if ( true ) {
764                writeNestedObject(subject) ;
765                out.println(" .") ;
766            } else {
767                // Alternative.
768                Collection<Triple> cluster = triplesOfSubject(subject) ;
769                print("[]") ;
770                writeClusterPredicateObjectList(0, cluster) ;
771            }
772        }
773
774        private void writeNestedObject(Node node) {
775            Collection<Triple> x = triplesOfSubject(node) ;
776
777            if ( x.isEmpty() ) {
778                print("[] ") ;
779                return ;
780            }
781
782            int pCount = countPredicates(x) ;
783
784            if ( pCount == 1 ) {
785                print("[ ") ;
786                out.incIndent(2) ;
787                writePredicateObjectList(x) ;
788                out.decIndent(2) ;
789                print(" ]") ;
790                return ;
791            }
792
793            // Two or more.
794            int indent0 = out.getAbsoluteIndent() ;
795            int here = out.getCol() ;
796            out.setAbsoluteIndent(here) ;
797            print("[ ") ;
798            out.incIndent(2) ;
799            writePredicateObjectList(x) ;
800            out.decIndent(2) ;
801            if ( true ) {
802                println() ; // Newline for "]"
803                print("]") ;
804            } else { // Compact
805                print(" ]") ;
806            }
807            out.setAbsoluteIndent(indent0) ;
808        }
809
810        // Write a list
811        private void writeList(List<Node> elts) {
812            if ( elts.size() == 0 ) {
813                out.print("()") ;
814                return ;
815            }
816
817            if ( false ) {
818                out.print("(") ;
819                for ( Node n : elts ) {
820                    out.print(" ") ;
821                    writeNodePretty(n) ;
822                }
823                out.print(" )") ;
824            }
825
826            if ( true ) {
827                // "fresh line mode" means printed one on new line
828                // Multi line items are ones that can be multiple lines. Non-literals.
829                // Was the previous row a multiLine?
830                boolean lastItemFreshLine = false ;
831                // Have there been any items that causes "fresh line" mode?
832                boolean multiLineAny = false ;
833                boolean first = true ;
834
835                // Where we started.
836                int originalIndent = out.getAbsoluteIndent() ;
837                // Rebase indent here.
838                int x = out.getCol() ;
839                out.setAbsoluteIndent(x);
840
841                out.print("(") ;
842                out.incIndent(2);
843                for ( Node n : elts ) {
844
845                    // Print this item on a fresh line? (still to check: first line)
846                    boolean thisItemFreshLine = /* multiLineAny | */ n.isBlank() ;
847
848                    // Special case List in List.
849                    // Start on this line if last item was on this line.
850                    if ( lists.containsKey(n) )
851                        thisItemFreshLine = lastItemFreshLine ;
852
853                    // Starting point.
854                    if ( ! first ) {
855                        if ( lastItemFreshLine | thisItemFreshLine )
856                            out.println() ;
857                        else
858                            out.print(" ") ;
859                    }
860
861                    first = false ;
862                    //Literals with newlines: int x1 = out.getRow() ;
863                    // Adds INDENT_OBJECT even for a [ one triple ]
864                    // Special case [ one triple ]??
865                    writeNodePretty(n) ;
866                    //Literals with newlines:int x2 = out.getRow() ;
867                    //Literals with newlines: boolean multiLineAnyway = ( x1 != x2 ) ;
868                    lastItemFreshLine = thisItemFreshLine ;
869                    multiLineAny  = multiLineAny | thisItemFreshLine ;
870
871                }
872                if ( multiLineAny )
873                    out.println() ;
874                else
875                    out.print(" ") ;
876                out.decIndent(2);
877                out.setAbsoluteIndent(x);
878                out.print(")") ;
879                out.setAbsoluteIndent(originalIndent) ;
880            }
881        }
882
883        private boolean isPrettyNode(Node n) {
884            // Order matters? - one connected objects may include list elements.
885            if ( allowDeepPretty ) {
886                if ( lists.containsKey(n) )
887                    return true ;
888                if ( nestedObjects.contains(n) )
889                    return true ;
890            }
891            if ( RDF_Nil.equals(n) )
892                return true ;
893            return false ;
894        }
895
896        // --> write S or O??
897        private void writeNodePretty(Node obj) {
898            // Assumes "isPrettyNode" is true.
899            // Order matters? - one connected objects may include list elements.
900            if ( lists.containsKey(obj) )
901                writeList(lists.get(obj)) ;
902            else if ( nestedObjects.contains(obj) )
903                writeNestedObject(obj) ;
904            else if ( RDF_Nil.equals(obj) )
905                out.print("()") ;
906            else
907                writeNode(obj) ;
908            if ( nestedObjects.contains(obj) )
909                nestedObjectsWritten.add(obj) ;
910
911        }
912
913        // Order of properties.
914        // rdf:type ("a")
915        // RDF and RDFS
916        // Other.
917        // Sorted by URI.
918
919        private void write_S_P_Gap() {
920            if ( out.getCol() > LONG_SUBJECT )
921                out.println() ;
922            else
923                gap(GAP_S_P) ;
924        }
925    }
926
927    // Order of properties.
928    // rdf:type ("a")
929    // RDF and RDFS
930    // Other.
931    // Sorted by URI.
932
933    private static final class ComparePredicates implements Comparator<Node> {
934        private static int classification(Node p) {
935            if ( p.equals(RDF_type) )
936                return 0 ;
937
938            if ( p.getURI().startsWith(RDF.getURI()) || p.getURI().startsWith(RDFS.getURI()) )
939                return 1 ;
940
941            return 2 ;
942        }
943
944        @Override
945        public int compare(Node t1, Node t2) {
946            int class1 = classification(t1) ;
947            int class2 = classification(t2) ;
948            if ( class1 != class2 ) {
949                // Java 1.7
950                // return Integer.compare(class1, class2) ;
951                if ( class1 < class2 )
952                    return -1 ;
953                if ( class1 > class2 )
954                    return 1 ;
955                return 0 ;
956            }
957            String p1 = t1.getURI() ;
958            String p2 = t2.getURI() ;
959            return p1.compareTo(p2) ;
960        }
961    }
962
963    private static Comparator<Node> compPredicates = new ComparePredicates() ;
964
965    protected final void writeNode(Node node) {
966        nodeFmt.format(out, node) ;
967    }
968
969    private void print(String x) {
970        out.print(x) ;
971    }
972
973    private void gap(int gap) {
974        out.print(' ', gap) ;
975    }
976
977    // flush aggressively (debugging)
978    private void println() {
979        out.println() ;
980        // out.flush() ;
981    }
982}