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);
46 private static final boolean DEBUG = false;
51 private final Pattern versionExtractPattern = Pattern.compile("^.*-(\\d+\\.\\d+)");
53 final StringBuilder output;
54 final Map<String,String> ontologies = new HashMap<>(knownOntologies);
55 final Set<String> referencedOntologies = new TreeSet<>();
57 static class ResourceInfo {
61 boolean newResource = false;
65 boolean inlined = false;
67 // -1 = no owner, -2 = multiple owners
69 // Set<ResourceInfo> ownedBy
70 Set<ResourceInfo> ownedBy = new HashSet<>();
72 // A Map<Integer, Integer> containing information about resource that this resource owns and what are the predicates for forming this ownership
73 TIntIntHashMap ownedResourcesWithPredicates = new TIntIntHashMap();
76 // int ownerPredicate = 0;
77 String aliasURI = null;
78 TIntArrayList owned = new TIntArrayList();
79 //TIntObjectHashMap<TIntHashSet> statements = new TIntObjectHashMap<TIntHashSet>();
80 public ResourceInfo(boolean hasURI, String name, int resource, int parent) {
83 this.resource = resource;
88 public String toString() {
89 return name + (aliasURI != null ? " = <" + aliasURI + ">" : "");
93 public PrettyPrintTG(StringBuilder b) throws NoSuchAlgorithmException {
95 m = MessageDigest.getInstance("SHA-256");
98 public PrettyPrintTG() throws NoSuchAlgorithmException {
99 this(new StringBuilder());
102 TIntObjectHashMap<ResourceInfo> infos = new TIntObjectHashMap<>();
104 String tgNodeName(String name) {
105 if(name.contains(" ")) return "\"" + name + "\"";
109 ResourceInfo recurseURI(TransferableGraph1 graph, Identity parent, String parentName, int parentId) {
110 String name = parentName + "." + tgNodeName(TransferableGraphUtils.getName(parent));
111 ResourceInfo info = new ResourceInfo(true, name, parent.resource, parentId);
112 infos.put(parent.resource, info);
113 for(Identity child : TransferableGraphUtils.getChildren(graph, parent)) {
114 recurseURI(graph, child, name, info.resource);
119 void discoverBlank(TransferableGraph1 graph, int resource, TIntArrayList todo) throws Exception {
120 TIntArrayList statements = TransferableGraphUtils.getStatements(graph, resource);
121 for(int i=0;i<statements.size();i+=2) {
122 int object = statements.get(i+1);
123 Identity objectId = TransferableGraphUtils.getIdentity(graph, object);
124 if(objectId != null) {
125 if(objectId.definition instanceof External) continue;
127 Value value = TransferableGraphUtils.findValue(graph, object);
129 infos.put(object, new ResourceInfo(false, printValue(value), object, resource));
132 ResourceInfo existing = infos.get(object);
133 if(existing == null) {
134 existing = new ResourceInfo(false, "blank" + blankCounter++, object, resource);
135 infos.put(object, existing);
141 void discoverOwners(TransferableGraph1 graph, ResourceInfo info) {
142 log("Discovering owners for " + info);
143 int resource = info.resource;
144 TIntArrayList statements = TransferableGraphUtils.getStatements(graph, resource);
145 for(int i=0;i<statements.size();i+=2) {
146 int predicate = statements.get(i);
147 int object = statements.get(i+1);
148 ResourceInfo existing = infos.get(object);
149 if(existing != null) {
150 // Add all owners here for now and resolve the best owner later
151 existing.ownedResourcesWithPredicates.put(resource, predicate);
152 // Check if predicate is inverse, this just resolves all predicates to be inverse with ending "Inverse"..
153 String predicateUri = rewritePredicateURI(graph, predicate);
154 if (!predicateUri.endsWith("Inverse")) {
155 existing.ownedBy.add(info);
156 if (LOGGER.isDebugEnabled()) {
157 LOGGER.debug(" " + existing + " owns " + info + " with " + predicateUri);
164 DataValueRepository repo = new DataValueRepository();
165 DataValuePrinter printer = new DataValuePrinter(null, repo);
167 String printValue(Value value) throws Exception {
168 StringBuilder sb = new StringBuilder();
169 printer.setFormat(PrintFormat.SINGLE_LINE);
170 printer.setOutput(sb);
172 Variant variant = value.value;
173 printer.print(variant.getBinding(), variant.getValue());
174 String formattedOutput = sb.toString();
175 if (formattedOutput.length() > 100) {
176 // Ok, value too long, lets calculate a hash for it and store first 100 chars as comment
177 byte[] data = Bindings.getSerializerUnchecked(variant.getBinding()).serialize(variant.getValue());
179 m.update(data, 0, data.length);
180 String hash = "\"" + new BigInteger(1, m.digest()).toString(16) + "\"";
181 return hash + " // " + formattedOutput.substring(0, 100) + "..";
183 return formattedOutput;
186 // Variant variant = value.value;
187 // Datatype dt = variant.getBinding().type();
188 // if (Datatypes.STRING.equals(dt)) {
189 // String s = (String) variant.getValue(Bindings.STRING);
190 // if (s.contains("\n")) {
191 // return "\"\"\"" + s + "\"\"\"";
193 // return "\"" + s + "\"";
195 // } else if (Datatypes.BOOLEAN.equals(dt)) {
196 // Boolean i = (Boolean) variant.getValue(Bindings.BOOLEAN);
197 // return i ? "true" : "false";
198 // } else if (Datatypes.INTEGER.equals(dt)) {
199 // Integer i = (Integer) variant.getValue(Bindings.INTEGER);
200 // return i.toString();
201 // } else if (Datatypes.LONG.equals(dt)) {
202 // Long i = (Long) variant.getValue(Bindings.LONG);
203 // return i.toString();
204 // } else if (Datatypes.DOUBLE.equals(dt)) {
205 // Double d = (Double) variant.getValue();
206 // return d.toString();
207 // } else if (Datatypes.FLOAT.equals(dt)) {
208 // Float f = (Float) variant.getValue();
209 // return f.toString();
210 // } else if (Datatypes.STRING_ARRAY.equals(dt)) {
211 // return Arrays.toString((String []) variant.getValue());
212 // } else if (Datatypes.BOOLEAN_ARRAY.equals(dt)) {
213 // return Arrays.toString((boolean []) variant.getValue());
214 // } else if (Datatypes.INTEGER_ARRAY.equals(dt)) {
215 // return Arrays.toString((int []) variant.getValue());
216 // } else if (Datatypes.LONG_ARRAY.equals(dt)) {
217 // return Arrays.toString((long []) variant.getValue());
218 // } else if (Datatypes.DOUBLE_ARRAY.equals(dt)) {
219 // return Arrays.toString((double []) variant.getValue());
220 // } else if (Datatypes.FLOAT_ARRAY.equals(dt)) {
221 // return Arrays.toString((float []) variant.getValue());
222 // } else if (Datatypes.BYTE_ARRAY.equals(dt)) {
223 // return Arrays.toString((byte []) variant.getValue());
224 //// } else if (dt instanceof ArrayType) {
225 //// return Arrays.toString((Object []) variant.getValue());
227 // byte[] data = Bindings.getSerializerUnchecked(variant.getBinding()).serialize(variant.getValue());
229 // m.update(data, 0, data.length);
230 // return "\"" + new BigInteger(1, m.digest()).toString(16) + "\"";
235 // void fixInstanceOf(TransferableGraph1 graph, ResourceInfo info) {
236 // Identity id = getIdentity(graph, info.resource);
237 // if(id == null) return;
238 // if(id.definition instanceof Internal) {
239 // Identity instanceOf = findExternal(graph, "http://www.simantics.org/Layer0-1.1/InstanceOf");
240 // Identity library = findExternal(graph, "http://www.simantics.org/Layer0-1.1/Library");
241 // info.statements.add(instanceOf.resource);
242 // info.statements.add(library.resource);
246 public static String getExternalURI(TransferableGraph1 tg, External ext) {
247 String name = ext.name;
248 if(name.contains(" ")) name = name.replace(" ", "_").replaceAll("@", "_");//name = "\"" + name + "\"";
249 int parentId = ext.parent;
250 //if(parentId == 0) return ext.name;
252 Identity id = TransferableGraphUtils.getIdentity(tg, parentId);
253 if(id.definition instanceof External) {
254 return getExternalURI(tg, (External)id.definition) + "/" + name;
255 } else if(id.definition instanceof Root) {
256 Root root = (Root)id.definition;
257 return "http:/" + root.name + "/" + name;
264 public static String getExternalURI(TransferableGraph1 tg, int resource) {
265 Identity id = TransferableGraphUtils.getIdentity(tg, resource);
266 if(id == null) return null;
267 if(id.definition instanceof External) {
268 External ext = (External)id.definition;
269 return getExternalURI(tg, ext);
274 String rewritePredicateURI(TransferableGraph1 graph, int predicate) {
276 String uri = getExternalURI(graph, predicate);
278 ResourceInfo info = infos.get(predicate);
279 if(info != null) return info.name;
283 for(String ontology : ontologies.keySet()) {
284 if(uri.contains(ontology)) {
285 String key = ontologies.get(ontology);
286 uri = uri.replace(ontology, key);
287 referencedOntologies.add(ontology);
291 uri = uri.replace("/", ".");
297 static void indent(StringBuilder output, int indent) {
298 for(int i=0;i<indent;i++)
302 String printBlank(TransferableGraph1 graph, String predicateURI2, ResourceInfo info, int indent) {
307 StringBuilder output = new StringBuilder();
308 indent(output, indent);
309 output.append(predicateURI2 + " " + info.name + "\n");
311 if (info.ownedResourcesWithPredicates.isEmpty()) {
312 String uri = printURI(graph, info, false, indent, false);
316 // if(info.owner < 0) {
317 // printURI(graph, info, false, indent);
319 return output.toString();
322 static long longStm(int predicate, int object) {
323 return (predicate<<32) | (object & 0xffffffffL);
327 private void addInlineStatement(TransferableGraph1 graph, Map<String, Set<String>> statements, String predicate, ResourceInfo objectInfo, int indent) {
328 Set<String> objects = statements.get(predicate);
329 if(objects == null) {
330 objects = new TreeSet<>();
331 statements.put(predicate, objects);
333 String uri = printURI(graph, objectInfo, false, indent + 1, true);
335 // TODO: this is not the right place to remove trailing newline
336 uri = uri.endsWith("\n") ? uri.substring(0, uri.length() - 2) : uri;
339 objectInfo.inlined = true;
343 void addStatement(Map<String,Set<String>> statements, String predicate, String object) {
345 if (predicate.endsWith("Inverse"))
347 Set<String> objects = statements.get(predicate);
348 if(objects == null) {
349 objects = new TreeSet<>();
350 statements.put(predicate, objects);
355 String printURI(TransferableGraph1 graph, ResourceInfo info, boolean requireURI, int indent, boolean inline) {
357 if(requireURI && !info.hasURI)
360 // Check if this ResourceInfo is already inlined with some other ResourceInfo
364 Map<String,Set<String>> statements = new TreeMap<>();
365 Identity consistsOf = TransferableGraphUtils.findExternal(graph, "http://www.simantics.org/Layer0-1.1/ConsistsOf");
366 TLongHashSet processed = new TLongHashSet();
367 for(int i=0;i<info.owned.size();i+=2) {
368 long stmId = longStm(info.owned.get(i), info.owned.get(i+1));
369 processed.add(stmId);
372 TIntArrayList rawStatements = TransferableGraphUtils.getStatements(graph, info.resource);
373 for(int i=0;i<rawStatements.size();i+=2) {
374 long stmId = longStm(rawStatements.get(i), rawStatements.get(i+1));
375 if(!processed.add(stmId)) continue;
376 if(consistsOf.resource == rawStatements.get(i)) continue;
377 String predicateURI = rewritePredicateURI(graph, rawStatements.get(i));
378 ResourceInfo objectInfo = infos.get(rawStatements.get(i+1));
379 if(objectInfo == null) {
380 String objectURI = rewritePredicateURI(graph, rawStatements.get(i+1));
381 addStatement(statements, predicateURI, objectURI);
382 } else if (objectInfo.ownedBy.size() == 1 && objectInfo.ownedBy.contains(info)) {
383 // inline printing with _
384 addInlineStatement(graph, statements, predicateURI, objectInfo, indent);
386 addStatement(statements, predicateURI, objectInfo.name);
390 HashSet<ResourceInfo> debug = new HashSet<>();
391 info.ownedResourcesWithPredicates.forEachEntry(new TIntIntProcedure() {
394 public boolean execute(int owner, int predicate) {
395 ResourceInfo ownerInfo = infos.get(owner);
396 debug.add(ownerInfo);
397 // ResourceInfo predicateInfo = infos.get(predicate);
398 // debug.add(predicateInfo);
403 StringBuilder output = new StringBuilder();
405 if(indent == 0 || inline) {
406 if("ROOT".equals(info.name)) {
407 output.append("ROOT=<http:/>");
408 } else if (info.aliasURI != null) {
409 output.append(info.name + " = <" + info.aliasURI + ">");
412 System.out.println("asdasd");
413 output.append(inline ? "_" : info.name);
415 Set<String> instanceOfs = statements.get("L0.InstanceOf");
416 if(instanceOfs != null) {
417 for(String instanceOf : instanceOfs) {
418 output.append(" : " + instanceOf);
421 Set<String> subrelationOfs = statements.get("L0.SubrelationOf");
422 if(subrelationOfs != null) {
423 for(String subrelationOf : subrelationOfs) {
424 output.append(" <R " + subrelationOf);
427 Set<String> inherits = statements.get("L0.Inherits");
428 if(inherits != null) {
429 for(String inherit : inherits) {
430 output.append(" <T " + inherit);
437 output.append(" @L0.new\n");
439 for(Map.Entry<String, Set<String>> entry : statements.entrySet()) {
440 String predicate = entry.getKey();
441 if("L0.InstanceOf".equals(predicate)) continue;
442 if("L0.SubrelationOf".equals(predicate)) continue;
443 if("L0.Inherits".equals(predicate)) continue;
444 if("L0.PartOf".equals(predicate)) continue;
445 Set<String> objects = entry.getValue();
446 indent(output, indent+1);
447 if(objects.size() == 1) {
448 output.append(predicate + " " + objects.iterator().next() + "\n");
450 output.append(predicate + "\n");
451 for(String object : objects) {
452 indent(output, indent+1);
453 output.append(" " + object + "\n");
458 for(int i=0;i<info.owned.size();i+=2) {
459 String predicateURI = rewritePredicateURI(graph, info.owned.get(i));
460 ResourceInfo ownedInfo = infos.get(info.owned.get(i+1));
461 String blank = printBlank(graph, predicateURI, ownedInfo, indent+1);
463 output.append(blank);
466 return output.toString();
469 void prettyPrint(Path input, Path output) throws Exception {
471 System.out.format("Converting exported shared ontology%n\t" + input.toString() + "%nto bundle-compatible ontology%n\t" + output.toString());
472 try (InputStream is = new BufferedInputStream(Files.newInputStream(input), 128*1024)) {
473 DataInput dis = new DataInputStream(is);
474 org.simantics.databoard.container.DataContainer container =
475 DataContainers.readFile(dis);
476 Binding binding = TransferableGraph1.BINDING;
477 TransferableGraph1 graph = (TransferableGraph1)container.content.getValue(binding);
479 Files.write(output, this.output.toString().getBytes());
485 static Map<String,String> knownOntologies = new HashMap<>();
488 knownOntologies.put("http://www.simantics.org/Layer0-1.1", "L0");
489 knownOntologies.put("http://www.simantics.org/Layer0X-1.1", "L0X");
490 knownOntologies.put("http://www.simantics.org/Modeling-1.2", "MOD");
491 knownOntologies.put("http://www.simantics.org/Diagram-2.2", "DIA");
492 knownOntologies.put("http://www.simantics.org/Structural-1.2", "STR");
493 knownOntologies.put("http://www.simantics.org/Document-1.2", "DOC");
494 knownOntologies.put("http://www.simantics.org/Documentation-1.2", "DOCU");
495 knownOntologies.put("http://www.simantics.org/G2D-1.1", "G2D");
496 knownOntologies.put("http://www.simantics.org/SelectionView-1.2", "SEL");
497 knownOntologies.put("http://www.simantics.org/Viewpoint-1.2", "VP");
498 knownOntologies.put("http://www.simantics.org/Image2-1.2", "IMAGE2");
499 knownOntologies.put("http://www.simantics.org/GraphFile-0.1", "GRAPHFILE");
500 knownOntologies.put("http://www.simantics.org/Project-1.2", "PROJECT");
501 knownOntologies.put("http://www.semantum.fi/Simupedia-1.0", "SIMUPEDIA");
502 knownOntologies.put("http://www.semantum.fi/SimupediaWorkbench-1.0", "SIMUPEDIA_WORKBENCH");
506 void prettyPrint(TransferableGraph1 graph) throws Exception {
508 if (LOGGER.isDebugEnabled())
509 LOGGER.debug("Starting prettyPrint for TransferableGraph with " + graph.resourceCount + " resources, " + graph.identities + " identities, " + graph.statements.length + " statements and " + graph.values.length + " values");
511 for(Identity id : graph.identities) {
512 if(id.definition instanceof Internal) {
513 Internal internal = (Internal)id.definition;
514 Identity parent = TransferableGraphUtils.getIdentity(graph, internal.parent);
515 if(parent.definition instanceof External) {
516 if (LOGGER.isDebugEnabled())
517 LOGGER.debug("Resolving internal identity " + id);
518 String name = "BASE";
519 ResourceInfo info = new ResourceInfo(true, name, id.resource, -1);
520 info.aliasURI = TransferableGraphUtils.getURI(graph, id.resource);
521 info.newResource = true;
522 infos.put(id.resource, info);
523 if (LOGGER.isDebugEnabled())
524 LOGGER.debug(" which parent is external " + parent + " and has an aliasURI " + info.aliasURI) ;
525 for(Identity child : TransferableGraphUtils.getChildren(graph, id)) {
526 recurseURI(graph, child, name, info.resource);
528 if (LOGGER.isDebugEnabled())
529 LOGGER.debug(" and has " + (infos.size() - 1) + " children");
531 } else if (id.definition instanceof External) {
532 External ext = (External)id.definition;
533 // Try to detect shared libraries
534 if(ext.name.contains("@")) {
536 if (LOGGER.isDebugEnabled())
537 LOGGER.debug("Detected an external shared library " + ext);
539 int index = ext.name.indexOf('@');
540 String prefix = ext.name.substring(0, index);
541 int index2 = ext.name.indexOf('/', index);
542 String ontology = index2 == -1 ? ext.name : ext.name.substring(0, index2);
543 String uri = TransferableGraphUtils.getURI(graph, id.resource);
545 if (LOGGER.isDebugEnabled())
546 LOGGER.debug(" which was resolved as URI=" + uri + " and prefix " + prefix);
548 ontologies.put(uri, prefix);
550 } else if (ext.name.contains("-")) {
551 if (LOGGER.isDebugEnabled())
552 LOGGER.debug("Resolving possible ontology " + ext);
553 String uri = TransferableGraphUtils.getURI(graph, id.resource);
554 Matcher m = versionExtractPattern.matcher(uri);
556 if(!ontologies.containsKey(uri)) {
557 int index = ext.name.indexOf('-');
558 String prefix = ext.name.substring(0, index);
559 if (LOGGER.isDebugEnabled())
560 LOGGER.debug(" and it was resolved as URI=" + uri + " and prefix " + prefix);
561 ontologies.put(uri, prefix);
567 // Discover other resources
568 if (LOGGER.isDebugEnabled())
569 LOGGER.debug("Discovering other resources..");
571 TIntArrayList todo = new TIntArrayList();
572 for(ResourceInfo info : infos.valueCollection())
573 todo.add(info.resource);
575 while(!todo.isEmpty()) {
576 int resource = todo.removeAt(todo.size()-1);
577 discoverBlank(graph, resource, todo);
579 for(ResourceInfo info : infos.valueCollection())
580 discoverOwners(graph, info);
581 // for(ResourceInfo info : infos.valueCollection())
582 // fixInstanceOf(graph, info);
584 for (ResourceInfo info : infos.valueCollection()) {
585 // Old implementation
586 // if (info.owner >= 0) {
587 // ResourceInfo ownerInfo = infos.get(info.owner);
588 // System.out.println("originalOwner : " + info.owner + " originalPredicate: " + info.ownerPredicate);
589 // ownerInfo.owned.add(info.ownerPredicate);
590 // ownerInfo.owned.add(info.resource);
593 if (!info.ownedResourcesWithPredicates.isEmpty() && info.ownedResourcesWithPredicates.size() == 1) {
594 info.ownedResourcesWithPredicates.forEachEntry(new TIntIntProcedure() {
597 public boolean execute(int owner, int predicate) {
598 ResourceInfo ownerInfo = infos.get(owner);
599 ownerInfo.owned.add(predicate);
600 ownerInfo.owned.add(info.resource);
607 // Resolve inverses from ownedBy list
608 for (ResourceInfo info : infos.valueCollection()) {
609 for (int i = 0; i < info.owned.size(); i+=2) {
610 int object = info.owned.get(i+1);
611 ResourceInfo inf = infos.get(object);
613 info.ownedBy.remove(inf);
618 TreeMap<String,ResourceInfo> order = new TreeMap<>();
619 for(ResourceInfo info : infos.valueCollection())
620 order.put(info.name, info);
622 for(ResourceInfo info : order.values()) {
623 String uri = printURI(graph, info, true, 0, false);
628 for (ResourceInfo info : order.values()) {
629 if (!info.hasURI && info.ownedResourcesWithPredicates.size() != 1) {
630 String uri = printURI(graph, info, false, 0, false);
635 // for(ResourceInfo info : order.values())
636 // if(!info.hasURI && info.owner < 0)
637 // printURI(graph, info, false, 0);
639 StringBuilder refs = new StringBuilder();
640 for(String ontology : referencedOntologies) {
641 String key = ontologies.get(ontology);
642 refs.append(key + " = <" + ontology + ">\n");
644 if (!referencedOntologies.isEmpty())
646 output.insert(0, refs.toString());
650 public static String print(TransferableGraph1 tg) throws Exception {
651 StringBuilder b = new StringBuilder();
652 new PrettyPrintTG(b).prettyPrint(tg);
656 public static void main(String[] args) throws Exception {
657 if (args.length < 1) {
658 System.out.println("Required arguments: <input .sharedOntology file> [<output .tg file>]");
659 } else if (args.length < 2) {
660 Path input = Paths.get(args[0]);
661 Path output = input.getParent().resolve(input.getName(input.getNameCount()-1) + ".fixed");
662 new PrettyPrintTG().prettyPrint(input, output);
664 new PrettyPrintTG().prettyPrint(Paths.get(args[0]), Paths.get(args[1]));
668 private static void log(String string) {
669 if (LOGGER.isDebugEnabled() && DEBUG)
670 LOGGER.debug(string);