1 package org.simantics.graph.representation;
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.HashMap;
14 import java.util.HashSet;
17 import java.util.TreeMap;
18 import java.util.TreeSet;
19 import java.util.regex.Matcher;
20 import java.util.regex.Pattern;
22 import org.simantics.databoard.Bindings;
23 import org.simantics.databoard.binding.Binding;
24 import org.simantics.databoard.binding.mutable.Variant;
25 import org.simantics.databoard.container.DataContainers;
26 import org.simantics.databoard.parser.DataValuePrinter;
27 import org.simantics.databoard.parser.PrintFormat;
28 import org.simantics.databoard.parser.repository.DataValueRepository;
29 import org.slf4j.Logger;
30 import org.slf4j.LoggerFactory;
32 import gnu.trove.list.array.TIntArrayList;
33 import gnu.trove.map.hash.TIntIntHashMap;
34 import gnu.trove.map.hash.TIntObjectHashMap;
35 import gnu.trove.procedure.TIntIntProcedure;
36 import gnu.trove.set.hash.TLongHashSet;
39 * @author Antti Villberg
42 public class PrettyPrintTG {
44 private static final Logger LOGGER = LoggerFactory.getLogger(PrettyPrintTG.class);
49 private final Pattern versionExtractPattern = Pattern.compile("^.*-(\\d+\\.\\d+)");
51 final StringBuilder output;
52 final Map<String,String> ontologies = new HashMap<>(knownOntologies);
53 final Set<String> referencedOntologies = new TreeSet<>();
55 static class ResourceInfo {
59 boolean newResource = false;
63 boolean inlined = false;
65 // -1 = no owner, -2 = multiple owners
67 // Set<ResourceInfo> ownedBy
68 Set<ResourceInfo> ownedBy = new HashSet<>();
70 // A Map<Integer, Integer> containing information about resource that this resource owns and what are the predicates for forming this ownership
71 TIntIntHashMap ownedResourcesWithPredicates = new TIntIntHashMap();
74 // int ownerPredicate = 0;
75 String aliasURI = null;
76 TIntArrayList owned = new TIntArrayList();
77 //TIntObjectHashMap<TIntHashSet> statements = new TIntObjectHashMap<TIntHashSet>();
78 public ResourceInfo(boolean hasURI, String name, int resource, int parent) {
81 this.resource = resource;
86 public String toString() {
87 return name + (aliasURI != null ? " = <" + aliasURI + ">" : "");
91 public PrettyPrintTG(StringBuilder b) throws NoSuchAlgorithmException {
93 m = MessageDigest.getInstance("SHA-256");
96 public PrettyPrintTG() throws NoSuchAlgorithmException {
97 this(new StringBuilder());
100 TIntObjectHashMap<ResourceInfo> infos = new TIntObjectHashMap<>();
102 String tgNodeName(String name) {
103 if(name.contains(" ")) return "\"" + name + "\"";
107 ResourceInfo recurseURI(TransferableGraph1 graph, Identity parent, String parentName, int parentId) {
108 String name = parentName + "." + tgNodeName(TransferableGraphUtils.getName(parent));
109 ResourceInfo info = new ResourceInfo(true, name, parent.resource, parentId);
110 infos.put(parent.resource, info);
111 for(Identity child : TransferableGraphUtils.getChildren(graph, parent)) {
112 recurseURI(graph, child, name, info.resource);
117 void discoverBlank(TransferableGraph1 graph, int resource, TIntArrayList todo) throws Exception {
118 TIntArrayList statements = TransferableGraphUtils.getStatements(graph, resource);
119 for(int i=0;i<statements.size();i+=2) {
120 int object = statements.get(i+1);
121 Identity objectId = TransferableGraphUtils.getIdentity(graph, object);
122 if(objectId != null) {
123 if(objectId.definition instanceof External) continue;
125 Value value = TransferableGraphUtils.findValue(graph, object);
127 infos.put(object, new ResourceInfo(false, printValue(value), object, resource));
130 ResourceInfo existing = infos.get(object);
131 if(existing == null) {
132 existing = new ResourceInfo(false, "blank" + blankCounter++, object, resource);
133 infos.put(object, existing);
139 void discoverOwners(TransferableGraph1 graph, ResourceInfo info) {
140 if (LOGGER.isDebugEnabled())
141 LOGGER.debug("Discovering owners for " + info);
142 int resource = info.resource;
143 TIntArrayList statements = TransferableGraphUtils.getStatements(graph, resource);
144 for(int i=0;i<statements.size();i+=2) {
145 int predicate = statements.get(i);
146 int object = statements.get(i+1);
147 ResourceInfo existing = infos.get(object);
148 if(existing != null) {
149 // Add all owners here for now and resolve the best owner later
150 existing.ownedResourcesWithPredicates.put(resource, predicate);
151 // Check if predicate is inverse, this just resolves all predicates to be inverse with ending "Inverse"..
152 String predicateUri = rewritePredicateURI(graph, predicate);
153 if (!predicateUri.endsWith("Inverse")) {
154 existing.ownedBy.add(info);
155 if (LOGGER.isDebugEnabled()) {
156 LOGGER.debug(" " + existing + " owns " + info + " with " + predicateUri);
163 DataValueRepository repo = new DataValueRepository();
164 DataValuePrinter printer = new DataValuePrinter(null, repo);
166 String printValue(Value value) throws Exception {
167 StringBuilder sb = new StringBuilder();
168 printer.setFormat(PrintFormat.SINGLE_LINE);
169 printer.setOutput(sb);
171 Variant variant = value.value;
172 printer.print(variant.getBinding(), variant.getValue());
173 String formattedOutput = sb.toString();
174 if (formattedOutput.length() > 100) {
175 // Ok, value too long, lets calculate a hash for it and store first 100 chars as comment
176 byte[] data = Bindings.getSerializerUnchecked(variant.getBinding()).serialize(variant.getValue());
178 m.update(data, 0, data.length);
179 String hash = "\"" + new BigInteger(1, m.digest()).toString(16) + "\"";
180 return hash + " // " + formattedOutput.substring(0, 100) + "..";
182 return formattedOutput;
185 // Variant variant = value.value;
186 // Datatype dt = variant.getBinding().type();
187 // if (Datatypes.STRING.equals(dt)) {
188 // String s = (String) variant.getValue(Bindings.STRING);
189 // if (s.contains("\n")) {
190 // return "\"\"\"" + s + "\"\"\"";
192 // return "\"" + s + "\"";
194 // } else if (Datatypes.BOOLEAN.equals(dt)) {
195 // Boolean i = (Boolean) variant.getValue(Bindings.BOOLEAN);
196 // return i ? "true" : "false";
197 // } else if (Datatypes.INTEGER.equals(dt)) {
198 // Integer i = (Integer) variant.getValue(Bindings.INTEGER);
199 // return i.toString();
200 // } else if (Datatypes.LONG.equals(dt)) {
201 // Long i = (Long) variant.getValue(Bindings.LONG);
202 // return i.toString();
203 // } else if (Datatypes.DOUBLE.equals(dt)) {
204 // Double d = (Double) variant.getValue();
205 // return d.toString();
206 // } else if (Datatypes.FLOAT.equals(dt)) {
207 // Float f = (Float) variant.getValue();
208 // return f.toString();
209 // } else if (Datatypes.STRING_ARRAY.equals(dt)) {
210 // return Arrays.toString((String []) variant.getValue());
211 // } else if (Datatypes.BOOLEAN_ARRAY.equals(dt)) {
212 // return Arrays.toString((boolean []) variant.getValue());
213 // } else if (Datatypes.INTEGER_ARRAY.equals(dt)) {
214 // return Arrays.toString((int []) variant.getValue());
215 // } else if (Datatypes.LONG_ARRAY.equals(dt)) {
216 // return Arrays.toString((long []) variant.getValue());
217 // } else if (Datatypes.DOUBLE_ARRAY.equals(dt)) {
218 // return Arrays.toString((double []) variant.getValue());
219 // } else if (Datatypes.FLOAT_ARRAY.equals(dt)) {
220 // return Arrays.toString((float []) variant.getValue());
221 // } else if (Datatypes.BYTE_ARRAY.equals(dt)) {
222 // return Arrays.toString((byte []) variant.getValue());
223 //// } else if (dt instanceof ArrayType) {
224 //// return Arrays.toString((Object []) variant.getValue());
226 // byte[] data = Bindings.getSerializerUnchecked(variant.getBinding()).serialize(variant.getValue());
228 // m.update(data, 0, data.length);
229 // return "\"" + new BigInteger(1, m.digest()).toString(16) + "\"";
234 // void fixInstanceOf(TransferableGraph1 graph, ResourceInfo info) {
235 // Identity id = getIdentity(graph, info.resource);
236 // if(id == null) return;
237 // if(id.definition instanceof Internal) {
238 // Identity instanceOf = findExternal(graph, "http://www.simantics.org/Layer0-1.1/InstanceOf");
239 // Identity library = findExternal(graph, "http://www.simantics.org/Layer0-1.1/Library");
240 // info.statements.add(instanceOf.resource);
241 // info.statements.add(library.resource);
245 public static String getExternalURI(TransferableGraph1 tg, External ext) {
246 String name = ext.name;
247 if(name.contains(" ")) name = name.replace(" ", "_").replaceAll("@", "_");//name = "\"" + name + "\"";
248 int parentId = ext.parent;
249 //if(parentId == 0) return ext.name;
251 Identity id = TransferableGraphUtils.getIdentity(tg, parentId);
252 if(id.definition instanceof External) {
253 return getExternalURI(tg, (External)id.definition) + "/" + name;
254 } else if(id.definition instanceof Root) {
255 Root root = (Root)id.definition;
256 return "http:/" + root.name + "/" + name;
263 public static String getExternalURI(TransferableGraph1 tg, int resource) {
264 Identity id = TransferableGraphUtils.getIdentity(tg, resource);
265 if(id == null) return null;
266 if(id.definition instanceof External) {
267 External ext = (External)id.definition;
268 return getExternalURI(tg, ext);
273 String rewritePredicateURI(TransferableGraph1 graph, int predicate) {
275 String uri = getExternalURI(graph, predicate);
277 ResourceInfo info = infos.get(predicate);
278 if(info != null) return info.name;
282 for(String ontology : ontologies.keySet()) {
283 if(uri.contains(ontology)) {
284 String key = ontologies.get(ontology);
285 uri = uri.replace(ontology, key);
286 referencedOntologies.add(ontology);
290 uri = uri.replace("/", ".");
296 static void indent(StringBuilder output, int indent) {
297 for(int i=0;i<indent;i++)
301 String printBlank(TransferableGraph1 graph, String predicateURI2, ResourceInfo info, int indent) {
306 StringBuilder output = new StringBuilder();
307 indent(output, indent);
308 output.append(predicateURI2 + " " + info.name + "\n");
310 if (info.ownedResourcesWithPredicates.isEmpty()) {
311 String uri = printURI(graph, info, false, indent, false);
315 // if(info.owner < 0) {
316 // printURI(graph, info, false, indent);
318 return output.toString();
321 static long longStm(int predicate, int object) {
322 return (predicate<<32) | (object & 0xffffffffL);
326 private void addInlineStatement(TransferableGraph1 graph, Map<String, Set<String>> statements, String predicate, ResourceInfo objectInfo, int indent) {
327 Set<String> objects = statements.get(predicate);
328 if(objects == null) {
329 objects = new TreeSet<>();
330 statements.put(predicate, objects);
332 String uri = printURI(graph, objectInfo, false, indent + 1, true);
334 // TODO: this is not the right place to remove trailing newline
335 uri = uri.endsWith("\n") ? uri.substring(0, uri.length() - 2) : uri;
338 objectInfo.inlined = true;
342 void addStatement(Map<String,Set<String>> statements, String predicate, String object) {
344 if (predicate.endsWith("Inverse"))
346 Set<String> objects = statements.get(predicate);
347 if(objects == null) {
348 objects = new TreeSet<>();
349 statements.put(predicate, objects);
354 String printURI(TransferableGraph1 graph, ResourceInfo info, boolean requireURI, int indent, boolean inline) {
356 if(requireURI && !info.hasURI)
359 // Check if this ResourceInfo is already inlined with some other ResourceInfo
363 Map<String,Set<String>> statements = new TreeMap<>();
364 Identity consistsOf = TransferableGraphUtils.findExternal(graph, "http://www.simantics.org/Layer0-1.1/ConsistsOf");
365 TLongHashSet processed = new TLongHashSet();
366 for(int i=0;i<info.owned.size();i+=2) {
367 long stmId = longStm(info.owned.get(i), info.owned.get(i+1));
368 processed.add(stmId);
371 TIntArrayList rawStatements = TransferableGraphUtils.getStatements(graph, info.resource);
372 for(int i=0;i<rawStatements.size();i+=2) {
373 long stmId = longStm(rawStatements.get(i), rawStatements.get(i+1));
374 if(!processed.add(stmId)) continue;
375 if(consistsOf.resource == rawStatements.get(i)) continue;
376 String predicateURI = rewritePredicateURI(graph, rawStatements.get(i));
377 ResourceInfo objectInfo = infos.get(rawStatements.get(i+1));
378 if(objectInfo == null) {
379 String objectURI = rewritePredicateURI(graph, rawStatements.get(i+1));
380 addStatement(statements, predicateURI, objectURI);
381 } else if (objectInfo.ownedBy.size() == 1 && objectInfo.ownedBy.contains(info)) {
382 // inline printing with _
383 addInlineStatement(graph, statements, predicateURI, objectInfo, indent);
385 addStatement(statements, predicateURI, objectInfo.name);
389 HashSet<ResourceInfo> debug = new HashSet<>();
390 info.ownedResourcesWithPredicates.forEachEntry(new TIntIntProcedure() {
393 public boolean execute(int owner, int predicate) {
394 ResourceInfo ownerInfo = infos.get(owner);
395 debug.add(ownerInfo);
396 // ResourceInfo predicateInfo = infos.get(predicate);
397 // debug.add(predicateInfo);
402 StringBuilder output = new StringBuilder();
404 if(indent == 0 || inline) {
405 if("ROOT".equals(info.name)) {
406 output.append("ROOT=<http:/>");
407 } else if (info.aliasURI != null) {
408 output.append(info.name + " = <" + info.aliasURI + ">");
411 System.out.println("asdasd");
412 output.append(inline ? "_" : info.name);
414 Set<String> instanceOfs = statements.get("L0.InstanceOf");
415 if(instanceOfs != null) {
416 for(String instanceOf : instanceOfs) {
417 output.append(" : " + instanceOf);
420 Set<String> subrelationOfs = statements.get("L0.SubrelationOf");
421 if(subrelationOfs != null) {
422 for(String subrelationOf : subrelationOfs) {
423 output.append(" <R " + subrelationOf);
426 Set<String> inherits = statements.get("L0.Inherits");
427 if(inherits != null) {
428 for(String inherit : inherits) {
429 output.append(" <T " + inherit);
436 output.append(" @L0.new\n");
438 for(Map.Entry<String, Set<String>> entry : statements.entrySet()) {
439 String predicate = entry.getKey();
440 if("L0.InstanceOf".equals(predicate)) continue;
441 if("L0.SubrelationOf".equals(predicate)) continue;
442 if("L0.Inherits".equals(predicate)) continue;
443 if("L0.PartOf".equals(predicate)) continue;
444 Set<String> objects = entry.getValue();
445 indent(output, indent+1);
446 if(objects.size() == 1) {
447 output.append(predicate + " " + objects.iterator().next() + "\n");
449 output.append(predicate + "\n");
450 for(String object : objects) {
451 indent(output, indent+1);
452 output.append(" " + object + "\n");
457 for(int i=0;i<info.owned.size();i+=2) {
458 String predicateURI = rewritePredicateURI(graph, info.owned.get(i));
459 ResourceInfo ownedInfo = infos.get(info.owned.get(i+1));
460 String blank = printBlank(graph, predicateURI, ownedInfo, indent+1);
462 output.append(blank);
465 return output.toString();
468 void prettyPrint(Path input, Path output) throws Exception {
470 System.out.format("Converting exported shared ontology%n\t" + input.toString() + "%nto bundle-compatible ontology%n\t" + output.toString());
471 try (InputStream is = new BufferedInputStream(Files.newInputStream(input), 128*1024)) {
472 DataInput dis = new DataInputStream(is);
473 org.simantics.databoard.container.DataContainer container =
474 DataContainers.readFile(dis);
475 Binding binding = TransferableGraph1.BINDING;
476 TransferableGraph1 graph = (TransferableGraph1)container.content.getValue(binding);
478 Files.write(output, this.output.toString().getBytes());
484 static Map<String,String> knownOntologies = new HashMap<>();
487 knownOntologies.put("http://www.simantics.org/Layer0-1.1", "L0");
488 knownOntologies.put("http://www.simantics.org/Layer0X-1.1", "L0X");
489 knownOntologies.put("http://www.simantics.org/Modeling-1.2", "MOD");
490 knownOntologies.put("http://www.simantics.org/Diagram-2.2", "DIA");
491 knownOntologies.put("http://www.simantics.org/Structural-1.2", "STR");
492 knownOntologies.put("http://www.simantics.org/Document-1.2", "DOC");
493 knownOntologies.put("http://www.simantics.org/Documentation-1.2", "DOCU");
494 knownOntologies.put("http://www.simantics.org/G2D-1.1", "G2D");
495 knownOntologies.put("http://www.simantics.org/SelectionView-1.2", "SEL");
496 knownOntologies.put("http://www.simantics.org/Viewpoint-1.2", "VP");
497 knownOntologies.put("http://www.simantics.org/Image2-1.2", "IMAGE2");
498 knownOntologies.put("http://www.simantics.org/GraphFile-0.1", "GRAPHFILE");
499 knownOntologies.put("http://www.simantics.org/Project-1.2", "PROJECT");
500 knownOntologies.put("http://www.semantum.fi/Simupedia-1.0", "SIMUPEDIA");
501 knownOntologies.put("http://www.semantum.fi/SimupediaWorkbench-1.0", "SIMUPEDIA_WORKBENCH");
505 void prettyPrint(TransferableGraph1 graph) throws Exception {
507 if (LOGGER.isDebugEnabled())
508 LOGGER.debug("Starting prettyPrint for TransferableGraph with " + graph.resourceCount + " resources, " + graph.identities + " identities, " + graph.statements.length + " statements and " + graph.values.length + " values");
510 for(Identity id : graph.identities) {
511 if(id.definition instanceof Internal) {
512 Internal internal = (Internal)id.definition;
513 Identity parent = TransferableGraphUtils.getIdentity(graph, internal.parent);
514 if(parent.definition instanceof External) {
515 if (LOGGER.isDebugEnabled())
516 LOGGER.debug("Resolving internal identity " + id);
517 String name = "BASE";
518 ResourceInfo info = new ResourceInfo(true, name, id.resource, -1);
519 info.aliasURI = TransferableGraphUtils.getURI(graph, id.resource);
520 info.newResource = true;
521 infos.put(id.resource, info);
522 if (LOGGER.isDebugEnabled())
523 LOGGER.debug(" which parent is external " + parent + " and has an aliasURI " + info.aliasURI) ;
524 for(Identity child : TransferableGraphUtils.getChildren(graph, id)) {
525 recurseURI(graph, child, name, info.resource);
527 if (LOGGER.isDebugEnabled())
528 LOGGER.debug(" and has " + (infos.size() - 1) + " children");
530 } else if (id.definition instanceof External) {
531 External ext = (External)id.definition;
532 // Try to detect shared libraries
533 if(ext.name.contains("@")) {
535 if (LOGGER.isDebugEnabled())
536 LOGGER.debug("Detected an external shared library " + ext);
538 int index = ext.name.indexOf('@');
539 String prefix = ext.name.substring(0, index);
540 int index2 = ext.name.indexOf('/', index);
541 String ontology = index2 == -1 ? ext.name : ext.name.substring(0, index2);
542 String uri = TransferableGraphUtils.getURI(graph, id.resource);
544 if (LOGGER.isDebugEnabled())
545 LOGGER.debug(" which was resolved as URI=" + uri + " and prefix " + prefix);
547 ontologies.put(uri, prefix);
549 } else if (ext.name.contains("-")) {
550 if (LOGGER.isDebugEnabled())
551 LOGGER.debug("Resolving possible ontology " + ext);
552 String uri = TransferableGraphUtils.getURI(graph, id.resource);
553 Matcher m = versionExtractPattern.matcher(uri);
555 if(!ontologies.containsKey(uri)) {
556 int index = ext.name.indexOf('-');
557 String prefix = ext.name.substring(0, index);
558 if (LOGGER.isDebugEnabled())
559 LOGGER.debug(" and it was resolved as URI=" + uri + " and prefix " + prefix);
560 ontologies.put(uri, prefix);
566 // Discover other resources
567 if (LOGGER.isDebugEnabled())
568 LOGGER.debug("Discovering other resources..");
570 TIntArrayList todo = new TIntArrayList();
571 for(ResourceInfo info : infos.valueCollection())
572 todo.add(info.resource);
574 while(!todo.isEmpty()) {
575 int resource = todo.removeAt(todo.size()-1);
576 discoverBlank(graph, resource, todo);
578 for(ResourceInfo info : infos.valueCollection())
579 discoverOwners(graph, info);
580 // for(ResourceInfo info : infos.valueCollection())
581 // fixInstanceOf(graph, info);
583 for (ResourceInfo info : infos.valueCollection()) {
584 // Old implementation
585 // if (info.owner >= 0) {
586 // ResourceInfo ownerInfo = infos.get(info.owner);
587 // System.out.println("originalOwner : " + info.owner + " originalPredicate: " + info.ownerPredicate);
588 // ownerInfo.owned.add(info.ownerPredicate);
589 // ownerInfo.owned.add(info.resource);
592 if (!info.ownedResourcesWithPredicates.isEmpty() && info.ownedResourcesWithPredicates.size() == 1) {
593 info.ownedResourcesWithPredicates.forEachEntry(new TIntIntProcedure() {
596 public boolean execute(int owner, int predicate) {
597 ResourceInfo ownerInfo = infos.get(owner);
598 ownerInfo.owned.add(predicate);
599 ownerInfo.owned.add(info.resource);
606 // Resolve inverses from ownedBy list
607 for (ResourceInfo info : infos.valueCollection()) {
608 for (int i = 0; i < info.owned.size(); i+=2) {
609 int object = info.owned.get(i+1);
610 ResourceInfo inf = infos.get(object);
612 info.ownedBy.remove(inf);
617 TreeMap<String,ResourceInfo> order = new TreeMap<>();
618 for(ResourceInfo info : infos.valueCollection())
619 order.put(info.name, info);
621 for(ResourceInfo info : order.values()) {
622 String uri = printURI(graph, info, true, 0, false);
627 for (ResourceInfo info : order.values()) {
628 if (!info.hasURI && info.ownedResourcesWithPredicates.size() != 1) {
629 String uri = printURI(graph, info, false, 0, false);
634 // for(ResourceInfo info : order.values())
635 // if(!info.hasURI && info.owner < 0)
636 // printURI(graph, info, false, 0);
638 StringBuilder refs = new StringBuilder();
639 for(String ontology : referencedOntologies) {
640 String key = ontologies.get(ontology);
641 refs.append(key + " = <" + ontology + ">\n");
643 if (!referencedOntologies.isEmpty())
645 output.insert(0, refs.toString());
649 public static String print(TransferableGraph1 tg) throws Exception {
650 StringBuilder b = new StringBuilder();
651 new PrettyPrintTG(b).prettyPrint(tg);
655 public static void main(String[] args) throws Exception {
656 if (args.length < 1) {
657 System.out.println("Required arguments: <input .sharedOntology file> [<output .tg file>]");
658 } else if (args.length < 2) {
659 Path input = Paths.get(args[0]);
660 Path output = input.getParent().resolve(input.getName(input.getNameCount()-1) + ".fixed");
661 new PrettyPrintTG().prettyPrint(input, output);
663 new PrettyPrintTG().prettyPrint(Paths.get(args[0]), Paths.get(args[1]));