1   /****************************************************************************
2    * This demo file is part of yFiles for Java 2.14.
3    * Copyright (c) 2000-2017 by yWorks GmbH, Vor dem Kreuzberg 28,
4    * 72070 Tuebingen, Germany. All rights reserved.
5    * 
6    * yFiles demo files exhibit yFiles for Java functionalities. Any redistribution
7    * of demo files in source code or binary form, with or without
8    * modification, is not permitted.
9    * 
10   * Owners of a valid software license for a yFiles for Java version that this
11   * demo is shipped with are allowed to use the demo source code as basis
12   * for their own yFiles for Java powered applications. Use of such programs is
13   * governed by the rights and conditions as set out in the yFiles for Java
14   * license agreement.
15   * 
16   * THIS SOFTWARE IS PROVIDED ''AS IS'' AND ANY EXPRESS OR IMPLIED
17   * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
18   * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN
19   * NO EVENT SHALL yWorks BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
20   * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
21   * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
22   * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
23   * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
24   * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
25   * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
26   *
27   ***************************************************************************/
28  package demo.io.graphml;
29  
30  import demo.view.DemoBase;
31  import demo.view.DemoDefaults;
32  import demo.view.flowchart.painters.FlowchartRealizerFactory;
33  import y.base.Edge;
34  import y.base.EdgeCursor;
35  import y.base.Node;
36  import y.base.NodeCursor;
37  import y.io.GraphMLIOHandler;
38  import y.io.graphml.output.GraphElementIdProvider;
39  import y.io.graphml.output.GraphMLWriteException;
40  import y.io.graphml.output.WriteEvent;
41  import y.io.graphml.output.WriteEventListenerAdapter;
42  import y.util.D;
43  import y.view.BevelNodePainter;
44  import y.view.EditMode;
45  import y.view.GenericNodeRealizer;
46  import y.view.Graph2D;
47  import y.view.Graph2DEvent;
48  import y.view.Graph2DListener;
49  import y.view.Graph2DUndoManager;
50  import y.view.Selections;
51  import y.view.Selections.SelectionStateObserver;
52  import y.view.ViewMode;
53  import y.view.hierarchy.HierarchyManager;
54  
55  import javax.swing.BorderFactory;
56  import javax.swing.JButton;
57  import javax.swing.JLabel;
58  import javax.swing.JPanel;
59  import javax.swing.JScrollPane;
60  import javax.swing.JSplitPane;
61  import javax.swing.JTextArea;
62  import javax.swing.Timer;
63  import javax.swing.text.BadLocationException;
64  import javax.swing.text.DefaultHighlighter;
65  import javax.swing.text.DefaultHighlighter.DefaultHighlightPainter;
66  import java.awt.BorderLayout;
67  import java.awt.Dimension;
68  import java.awt.EventQueue;
69  import java.awt.Font;
70  import java.awt.event.ActionEvent;
71  import java.awt.event.ActionListener;
72  import java.beans.PropertyChangeEvent;
73  import java.beans.PropertyChangeListener;
74  import java.io.ByteArrayInputStream;
75  import java.io.IOException;
76  import java.io.StringWriter;
77  import java.util.HashMap;
78  import java.util.Iterator;
79  import java.util.Locale;
80  import java.util.Map;
81  import java.util.regex.Matcher;
82  import java.util.regex.Pattern;
83  
84  
85  /**
86   * This shows the basic usage of GraphMLIOHandler to load and save in the GraphML file format.
87   *
88   * In addition, it shows the graphml representation of the current graph in the lower text pane.
89   * This representation is updated dynamically. Also, edits in the graphml text can be applied
90   * to the current graph by pressing the "Apply GraphML" button.
91   *
92   * A small list of predefined GraphML files can be accessed from the combobox in the toolbar.
93   *
94   * @see <a href="http://docs.yworks.com/yfiles/doc/api/index.html#/dguide/graphml#graphml_extension" target="_blank">Section Reading and Writing Additional Data</a> in the yFiles for Java Developer's Guide
95   */
96  public class GraphMLDemo extends DemoBase {
97    private static final String BEVEL_NODE_CONFIGURATION = "BevelNodeConfig";
98  
99    protected GraphMLPane graphMLPane;
100   private Graph2DUndoManager undoManager;
101 
102   /**
103    * Creates a new instance of GraphMLDemo
104    */
105   public GraphMLDemo() {
106 
107     graphMLPane = new GraphMLPane();
108     graphMLPane.setPreferredSize(new Dimension(600, 350));
109     graphMLPane.setMinimumSize(new Dimension(0, 100));
110 
111     //plug the gui elements together and add them to the pane
112 
113     JSplitPane splitPane = new JSplitPane(JSplitPane.VERTICAL_SPLIT, view, graphMLPane);
114     splitPane.setBorder(BorderFactory.createEmptyBorder());
115     view.setPreferredSize(new Dimension(600, 350));
116     view.setMinimumSize(new Dimension(0, 200));
117     loadInitialGraph();
118     view.fitContent();
119     contentPane.add(splitPane, BorderLayout.CENTER);
120   }
121 
122   protected void initialize() {
123     final Graph2D graph = view.getGraph2D();
124 
125     //Create a hierarchy manager that allows us to read/write hierarchically structured graphs.
126     new HierarchyManager(graph);
127   }
128 
129   protected void configureDefaultRealizers() {
130     super.configureDefaultRealizers();
131 
132     final GenericNodeRealizer.Factory f = GenericNodeRealizer.getFactory();
133     if (!f.getAvailableConfigurations().contains(BEVEL_NODE_CONFIGURATION)) {
134       BevelNodePainter bevelNodePainter = new BevelNodePainter();
135       bevelNodePainter.setDrawShadow(true);
136       Map impls = f.createDefaultConfigurationMap();
137       impls.put(GenericNodeRealizer.Painter.class, bevelNodePainter);
138       impls.put(GenericNodeRealizer.ContainsTest.class, bevelNodePainter);
139       f.addConfiguration(BEVEL_NODE_CONFIGURATION, impls);
140     }
141 
142     // registers all flowchart configurations
143     FlowchartRealizerFactory.createData();
144   }
145 
146   protected void loadInitialGraph() {
147     loadGraph("resources/ygraph/visual_features.graphml");
148   }
149 
150   /**
151    * Create a customized undo manager which triggers updates of the GraphML section.
152    */
153   protected Graph2DUndoManager getUndoManager() {
154     if (undoManager == null) {
155       final Graph2D graph = view.getGraph2D();
156       undoManager = new Graph2DUndoManager(graph) {
157         /**
158          * Stores all edge realizer so their changes can be undone/redone and and informs the GraphML section about a
159          * potential change.
160          * @param graph The graph where the edges belong to.
161          * @param ec    The edges which realizers are stored.
162          */
163         public void backupRealizers(Graph2D graph, EdgeCursor ec) {
164           super.backupRealizers(graph, ec);
165           graphMLPane.firePotentialChange();
166         }
167 
168         /**
169          * Stores all node realizer so their changes can be undone/redone and and informs the GraphML section about a
170          * potential change.
171          * @param graph The graph where the nodes belong to.
172          * @param nc    The nodes which realizers are stored.
173          */
174         public void backupRealizers(Graph2D graph, NodeCursor nc) {
175           super.backupRealizers(graph, nc);
176           graphMLPane.firePotentialChange();
177         }
178       };
179       undoManager.setViewContainer(view);
180 
181       graph.setBackupRealizersHandler(undoManager);
182     }
183 
184     return undoManager;
185   }
186 
187   /**
188    * Returns the list of sample files of this demo.
189    */
190   protected String[] getExampleResources() {
191     return new String[]{
192         "resources/ygraph/visual_features.graphml",
193         "resources/ygraph/problemsolving.graphml",
194         "resources/ygraph/simple.graphml",
195         "resources/ygraph/grouping.graphml",
196     };
197   }
198 
199   protected void loadGraph(Class aClass, String resourceString) {
200     try {
201       graphMLPane.setUpdating(true);
202       super.loadGraph(aClass, resourceString);
203       graphMLPane.setUpdating(false);
204       graphMLPane.showGraphMLText(view.getGraph2D());
205     }
206     finally {
207       graphMLPane.setUpdating(false);
208     }
209   }
210 
211   /**
212    * Launches this demo.
213    */
214   public static void main(String[] args) {
215     EventQueue.invokeLater(new Runnable() {
216       public void run() {
217         Locale.setDefault(Locale.ENGLISH);
218         initLnF();
219         new GraphMLDemo().start();
220       }
221     });
222   }
223 
224   class GraphMLPane extends JPanel {
225 
226     /**
227      * Re-entrance lock
228      */
229     protected boolean updating;
230     private boolean editable;
231 
232     private final JButton applyButton;
233     private Map elementIdMap;
234     private final JTextArea graphMLTextPane;
235     private int lastStartIndex;
236     Timer timer;
237 
238     GraphMLPane() {
239       editable = true;
240 
241       JPanel graphMLHeader = new JPanel();
242       JLabel graphMLLabel = new JLabel("GraphML representation");
243       graphMLLabel.setFont(graphMLLabel.getFont().deriveFont(Font.BOLD));
244       graphMLLabel.setFont(graphMLLabel.getFont().deriveFont(12.0f));
245       graphMLLabel.setBorder(BorderFactory.createEmptyBorder(5, 5, 5, 5));
246       setLayout(new BorderLayout());
247 
248       graphMLTextPane = new JTextArea();
249 
250       JScrollPane scrollPane = new JScrollPane(graphMLTextPane);
251       scrollPane.setPreferredSize(new Dimension(0, 350));
252 
253       graphMLHeader.setLayout(new BorderLayout());
254       graphMLHeader.add(graphMLLabel, BorderLayout.WEST);
255       applyButton = new JButton("Apply GraphML");
256 
257       applyButton.addActionListener(new ActionListener() {
258         public void actionPerformed(ActionEvent e) {
259           applyGraphMLText(view.getGraph2D());
260         }
261       });
262 
263       graphMLHeader.add(applyButton, BorderLayout.EAST);
264 
265       add(graphMLHeader, BorderLayout.NORTH);
266       add(scrollPane, BorderLayout.CENTER);
267       setEditable(editable);
268       registerView();
269     }
270 
271 
272     public boolean isEditable() {
273       return editable;
274     }
275 
276     public void setEditable(boolean editable) {
277       this.editable = editable;
278       graphMLTextPane.setEditable(editable);
279       applyButton.setVisible(editable);
280     }
281 
282     void registerView() {
283 
284       for(Iterator iter = view.getViewModes(); iter.hasNext();) {
285         ViewMode mode = (ViewMode) iter.next();
286         if(mode instanceof EditMode) {
287           EditMode editMode = (EditMode) mode;
288           PropertyChangeListener pcl = new PropertyChangeListener() {
289             public void propertyChange(PropertyChangeEvent evt) {
290               if (evt.getPropertyName().equals(ViewMode.EDITING_PROPERTY) && evt.getNewValue().equals(Boolean.FALSE)) {
291                 firePotentialChange();                
292               }
293             }
294           };
295           editMode.addPropertyChangeListener(pcl);
296           editMode.getHotSpotMode().addPropertyChangeListener(pcl);
297           editMode.getMovePortMode().addPropertyChangeListener(pcl);
298           editMode.getMoveLabelMode().addPropertyChangeListener(pcl);
299           editMode.getMoveSelectionMode().addPropertyChangeListener(pcl);
300           editMode.getMoveSelectionMode().addPropertyChangeListener(pcl);
301           break;
302         }
303       }
304 
305       SelectionStateObserver sto = new SelectionStateObserver() {
306         protected void updateSelectionState(Graph2D graph) {
307           firePotentialChange();
308         }
309       };
310       view.getGraph2D().addGraphListener(sto);
311       view.getGraph2D().addGraph2DSelectionListener(sto);
312 
313       view.getGraph2D().addGraph2DListener(new Graph2DListener() {
314         public void onGraph2DEvent(Graph2DEvent e) {
315           firePotentialChange();
316         }
317       });      
318     }
319 
320     
321     public void firePotentialChange() {
322       if(!isUpdating()) {
323         if(timer == null) {
324           timer = new Timer(100, new ActionListener() {
325             
326             public void actionPerformed(ActionEvent e) {
327               updateGraphMLText(view.getGraph2D());
328             }
329           });
330           timer.setRepeats(false);
331         }
332         timer.restart();
333       }
334     }
335     
336     private void setUpdating(boolean updating) {
337       this.updating = updating;
338     }
339 
340     private boolean isUpdating() {
341       return updating;
342     }
343 
344     public void updateGraphMLText(Graph2D graph) {
345       // show graphml text
346       showGraphMLText(view.getGraph2D());
347 
348       // scroll to selected node/edge in graphml text
349       if (!Selections.isNodeSelectionEmpty(graph)) {
350         scrollGraphMLTextTo(view.getGraph2D().selectedNodes().node());
351       } else if (!Selections.isEdgeSelectionEmpty(graph)) {
352         scrollGraphMLTextTo(view.getGraph2D().selectedEdges().edge());
353       }
354     }
355 
356     private void scrollGraphMLTextTo(Node node) {
357       if (elementIdMap != null && elementIdMap.get(node) instanceof String) {
358         scrollGraphMLTextTo("node", elementIdMap.get(node).toString());
359       }
360     }
361 
362     private void scrollGraphMLTextTo(Edge edge) {
363       if (elementIdMap != null && elementIdMap.get(edge) instanceof String) {
364         scrollGraphMLTextTo("edge", elementIdMap.get(edge).toString());
365       }
366     }
367 
368     private void scrollGraphMLTextTo(String tag, String elementId) {
369       String text = graphMLTextPane.getText();
370       Pattern pattern = Pattern.compile("<" + tag + " .*id=\"" + elementId + "\"");
371       int startIndex = 0;
372       Matcher matcher = pattern.matcher(text);
373       if (matcher.find()) {
374         startIndex = matcher.start();
375       }
376       int endIndex = text.indexOf("</" + tag + ">", startIndex) + (tag.length() + 3);
377 
378       DefaultHighlighter highlighter = new DefaultHighlighter();
379       DefaultHighlightPainter painter = new DefaultHighlightPainter(DemoDefaults.DEFAULT_CONTRAST_COLOR);
380       graphMLTextPane.setHighlighter(highlighter);
381       try {
382         highlighter.addHighlight(startIndex, endIndex, painter);
383         lastStartIndex = startIndex;
384       } catch (BadLocationException e1) {
385         lastStartIndex = 0;
386         e1.printStackTrace();
387       }
388 
389       graphMLTextPane.requestFocus();
390       graphMLTextPane.setCaretPosition(startIndex);
391       graphMLTextPane.moveCaretPosition(endIndex);
392       view.getCanvasComponent().requestFocus();
393 
394       try {
395         graphMLTextPane.scrollRectToVisible(graphMLTextPane.modelToView(startIndex));
396       } catch (BadLocationException e) {
397         e.printStackTrace();
398       }
399 
400     }
401 
402     private String createGraphMLTextAndUpdateGraphElementIdMap(final Graph2D graph) {
403       StringWriter buffer = new StringWriter();
404       if (graph != null) {
405         GraphMLIOHandler ioh = createGraphMLIOHandler();
406         ioh.getGraphMLHandler().addWriteEventListener(
407             new WriteEventListenerAdapter() {
408               public void onDocumentWritten(WriteEvent event)
409                   throws GraphMLWriteException {
410                 GraphElementIdProvider idProvider = (GraphElementIdProvider) event
411                     .getContext().lookup(GraphElementIdProvider.class);
412                 elementIdMap = new HashMap();
413                 for (NodeCursor nc = graph.nodes(); nc.ok(); nc.next()) {
414                   Node n = nc.node();
415                   elementIdMap.put(n, idProvider.getNodeId(n, event
416                       .getContext()));
417                 }
418                 for (EdgeCursor ec = graph.edges(); ec.ok(); ec.next()) {
419                   Edge e = ec.edge();
420                   elementIdMap.put(e, idProvider.getEdgeId(e, event
421                       .getContext()));
422                 }
423               }
424             });
425         try {
426           ioh.write(graph, buffer);
427         }catch(IOException ex) {
428          ex.printStackTrace();
429          return "";
430         }
431       }
432       buffer.flush();
433       return buffer.toString();
434     }
435     
436     /**
437      * Helper method that serializes the current graph content into a string which is shown in the graphml text pane.
438      */
439     protected void showGraphMLText(final Graph2D graph) {
440       if (!isUpdating()) {
441         setUpdating(true);
442         try {
443           graphMLTextPane.setText(createGraphMLTextAndUpdateGraphElementIdMap(graph));
444           graphMLTextPane.setCaretPosition(Math.min(lastStartIndex, graphMLTextPane.getText().length()));
445         }finally {
446           setUpdating(false);
447         }       
448       }
449     }
450 
451     /**
452      * Helper method that applies the text content of the graphml text pane to the current graph.
453      */
454     protected void applyGraphMLText(final Graph2D graph) {
455       if (graph != null) {
456 
457         Graph2D testGraph = (Graph2D) graph.createGraph();
458         new HierarchyManager(testGraph);
459         try {
460           byte[] input = graphMLTextPane.getText().getBytes("UTF-8");
461           
462           {
463             ByteArrayInputStream byteStream = new ByteArrayInputStream(input);
464             createGraphMLIOHandler().read(testGraph, byteStream);
465           }
466 
467           //seems to work well. try it with original graph then.          
468           {
469             setUpdating(true);
470             ByteArrayInputStream byteStream = new ByteArrayInputStream(input);
471             GraphMLIOHandler ioh = createGraphMLIOHandler();                                              
472             ioh.read(graph, byteStream);
473             
474             createGraphMLTextAndUpdateGraphElementIdMap(graph); 
475             
476             setUpdating(false);            
477            
478           }
479         } catch (Exception e) {
480           D.show(e);
481         }
482         finally {
483           graph.updateViews(); 
484         }
485       }
486     }
487 
488   }
489 }
490