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.Document;
17  import org.w3c.dom.DocumentFragment;
18  import org.w3c.dom.Element;
19  import org.w3c.dom.NamedNodeMap;
20  import org.w3c.dom.Node;
21  import org.w3c.dom.NodeList;
22  import y.base.DataMap;
23  import y.io.GraphMLIOHandler;
24  import y.io.graphml.GraphMLHandler;
25  import y.io.graphml.KeyScope;
26  import y.io.graphml.input.AbstractDataAcceptorInputHandler;
27  import y.io.graphml.input.GraphMLParseException;
28  import y.io.graphml.input.GraphMLParseContext;
29  import y.io.graphml.input.ParseEvent;
30  import y.io.graphml.input.ParseEventListenerAdapter;
31  import y.io.graphml.input.InputHandlerProvider;
32  import y.io.graphml.input.QueryInputHandlersEvent;
33  import y.io.graphml.output.AbstractDataProviderOutputHandler;
34  import y.io.graphml.output.GraphMLWriteException;
35  import y.io.graphml.output.GraphMLXmlAttribute;
36  import y.io.graphml.output.OutputHandlerProvider;
37  import y.io.graphml.output.QueryOutputHandlersEvent;
38  import y.io.graphml.output.GraphMLWriteContext;
39  import y.util.Maps;
40  
41  import javax.xml.parsers.DocumentBuilder;
42  import javax.xml.parsers.DocumentBuilderFactory;
43  import javax.xml.parsers.ParserConfigurationException;
44  import java.util.Collection;
45  import java.util.HashMap;
46  import java.util.HashSet;
47  import java.util.Iterator;
48  import java.util.LinkedList;
49  import java.util.Locale;
50  import java.util.Set;
51  import java.awt.EventQueue;
52  
53  /**
54   * Demo application that shows how input handlers for GraphML attributes can be dynamically registered,
55   * depending on the actual input.
56   *
57   * <p>
58   * Each unknown attribute is saved by means of an input handler that creates document fragments
59   * from the <code>data</code> element content and stores these in a map.
60   * </p>
61   * <p>
62   * <b>Note:</b> Due to the way how nested graphs are handled internally by yFiles, data content that is bound to
63   * <b>nested</b> graph elements will not be handled correctly by this demo (you'll have to associate the data with the
64   * parent node instead, which is not shown here to keep the demo short). 
65   * </p>
66   */
67  public class DynamicAttributesDemo extends GraphMLDemo {
68  
69    protected void loadInitialGraph() {
70      dynamicAttributes = new LinkedList();
71      //We load a graph that contains already some attributes - note that we don't have to know which attributes are present in the file.
72      loadGraph("resources/custom/simple-attributes.graphml");
73    }
74  
75    /**
76     * Creates a customized GraphMLHandler since we want to listen to a parse event.
77     */
78    protected GraphMLIOHandler createGraphMLIOHandler() {
79      GraphMLIOHandler ioHandler = new GraphMLIOHandler();
80      ioHandler.getGraphMLHandler().addParseEventListener(
81          new ParseEventListenerAdapter() {
82            public void onGraphMLParsing(ParseEvent event) {
83              //reset the attribute list whenever we started to parse a new document.
84              dynamicAttributes.clear();
85            }
86          });
87      //Register event listeners that handle our dynamic attributes...
88      ioHandler.getGraphMLHandler().addInputHandlerProvider(new DynamicAttributesInputHandlerProvider());
89      ioHandler.getGraphMLHandler().addOutputHandlerProvider(new DynamicAttributesOutputHandlerProvider());
90  
91      return ioHandler;
92    }
93  
94    /**
95     * Helper class that captures GraphML key definition data for roundtrips.
96     */
97    private static class AttributeDescriptor {
98      private Set attributeMetadata = new HashSet();
99      private DataMap dataMap;
100     private final KeyScope scope;
101 
102 
103     AttributeDescriptor(DataMap dataMap, KeyScope scope) {
104       this.dataMap = dataMap;
105       this.scope = scope;
106     }
107 
108     public void addAttribute(GraphMLXmlAttribute attr) {
109       attributeMetadata.add(attr);
110     }
111 
112     public Collection getAttributeMetadata() {
113       return attributeMetadata;
114     }
115 
116     public DataMap getDataMap() {
117       return dataMap;
118     }
119 
120     public KeyScope getScope() {
121       return scope;
122     }
123 
124     public Object getDefaultValue() {
125       return defaultValue;
126     }
127 
128     public void setDefaultValue(Object defaultValue) {
129       this.defaultValue = defaultValue;
130     }
131 
132     private Object defaultValue;
133 
134     public boolean isDefaultSet() {
135       return isDefaultSet;
136     }
137 
138     public void setDefaultSet(boolean defaultSet) {
139       isDefaultSet = defaultSet;
140     }
141 
142     private boolean isDefaultSet;
143   }
144 
145   private Collection dynamicAttributes;
146 
147   private class DynamicAttributesInputHandlerProvider implements InputHandlerProvider {
148     public void onQueryInputHandler(QueryInputHandlersEvent event) throws GraphMLParseException {
149       if (!event.isHandled()) {
150         //If the event is not handled, we register a new Input handler that stores the
151         //XML representation in a map
152         DataMap map = Maps.createDataMap(new HashMap());
153         KeyScope scope = GraphMLHandler.getKeyScope(event.getKeyDefinition());
154 
155         //Store the attribute data along with the key definition...
156         //Aditionally, we could register the map as a data provider on the graph
157         //(however, since we really only want to store them for a roundtrip, we just don't care...)
158         AttributeDescriptor descriptor = new AttributeDescriptor(map, scope);
159         dynamicAttributes.add(descriptor);
160 
161         XmlInputHandler handler = null;
162         try {
163           handler = new XmlInputHandler(descriptor);
164         } catch (ParserConfigurationException e) {
165           throw new GraphMLParseException("Error configuring internal DocumentBuilder", e);
166         }
167         handler.initializeFromKeyDefinition(event.getContext(), event.getKeyDefinition());
168         event.addInputHandler(handler);
169       }
170     }
171   }
172 
173   private class DynamicAttributesOutputHandlerProvider implements OutputHandlerProvider {
174     public void onQueryOutputHandler(QueryOutputHandlersEvent event) throws GraphMLWriteException {
175       for (Iterator iterator = dynamicAttributes.iterator(); iterator.hasNext();) {
176         //For all attribute descriptors, we add an output handler that can write
177         //the data (actually just the document fragments in the map)
178         AttributeDescriptor descriptor = (AttributeDescriptor) iterator.next();
179         KeyScope scope = descriptor.getScope();
180         XmlOutputHandler outputHandler = new XmlOutputHandler(descriptor);
181         event.addOutputHandler(outputHandler, scope);
182       }
183     }
184   }
185 
186   /**
187    * Custom input handler that stores data nodes as DocumentFragments and captures the key definition
188    */
189   private static class XmlInputHandler extends AbstractDataAcceptorInputHandler {
190 
191     private final DocumentBuilder documentBuilder;
192 
193     XmlInputHandler(AttributeDescriptor descriptor) throws ParserConfigurationException {
194       this.descriptor = descriptor;
195       setDataAcceptor(descriptor.getDataMap());
196       DocumentBuilderFactory builderFactory = DocumentBuilderFactory.newInstance();
197       builderFactory.setNamespaceAware(true);
198       documentBuilder = builderFactory.newDocumentBuilder();
199     }
200 
201     private AttributeDescriptor descriptor;
202 
203     protected Object parseDataCore(GraphMLParseContext context, Node node) throws GraphMLParseException {
204       NodeList children = node.getChildNodes();
205       //Extract the data nodes' content into a document fragment which will be
206       //saved in the map later on...
207       Document targetDoc = documentBuilder.newDocument();
208       DocumentFragment fragment = node.getOwnerDocument().createDocumentFragment();
209       if (children != null) {
210         for (int i = 0; i < children.getLength(); i++) {
211           Node n = children.item(i);
212           fragment.appendChild(n.cloneNode(true));
213         }
214       }
215       Element rootElement = targetDoc.createElement("DocumentRoot");
216       targetDoc.appendChild(rootElement);
217       Node node1 = targetDoc.importNode(fragment, true);
218       rootElement.appendChild(node1);
219       return targetDoc;
220     }
221 
222     public void initializeFromKeyDefinition(GraphMLParseContext context, Element definition) throws
223         GraphMLParseException {
224       super.initializeFromKeyDefinition(context, definition);
225 
226       //Rescue values from the key definition into descriptor, so that they can be reused for writing the attribute
227       NamedNodeMap attributes = definition.getAttributes();
228       for (int i = 0; i < attributes.getLength(); ++i) {
229         Node attr = attributes.item(i);
230         descriptor.addAttribute(new GraphMLXmlAttribute(attr.getNodeName(), attr.getNamespaceURI(),
231             attr.getNodeValue()));
232       }
233       descriptor.setDefaultSet(isDefaultExists());
234       descriptor.setDefaultValue(getDefaultValue());
235     }
236   }
237 
238   /**
239    * Custom output handler that writes DocumentFragments as data nodes and reuses a given key definition
240    */
241   private static class XmlOutputHandler extends AbstractDataProviderOutputHandler {
242 
243     XmlOutputHandler(AttributeDescriptor descriptor) {
244       this.descriptor = descriptor;
245       setDefaultValue(descriptor.getDefaultValue());
246       setDataProvider(descriptor.getDataMap());
247       setDefaultValueAssigned(descriptor.isDefaultSet());
248     }
249 
250     private AttributeDescriptor descriptor;
251 
252     public Collection getKeyDefinitionAttributes() {
253       return descriptor.getAttributeMetadata();
254     }
255 
256     protected void writeValueCore(GraphMLWriteContext context, Object data) throws GraphMLWriteException {
257       if (data != null) {
258         //Just dump the document fragment...
259         Document savedDoc = (Document) data;
260 
261         DocumentFragment fragment = savedDoc.createDocumentFragment();
262         NodeList children = savedDoc.getDocumentElement().getChildNodes();
263         if (children != null) {
264           for (int i = 0; i < children.getLength(); i++) {
265             Node n = children.item(i);
266             fragment.appendChild(n.cloneNode(true));
267           }
268         }
269         context.getWriter().writeDocumentFragment(fragment);
270       }
271     }
272   }
273 
274   /**
275    * Add sample graphs to the menu.
276    */
277   protected String[] getExampleResources() {
278     return new String[]{
279         "resources/custom/simple-attributes.graphml",
280         "resources/custom/complexdemo.graphml",
281     };
282   }
283 
284   /**
285    * Launches this demo.
286    */
287   public static void main(String[] args) {
288     EventQueue.invokeLater(new Runnable() {
289       public void run() {
290         Locale.setDefault(Locale.ENGLISH);
291         initLnF();
292         (new DynamicAttributesDemo()).start();
293       }
294     });
295   }
296 }
297