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.view.mindmap;
29  
30  import org.w3c.dom.NamedNodeMap;
31  import org.w3c.dom.Node;
32  import org.xml.sax.SAXException;
33  import y.base.EdgeCursor;
34  import y.io.IOHandler;
35  import y.view.Graph2D;
36  import y.view.LineType;
37  import y.view.NodeRealizer;
38  
39  import java.awt.Color;
40  import java.io.BufferedWriter;
41  import java.io.IOException;
42  import java.io.InputStream;
43  import java.io.OutputStream;
44  import java.io.OutputStreamWriter;
45  import javax.xml.parsers.DocumentBuilderFactory;
46  import javax.xml.parsers.ParserConfigurationException;
47  
48  /**
49   * Provides methods to import and export FreeMind documents.
50   * It is tested with FreeMind 0.9.0 and Freeplane 1.2.0.
51   * It should work for other versions, too, though.
52   */
53  public class FreeMindIOHandler extends IOHandler {
54    private static final String ENCODING = "UTF-8" ;
55    private static final String ATTRIBUTE_COLOR = "COLOR";
56    private static final String ATTRIBUTE_POSITION = "POSITION";
57    private static final String ATTRIBUTE_WIDTH = "WIDTH";
58    private static final String ATTRIBUTE_TEXT = "TEXT";
59    private static final String TAG_EDGE = "edge";
60    private static final String TAG_NODE = "node";
61  
62    /**
63     * Write a FreeMind document.
64     */
65    public void write( final Graph2D graph, final OutputStream out ) throws IOException {
66      final BufferedWriter bufferedWriter = new BufferedWriter(
67              new OutputStreamWriter(new OutputStreamGuardian(out), ENCODING));
68      try {
69        bufferedWriter.write("<map version=\"0.9.0\">");
70        bufferedWriter.newLine();
71        writeNode(graph, ViewModel.instance.getRoot(), bufferedWriter);
72        bufferedWriter.write("</map>");
73        bufferedWriter.flush();
74      } finally {
75        bufferedWriter.close();
76      }
77    }
78  
79    /**
80     * Write an item and its children to a FreeMind document.
81     * @param graph current Graph2D
82     * @param node item
83     * @param writer the BufferedWrite to write to
84     * @throws IOException
85     */
86    private static void writeNode(
87            final Graph2D graph, final y.base.Node node, final BufferedWriter writer
88    ) throws IOException {
89      // As Freeplane can only handle XML documents with specific formatting,
90      // no XML Writer is used here. Instead, files are written manually
91  
92      final NodeRealizer nodeRealizer = graph.getRealizer(node);
93      writer.write("<" + TAG_NODE + " " + ATTRIBUTE_TEXT + "=\"" + nodeRealizer.getLabelText() + "\"");
94  
95      final boolean isSecondLevel = node.inDegree() > 0 && node.firstInEdge().source().inDegree() == 0;
96      final boolean isLeftSide = ViewModel.instance.isLeft(node);
97      //The position attribute is only needed for direct children of the center item
98      if (isSecondLevel && isLeftSide) {
99        writer.write(" " + ATTRIBUTE_POSITION + "=\"left\" ");
100     }
101     writer.write(" >");
102     writer.newLine();
103     writeEdge(nodeRealizer, writer);
104 
105     for (EdgeCursor ec = MindMapUtil.outEdges(node).edges(); ec.ok(); ec.next()) {
106       writeNode(graph, ec.edge().target(), writer);
107     }
108 
109     writer.write("</" + TAG_NODE + ">");
110     writer.newLine();
111   }
112 
113   private static void writeEdge(
114           final NodeRealizer target, final BufferedWriter writer
115   ) throws IOException {
116     writer.write("<" + TAG_EDGE + " ");
117     String value = Integer.toHexString(target.getFillColor().getRGB());
118     writer.write(ATTRIBUTE_COLOR + "=\"#" + value.substring(2, 8) + "\" ");
119     writer.write(ATTRIBUTE_WIDTH + "=\"" + ((int) target.getLineType().getLineWidth()) + "\"");
120     writer.write("/>");
121     writer.newLine();
122   }
123 
124 
125   /**
126    * Read a FreeMind document.
127    */
128   public void read( final Graph2D graph, final InputStream in ) throws IOException {
129     try {
130       readNode(
131               DocumentBuilderFactory.newInstance().newDocumentBuilder().parse(in),
132               graph,
133               null);
134     } catch (SAXException e) {
135       final IOException ioe = new IOException();
136       ioe.initCause(ioe);
137       throw ioe;
138     } catch (ParserConfigurationException e) {
139       final IOException ioe = new IOException();
140       ioe.initCause(ioe);
141       throw ioe;
142     }
143   }
144 
145 
146   /**
147    * Read a XML Document and create items.
148    * @param node the current xml node
149    * @param graph the current Graph2D
150    * @param parent the parent item
151    */
152   private static void readNode(final Node node, final Graph2D graph, final y.base.Node parent) {
153     final String nodeName = node.getNodeName();
154     y.base.Node n = parent;
155     if (nodeName.equals(TAG_NODE)) {
156       final NamedNodeMap attributes = node.getAttributes();
157       Node attr = attributes.getNamedItem(ATTRIBUTE_TEXT);
158       String name = "";
159       boolean side = false;
160       if (attr != null) {
161         name = attr.getNodeValue();
162       }
163       attr = attributes.getNamedItem(ATTRIBUTE_POSITION);
164       if (attr != null) {
165         side = attr.getNodeValue().equalsIgnoreCase("left");
166       }
167       if (parent == null) {
168         //if an item has no parent, it is this the center item.
169         //as it has no parent, the addNode method can't be used here,
170         //so the item is created manually. It will be updated later
171         n = graph.createNode(0,0,name);
172       } else {
173         //uses fill color from parent - if a color is specified, it will be adjusted later
174         n = MindMapUtil.addNode(graph, parent, name, side);
175       }
176       if (parent != null && attr == null) {
177         MindMapUtil.updateVisuals(graph, n, ViewModel.instance.isLeft(parent));
178       } else {
179         MindMapUtil.updateVisuals(graph, n, side);
180       }
181     } else if (nodeName.equals(TAG_EDGE)) {
182       final NamedNodeMap attributes = node.getAttributes();
183       Node attr = attributes.getNamedItem(ATTRIBUTE_COLOR);
184       //Freeplane saves incoming edges as a children of its target.
185       //Therefore we need to change the parents attributes.
186       final NodeRealizer nodeRealizer = graph.getRealizer(parent);
187       if (attr != null) {
188         nodeRealizer.setFillColor(Color.decode(attr.getNodeValue()));
189       }
190       attr = attributes.getNamedItem(ATTRIBUTE_WIDTH);
191       if (attr != null) {
192         int lineWidth = Integer.parseInt(attr.getNodeValue());
193         if (lineWidth == 2) {
194           nodeRealizer.setLineType(LineType.LINE_3);
195         } else {
196           nodeRealizer.setLineType(LineType.LINE_6);
197         }
198       }
199     }
200 
201     // read children
202     for (Node child = node.getFirstChild(); child != null; child = child.getNextSibling()) {
203       readNode(child, graph, n);
204     }
205   }
206 
207 
208   /**
209    * Returns <em>FreeMind file</em>.
210    * @return <em>FreeMind file</em>.
211    */
212   public String getFileFormatString() {
213     return "FreeMind file";
214   }
215 
216   /**
217    * Returns <em>mm</em>.
218    * @return <em>mm</em>.
219    */
220   public String getFileNameExtension() {
221     return "mm";
222   }
223 
224 
225   /**
226    * Prevents {@link java.io.OutputStream#close()} calls from being propagated
227    * to the decorated output stream.
228    */
229   private static final class OutputStreamGuardian extends OutputStream {
230     private final OutputStream os;
231 
232     OutputStreamGuardian( final OutputStream os ) {
233       this.os = os;
234     }
235 
236     public void close() throws IOException {
237       // do nothing
238     }
239 
240     public void flush() throws IOException {
241       os.flush();
242     }
243 
244     public void write( final int b ) throws IOException {
245       os.write(b);
246     }
247 
248     public void write( final byte[] b ) throws IOException {
249       os.write(b);
250     }
251 
252     public void write( final byte[] b, final int off, final int len ) throws IOException {
253       os.write(b, off, len);
254     }
255   }
256 }
257