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