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 org.w3c.dom.Element;
31  import org.w3c.dom.NodeList;
32  
33  import y.base.DataMap;
34  import y.base.Edge;
35  import y.base.EdgeMap;
36  import y.base.Node;
37  import y.base.NodeMap;
38  import y.io.GraphMLIOHandler;
39  import y.io.graphml.GraphMLHandler;
40  import y.io.graphml.KeyScope;
41  import y.io.graphml.KeyType;
42  import y.io.graphml.input.AbstractDataAcceptorInputHandler;
43  import y.io.graphml.input.DeserializationEvent;
44  import y.io.graphml.input.DeserializationHandler;
45  import y.io.graphml.input.GraphMLParseContext;
46  import y.io.graphml.input.GraphMLParseException;
47  import y.io.graphml.input.InputHandlerProvider;
48  import y.io.graphml.input.NameBasedDeserializer;
49  import y.io.graphml.input.QueryInputHandlersEvent;
50  import y.io.graphml.output.AbstractOutputHandler;
51  import y.io.graphml.output.GraphMLWriteContext;
52  import y.io.graphml.output.GraphMLWriteException;
53  import y.io.graphml.output.SerializationEvent;
54  import y.io.graphml.output.SerializationHandler;
55  import y.io.graphml.output.TypeBasedSerializer;
56  import y.io.graphml.output.XmlWriter;
57  import y.option.OptionHandler;
58  import y.view.EditMode;
59  import y.view.PopupMode;
60  import y.view.TooltipMode;
61  
62  import javax.swing.AbstractAction;
63  import javax.swing.JPopupMenu;
64  import java.awt.EventQueue;
65  import java.awt.event.ActionEvent;
66  import java.text.ParseException;
67  import java.text.SimpleDateFormat;
68  import java.util.ArrayList;
69  import java.util.Collection;
70  import java.util.Date;
71  import java.util.Iterator;
72  import java.util.Locale;
73  import java.util.StringTokenizer;
74  
75  /**
76   * This demo shows how to configure GraphMLIOHandler to be able to handle
77   * extra node and edge data of complex type.
78   * Additional data for a node can be edited by right-clicking on the
79   * corresponding element.
80   * The element tool tip will show the currently set data values for each element.
81   *
82   * @see <a href="http://docs.yworks.com/yfiles/doc/api/index.html#/dguide/graphml#graphml_extension_dynamic" target="_blank">Section Dynamic Registration of Attributes</a> in the yFiles for Java Developer's Guide
83   */
84  public class ComplexExtensionGraphMLDemo extends GraphMLDemo {
85  
86    /**
87     * Store node/edge data
88     */
89    private NodeMap nodeDataMap;
90    private EdgeMap edgeDataMap;
91  
92    protected void loadInitialGraph() {
93      loadGraph("resources/custom/complexdemo.graphml");    
94    }
95  
96    /**
97     * Configures GraphMLIOHandler to read and write additional node and edge data
98     * of complex type.
99     */
100   protected GraphMLIOHandler createGraphMLIOHandler() {
101     if (nodeDataMap == null) {
102       nodeDataMap = view.getGraph2D().createNodeMap();
103     }
104     if (edgeDataMap == null) {
105       edgeDataMap = view.getGraph2D().createEdgeMap();
106     }
107 
108 
109     GraphMLIOHandler ioHandler = super.createGraphMLIOHandler();
110 
111 
112     //For our top-level node data, which consists of {@link Item} collections, we add (de)serializers only for
113     //the parsing of the node attributes
114     ioHandler.getGraphMLHandler().addInputDataAcceptor("myNodeAttribute", nodeDataMap, KeyScope.NODE,
115         new ItemListDeserializer());
116 
117     ioHandler.getGraphMLHandler().addOutputDataProvider("myNodeAttribute", nodeDataMap, KeyScope.NODE,
118         new ItemListSerializer());
119 
120     //We add serializers/deserializers for Item objects globally (so they can be used from inside other
121     //(de)serializers
122     ioHandler.getGraphMLHandler().addDeserializationHandler(new ItemDeserializer());
123     ioHandler.getGraphMLHandler().addSerializationHandler(new ItemSerializer());
124 
125     //Add an explicit input handler to parse our edge data (just to show how it works - usually, a deserializer for date would be easier...)
126     ioHandler.getGraphMLHandler().addInputHandlerProvider(new InputHandlerProvider() {
127       public void onQueryInputHandler(QueryInputHandlersEvent event) throws GraphMLParseException {
128         Element keyDefinition = event.getKeyDefinition();
129         if (!event.isHandled()
130             && GraphMLHandler.matchesScope(keyDefinition, KeyScope.EDGE)
131             && GraphMLHandler.matchesName(keyDefinition, "myEdgeAttribute")) {
132           MyDateInputHandler handler = new MyDateInputHandler();
133           handler.setDataAcceptor(edgeDataMap);
134           handler.initializeFromKeyDefinition(event.getContext(), keyDefinition);
135           event.addInputHandler(handler);
136         }
137       }
138     });
139 
140     //Add an explicit output handler to write our edge data (just to show how it works - usually, a serializer for date would be easier...)
141     ioHandler.getGraphMLHandler().addOutputHandlerProvider(
142         new AbstractOutputHandler("myEdgeAttribute", KeyScope.EDGE, KeyType.COMPLEX) {
143           protected void writeValueCore(GraphMLWriteContext context, Object data) throws GraphMLWriteException {
144             if (data instanceof Date) {
145               context.getWriter().writeText(dateFormat.format(data));
146             }
147           }
148 
149           protected Object getValue(GraphMLWriteContext context, Object key) throws GraphMLWriteException {
150             return edgeDataMap.get(key);
151           }
152         });
153 
154     return ioHandler;
155   }
156 
157 
158   public static final String DEMO_NS = "demo.io.graphml.complex";
159 
160   /**
161    * Example class that is used as an example for complex objects
162    */
163   public static class Item {
164     private String value;
165 
166     public Item(String value) {
167       this.value = value;
168     }
169 
170     public String getValue() {
171       return value;
172     }
173 
174     public String toString() {
175       return value;
176     }
177   }
178 
179   /**
180    * Custom Serializer for {@link Item} objects
181    */
182   public static class ItemSerializer extends TypeBasedSerializer {
183     public void serializeItem(Object o, XmlWriter writer, GraphMLWriteContext context) throws GraphMLWriteException {
184       Item item = (Item) o;
185       writer.writeStartElement("Item", DEMO_NS).writeAttribute("value", item.getValue()).writeEndElement();
186     }
187 
188     protected Class getSerializationType(GraphMLWriteContext context) {
189       //We are only valid for Item objects
190       return Item.class;
191     }
192   }
193 
194   /**
195    * Custom deserializer for {@link Item} objects
196    */
197   public static class ItemDeserializer extends NameBasedDeserializer {
198     public Object deserializeNode(org.w3c.dom.Node xmlNode, GraphMLParseContext context) throws GraphMLParseException {
199       if (xmlNode.getNodeType() == org.w3c.dom.Node.ELEMENT_NODE
200           && DEMO_NS.equals(xmlNode.getNamespaceURI())
201           && "Item".equals(xmlNode.getLocalName())) {
202         return new Item(((Element) xmlNode).getAttribute("value"));
203       }
204       return null;
205     }
206 
207 
208     public String getNamespaceURI(GraphMLParseContext context) {
209       return DEMO_NS;
210     }
211 
212     public String getNodeName(GraphMLParseContext context) {
213       return "Item";
214     }
215   }
216 
217   /**
218    * Custom serializer for Collections of {@link Item}s
219    */
220   public static class ItemListSerializer implements SerializationHandler {
221 
222     public void onHandleSerialization(SerializationEvent event) throws GraphMLWriteException {
223       Object o = event.getItem();
224       if (o instanceof Collection) {
225         Collection coll = (Collection) o;
226         event.getWriter().writeStartElement("ItemList", DEMO_NS);
227         for (Iterator iterator = coll.iterator(); iterator.hasNext();) {
228           Object item = iterator.next();
229           event.getContext().serialize(item);
230         }
231         event.getWriter().writeEndElement();
232       }
233     }
234   }
235 
236   /**
237    * Custom deserializer for Collections of {@link Item}s
238    */
239   public static class ItemListDeserializer implements DeserializationHandler {
240     public void onHandleDeserialization(DeserializationEvent event) throws GraphMLParseException {
241       org.w3c.dom.Node xmlNode = event.getXmlNode();
242       if (xmlNode.getNodeType() == org.w3c.dom.Node.ELEMENT_NODE
243           && DEMO_NS.equals(xmlNode.getNamespaceURI())
244           && "ItemList".equals(xmlNode.getLocalName())) {
245         Collection retval = new ArrayList();
246         NodeList childNodes = xmlNode.getChildNodes();
247         for (int i = 0; i < childNodes.getLength(); ++i) {
248           org.w3c.dom.Node child = childNodes.item(i);
249           if (child.getNodeType() == org.w3c.dom.Node.ELEMENT_NODE) {
250             Object o = event.getContext().deserialize(child);
251             retval.add(o);
252           }
253         }
254         event.setResult(retval);
255       }
256     }
257   }
258 
259   private static SimpleDateFormat dateFormat = new SimpleDateFormat("dd.MM.yy");
260 
261   /** Explicit input handler for Date attributes */
262   public static class MyDateInputHandler extends AbstractDataAcceptorInputHandler {
263     protected Object parseDataCore(GraphMLParseContext context, org.w3c.dom.Node node) throws GraphMLParseException {
264       if (node.getChildNodes().getLength() != 1) {
265         throw new GraphMLParseException("Invalid data format - single text node expected");
266       }
267       org.w3c.dom.Node n = node.getFirstChild();
268       if (n.getNodeType() == org.w3c.dom.Node.TEXT_NODE) {
269         try {
270           return dateFormat.parse(n.getNodeValue());
271         } catch (ParseException e) {
272           throw new GraphMLParseException("Invalid date format: " + e.getMessage(), e);
273         }
274       } else {
275         throw new GraphMLParseException("Invalid data format - Text node expected");
276       }
277     }
278   }
279 
280   protected String[] getExampleResources() {
281     return null;
282   }
283 
284   /**
285    * Create an edit mode that displays a context-sensitive popup-menu when
286    * right-clicking on a node or an edge.
287    */
288   protected EditMode createEditMode() {
289     EditMode editMode = super.createEditMode();
290 
291     editMode.setPopupMode(new PopupMode() {
292       public JPopupMenu getNodePopup(Node v) {
293         JPopupMenu pm = new JPopupMenu();
294         pm.add(new EditAttributeAction("Edit Node Attribute...", v, nodeDataMap));
295         return pm;
296       }
297     });
298     return editMode;
299   }
300 
301   /**
302    * Create a customized {@link TooltipMode}.
303    * @return a <code>TooltipMode</code> that displays information about nodes and edges.
304    */
305   protected TooltipMode createTooltipMode() {
306     TooltipMode tooltipMode = new TooltipMode() {
307       /**
308        * Overwritten to display the data values of the node in a tooltip.
309        * @param node the node for which the tooltip is set
310        * @return the tooltip string that is used by the <code>TooltipMode</code>
311        */
312       protected String getNodeTip(Node node) {
313         Collection items = (Collection) nodeDataMap.get(node);
314         String tipText = null;
315         if (items != null) {
316           tipText = "<html><body>Items:<table>";
317           for (Iterator iterator = items.iterator(); iterator.hasNext(); ) {
318             tipText += "</tr></td>" + iterator.next() + "</td></tr>";
319           }
320           tipText += "</table></body></html>";
321         }
322         return tipText;
323       }
324 
325       /**
326        * Overwritten to display the data values of the edge in a tooltip.
327        * @param edge the edge for which the tooltip is set
328        * @return the tooltip string that is used by the <code>TooltipMode</code>
329        */
330       protected String getEdgeTip(Edge edge) {
331         Object o = edgeDataMap.get(edge);
332         String tipText = null;
333         if (o instanceof Date) {
334           tipText = o.toString();
335         }
336         return tipText;
337       }
338     };
339 
340     return tooltipMode;
341   }
342 
343   /**
344    * Editor action for the additional node attributes.
345    */
346   class EditAttributeAction extends AbstractAction {
347     private Object object;
348     private DataMap dataMap;
349 
350     private OptionHandler op;
351 
352     EditAttributeAction(String name, Object object, DataMap dataMap) {
353       super(name);
354       this.object = object;
355       this.dataMap = dataMap;
356       op = new OptionHandler(name);
357       if (object instanceof Node) {
358         Object o = dataMap.get(object);
359         if (o instanceof Collection) {
360           Collection coll = (Collection) o;
361           String str = "";
362           for (Iterator iterator = coll.iterator(); iterator.hasNext();) {
363             str += iterator.next();
364             if (iterator.hasNext()) {
365               str += "\n";
366             }
367           }
368           op.addString("Node Items", str, 10);
369         } else {
370           op.addString("Node Items", "", 10);
371         }
372       }      
373     }
374 
375     public void actionPerformed(ActionEvent actionEvent) {
376       if (op.showEditor(view.getFrame())) {
377         if (object instanceof Node) {          
378           Collection coll = new ArrayList();
379           String s = op.getString("Node Items");
380           StringTokenizer tokenizer = new StringTokenizer(s, "\n");
381           while (tokenizer.hasMoreElements()) {
382             String s1 = (String) tokenizer.nextElement();
383             coll.add(new Item(s1));
384           }
385           dataMap.set(object, coll);          
386         }
387         graphMLPane.updateGraphMLText(view.getGraph2D());        
388       }
389     }
390   }
391 
392   /**
393    * Launches this demo.
394    */
395   public static void main(String[] args) {
396     EventQueue.invokeLater(new Runnable() {
397       public void run() {
398         Locale.setDefault(Locale.ENGLISH);
399         initLnF();
400         (new ComplexExtensionGraphMLDemo()).start();
401       }
402     });
403   }
404 }
405