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