Reading background color of a ICanvasContext with SCL
[simantics/platform.git] / bundles / org.simantics.modeling / src / org / simantics / modeling / SCLScenegraph.java
1 package org.simantics.modeling;
2
3 import java.awt.BasicStroke;
4 import java.awt.Color;
5 import java.awt.Dimension;
6 import java.awt.RenderingHints;
7 import java.awt.RenderingHints.Key;
8 import java.awt.geom.AffineTransform;
9 import java.awt.geom.Rectangle2D;
10 import java.io.ByteArrayOutputStream;
11 import java.io.OutputStreamWriter;
12 import java.util.ArrayList;
13 import java.util.Arrays;
14 import java.util.Collection;
15 import java.util.HashMap;
16 import java.util.HashSet;
17 import java.util.List;
18 import java.util.Map;
19 import java.util.Random;
20 import java.util.Set;
21 import java.util.UUID;
22 import java.util.function.Function;
23 import java.util.stream.Collectors;
24
25 import javax.xml.transform.OutputKeys;
26 import javax.xml.transform.Transformer;
27 import javax.xml.transform.TransformerFactory;
28 import javax.xml.transform.dom.DOMSource;
29 import javax.xml.transform.stream.StreamResult;
30
31 import org.apache.batik.dom.GenericDOMImplementation;
32 import org.apache.batik.svggen.SVGGeneratorContext;
33 import org.apache.batik.svggen.SVGGraphics2D;
34 import org.simantics.Simantics;
35 import org.simantics.datatypes.literal.GUID;
36 import org.simantics.db.ReadGraph;
37 import org.simantics.db.Resource;
38 import org.simantics.db.common.request.IndexRoot;
39 import org.simantics.db.common.request.ResourceRead;
40 import org.simantics.db.common.request.UnaryRead;
41 import org.simantics.db.exception.DatabaseException;
42 import org.simantics.db.exception.RuntimeDatabaseException;
43 import org.simantics.db.layer0.variable.Variables;
44 import org.simantics.diagram.elements.DecorationSVGNode;
45 import org.simantics.diagram.elements.DiagramNodeUtil;
46 import org.simantics.diagram.elements.TextGridNode;
47 import org.simantics.diagram.elements.TextNode;
48 import org.simantics.diagram.stubs.DiagramResource;
49 import org.simantics.g2d.canvas.Hints;
50 import org.simantics.g2d.canvas.ICanvasContext;
51 import org.simantics.g2d.diagram.DiagramHints;
52 import org.simantics.g2d.diagram.IDiagram;
53 import org.simantics.g2d.diagram.handler.DataElementMap;
54 import org.simantics.g2d.diagram.participant.Selection;
55 import org.simantics.g2d.element.IElement;
56 import org.simantics.g2d.scenegraph.ICanvasSceneGraphProvider;
57 import org.simantics.g2d.utils.CanvasUtils;
58 import org.simantics.layer0.Layer0;
59 import org.simantics.scenegraph.INode;
60 import org.simantics.scenegraph.ParentNode;
61 import org.simantics.scenegraph.g2d.G2DParentNode;
62 import org.simantics.scenegraph.g2d.G2DRenderingHints;
63 import org.simantics.scenegraph.g2d.G2DSceneGraph;
64 import org.simantics.scenegraph.g2d.IG2DNode;
65 import org.simantics.scenegraph.g2d.IG2DNodeVisitor;
66 import org.simantics.scenegraph.g2d.events.command.Commands;
67 import org.simantics.scenegraph.g2d.nodes.BackgroundNode;
68 import org.simantics.scenegraph.g2d.nodes.BoundsNode;
69 import org.simantics.scenegraph.g2d.nodes.ConnectionNode;
70 import org.simantics.scenegraph.g2d.nodes.DataNode;
71 import org.simantics.scenegraph.g2d.nodes.NavigationNode;
72 import org.simantics.scenegraph.g2d.nodes.SVGNode;
73 import org.simantics.scenegraph.g2d.nodes.SelectionNode;
74 import org.simantics.scenegraph.g2d.nodes.SingleElementNode;
75 import org.simantics.scenegraph.g2d.nodes.connection.RouteGraphNode;
76 import org.simantics.scenegraph.g2d.nodes.spatial.RTreeNode;
77 import org.simantics.scenegraph.utils.NodeUtil;
78 import org.simantics.scl.runtime.function.Function1;
79 import org.simantics.scl.runtime.tuple.Tuple2;
80 import org.simantics.trend.impl.ItemNode;
81 import org.simantics.utils.threads.ThreadUtils;
82 import org.slf4j.Logger;
83 import org.slf4j.LoggerFactory;
84 import org.w3c.dom.DOMImplementation;
85 import org.w3c.dom.Document;
86 import org.w3c.dom.Element;
87 import org.w3c.dom.NodeList;
88
89 public class SCLScenegraph {
90
91         private static final Logger LOGGER = LoggerFactory.getLogger(SCLScenegraph.class);
92         
93         private static final String MAIN_SECTION = "main";
94         private static final String SELECTION_SECTION = "selection";
95         private static final String SELECTION_MASK_SECTION = "selectionMask";
96         
97         private static final String[] ALL_SECTIONS = { MAIN_SECTION, SELECTION_SECTION, SELECTION_MASK_SECTION };
98
99         @Deprecated
100         public static ICanvasSceneGraphProvider getICanvasSceneGraphProvider(Resource model, Resource diagram, String diagramRVI) throws DatabaseException, InterruptedException {
101                 ICanvasSceneGraphProvider provider = DiagramNodeUtil.loadSceneGraphProvider(model, diagram, diagramRVI);
102                 return provider;
103         }
104         
105         @Deprecated
106         public static void disposeSceneGraphProvider(ICanvasSceneGraphProvider provider) {
107                 provider.dispose();
108         }
109
110     public static <T> T doWithICanvasSceneGraphProvider(Resource diagram, Function1<ICanvasSceneGraphProvider, T> func) throws DatabaseException {
111         return doWithICanvasSceneGraphProvider(diagram, (Function<ICanvasSceneGraphProvider, T>) provider -> func.apply(provider));
112     }
113
114     public static <T> T doWithICanvasSceneGraphProvider(Resource diagram, Function<ICanvasSceneGraphProvider, T> func) throws DatabaseException {
115         Tuple2 result = Simantics.getSession().syncRequest(new ResourceRead<Tuple2>(diagram) {
116
117             @Override
118             public Tuple2 perform(ReadGraph graph) throws DatabaseException {
119                 Resource indexRoot = graph.syncRequest(new IndexRoot(resource));
120                 String diagramRVI = Variables.getRVI(graph, resource);
121                 return new Tuple2(indexRoot, diagramRVI);
122             }
123         });
124         ICanvasSceneGraphProvider provider = DiagramNodeUtil.loadSceneGraphProvider((Resource) result.c0, diagram, (String) result.c1);
125         try {
126             return func.apply(provider);
127         } finally {
128             provider.dispose();
129         }
130     }
131
132     public static <T> T doWithCanvasContext(Resource diagram, Function1<ICanvasContext, T> func) throws DatabaseException {
133         return doWithCanvasContext(diagram, (Function<ICanvasContext, T>) canvasContext -> func.apply(canvasContext));
134     }
135
136     public static <T> T doWithCanvasContext(Resource diagram, Function<ICanvasContext, T> func) throws DatabaseException {
137         return doWithICanvasSceneGraphProvider(diagram, (Function<ICanvasSceneGraphProvider, T>) provider -> func.apply(provider.getCanvasContext()));
138     }
139
140         public static String getNodeTransform(ICanvasContext ctx, String name) {
141                 
142                 Set<TextNode> texts = NodeUtil.collectNodes(ctx.getSceneGraph(), TextNode.class);
143                 for (TextNode text : texts) {
144                         String nodeName = NodeUtil.getNodeName(text);
145                         if (nodeName.equals(name)) {
146                                 String transform = text.getTransform().toString();
147                                 return transform;
148                         }
149                 }
150                 return "No node found";
151         }
152         
153         public static String getNodeText(ICanvasContext ctx, String name) {
154                 
155                 Set<TextNode> texts = NodeUtil.collectNodes(ctx.getSceneGraph(), TextNode.class);
156                 for (TextNode text : texts) {
157                         String nodeName = NodeUtil.getNodeName(text);
158                         if (nodeName.equals(name)) {
159                                 String texti = text.getText();
160                                 return texti;
161                         }
162                 }
163                 return "No node found";
164         }
165         
166         public static String getNodeCount(ICanvasContext ctx) {
167                 G2DSceneGraph g2 = ctx.getSceneGraph();
168                 int amount = NodeUtil.countTreeNodes(g2);
169                 return "Node count: " + amount;
170         }
171         
172     public static String getAllNodes (ICanvasContext ctx) {
173         
174         Set<G2DSceneGraph> g2 = NodeUtil.collectNodes(ctx.getSceneGraph(), G2DSceneGraph.class);
175         int amount = g2.size() +1;
176         return "All nodes: " + amount;
177     }
178     
179     public static String getBoundsNodes (ICanvasContext ctx) {
180         
181         Set<BoundsNode> bn = NodeUtil.collectNodes(ctx.getSceneGraph(), BoundsNode.class);
182         int amount = bn.size();
183         return "BoundsNodes: " + amount;
184     }
185     
186     public static String getBackgroundNodes (ICanvasContext ctx) {
187         
188         Set<BackgroundNode> bg = NodeUtil.collectNodes(ctx.getSceneGraph(), BackgroundNode.class);
189         int amount = bg.size();
190         return "BackgroundNodes: " + amount;
191     }
192     
193     public static String getDataNodes (ICanvasContext ctx) {
194         
195         Set<DataNode> dn = NodeUtil.collectNodes(ctx.getSceneGraph(), DataNode.class);
196         int amount = dn.size();
197         return "DataNodes: " + amount;
198     }
199     
200     public static String getNavigationNodes (ICanvasContext ctx) {
201         
202         Set<NavigationNode> g2 = NodeUtil.collectNodes(ctx.getSceneGraph(), NavigationNode.class);
203         int amount = g2.size();
204         return "NavigationNodes: " + amount;
205     }
206     
207     public static String getParentNodes (ICanvasContext ctx) {
208         
209         Set<G2DParentNode> g2 = NodeUtil.collectNodes(ctx.getSceneGraph(), G2DParentNode.class);
210         int amount = g2.size();
211         return "ParentNodes: " + amount;
212     }
213     
214     public static String getDecorationNodes (ICanvasContext ctx) {
215         
216         Set<DecorationSVGNode> deco = NodeUtil.collectNodes(ctx.getSceneGraph(), DecorationSVGNode.class);
217         int amount = deco.size();
218         return "DecorationNodes: " + amount;
219     }
220     
221     public static String getSingleElementNodes (ICanvasContext ctx) {
222         
223         Set<SingleElementNode> g2 = NodeUtil.collectNodes(ctx.getSceneGraph(), SingleElementNode.class);
224         int amount = g2.size();
225         return "SingleElementNodes: " + amount;
226     }
227     
228     public static String getConnectionNodes (ICanvasContext ctx) {
229         
230         Set<ConnectionNode> g2 = NodeUtil.collectNodes(ctx.getSceneGraph(), ConnectionNode.class);
231         int amount = g2.size();
232         return "ConnectionNodes: " + amount;
233     }
234     
235     public static String getTextNodes (ICanvasContext ctx) {
236         
237         Set<TextNode> tn = NodeUtil.collectNodes(ctx.getSceneGraph(), TextNode.class);
238         Set<TextGridNode> tgn = NodeUtil.collectNodes(ctx.getSceneGraph(), TextGridNode.class);
239         int amount = tn.size() + tgn.size();
240         return "TextNodes: " + amount;
241     }
242     
243     public static String getItemNodes (ICanvasContext ctx) {
244         
245         Set<ItemNode> item = NodeUtil.collectNodes(ctx.getSceneGraph(), ItemNode.class);
246         int amount = item.size();
247         return "ItemNodes: " + amount;
248     }
249   
250     public static String editNodeText (ICanvasContext ctx, String module, String previous_value, String new_value) {
251                 
252         Set<TextNode> textGridNodes = NodeUtil.collectNodes(ctx.getSceneGraph(), TextNode.class);
253         for (TextNode modulenode : textGridNodes) {
254                 if (module.equals(modulenode.getText())) {
255                         //System.out.println("Module what we were looking for: " + module);
256                         //System.out.println("Modulenode: " + modulenode.getText());
257                         
258                         ParentNode<?> parentnode = modulenode.getParent();
259                         //System.out.println("Parentnode: " + parentnode);
260                         
261                         Collection<TextNode> textnodes = (Collection<TextNode>) parentnode.getNodes();
262                         for (TextNode valuenode : textnodes) {
263                                 if (previous_value.equals(valuenode.getText())) {
264                                         //System.out.println("Value what we were looking for: " + previous_value);
265                                         //System.out.println("Valuenode: " + valuenode.getText());
266                                         
267                                         //valuenode.setEditMode(true);
268                                         valuenode.activateEdit(0, null, ctx);
269                                         valuenode.setText(new_value);
270                                         valuenode.fireTextEditingEnded();
271                                         
272                                         //System.out.println("valuenode modified: " + valuenode);
273                                         return "Modified module " + module + " with value " + new_value;
274                                 }
275                         }
276                         return "Not found module : " + module;
277                 }
278         }
279         return "No nodes in scenegraph!";
280     }
281     
282     /**
283      * Returns background color of a canvasContext or null.
284      * @param ctx
285      * @return color in RGBA List<Integer> format, or null.
286      */
287     public static List<Integer> getBackgroundColor(ICanvasContext ctx) {
288         Color color = ctx.getDefaultHintContext().getHint(Hints.KEY_BACKGROUND_COLOR);
289         if (color == null)
290                 return null;
291         ArrayList<Integer> rgba = new ArrayList<>(4);
292         rgba.add(color.getRed());
293         rgba.add(color.getGreen());
294         rgba.add(color.getBlue());
295         rgba.add(color.getAlpha());
296         return rgba;
297     }
298
299     public static String sceneGraphTest (ICanvasContext ctx, String module, String value) {
300         
301         boolean module_founded = false;
302         boolean value_founded = false;
303         
304         Set<G2DSceneGraph> g2 = NodeUtil.collectNodes(ctx.getSceneGraph(), G2DSceneGraph.class);
305         System.out.println("Total amount of nodes: " + g2.size() + 1);
306         
307         Set<TextGridNode> grid = NodeUtil.collectNodes(ctx.getSceneGraph(), TextGridNode.class);
308         Integer textGridNodeAmount = grid.size();
309         System.out.println("Amount of TextGridNodes " + textGridNodeAmount);
310         
311         Set<TextNode> texts = NodeUtil.collectNodes(ctx.getSceneGraph(), TextNode.class);
312         Integer textNodeAmount = grid.size();
313         System.out.println("Amount of TextNodes " + textNodeAmount);
314
315         for (TextNode node : texts) {
316             if (module.equals(node.getText())) {
317                 module_founded = true;
318                 System.out.println("Correct module " + module + " founded.");
319             }
320             if (value.equals(node.getText())) {
321                 value_founded = true;
322                 System.out.println("Correct value " + value + " founded.");
323             }
324         }
325         
326         if (value_founded == true && module_founded == true) {
327                 return "Found both correct module " + module + " and value " + value;
328         }
329         if (value_founded == false && module_founded == true) {
330                 return "Found only correct module " + module + " but not value " + value;
331         }
332         if (value_founded == true && module_founded == false) {
333                 return "Found only correct value " + value + " but not module " + module;
334         }
335         else {
336                 return "Didn't found either module " + module + " or value " + value;
337         }
338     }
339     
340     public static boolean copyPaste (final ICanvasContext source_ctx, final ICanvasContext target_ctx, List<Resource> modules) throws DatabaseException {
341         
342         IDiagram idiagram = source_ctx.getDefaultHintContext().getHint(DiagramHints.KEY_DIAGRAM);
343
344                 DataElementMap dem = idiagram.getDiagramClass().getAtMostOneItemOfClass(DataElementMap.class);
345                 if (dem != null) {
346                         final Collection<IElement> newSelection = new ArrayList<IElement>();
347                         for (Resource module : modules) {
348                                 IElement element = dem.getElement(idiagram, module);
349                                 if (element != null) {
350                                         newSelection.add(element);
351                                 } else {
352                                         throw new DatabaseException("Could not find IElement for " + element);
353                                 }
354                         }
355                         
356                         ThreadUtils.syncExec(source_ctx.getThreadAccess(), new Runnable() {
357                     @Override
358                     public void run() {
359                         if (source_ctx.isDisposed())
360                             return;
361                         Selection selection = source_ctx.getAtMostOneItemOfClass(Selection.class);
362                         if (selection != null) {
363                             // This prevents workbench selection from being left over.
364                             // Also prevents scene graph crap from being left on the screen.
365                             selection.setSelection(0, newSelection);
366                         }
367                                 CanvasUtils.sendCommand(source_ctx, Commands.COPY);
368                                 CanvasUtils.sendCommand(target_ctx, Commands.PASTE);
369                     }
370                 });
371                         
372                 //}
373                 
374                 while(source_ctx.getEventQueue().size() > 0) {
375                         try {
376                                 Thread.sleep(10);
377                         } catch (InterruptedException e) {
378                                 throw new DatabaseException(e);
379                         }
380                 }
381
382                 ThreadUtils.syncExec(source_ctx.getThreadAccess(), new Runnable() {
383             @Override
384             public void run() {
385             }
386         });             
387                                 
388                 }
389                 return true;
390     }
391
392         static class Generator extends SVGGraphics2D {
393
394                 int elemLevel = 0;
395                 String newElementId = null;
396                 ArrayList<Element> elements = new ArrayList<Element>();
397
398                 public static final String svgNS = "http://www.w3.org/2000/svg";
399
400                 public Generator(SVGGeneratorContext ctx, boolean textAsShapes) {
401                         super(ctx, textAsShapes);
402                 }
403
404                 public Generator(Document document) {
405                         super(document);
406                         // prevent batik comment in each g-element
407                         getGeneratorContext().setComment(null);
408                 }
409
410                 @Override
411                 public Element getRoot() {
412                         Element root = super.getRoot();
413                         for(Element e : elements) {
414                                 root.appendChild(e);
415                         }
416                         return root;
417                 }
418
419                 @Override
420                 public void setRenderingHint(Key arg0, Object arg1) {
421                         if(G2DRenderingHints.KEY_BEGIN_ELEMENT == arg0) {
422                                 elemLevel++;
423                         }
424                         if(G2DRenderingHints.KEY_ELEMENT_ID == arg0) {
425                                 if(arg1 != null)
426                                         newElementId = arg1.toString();
427                                 else
428                                         newElementId = UUID.randomUUID().toString();
429                         }
430                         if(G2DRenderingHints.KEY_END_ELEMENT == arg0) {
431                                 elemLevel--;
432                                 if(elemLevel == 0) {
433                                         Element group = getDOMFactory().createElement(SVG_G_TAG);
434                                         //Element group = getDOMFactory().createElementNS(SVG_NAMESPACE_URI, SVG_G_TAG);
435                                         group.setAttributeNS(null, "id", newElementId);
436                                         group.setAttributeNS(null, "class", arg1.toString());
437                                         getRoot(group);
438                                         elements.add(group);
439                                 }
440                         }
441                         super.setRenderingHint(arg0, arg1);
442                 }
443
444         }
445
446         public static Element renderSVGNode(SVGGraphics2D svgGenerator, IG2DNode node) {
447
448                 try {
449
450                         svgGenerator.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
451                         svgGenerator.setRenderingHint(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY);
452                         svgGenerator.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_ON);
453
454                         node.render(svgGenerator);
455
456                 } catch (Throwable t) {
457                         LOGGER.error("Problems rendering scene graph to SVG", t);
458                 }
459
460                 return svgGenerator.getRoot();
461         }
462
463         public static String printSVGDocument(Element doce) {
464
465                 StringBuilder result = new StringBuilder();
466
467                 NodeList nl =  doce.getChildNodes();
468
469                 for(int i=0;i<nl.getLength();i++) {
470
471                         ByteArrayOutputStream os = new ByteArrayOutputStream();
472
473                         try {
474
475                                 TransformerFactory tf = TransformerFactory.newInstance();
476                                 Transformer transformer = tf.newTransformer();
477                                 transformer.setOutputProperty(OutputKeys.OMIT_XML_DECLARATION, "yes");
478                                 transformer.setOutputProperty(OutputKeys.STANDALONE, "no");
479                                 transformer.setOutputProperty(OutputKeys.METHOD, "xml");
480                                 transformer.setOutputProperty(OutputKeys.INDENT, "yes");
481                                 transformer.setOutputProperty(OutputKeys.ENCODING, "UTF-8");
482                                 transformer.setOutputProperty("{http://xml.apache.org/xslt}indent-amount", "4");
483
484                                 transformer.transform(new DOMSource(nl.item(i)), 
485                                                 new StreamResult(new OutputStreamWriter(os, "UTF-8")));
486
487                                 os.flush();
488                                 os.close();
489
490                         } catch (Throwable t) {
491                                 LOGGER.error("Problems formatting SVGDocument to text.", t);
492                         }
493
494                         result.append(new String(os.toByteArray()));
495
496                 }
497
498                 return result.toString();
499
500         }
501
502         public static String renderSVG3(ICanvasContext ctx) {
503                 return renderSVG3(ctx, -1, -1);
504         }
505
506         public static String renderSVG3(ICanvasContext ctx, double width, double height) {
507         return renderSVG0(width, height, ctx, p0 -> p0.stream().collect(Collectors.toMap(p1 -> p1, p2 -> p2)));
508         }
509
510     /**
511      * @param graph
512      * @param component
513      * @throws DatabaseException
514      */
515     private static Object[] createURIBasedL0Identifier(ReadGraph graph, Resource component) throws DatabaseException {
516         String uri = graph.getPossibleURI(component);
517         int hashCode = uri.hashCode();
518         Random random = new Random(hashCode);
519         long l1 = random.nextLong();
520         long l2 = random.nextLong();
521         return new Object[] { l1, l2 };
522     }
523
524     /**
525      * Default no-op mapper
526      */
527     private static final Function1<Set<?>, Map<?, ?>> mapper = p0 -> p0.stream().collect(Collectors.toMap(p1 -> p1, p2 -> p2));
528
529     public static String renderSVG(ICanvasContext ctx, double width, double height) {
530         return renderSVG0(width, height, ctx, mapper);
531     }
532
533     public static String renderSVG(ICanvasContext ctx) {
534         return renderSVG(ctx, -1, -1);
535     }
536
537     public static String renderSVGMapIdentifiers(ICanvasContext ctx) {
538                 return renderSVGMapIdentifiers(ctx, -1, -1);
539     }
540     
541     /**
542      * Renders ICanvasContext into SVG by mapping the SVG id's into URI based
543      * GUID's
544      * 
545      * @param ctx
546      * @return
547      */
548     public static String renderSVGMapIdentifiers(ICanvasContext ctx, double width, double height) {
549         return renderSVG0(width, height, ctx, new Function1<Set<?>, Map<?, ?>>() {
550
551             @Override
552             public Map<?, ?> apply(Set<?> p0) {
553                 try {
554                     return Simantics.getSession().syncRequest(new UnaryRead<Set<?>, Map<?, ?>>(p0) {
555
556                         @Override
557                         public Map<?, ?> perform(ReadGraph graph) throws DatabaseException {
558                             ModelingResources MOD = ModelingResources.getInstance(graph);
559                             DiagramResource DIA = DiagramResource.getInstance(graph);
560                             Layer0 L0 = Layer0.getInstance(graph);
561                             return parameter.stream().collect(Collectors.toMap(p -> p, p -> {
562                                 try {
563                                     if (p instanceof Resource) {
564                                         Resource element = (Resource) p;
565                                         if (graph.isInstanceOf(element, DIA.Element)) {
566                                             Resource component = graph.getPossibleObject(element, MOD.ElementToComponent);
567                                             if (component != null) {
568                                                 GUID identifier = graph.getPossibleRelatedValue(component, L0.identifier, GUID.BINDING);
569                                                 if (identifier != null) {
570                                                     return Arrays.toString(createURIBasedL0Identifier(graph, component));
571                                                 } else {
572                                                     LOGGER.error("Component {} does not have GUID identifier!", component);
573                                                 }
574                                             } else if (graph.isInstanceOf(element, DIA.Connection) || graph.isInstanceOf(element, DIA.Terminal)) {
575                                                 // Ok, lets create a hashcode for connections and terminals as they do not have identifiers 
576                                                 return graph.getURI(element).hashCode();
577                                             } else {
578                                                 // Ok, lets create a hashcode or either return empty string in cases where no ID is required
579                                                 if (graph.hasStatement(element, L0.HasName))
580                                                     return graph.getURI(element).hashCode();
581                                                 return "";
582                                             }
583                                         }
584                                     } else {
585                                         LOGGER.error("Parameter p {} is not resource but it is {}", p, p.getClass());
586                                     }
587                                     return p;
588                                 } catch (DatabaseException e) {
589                                     throw new RuntimeDatabaseException(e);
590                                 }
591                             }));
592                         }
593                     });
594                 } catch (DatabaseException e) {
595                     LOGGER.error("Could not apply mappings", e);
596                     throw new RuntimeDatabaseException(e);
597                 }
598             }
599         });
600         }
601         
602     static class RenderSVGContext {
603         
604         Map<String,StringBuilder> documents = new HashMap<>();
605         
606         public void append(String[] keys, String svgText) {
607                 for(String key : keys) append(key, svgText);
608         }
609
610         public void append(String key, String svgText) {
611                 StringBuilder builder = documents.get(key);
612                 if(builder == null) {
613                         builder = new StringBuilder();
614                         documents.put(key, builder);
615                 }
616                 builder.append(svgText);
617         }
618         
619         public void append(RenderSVGContext other) {
620                 for(String key : other.documents.keySet()) {
621                         append(key, other.get(key));
622                 }
623         }
624         
625         public String get(String key) {
626                 StringBuilder builder = documents.get(key);
627                 if(builder == null) return "";
628                 else return builder.toString();
629         }
630         
631     }
632     
633         private static String renderSVG0(double width, double height, ICanvasContext ctx, Function1<Set<?>, Map<?, ?>> mappingFunction) {
634
635                 // Get a DOMImplementation.
636                 DOMImplementation domImpl = GenericDOMImplementation.getDOMImplementation();
637
638                 // Create an instance of org.w3c.dom.Document.
639                 String svgNS = "http://www.w3.org/2000/svg";
640                 Document document = domImpl.createDocument(svgNS, "svg", null);
641
642                 // Create an instance of the SVG Generator.
643                 SVGGraphics2D svgGenerator = new Generator(document);
644
645                 RenderSVGContext result = new RenderSVGContext();
646
647                 double[] matrix = new double[6];
648                 try {
649
650             Selection selection = ctx.getAtMostOneItemOfClass(Selection.class);
651             if (selection != null) {
652                 // This prevents workbench selection from being left over.
653                 // Also prevents scene graph crap from being left on the screen.
654                 IDiagram d = ctx.getDefaultHintContext().getHint(DiagramHints.KEY_DIAGRAM);
655                 selection.setSelection(0, d.getElements());
656             }
657
658                         G2DSceneGraph sg = ctx.getSceneGraph();
659                         sg.performCleanup();
660                         G2DParentNode root = (G2DParentNode) sg.getRootNode();
661
662                         // rtree is the actual content of the diagram
663                         RTreeNode rtree = NodeUtil.getNearestChildByClass(root, RTreeNode.class);
664                         Rectangle2D rtreeBounds = NodeUtil.getLocalBounds(rtree);
665
666                         // get the bounds of the content
667                         Rectangle2D content = rtreeBounds;
668                         
669                         // To account for dynamic padding of selection rectangles (5 units + stroke width)
670                         int offset = 6;
671                         
672                         double scale = width < 0 || height < 0 ? 1.0 : Math.min((width - 2*offset) / content.getWidth(), (height - 2*offset) / content.getHeight());
673                 
674                         AffineTransform tr = new AffineTransform();
675                         tr.translate(offset, offset);
676                         tr.scale(scale, scale);
677                         tr.translate(-content.getX(), -content.getY());
678                         tr.getMatrix(matrix);
679                         //svgGenerator.translate(offset, offset);
680                         //svgGenerator.scale(scale, scale);
681                         // translate svgGenerator to the x and y coordinates of current content
682                         //svgGenerator.translate(-content.getX(), -content.getY());
683
684                         svgGenerator.setSVGCanvasSize(new Dimension((int)Math.ceil(scale * content.getWidth()) + 2*offset, (int)Math.ceil(scale * content.getHeight()) + 2*offset));
685                         //svgGenerator.setClip(content);
686                         
687                         result.append(MAIN_SECTION, "<g class=\"symbols\">");
688                         result.append(SELECTION_SECTION, "<g class=\"selections\">");
689                         result.append(SELECTION_MASK_SECTION, "<g class=\"selectionMasks\">");
690
691                         KeyVisitor keyVisitor = new KeyVisitor();
692                         sg.accept(keyVisitor);
693                         
694                         Set<Object> keys = keyVisitor.getKeys();
695                         
696                         Map<?, ?> mappings = mappingFunction.apply(keys);
697
698                         IG2DNodeVisitor visitor = new PrintingVisitor(svgGenerator, result, mappings);
699                         
700                         sg.accept(visitor);
701
702                 } catch (Throwable t) {
703                         LOGGER.error("Problems rendering canvas context to SVG", t);
704                 }
705
706
707                 result.append(ALL_SECTIONS, "</g>");
708
709                 StringBuilder res = new StringBuilder();
710                 res.append("<svg width=\"100%\" height=\"100%\" stroke=\"black\">");
711                 res.append("<g transform=\"matrix(").append(matrix[0]).append(",").append(matrix[1]).append(",").append(matrix[2]).append(",").append(matrix[3]).append(",").append(matrix[4]).append(",").append(matrix[5]).append(")\">");
712                 res.append(result.get(MAIN_SECTION));
713                 res.append(result.get(SELECTION_SECTION));
714                 res.append(result.get(SELECTION_MASK_SECTION));
715                 res.append("</g>");
716                 res.append("</svg>");
717
718 //              System.err.println(" == FINAL RESULT == ");
719 //              System.err.println(res.toString());
720                 
721                 return res.toString();
722                 
723         }
724         
725         
726         
727         private static class KeyVisitor implements IG2DNodeVisitor {
728
729         private Set<Object> keys = new HashSet<>();
730
731         @Override
732         public void enter(IG2DNode node) {
733             if (node instanceof SingleElementNode) {
734                 Object key = ((SingleElementNode) node).getKey();
735                 if (key != null) {
736                     keys.add(key);
737                 }
738             }
739         }
740
741         @Override
742         public void leave(IG2DNode node) {
743             // Nothing to do
744         }
745
746         public Set<Object> getKeys() {
747             return keys;
748         }
749         }
750
751         private static class PrintingVisitor implements IG2DNodeVisitor {
752
753         int indent = 0;
754
755         HashMap<SingleElementNode,RenderSVGContext> senBuilders = new HashMap<>();
756
757         private RenderSVGContext result;
758         private SVGGraphics2D svgGenerator;
759
760         private Map<?, ?> mappings;
761
762         public PrintingVisitor(SVGGraphics2D svgGenerator, RenderSVGContext result, Map<?, ?> mappings) {
763             this.result = result;
764             this.mappings = mappings;
765             this.svgGenerator = svgGenerator;
766         }
767
768         private String getKey(SingleElementNode node) {
769             String key;
770                 if(node.getKey() != null) {
771                 if (mappings.containsKey(node.getKey()))
772                     key = mappings.get(node.getKey()).toString();
773                 else
774                     key = node.getKey().toString();
775                 } else {
776                         key = Long.toString(node.getId());
777                 }
778             return key;
779         }
780
781         @Override
782         public void enter(IG2DNode node) {
783             
784                 RenderSVGContext parentBuilder = getParentBuilder(node);
785             
786             indent++;
787             if(node instanceof ConnectionNode) {
788                 
789                 for(RouteGraphNode n : NodeUtil.collectNodes(node, RouteGraphNode.class)) {
790                     n.setIgnoreSelection(true);
791                 }
792
793                 String key = getKey((ConnectionNode) node);
794                 parentBuilder.append(MAIN_SECTION, "\n<g class=\"connection\" id=\"" + key + "\">");
795                 parentBuilder.append(SELECTION_SECTION, "\n<g style=\"visibility:hidden\" class=\"selection\" id=\"" + key + "\">");
796                 parentBuilder.append(SELECTION_MASK_SECTION, "\n<g class=\"selectionMask\" opacity=\"0.001\" id=\"" + key + "\">");
797                 
798                 Element doc = renderSVGNode(svgGenerator, (IG2DNode)node);
799                 String svg = printSVGDocument(doc);
800                 parentBuilder.append(MAIN_SECTION, svg);
801
802                 for(RouteGraphNode n : NodeUtil.collectNodes(node, RouteGraphNode.class)) {
803                     n.setIgnoreSelection(false);
804                 }
805
806                 doc = renderSVGNode(svgGenerator, (IG2DNode)node);
807                 svg = printSVGDocument(doc);
808                 parentBuilder.append(SELECTION_SECTION, svg);
809
810                 BasicStroke bs = new BasicStroke(10f);
811                 
812                 for(RouteGraphNode n : NodeUtil.collectNodes(node, RouteGraphNode.class)) {
813                     n.setDynamicStroke(bs);
814                 }
815
816                 doc = renderSVGNode(svgGenerator, (IG2DNode)node);
817                 svg = printSVGDocument(doc);
818                 parentBuilder.append(SELECTION_MASK_SECTION, svg);
819
820                 parentBuilder.append(SELECTION_MASK_SECTION, "\n</g>");
821                 parentBuilder.append(SELECTION_SECTION, "\n</g>");
822                 parentBuilder.append(MAIN_SECTION, "\n</g>");
823                 
824             } else if (node instanceof SelectionNode) {
825                 
826                 SelectionNode n = (SelectionNode)node;
827                 SingleElementNode parentSEN = (SingleElementNode)NodeUtil.getNearestParentOfType(node, SingleElementNode.class);
828                 if(parentSEN != null) {
829                     
830                         RenderSVGContext parentBuilder2 = getParentBuilder(parentSEN);
831                     
832                     String key = getKey(parentSEN);
833                     n.setIgnore(false);
834                     Element doc = renderSVGNode(svgGenerator, (IG2DNode)node);
835                     n.setIgnore(true);
836                     String svg = printSVGDocument(doc);
837                     parentBuilder2.append(SELECTION_SECTION, "\n<g style=\"visibility:hidden\" class=\"selection\" id=\"" + key + "\">");
838                     parentBuilder2.append(SELECTION_SECTION, svg);
839                     parentBuilder2.append(SELECTION_SECTION, "\n</g>");
840                     parentBuilder2.append(SELECTION_MASK_SECTION, "\n<g class=\"selectionMask\" id=\"" + key /*+ "\" transform=\"" + matrixString + "\"*/+ "\">");
841                     Rectangle2D rect = n.getRect();
842                     // NaN
843                     if(rect.getHeight() == rect.getHeight() && rect.getWidth() == rect.getWidth()) {
844                             parentBuilder2.append(SELECTION_MASK_SECTION,"<rect style=\"fill:#fff\" opacity=\"0.001\"");
845                             parentBuilder2.append(SELECTION_MASK_SECTION," x=\"" + rect.getX() + "\" y=\"" + rect.getY() + "\"");
846                             parentBuilder2.append(SELECTION_MASK_SECTION," width=\"" + rect.getWidth() + "\" height=\"" + rect.getHeight() + "\"");
847                             parentBuilder2.append(SELECTION_MASK_SECTION,"></rect>");
848                     }
849                     parentBuilder2.append(SELECTION_MASK_SECTION,"\n</g>");
850                    
851                 }
852             } else if (node instanceof SVGNode) {
853                 SVGNode svg = (SVGNode)node;
854                 parentBuilder.append(MAIN_SECTION, svg.getSVGText());
855             } else if (node instanceof G2DParentNode) {
856                 AffineTransform at = node.getTransform();
857                 if(node instanceof SingleElementNode) {
858                     SingleElementNode sen = (SingleElementNode)node;
859                     String key = getKey(sen);
860                     String typeClass = sen.getTypeClass();
861                     String clazz = "definedElement";
862                     if(typeClass != null && !typeClass.isEmpty())
863                         clazz = clazz + " " + typeClass;
864
865                     parentBuilder.append(MAIN_SECTION, "\n<g class=\""+clazz+"\" id=\"" + key + "\">");
866                     senBuilders.put(sen, new RenderSVGContext());
867                 }
868                 if(!at.isIdentity()) {
869                     if(at.getScaleX() == 1.0 && at.getScaleY() == 1.0 && at.getShearX() == 0.0 && at.getShearY() == 0.0) {
870                         String m = "translate(" + at.getTranslateX() + " " + at.getTranslateY() + ")";
871                         parentBuilder.append(ALL_SECTIONS, "\n<g transform=\"" + m + "\">");
872                     } else {
873                         double[] ds = new double[6];
874                         at.getMatrix(ds);
875                         String m = "matrix(" + ds[0] + " " + ds[1] + " " + ds[2] + " " + ds[3] + " " + ds[4] + " " + ds[5] + ")";
876                         parentBuilder.append(ALL_SECTIONS, "\n<g transform=\"" + m + "\">");
877                     }
878                 }
879             }
880
881             //enters.put(node, b.length());
882
883         }
884         
885         private RenderSVGContext getParentBuilder(IG2DNode node) {
886             
887             INode parentSEN = NodeUtil.getNearestParentOfType(node, SingleElementNode.class);
888             if(parentSEN instanceof G2DSceneGraph) return result;
889
890             RenderSVGContext parentBuilder = senBuilders.get(parentSEN);
891             if(parentBuilder == null) return result;
892             
893             return parentBuilder;
894             
895         }
896
897         @Override
898         public void leave(IG2DNode node) {
899
900             if(node instanceof ConnectionNode || node instanceof SVGNode) {
901                 // We are done
902             } else if (node instanceof G2DParentNode) {
903                 
904                 RenderSVGContext parentBuilder = getParentBuilder(node);
905                 
906                 if(node instanceof SingleElementNode) {
907                         SingleElementNode sen = (SingleElementNode)node;
908                         RenderSVGContext b = senBuilders.get(sen);
909                         String content = b.get(MAIN_SECTION);
910                         if(content.isEmpty()) {
911                                 if(sen.getKey() != null) {
912
913                                         for(SelectionNode n : NodeUtil.collectNodes(node, SelectionNode.class)) {
914                                                 n.setIgnore(true);
915                                         }
916
917                                         Element doc = renderSVGNode(svgGenerator, (IG2DNode)node);
918                                         String svg = printSVGDocument(doc);
919                                         parentBuilder.append(MAIN_SECTION, svg);
920                                 }
921                         } else {
922                                 parentBuilder.append(b);
923                         }
924                 }
925                 
926                 AffineTransform at = node.getTransform();
927                 if(!at.isIdentity()) {
928                     parentBuilder.append(ALL_SECTIONS, "</g>");
929                 }
930                 if(node instanceof SingleElementNode) {
931                     SingleElementNode sen = (SingleElementNode)node;
932                     //if(sen.getKey() != null) {
933                         parentBuilder.append(MAIN_SECTION, "</g>");
934                     //}
935                 }
936             }
937             indent --;
938         }
939         }
940 }