Improve PrettyPrintTG performance
[simantics/platform.git] / bundles / org.simantics.graph / src / org / simantics / graph / representation / PrettyPrintTG.java
1 package org.simantics.graph.representation;
2
3 import java.io.BufferedInputStream;
4 import java.io.DataInput;
5 import java.io.DataInputStream;
6 import java.io.InputStream;
7 import java.math.BigInteger;
8 import java.nio.file.Files;
9 import java.nio.file.Path;
10 import java.nio.file.Paths;
11 import java.security.MessageDigest;
12 import java.security.NoSuchAlgorithmException;
13 import java.util.ArrayList;
14 import java.util.HashMap;
15 import java.util.HashSet;
16 import java.util.List;
17 import java.util.Map;
18 import java.util.Map.Entry;
19 import java.util.Set;
20 import java.util.TreeMap;
21 import java.util.TreeSet;
22 import java.util.regex.Matcher;
23 import java.util.regex.Pattern;
24 import java.util.stream.Collectors;
25
26 import org.simantics.databoard.Bindings;
27 import org.simantics.databoard.binding.Binding;
28 import org.simantics.databoard.binding.mutable.Variant;
29 import org.simantics.databoard.container.DataContainers;
30 import org.simantics.databoard.parser.DataValuePrinter;
31 import org.simantics.databoard.parser.PrintFormat;
32 import org.simantics.databoard.parser.repository.DataValueRepository;
33 import org.slf4j.Logger;
34 import org.slf4j.LoggerFactory;
35
36 import gnu.trove.list.array.TIntArrayList;
37 import gnu.trove.map.hash.THashMap;
38 import gnu.trove.map.hash.TIntIntHashMap;
39 import gnu.trove.map.hash.TIntObjectHashMap;
40 import gnu.trove.procedure.TIntIntProcedure;
41 import gnu.trove.set.hash.TLongHashSet;
42
43 /**
44  * @author Antti Villberg
45  * @since 1.24.0
46  */
47 public class PrettyPrintTG {
48
49     private static final Logger LOGGER = LoggerFactory.getLogger(PrettyPrintTG.class);
50
51     private static final boolean DEBUG = false;
52
53     int blankCounter = 0;
54     int newBlankCounter = 0;
55     Map<String, String> blankRewrites = new HashMap<>();
56     MessageDigest m;
57
58     private final Pattern versionExtractPattern = Pattern.compile("^.*-(\\d+\\.\\d+)");
59
60     final StringBuilder output;
61     final Map<String, String> ontologies = new HashMap<>(knownOntologies);
62     final Set<String> referencedOntologies = new TreeSet<>();
63
64     private boolean ignoreIdentifiers;
65
66     private TransferableGraphQueries query;
67
68     static class ResourceInfo {
69         final boolean hasURI;
70         String name;
71         final int resource;
72         boolean newResource = false;
73
74         int parent;
75
76         boolean inlined = false;
77
78         // -1 = no owner, -2 = multiple owners
79
80         // Set<ResourceInfo> ownedBy
81         Set<ResourceInfo> ownedBy = new HashSet<>();
82
83         // A Map<Integer, Integer> containing information about resource that
84         // this resource owns and what are the predicates for forming this
85         // ownership
86         TIntIntHashMap ownedResourcesWithPredicates = new TIntIntHashMap();
87
88         // int owner = -1;
89         // int ownerPredicate = 0;
90         String aliasURI = null;
91         TIntArrayList owned = new TIntArrayList();
92
93         // TIntObjectHashMap<TIntHashSet> statements = new
94         // TIntObjectHashMap<TIntHashSet>();
95         public ResourceInfo(boolean hasURI, String name, int resource, int parent) {
96             this.hasURI = hasURI;
97             this.name = name;
98             this.resource = resource;
99             this.parent = parent;
100         }
101
102         @Override
103         public String toString() {
104             return name + (aliasURI != null ? " = <" + aliasURI + ">" : "");
105         }
106     }
107
108     public PrettyPrintTG(TransferableGraph1 tg, StringBuilder b, boolean ignoreIdentifiers) throws NoSuchAlgorithmException {
109         output = b;
110         m = MessageDigest.getInstance("SHA-256");
111         this.ignoreIdentifiers = ignoreIdentifiers;
112         
113         this.query = new TransferableGraphQueries(tg);
114     }
115
116     public PrettyPrintTG(TransferableGraph1 tg) throws NoSuchAlgorithmException {
117         this(tg, new StringBuilder(), false);
118     }
119
120     TreeMap<String, ResourceInfo> orderedInfos = new TreeMap<>();
121     TIntObjectHashMap<ResourceInfo> infos = new TIntObjectHashMap<>();
122
123     private static String tgNodeName(String name) {
124         if (name.contains(" "))
125             return "\"" + name + "\"";
126         else
127             return name;
128     }
129
130     private ResourceInfo recurseURI(Map<String, ResourceInfo> infos, Identity identity, String parentName, int parentId) {
131         String name = parentName + "." + tgNodeName(TransferableGraphUtils.getName(identity));
132         int identityResource = identity.resource;
133         ResourceInfo info = new ResourceInfo(true, name, identityResource, parentId);
134         infos.put(name, info);
135         for (Identity child : query.getChildren(identity))
136             recurseURI(infos, child, name, identityResource);
137         return info;
138     }
139
140     /**
141      * Sorts statements by predicateURI in natural order
142      * 
143      * @param graph
144      * @param resource
145      * @return
146      */
147     private TreeMap<String, TreeSet<Integer>> sortByPredicateUniqueStatements(int resource) {
148         TreeMap<String, TreeSet<Integer>> results = new TreeMap<>();
149         TIntArrayList statements = query.getStatements(resource);
150         for (int i = 0; i < statements.size(); i += 2) {
151             int predicate = statements.get(i);
152             String predicateURI = query.getURI(predicate);
153             TreeSet<Integer> objects = results.get(predicateURI);
154             if (objects == null) {
155                 objects = new TreeSet<>();
156             }
157             objects.add(statements.get(i + 1));
158             results.put(predicateURI, objects);
159         }
160         return results;
161     }
162
163     void discoverBlank(int resource, TIntArrayList todo) throws Exception {
164         // TIntArrayList statements =
165         // TransferableGraphUtils.getStatements(graph, resource);
166         // for(int i=0;i<statements.size();i+=2) {
167         for (TreeSet<Integer> objects : sortByPredicateUniqueStatements(resource).values()) {
168             for (int object : objects) {
169                 // int object = statements.get(i+1);
170                 Identity objectId = query.getIdentity(object);
171                 if (objectId != null) {
172                     if (objectId.definition instanceof External)
173                         continue;
174                 }
175                 Value value = query.findValue(object);
176                 if (value != null) {
177                     infos.put(object, new ResourceInfo(false, printValue(value), object, resource));
178                 } else {
179                     ResourceInfo existing = infos.get(object);
180                     if (existing == null) {
181     
182                         existing = new ResourceInfo(false, "blank" + blankCounter++, object, resource);
183     
184                         // System.out.println("created blank" + blankCounter + "
185                         // with object " + object + " resource " + resource);
186                         infos.put(object, existing);
187                         todo.add(object);
188                     }
189                 }
190             }
191         }
192     }
193
194     private String makeHash(byte[] data) {
195         m.reset();
196         m.update(data, 0, data.length);
197         return new BigInteger(1, m.digest()).toString(16);
198     }
199
200     void discoverOwners(ResourceInfo info) {
201         log("Discovering owners for {}", info);
202         int resource = info.resource;
203         TIntArrayList statements = query.getStatements(resource);
204         for (int i = 0; i < statements.size(); i += 2) {
205             int predicate = statements.get(i);
206             int object = statements.get(i + 1);
207             ResourceInfo existing = infos.get(object);
208             if (existing != null) {
209                 // Add all owners here for now and resolve the best owner later
210
211                 // Check if predicate is inverse, this just resolves all
212                 // predicates to be inverse with ending "Inverse"..
213                 String predicateUri = rewritePredicateURI(predicate);
214                 if (!predicateUri.endsWith("Inverse") && !predicateUri.endsWith("Of")) {
215                     existing.ownedResourcesWithPredicates.put(resource, predicate);
216                     // if (predicateUri.endsWith("Of")) {
217                     // System.out.println("asd");
218                     // } else {
219                     existing.ownedBy.add(info);
220                     log("    {} owns {} with {}", existing, info, predicateUri);
221                     // }
222                 } else {
223                     // System.out.println("asd");
224                 }
225             }
226         }
227     }
228
229     DataValueRepository repo = new DataValueRepository();
230     DataValuePrinter printer = new DataValuePrinter(null, repo);
231
232     String printValue(Value value) throws Exception {
233         StringBuilder sb = new StringBuilder();
234         printer.setFormat(PrintFormat.SINGLE_LINE);
235         printer.setOutput(sb);
236
237         Variant variant = value.value;
238         printer.print(variant.getBinding(), variant.getValue());
239         String formattedOutput = sb.toString();
240         if (formattedOutput.length() > 100) {
241             // Ok, value too long, lets calculate a hash for it and store first
242             // 100 chars as comment
243             byte[] data = Bindings.getSerializerUnchecked(variant.getBinding()).serialize(variant.getValue());
244             m.reset();
245             m.update(data, 0, data.length);
246             String hash = "\"" + new BigInteger(1, m.digest()).toString(16) + "\"";
247             return hash + " // " + formattedOutput.substring(0, 100) + "..";
248         } else {
249             return formattedOutput;
250         }
251     }
252
253     public String getExternalURI(External ext) {
254         String name = ext.name;
255         if (name.contains(" "))
256             name = name.replace(" ", "_").replaceAll("@", "_");// name = "\"" +
257                                                                // name + "\"";
258         int parentId = ext.parent;
259         // if(parentId == 0) return ext.name;
260         // else {
261         Identity id = query.getIdentity(parentId);
262         if (id.definition instanceof External) {
263             return getExternalURI((External) id.definition) + "/" + name;
264         } else if (id.definition instanceof Root) {
265             Root root = (Root) id.definition;
266             return "http:/" + root.name + "/" + name;
267         } else {
268             return null;
269         }
270         // }
271     }
272
273     public String getExternalURI(int resource) {
274         Identity id = query.getIdentity(resource);
275         if (id == null)
276             return null;
277         if (id.definition instanceof External) {
278             External ext = (External) id.definition;
279             return getExternalURI(ext);
280         }
281         return null;
282     }
283
284     String rewritePredicateURI(int predicate) {
285
286         String uri = getExternalURI(predicate);
287         if (uri == null) {
288             ResourceInfo info = infos.get(predicate);
289             if (info != null)
290                 return info.name;
291             return "_";
292         }
293
294         for (String ontology : ontologies.keySet()) {
295             if (uri.contains(ontology)) {
296                 String key = ontologies.get(ontology);
297                 uri = uri.replace(ontology, key);
298                 referencedOntologies.add(ontology);
299             }
300         }
301
302         uri = uri.replace("/", ".");
303
304         return uri;
305
306     }
307
308     static void indent(StringBuilder output, int indent) {
309         for (int i = 0; i < indent; i++)
310             output.append("  ");
311     }
312
313     String printBlank(String predicateURI2, ResourceInfo info, int indent) {
314
315         if (info.hasURI)
316             return null;
317
318         StringBuilder output = new StringBuilder();
319
320         String infoName = info.name;
321         if (infoName.startsWith("blank")) {
322             infoName = getBlankRewrite(infoName);
323         }
324
325         if (ignoreIdentifiers) {
326             if (predicateURI2.contains("L0.identifier")) {
327                 // Do nothing
328             } else {
329                 indent(output, indent);
330                 output.append(predicateURI2 + " " + infoName + "\n");
331             }
332         } else {
333             indent(output, indent);
334             output.append(predicateURI2 + " " + infoName + "\n");
335         }
336
337         if (info.ownedResourcesWithPredicates.isEmpty()) {
338             if (DEBUG)
339                 System.out.print("printBlank");
340             String uri = printURI(info, false, indent, false);
341             if (uri != null)
342                 output.append(uri);
343         }
344         // if(info.owner < 0) {
345         // printURI(graph, info, false, indent);
346         // }
347         return output.toString();
348     }
349
350     private String getBlankRewrite(String infoName) {
351         String rewrite = blankRewrites.get(infoName);
352         if (rewrite == null) {
353             rewrite = "rblank" + newBlankCounter++;
354             if (DEBUG)
355                 System.out.println("rewrote " + infoName + " to " + rewrite);
356             blankRewrites.put(infoName, rewrite);
357         }
358         return rewrite;
359         // return infoName;
360     }
361
362     static long longStm(int predicate, int object) {
363         return ((predicate & 0xffffffffL) << 32) | (object & 0xffffffffL);
364     }
365
366     private void addInlineStatement(Map<String, Set<String>> statements, String predicate,
367             ResourceInfo objectInfo, int indent) {
368         Set<String> objects = statements.get(predicate);
369         if (objects == null) {
370             objects = new TreeSet<>();
371             statements.put(predicate, objects);
372         }
373         String uri = printURI(objectInfo, false, indent + 1, true);
374         if (uri != null) {
375             // TODO: this is not the right place to remove trailing newline
376             uri = uri.endsWith("\n") ? uri.substring(0, uri.length() - 2) : uri;
377             objects.add(uri);
378         }
379         objectInfo.inlined = true;
380     }
381
382     void addStatement(Map<String, Set<String>> statements, String predicate, String object) {
383         // TODO: fix this
384         if (predicate.endsWith("Inverse"))
385             return;
386         Set<String> objects = statements.get(predicate);
387         if (objects == null) {
388             objects = new TreeSet<>();
389             statements.put(predicate, objects);
390         }
391         objects.add(object);
392     }
393
394     String printURI(ResourceInfo info, boolean requireURI, int indent, boolean inline) {
395
396         if (requireURI && !info.hasURI)
397             return null;
398
399         // Check if this ResourceInfo is already inlined with some other
400         // ResourceInfo
401         if (info.inlined)
402             return null;
403
404         Map<String, Set<String>> statements = new TreeMap<>();
405         Identity consistsOf = query.findExternalByURI("http://www.simantics.org/Layer0-1.1/ConsistsOf");
406         // Identity partOf = TransferableGraphUtils.findExternal(graph,
407         // "http://www.simantics.org/Layer0-1.1/PartOf");
408         TLongHashSet processed = new TLongHashSet();
409         if (DEBUG)
410             System.out.println("info.owned.size " + info.owned.size() + info.owned);
411         for (int i = 0; i < info.owned.size(); i += 2) {
412             int predicate = info.owned.get(i);
413             int object = info.owned.get(i + 1);
414             long stmId = longStm(predicate, object);
415             if (DEBUG)
416                 System.out.println(
417                         "  " + stmId + " is already processed as it is owned (" + predicate + " " + object + ")");
418             processed.add(stmId);
419         }
420
421         TreeMap<String, List<Integer>> predicateURIs = new TreeMap<>();
422
423         TIntArrayList rawStatements = query.getStatements(info.resource);
424         if (DEBUG)
425             System.out.println(
426                     "rawStatements size for " + info.name + " : " + rawStatements.size() + " " + rawStatements);
427         for (int i = 0; i < rawStatements.size(); i += 2) {
428             int predicate = rawStatements.get(i);
429             int object = rawStatements.get(i + 1);
430             long stmId = longStm(predicate, object);
431             if (!processed.add(stmId)) {
432                 if (DEBUG)
433                     System.out.println("  " + stmId + " is already processed (" + (predicate & 0xffffffffL) + " "
434                             + (object & 0xffffffffL) + ")");
435                 continue;
436             }
437             if (DEBUG)
438                 System.out.println("   " + stmId + " is currently being processed (" + (predicate & 0xffffffffL) + " "
439                         + (object & 0xffffffffL) + ")");
440             // if (partOf.resource == rawStatements.get(i))
441             // continue;
442             if (consistsOf.resource == predicate) {
443                 // if (!info.owned.isEmpty() && !info.name.startsWith("blank"))
444                 // {
445                 if (DEBUG)
446                     System.out.println("  is consistsof " + predicate + " (" + consistsOf.resource + ")");
447                 continue;
448                 // } else {
449                 // // this will be inlined so lets indent
450                 // indent++;
451                 // }
452             }
453             String predicateURI = rewritePredicateURI(predicate);
454             List<Integer> objects = predicateURIs.get(predicateURI);
455             if (objects == null) {
456                 objects = new ArrayList<>();
457                 predicateURIs.put(predicateURI, objects);
458             }
459             objects.add(object);
460         }
461         for (Entry<String, List<Integer>> entry : predicateURIs.entrySet()) {
462             String predicateURI = entry.getKey();
463             List<Integer> objects = entry.getValue();
464             for (int object : objects) {
465                 ResourceInfo objectInfo = infos.get(object);
466                 if (objectInfo == null) {
467                     String objectURI = rewritePredicateURI(object);
468                     if (DEBUG)
469                         System.out.println("  adding statement " + predicateURI + " " + objectURI);
470                     addStatement(statements, predicateURI, objectURI);
471                 } else if (objectInfo.ownedBy.size() == 1 && objectInfo.ownedBy.contains(info)) {
472                     // inline printing with _
473                     if (DEBUG)
474                         System.out.println("  adding inline statement " + predicateURI + " " + objectInfo.name);
475                     addInlineStatement(statements, predicateURI, objectInfo, indent);
476                 } else {
477                     String objectName = objectInfo.name;
478                     if (objectName.startsWith("blank")) {
479                         objectName = getBlankRewrite(objectName);
480                     }
481                     if (DEBUG)
482                         System.out.println("  adding statement " + predicateURI + " " + objectName);
483                     addStatement(statements, predicateURI, objectName);
484                 }
485             }
486         }
487
488         if (DEBUG)
489             System.out.println(
490                     "statements size for " + info.name + " : " + statements.size() + " " + statements.keySet());
491
492         StringBuilder output = new StringBuilder();
493
494         if (indent == 0 || inline) {
495             if ("ROOT".equals(info.name)) {
496                 output.append("ROOT=<http:/>");
497             } else if (info.aliasURI != null) {
498                 output.append(info.name + " = <" + info.aliasURI + ">");
499             } else {
500                 String infoName = info.name;
501                 if (infoName.startsWith("blank")) {
502                     infoName = getBlankRewrite(infoName);
503                 }
504                 output.append(inline ? "_" : infoName);
505             }
506             Set<String> instanceOfs = statements.get("L0.InstanceOf");
507             if (instanceOfs != null) {
508                 for (String instanceOf : instanceOfs) {
509                     output.append(" : " + instanceOf);
510                 }
511             }
512             Set<String> subrelationOfs = statements.get("L0.SubrelationOf");
513             if (subrelationOfs != null) {
514                 for (String subrelationOf : subrelationOfs) {
515                     output.append(" <R " + subrelationOf);
516                 }
517             }
518             Set<String> inherits = statements.get("L0.Inherits");
519             if (inherits != null) {
520                 for (String inherit : inherits) {
521                     output.append(" <T " + inherit);
522                 }
523             }
524             output.append("\n");
525         }
526
527         if (info.newResource)
528             output.append("  @L0.new\n");
529
530         for (Map.Entry<String, Set<String>> entry : statements.entrySet()) {
531             String predicate = entry.getKey();
532             if (ignoreIdentifiers) {
533                 if (predicate.equals("L0.identifier")) {
534                     continue;
535                 }
536             }
537             if ("L0.InstanceOf".equals(predicate))
538                 continue;
539             if ("L0.SubrelationOf".equals(predicate))
540                 continue;
541             if ("L0.Inherits".equals(predicate))
542                 continue;
543             if ("L0.PartOf".equals(predicate))
544                 continue;
545
546             // predicates can be blank
547             if (predicate.startsWith("blan")) {
548                 predicate = getBlankRewrite(predicate);
549             }
550
551             Set<String> objects = entry.getValue();
552             indent(output, indent + 1);
553             if (objects.size() == 1) {
554                 output.append(predicate + " " + objects.iterator().next() + "\n");
555             } else {
556                 output.append(predicate + "\n");
557                 for (String object : objects) {
558                     indent(output, indent + 1);
559                     output.append("  " + object + "\n");
560                 }
561             }
562         }
563
564         TreeMap<String, Set<Integer>> ownedOrdered = new TreeMap<>();
565
566         for (int i = 0; i < info.owned.size(); i += 2) {
567             String predicateURI = rewritePredicateURI(info.owned.get(i));
568             Set<Integer> owneds = ownedOrdered.get(predicateURI);
569             if (owneds == null) {
570                 owneds = new TreeSet<>();
571                 ownedOrdered.put(predicateURI, owneds);
572             }
573             owneds.add(info.owned.get(i + 1));
574         }
575
576         if (DEBUG)
577             System.out.println(info.name + " : " + ownedOrdered.keySet());
578
579         for (Entry<String, Set<Integer>> entry : ownedOrdered.entrySet()) {
580             String predicateURI = entry.getKey();
581             Set<Integer> owneds = entry.getValue();
582             for (int owned : owneds) {
583                 ResourceInfo ownedInfo = infos.get(owned);
584     
585                 String blank = printBlank(predicateURI, ownedInfo, indent + 1);
586                 if (blank != null) {
587                     output.append(blank);
588                 }
589             }
590         }
591
592         return output.toString();
593     }
594
595     static Map<String, String> knownOntologies = new HashMap<>();
596
597     static {
598         knownOntologies.put("http://www.simantics.org/Layer0-1.1", "L0");
599         knownOntologies.put("http://www.simantics.org/Layer0X-1.1", "L0X");
600         knownOntologies.put("http://www.simantics.org/Modeling-1.2", "MOD");
601         knownOntologies.put("http://www.simantics.org/Diagram-2.2", "DIA");
602         knownOntologies.put("http://www.simantics.org/Structural-1.2", "STR");
603         knownOntologies.put("http://www.simantics.org/Document-1.2", "DOC");
604         knownOntologies.put("http://www.simantics.org/Documentation-1.2", "DOCU");
605         knownOntologies.put("http://www.simantics.org/G2D-1.1", "G2D");
606         knownOntologies.put("http://www.simantics.org/SelectionView-1.2", "SEL");
607         knownOntologies.put("http://www.simantics.org/Viewpoint-1.2", "VP");
608         knownOntologies.put("http://www.simantics.org/Image2-1.2", "IMAGE2");
609         knownOntologies.put("http://www.simantics.org/GraphFile-0.1", "GRAPHFILE");
610         knownOntologies.put("http://www.simantics.org/Project-1.2", "PROJECT");
611         knownOntologies.put("http://www.semantum.fi/Simupedia-1.0", "SIMUPEDIA");
612         knownOntologies.put("http://www.semantum.fi/SimupediaWorkbench-1.0", "SIMUPEDIA_WORKBENCH");
613     }
614
615     void prettyPrint() throws Exception {
616         log("Starting prettyPrint for TransferableGraph with {} resources, {} identities, {} statements and {} values",
617                 query.getGraph().resourceCount, query.getGraph().identities, query.getGraph().statements.length, query.getGraph().values.length);
618
619         query.forIdentities(id -> {
620             int identityResource = id.resource;
621             if (id.definition instanceof Internal) {
622                 Internal internal = (Internal) id.definition;
623                 Identity parent = query.getIdentity(internal.parent);
624                 if (parent.definition instanceof External || parent.definition instanceof Root) {
625                     log("Resolving internal identity {}", id);
626                     String name = "BASE";
627                     ResourceInfo info = new ResourceInfo(true, name, identityResource, -1);
628                     info.aliasURI = query.getURI(identityResource);
629                     info.newResource = true;
630                     orderedInfos.put(name, info);
631                     // infos.put(id.resource, info);
632                     log("    which parent is external {} and has an aliasURI {}", parent, info.aliasURI);
633                     for (Identity child : query.getChildren(id))
634                         recurseURI(orderedInfos, child, name, identityResource);
635                     log("    and has {} children", infos.size());
636                 }
637             } else if (id.definition instanceof External) {
638                 External ext = (External) id.definition;
639                 // Try to detect shared libraries
640                 if (ext.name.contains("@")) {
641
642                     log("Detected an external shared library {}", ext);
643
644                     int index = ext.name.indexOf('@');
645                     String prefix = ext.name.substring(0, index);
646                     int index2 = ext.name.indexOf('/', index);
647                     String ontology = index2 == -1 ? ext.name : ext.name.substring(0, index2);
648                     String uri = query.getURI(identityResource);
649
650                     log("    which was resolved as URI={} and prefix={}", uri, prefix);
651
652                     ontologies.put(uri, prefix);
653
654                 } else if (ext.name.contains("-")) {
655                     log("Resolving possible ontology {}", ext);
656                     String uri = query.getURI(identityResource);
657                     Matcher m = versionExtractPattern.matcher(uri);
658                     if (m.matches()) {
659                         if (!ontologies.containsKey(uri)) {
660                             int index = ext.name.indexOf('-');
661                             String prefix = ext.name.substring(0, index);
662                             log("    and it was resolved as URI={} and prefix {}", uri, prefix);
663                             ontologies.put(uri, prefix);
664                         }
665                     }
666                 }
667             }
668             return true;
669         });
670
671         // Discover other resources
672         log("Discovering other resources..");
673
674         TIntArrayList todo = new TIntArrayList();
675         for (ResourceInfo info : orderedInfos.values()) {
676             todo.add(info.resource);
677
678             // put orderedInfos to infos
679             infos.put(info.resource, info);
680         }
681
682         while (!todo.isEmpty()) {
683             int resource = todo.removeAt(todo.size() - 1);
684             discoverBlank(resource, todo);
685         }
686         for (ResourceInfo info : infos.valueCollection())
687             discoverOwners(info);
688         // for(ResourceInfo info : infos.valueCollection())
689         // fixInstanceOf(graph, info);
690
691         for (ResourceInfo info : infos.valueCollection()) {
692             // Old implementation
693             // if (info.owner >= 0) {
694             // ResourceInfo ownerInfo = infos.get(info.owner);
695             // System.out.println("originalOwner : " + info.owner + "
696             // originalPredicate: " + info.ownerPredicate);
697             // ownerInfo.owned.add(info.ownerPredicate);
698             // ownerInfo.owned.add(info.resource);
699             // }
700
701             if (!info.ownedResourcesWithPredicates.isEmpty() && info.ownedResourcesWithPredicates.size() == 1) {
702                 info.ownedResourcesWithPredicates.forEachEntry(new TIntIntProcedure() {
703
704                     @Override
705                     public boolean execute(int owner, int predicate) {
706                        
707                         ResourceInfo ownerInfo = infos.get(owner);
708                         ownerInfo.owned.add(predicate);
709                         ownerInfo.owned.add(info.resource);
710                         return false;
711                     }
712                 });
713             } else {
714                 System.err.println("Here we are with " + info);
715             }
716         }
717
718         // Resolve inverses from ownedBy list
719         for (ResourceInfo info : infos.valueCollection()) {
720             for (int i = 0; i < info.owned.size(); i += 2) {
721                 int object = info.owned.get(i + 1);
722                 ResourceInfo inf = infos.get(object);
723                 if (inf != null) {
724                     info.ownedBy.remove(inf);
725                 }
726             }
727         }
728
729         Identity routeGraphConn = query.findExternalByURI("http://www.simantics.org/Diagram-2.2/RouteGraphConnection");
730         Identity instanceOf = query.findExternalByURI("http://www.simantics.org/Layer0-1.1/InstanceOf");
731         Identity diagramConnetionToConnection = query.findExternalByURI("http://www.simantics.org/Modeling-1.2/DiagramConnectionToConnection");
732
733         Identity elemTo = query.findExternalByURI("http://www.simantics.org/Modeling-1.2/ElementToComponent");
734         for (ResourceInfo infoo : infos.valueCollection()) {
735             if (elemTo != null) {
736                 int elemToComponent = query.getPossibleObject(infoo.resource, elemTo);
737                 if (elemToComponent != TransferableGraphUtils.NOT_FOUND) {
738
739                     Identity component = query.getIdentity(elemToComponent);
740                     Identity internal = query.getIdentity(infoo.resource);
741                     if (internal.definition instanceof Internal && component.definition instanceof Internal) {
742                         Internal iComponent = (Internal) component.definition;
743                         infoo.name = infoo.name.substring(0, infoo.name.lastIndexOf(".") + 1) + iComponent.name;
744                     }
745                 }
746             }
747
748             if (instanceOf != null) {
749                 int instOf = query.getPossibleObject( infoo.resource, instanceOf);
750                 if (instOf != TransferableGraphUtils.NOT_FOUND && routeGraphConn != null) {
751                     if (instOf == routeGraphConn.resource) {
752                         // Found routegraphconnection, change name
753                         // Lets go to configuration
754
755                         int connection = query.getPossibleObject( infoo.resource,
756                                 diagramConnetionToConnection);
757                         if (connection != TransferableGraphUtils.NOT_FOUND) {
758                             // Gather all inverse statements to construct unique
759                             // name
760                             List<String> nameParts = new ArrayList<>();
761                             TIntArrayList statements = query.getStatements(connection);
762                             for (int i = 0; i < statements.size(); i += 2) {
763                                 int predicate = statements.get(i);
764                                 Identity possibleInverse = query.getIdentity(predicate);
765                                 if (possibleInverse != null) {
766                                     int inverseRelation = TransferableGraphUtils.NOT_FOUND;
767                                     int parentId = TransferableGraphUtils.NOT_FOUND;
768                                     if (possibleInverse.definition instanceof Internal) {
769                                         Internal iPossibleInverse = (Internal) possibleInverse.definition;
770                                         if (iPossibleInverse.name.equals("Inverse")) {
771                                             inverseRelation = query.getPossibleObject(
772                                                     connection, possibleInverse);
773                                             parentId = iPossibleInverse.parent;
774                                         } else {
775                                             log("Unsupported inverse relation found for {} {}", infoo, iPossibleInverse);
776                                         }
777                                     } else if (possibleInverse.definition instanceof External) {
778                                         External ePossibleInverse = (External) possibleInverse.definition;
779                                         if (ePossibleInverse.name.equals("Inverse")) {
780                                             inverseRelation = query.getPossibleObject(
781                                                     connection, possibleInverse);
782                                             parentId = ePossibleInverse.parent;
783                                         } else {
784                                             log("This external inverse is unsupported for {} {}", infoo, ePossibleInverse);
785                                         }
786                                     } else {
787                                         log("This type of definition is not supported {}", infoo);
788                                     }
789                                     if (inverseRelation != TransferableGraphUtils.NOT_FOUND) {
790                                         // Ok found something
791                                         Identity object = query.getIdentity(inverseRelation);
792                                         Identity parent = query.getIdentity(parentId);
793                                         String objectName, parentName;
794                                         if (object.definition instanceof Internal) {
795                                             objectName = ((Internal) object.definition).name;
796                                         } else if (object.definition instanceof External) {
797                                             objectName = ((External) object.definition).name;
798                                         } else {
799                                             log("This type of definition is not supported {}", infoo);
800                                             throw new Error("UNSUPPORTED " + infoo);
801                                         }
802                                         if (parent.definition instanceof Internal) {
803                                             parentName = ((Internal) parent.definition).name;
804                                         } else if (parent.definition instanceof External) {
805                                             parentName = ((External) parent.definition).name;
806                                         } else {
807                                             log("This type of definition is not supported {}", infoo);
808                                             throw new Error("UNSUPPORTED " + infoo);
809                                         }
810                                         String fullName = parentName + "_" + objectName;
811                                         nameParts.add(fullName);
812                                     } else {
813                                         log("No inverse relation found for {}", infoo);
814                                     }
815                                 } else {
816                                     log("Did not find possible inverse relation for {}", infoo);
817                                 }
818                             }
819                             nameParts.sort((o1, o2) -> o1.compareTo(o2));
820                             String name = "";
821                             for (String namep : nameParts) {
822                                 name += namep;
823                             }
824                             infoo.name = infoo.name.substring(0, infoo.name.lastIndexOf(".") + 1) + name;
825                         } else {
826                             LOGGER.error("Could not find connection for " + infoo + ". Statements of graph below");
827                             LOGGER.error("Subject -> Predicate : " + infoo.resource + " -> "
828                                     + diagramConnetionToConnection.resource);
829                         }
830                     }
831                 }
832             }
833         }
834         for (ResourceInfo info : infos.valueCollection()) {
835             if (info.name.startsWith("blank")) {
836                 info.name = "blank" + findHash(info);
837             }
838         }
839
840         TreeMap<String, ResourceInfo> order = new TreeMap<>();
841         for (ResourceInfo info : infos.valueCollection())
842             order.put(info.name, info);
843
844         for (ResourceInfo info : order.values()) {
845             if (DEBUG)
846                 System.out.print("info ");
847             String uri = printURI(info, true, 0, false);
848             if (uri != null)
849                 output.append(uri);
850         }
851
852         TreeMap<String, ResourceInfo> rblanks = new TreeMap<>();
853
854         for (ResourceInfo info : order.values()) {
855             if (!info.hasURI && info.ownedResourcesWithPredicates.size() != 1) {
856                 if (DEBUG)
857                     System.out.print("ownedResources ");
858                 if (info.name.startsWith("blank")) {
859                     // These will be printed later
860                     rblanks.put(getBlankRewrite(info.name), info);
861                 } else {
862                     String uri = printURI(info, false, 0, false);
863                     if (uri != null)
864                         output.append(uri);
865                 }
866             }
867         }
868         // Now print blanks in order
869         for (ResourceInfo info : rblanks.values()) {
870             if (!info.hasURI && info.ownedResourcesWithPredicates.size() != 1) {
871                 if (DEBUG)
872                     System.out.print("ownedResources ");
873                 String uri = printURI(info, false, 0, false);
874                 if (uri != null)
875                     output.append(uri);
876             }
877         }
878
879         // for(ResourceInfo info : order.values())
880         // if(!info.hasURI && info.owner < 0)
881         // printURI(graph, info, false, 0);
882
883         StringBuilder refs = new StringBuilder();
884         for (String ontology : referencedOntologies) {
885             String key = ontologies.get(ontology);
886             refs.append(key + " = <" + ontology + ">\n");
887         }
888         if (!referencedOntologies.isEmpty())
889             refs.append("\n");
890         output.insert(0, refs.toString());
891
892     }
893
894     private String calculateHash(ResourceInfo info) {
895         StringBuilder statementHash = new StringBuilder();
896         TreeSet<String> parts = new TreeSet<>();
897         for (int i = 0; i < info.owned.size(); i += 2) {
898             int predicate = info.owned.get(i);
899             int object = info.owned.get(i + 1);
900             // Lets resolve a unique name for this based on the statements this
901             // one has
902
903             String predicatee = rewritePredicateURI(predicate);
904             ResourceInfo objInfo = infos.get(object);
905             parts.add(predicatee + "->" + objInfo.name + ";;;");
906         }
907         // Remove this from the list
908         List<ResourceInfo> filtered = info.ownedBy.stream().filter(ri -> !ri.name.startsWith("blank"))
909                 .collect(Collectors.toList());
910         for (ResourceInfo ownedBy : filtered) {
911             parts.add(ownedBy.name);
912         }
913         // check parent
914         ResourceInfo parentInfo = infos.get(info.parent);
915         if (parentInfo != null && !parentInfo.name.startsWith("blank")) {
916             parts.add("parent" + parentInfo.name);
917         } else {
918             // LOGGER.error("This should not happen");
919         }
920         for (String s : parts) {
921             statementHash.append(s);
922         }
923         String hash = makeHash(statementHash.toString().getBytes());
924         if (DEBUG)
925             System.out.println(statementHash + " -> " + hash);
926         return hash;
927     }
928
929     private String findHash(ResourceInfo info) {
930         if (info.name.startsWith("blank")) {
931             String hash = hashes.get(info.name);
932             if (hash == null) {
933                 String oldName = info.name;
934                 if (DEBUG)
935                     System.out.print("calculating hash for " + oldName + " ");
936                 hash = calculateHash(info);
937                 if (hashes.put(oldName, hash) != null) {
938                     System.err.println("!!!!A clash occured for " + info + " with hash " + hash);
939                 }
940             }
941             return hash;
942         } else {
943             return info.name;
944         }
945     }
946
947     private THashMap<String, String> hashes = new THashMap<>();
948
949     public static String print(TransferableGraph1 tg, boolean ignoreIdentifiers) throws Exception {
950         StringBuilder b = new StringBuilder();
951         new PrettyPrintTG(tg, b, ignoreIdentifiers).prettyPrint();
952         return b.toString();
953     }
954
955     public static void main(String[] args) throws Exception {
956         if (args.length < 1) {
957             System.out.println("Required arguments: <input .sharedOntology file> [<output .tg file>]");
958         }
959         Path input;
960         Path output;
961         if (args.length < 2) {
962             input = Paths.get(args[0]);
963             output = input.getParent().resolve(input.getName(input.getNameCount() - 1) + ".fixed");
964         } else {
965             input = Paths.get(args[0]);
966             output = Paths.get(args[1]);
967         }
968         System.out.format("Converting exported shared ontology%n\t" + input.toString()
969         + "%nto bundle-compatible ontology%n\t" + output.toString());
970         try (InputStream is = new BufferedInputStream(Files.newInputStream(input), 128 * 1024)) {
971             DataInput dis = new DataInputStream(is);
972             org.simantics.databoard.container.DataContainer container = DataContainers.readFile(dis);
973             Binding binding = TransferableGraph1.BINDING;
974             TransferableGraph1 graph = (TransferableGraph1) container.content.getValue(binding);
975             new PrettyPrintTG(graph).prettyPrint();
976         }
977     }
978
979     private static void log(String string, Object... args) {
980         if (LOGGER.isDebugEnabled() && DEBUG)
981             LOGGER.debug(string, args);
982     }
983
984 }