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}