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