f@0: /* f@0: CCmI Editor - A Collaborative Cross-Modal Diagram Editing Tool f@0: f@0: Copyright (C) 2011 Queen Mary University of London (http://ccmi.eecs.qmul.ac.uk/) f@0: f@0: This program is free software: you can redistribute it and/or modify f@0: it under the terms of the GNU General Public License as published by f@0: the Free Software Foundation, either version 3 of the License, or f@0: (at your option) any later version. f@0: f@0: This program is distributed in the hope that it will be useful, f@0: but WITHOUT ANY WARRANTY; without even the implied warranty of f@0: MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the f@0: GNU General Public License for more details. f@0: f@0: You should have received a copy of the GNU General Public License f@0: along with this program. If not, see . f@0: */ f@0: f@0: package uk.ac.qmul.eecs.ccmi.gui.persistence; f@0: f@0: import java.io.BufferedWriter; f@0: import java.io.File; f@0: import java.io.FileWriter; f@0: import java.io.IOException; f@0: import java.io.InputStream; f@0: import java.io.OutputStream; f@0: import java.text.MessageFormat; f@0: import java.util.ArrayList; f@0: import java.util.Collection; f@0: import java.util.Enumeration; f@0: import java.util.LinkedHashMap; f@0: import java.util.List; f@0: import java.util.Map; f@0: import java.util.ResourceBundle; f@0: f@0: import javax.swing.tree.TreeNode; f@0: import javax.xml.parsers.DocumentBuilder; f@0: import javax.xml.parsers.DocumentBuilderFactory; f@0: import javax.xml.parsers.ParserConfigurationException; f@0: import javax.xml.transform.OutputKeys; f@0: import javax.xml.transform.Transformer; f@0: import javax.xml.transform.TransformerConfigurationException; f@0: import javax.xml.transform.TransformerException; f@0: import javax.xml.transform.TransformerFactory; f@0: import javax.xml.transform.dom.DOMSource; f@0: import javax.xml.transform.stream.StreamResult; f@0: f@0: import org.w3c.dom.Document; f@0: import org.w3c.dom.Element; f@0: import org.w3c.dom.NodeList; f@0: import org.xml.sax.SAXException; f@0: f@0: import uk.ac.qmul.eecs.ccmi.diagrammodel.CollectionModel; f@0: import uk.ac.qmul.eecs.ccmi.diagrammodel.DiagramTreeNode; f@0: import uk.ac.qmul.eecs.ccmi.diagrammodel.TreeModel; f@0: import uk.ac.qmul.eecs.ccmi.gui.Diagram; f@0: import uk.ac.qmul.eecs.ccmi.gui.DiagramEventSource; f@0: import uk.ac.qmul.eecs.ccmi.gui.Edge; f@0: import uk.ac.qmul.eecs.ccmi.gui.Node; f@0: import uk.ac.qmul.eecs.ccmi.utils.CharEscaper; f@0: f@0: /** f@0: * The PersistanceManager provides methods for saving and retrieving diagrams from an XML f@0: * file. Both templates diagrams (prototypes from which actual diagram instances are created f@0: * through cloning) and diagram instances can be saved to a file. The tag name used in the XML f@0: * file can be accessed via the static {@code String} variables of this class. f@0: * f@0: */ f@0: public abstract class PersistenceManager { f@0: /** f@0: * Encodes a diagram template in a file in XML format f@0: * f@0: * @param diagram the diagram to be encoded f@0: * @param file the file where the diagram is going to be encoded f@0: * @throws IOException if there are any I/O problems with the file f@0: */ f@0: public static void encodeDiagramTemplate(Diagram diagram, File file) throws IOException{ f@0: ResourceBundle resources = ResourceBundle.getBundle(PersistenceManager.class.getName()); f@0: if(file.createNewFile() == false) f@0: throw new IOException(resources.getString("dialog.error.file_exists")); f@0: f@0: DocumentBuilderFactory dbfac = DocumentBuilderFactory.newInstance(); f@0: DocumentBuilder docBuilder = null; f@0: try { f@0: docBuilder = dbfac.newDocumentBuilder(); f@0: } catch (ParserConfigurationException e) { f@0: throw new IOException(resources.getString("dialog.error.problem.save"),e); f@0: } f@0: Document doc = docBuilder.newDocument(); f@0: f@0: Element root = doc.createElement(DIAGRAM); f@0: doc.appendChild(root); f@0: /* diagram name and prototypePersstenceDelegate */ f@0: root.setAttribute(NAME, diagram.getName()); f@0: root.setAttribute(PROTOTYPE_PERSISTENCE_DELEGATE, diagram.getPrototypePersistenceDelegate().getClass().getName()); f@0: f@0: writePrototypes(doc, root, diagram); f@0: f@0: //set up a transformer f@0: TransformerFactory transfac = TransformerFactory.newInstance(); f@0: Transformer trans = null; f@0: try { f@0: trans = transfac.newTransformer(); f@0: } catch (TransformerConfigurationException tce) { f@0: throw new IOException(resources.getString("dialog.error.problem.save"),tce); f@0: } f@0: trans.setOutputProperty(OutputKeys.INDENT, "yes"); f@0: trans.setOutputProperty("{http://xml.apache.org/xslt}indent-amount", String.valueOf(2)); f@0: f@0: StreamResult result = new StreamResult(new BufferedWriter(new FileWriter(file))); f@0: DOMSource source = new DOMSource(doc); f@0: try { f@0: trans.transform(source, result); f@0: } catch (TransformerException te) { f@0: throw new IOException(resources.getString("dialog.error.problem.save"),te); f@0: } f@0: } f@0: f@0: /** f@0: * Decodes a diagram template from a file in XML format f@0: * f@0: * @param XMLFile the file to read the diagram from f@0: * @throws IOException if there are any I/O problems with the file f@0: * f@0: * @return the diagram encoded in {@code XMLFile} f@0: */ f@0: public static Diagram decodeDiagramTemplate(File XMLFile) throws IOException{ f@0: ResourceBundle resources = ResourceBundle.getBundle(PersistenceManager.class.getName()); f@0: DocumentBuilderFactory dbFactory = DocumentBuilderFactory.newInstance(); f@0: DocumentBuilder dBuilder = null; f@0: try { f@0: dBuilder = dbFactory.newDocumentBuilder(); f@0: } catch (ParserConfigurationException pce) { f@0: throw new IOException(resources.getString("dialog.error.problem.open"),pce); f@0: } f@0: Document doc = null; f@0: try { f@0: doc = dBuilder.parse(XMLFile); f@0: } catch (SAXException se) { f@0: throw new IOException(resources.getString("dialog.error.problem.open"),se); f@0: } f@0: doc.getDocumentElement().normalize(); f@0: f@0: if(doc.getElementsByTagName(DIAGRAM).item(0) == null) f@0: throw new IOException(resources.getString("dialog.error.malformed_file")); f@0: Element root = (Element)doc.getElementsByTagName(DIAGRAM).item(0); f@0: String diagramName = root.getAttribute(NAME); f@0: if(diagramName.isEmpty()) f@0: throw new IOException(resources.getString("dialog.error.malformed_file")); f@0: String persistenceDelegateClassName = root.getAttribute(PROTOTYPE_PERSISTENCE_DELEGATE); f@0: PrototypePersistenceDelegate persistenceDelegate = null; f@0: try{ f@0: Class c = Class.forName(persistenceDelegateClassName).asSubclass(PrototypePersistenceDelegate.class); f@0: persistenceDelegate = c.newInstance(); f@0: }catch(Exception e){ f@0: throw new IOException(resources.getString("dialog.error.problem.open"),e); f@0: } f@0: f@0: final List nList = readNodePrototypes(doc,persistenceDelegate); f@0: final List eList = readEdgePrototypes(doc,persistenceDelegate); f@0: Node[] nArray = new Node[nList.size()]; f@0: Edge[] eArray = new Edge[eList.size()]; f@0: return Diagram.newInstance(diagramName,nList.toArray(nArray),eList.toArray(eArray),persistenceDelegate); f@0: } f@0: f@0: /** f@0: * Encodes a diagram instance into the given output stream. Using output stream f@0: * instead of {@code Writer} as it's advised by the StreamResult API f@0: * @see http://download.oracle.com/javase/6/docs/api/javax/xml/transform/stream/StreamResult.html f@0: * f@0: * @param diagram the diagram to encode f@0: * @param newName the new name of the diagram to encode. This will also be the name of the file but {@code diagram} f@0: * will still keep the old name, that is the value returned by {@code getName()} won't be changed to {@code newName}. f@0: * If a {@code null} value is passed than the value returned by {@code diagram.getName()} will be used. f@0: * f@0: * @param out where the diagram will be encoded f@0: * @throws IOException if there are any I/O problems with the file f@0: */ f@0: public static void encodeDiagramInstance(Diagram diagram, String newName, OutputStream out) throws IOException{ f@0: ResourceBundle resources = ResourceBundle.getBundle(PersistenceManager.class.getName()); f@0: DocumentBuilderFactory dbfac = DocumentBuilderFactory.newInstance(); f@0: DocumentBuilder docBuilder = null; f@0: try { f@0: docBuilder = dbfac.newDocumentBuilder(); f@0: } catch (ParserConfigurationException pce) { f@0: throw new IOException(resources.getString("dialog.error.problem.save"),pce); f@0: } f@0: Document doc = docBuilder.newDocument(); f@0: f@0: Element root = doc.createElement(DIAGRAM); f@0: root.setAttribute(NAME, (newName != null) ? newName : diagram.getName()); f@0: root.setAttribute(PROTOTYPE_PERSISTENCE_DELEGATE, diagram.getPrototypePersistenceDelegate().getClass().getName()); f@0: doc.appendChild(root); f@0: f@0: /* store bookmarks */ f@0: Element bookmarksTag = doc.createElement(BOOKMARKS); f@0: TreeModel treeModel = diagram.getTreeModel(); f@0: for(String key : treeModel.getBookmarks()){ f@0: Element bookmarkTag = doc.createElement(BOOKMARK); f@0: bookmarkTag.setAttribute(KEY, key); f@0: if(treeModel.getBookmarkedTreeNode(key).isRoot()) f@0: bookmarkTag.setTextContent(ROOT_AS_STRING); f@0: else f@0: bookmarkTag.setTextContent(getTreeNodeAsString(treeModel.getBookmarkedTreeNode(key))); f@0: bookmarksTag.appendChild(bookmarkTag); f@0: } f@0: if(bookmarksTag.hasChildNodes()) f@0: root.appendChild(bookmarksTag); f@0: f@0: /* store notes */ f@0: Element notesTag = doc.createElement(NOTES); f@0: DiagramTreeNode treeRoot = (DiagramTreeNode)diagram.getTreeModel().getRoot(); f@0: for( @SuppressWarnings("unchecked") f@0: Enumeration enumeration = treeRoot.depthFirstEnumeration(); enumeration.hasMoreElements();){ f@0: DiagramTreeNode treeNode = enumeration.nextElement(); f@0: if(!treeNode.getNotes().isEmpty()){ f@0: Element noteTag = doc.createElement(NOTE); f@0: Element treeNodeTag = doc.createElement(TREE_NODE); f@0: if(treeNode.isRoot()) f@0: treeNodeTag.setTextContent(ROOT_AS_STRING); f@0: else f@0: treeNodeTag.setTextContent(getTreeNodeAsString(treeNode)); f@0: Element contentTag = doc.createElement(CONTENT); f@0: contentTag.setTextContent(CharEscaper.replaceNewline(treeNode.getNotes())); f@0: noteTag.appendChild(treeNodeTag); f@0: noteTag.appendChild(contentTag); f@0: notesTag.appendChild(noteTag); f@0: } f@0: } f@0: f@0: if(notesTag.hasChildNodes()) f@0: root.appendChild(notesTag); f@0: f@0: writePrototypes(doc,root,diagram); f@0: f@0: Element components = doc.createElement(COMPONENTS); f@0: root.appendChild(components); f@0: f@0: synchronized(diagram.getCollectionModel().getMonitor()){ f@0: Collection nodes = diagram.getCollectionModel().getNodes(); f@0: Collection edges = diagram.getCollectionModel().getEdges(); f@0: f@0: /* store nodes */ f@0: Element nodesTag = doc.createElement(NODES); f@0: components.appendChild(nodesTag); f@0: List nList = new ArrayList(nodes); f@0: for(Node n : nList){ f@0: Element nodeTag = doc.createElement(NODE); f@0: nodeTag.setAttribute(ID, String.valueOf(n.getId())); f@0: nodeTag.setAttribute(TYPE, n.getType()); f@0: nodesTag.appendChild(nodeTag); f@0: n.encode(doc, nodeTag); f@0: } f@0: f@0: Element edgesTag = doc.createElement(EDGES); f@0: components.appendChild(edgesTag); f@0: for(Edge e : edges){ f@0: Element edgeTag = doc.createElement(EDGE); f@0: edgesTag.appendChild(edgeTag); f@0: e.encode(doc,edgeTag,nList); f@0: } f@0: } f@0: //set up a transformer f@0: TransformerFactory transfac = TransformerFactory.newInstance(); f@0: Transformer trans = null; f@0: try { f@0: trans = transfac.newTransformer(); f@0: } catch (TransformerConfigurationException tec) { f@0: throw new IOException(resources.getString("dialog.error.problem.save")); f@0: } f@0: trans.setOutputProperty(OutputKeys.INDENT, "yes"); f@0: trans.setOutputProperty("{http://xml.apache.org/xslt}indent-amount", String.valueOf(2)); f@0: f@0: f@0: StreamResult result = new StreamResult(out); f@0: DOMSource source = new DOMSource(doc); f@0: try { f@0: trans.transform(source, result); f@0: } catch (TransformerException te) { f@0: throw new IOException(resources.getString("dialog.error.problem.save"),te); f@0: } f@0: } f@0: f@0: /** f@0: * Encodes a diagram instance into the given output stream. Using output stream f@0: * instead of {@code Writer} as it's advised by the StreamResult API f@0: * @see http://download.oracle.com/javase/6/docs/api/javax/xml/transform/stream/StreamResult.html f@0: * f@0: * @param diagram the diagram to encode f@0: * @param out an output stram to the file where the diagram will be encoded f@0: * @throws IOException if there are any I/O problems with the file f@0: */ f@0: public static void encodeDiagramInstance(Diagram diagram, OutputStream out) throws IOException{ f@0: encodeDiagramInstance(diagram,null,out); f@0: } f@0: f@0: /** f@0: * Decodes a diagram instance from the given input stream. Using input stream f@0: * instead of {@code Reader} as it's advised by the StreamResult API f@0: * @see http://download.oracle.com/javase/6/docs/api/javax/xml/transform/stream/StreamResult.html f@0: * f@0: * @param in an input stream to the file the diagram is decoded from f@0: * @throws IOException if there are any I/O problems with the file f@0: * f@0: * @return the diagram encoded in the file f@0: */ f@0: public static Diagram decodeDiagramInstance(InputStream in) throws IOException { f@0: ResourceBundle resources = ResourceBundle.getBundle(PersistenceManager.class.getName()); f@0: DocumentBuilderFactory dbFactory = DocumentBuilderFactory.newInstance(); f@0: DocumentBuilder dBuilder = null; f@0: try { f@0: dBuilder = dbFactory.newDocumentBuilder(); f@0: } catch (ParserConfigurationException pce) { f@0: throw new IOException(resources.getString("dialog.error.problem.open"),pce); f@0: } f@0: Document doc = null; f@0: try { f@0: doc = dBuilder.parse(in); f@0: } catch (SAXException se) { f@0: throw new IOException(resources.getString("dialog.error.problem.open"),se); f@0: } f@0: doc.getDocumentElement().normalize(); f@0: f@0: if(doc.getElementsByTagName(DIAGRAM).item(0) == null) f@0: throw new IOException(resources.getString("dialog.error.malformed_file")); f@0: Element root = (Element)doc.getElementsByTagName(DIAGRAM).item(0); f@0: String diagramName = root.getAttribute(NAME); f@0: if(diagramName.isEmpty()) f@0: throw new IOException(resources.getString("dialog.error.malformed_file")); f@0: String persistenceDelegateClassName = root.getAttribute(PROTOTYPE_PERSISTENCE_DELEGATE); f@0: PrototypePersistenceDelegate persistenceDelegate = null; f@0: try{ f@0: Class c = Class.forName(persistenceDelegateClassName).asSubclass(PrototypePersistenceDelegate.class); f@0: persistenceDelegate = c.newInstance(); f@0: }catch(Exception e){ f@0: throw new IOException(resources.getString("dialog.error.problem.open"),e); f@0: } f@0: f@0: final List nList = readNodePrototypes(doc,persistenceDelegate); f@0: final List eList = readEdgePrototypes(doc,persistenceDelegate); f@0: f@0: final Node[] nodes = nList.toArray(new Node[nList.size()]); f@0: final Edge[] edges = eList.toArray(new Edge[eList.size()]); f@0: f@0: Diagram diagram = Diagram.newInstance(diagramName, nodes,edges,persistenceDelegate); f@0: CollectionModel collectionModel = diagram.getCollectionModel(); f@0: TreeModel treeModel = diagram.getTreeModel(); f@0: f@0: /* a map linking node ids in the XML file to the actual Node object they represent */ f@0: Map nodesId = new LinkedHashMap(); f@0: f@0: if(doc.getElementsByTagName(COMPONENTS).item(0) == null) f@0: throw new IOException(resources.getString("dialog.error.malformed_file")); f@0: Element componentsTag = (Element)doc.getElementsByTagName(COMPONENTS).item(0); f@0: NodeList componentsChildren = componentsTag.getChildNodes(); f@0: Element nodesTag = null; f@0: for(int i=0;i readNodePrototypes(Document doc, PrototypePersistenceDelegate delegate) throws IOException{ f@0: if(doc.getElementsByTagName(PROTOTYPES).item(0) == null) f@0: throw new IOException(ResourceBundle.getBundle(PersistenceManager.class.getName()).getString("dialog.error.malformed_file")); f@0: Element prototypesTag = (Element)doc.getElementsByTagName(PROTOTYPES).item(0); f@0: NodeList elemList = prototypesTag.getElementsByTagName(NODE); f@0: final List nList = new ArrayList(elemList.getLength()); f@0: for(int i=0; i readEdgePrototypes(Document doc, PrototypePersistenceDelegate delegate) throws IOException{ f@0: if(doc.getElementsByTagName(PROTOTYPES).item(0) == null) f@0: throw new IOException(ResourceBundle.getBundle(PersistenceManager.class.getName()).getString("dialog.error.malformed_file")); f@0: Element prototypesTag = (Element)doc.getElementsByTagName(PROTOTYPES).item(0); f@0: NodeList elemList = prototypesTag.getElementsByTagName(EDGE); f@0: final List eList = new ArrayList(elemList.getLength()); f@0: for(int i=0; i model, String path) throws IOException{ f@0: DiagramTreeNode treeNode = (DiagramTreeNode)model.getRoot(); f@0: if(ROOT_AS_STRING.equals(path)) f@0: return treeNode; f@0: String[] nodesAsString = path.split(" "); f@0: f@0: try { f@0: for(String nodeAsString : nodesAsString) f@0: treeNode = (DiagramTreeNode) treeNode.getChildAt(Integer.parseInt(nodeAsString)); f@0: }catch(Exception e){ f@0: throw new IOException(e); f@0: } f@0: return treeNode; f@0: } f@0: f@0: public final static String NAME = "Name"; f@0: public final static String DIAGRAM = "Diagram"; f@0: public final static String PROTOTYPE_PERSISTENCE_DELEGATE = "PrototypeDelegate"; f@0: public final static String COMPONENTS = "Components"; f@0: public final static String PROTOTYPES = "Prototypes"; f@0: public final static String NODE = "Node"; f@0: public final static String NODES = "Nodes"; f@0: public final static String EDGE = "Edge"; f@0: public final static String EDGES = "Edges"; f@0: public final static String POSITION = "Position"; f@0: public final static String PROPERTIES = "Properties"; f@0: public final static String PROPERTY = "Property"; f@0: public final static String TYPE = "Type"; f@0: public final static String VALUE = "Value"; f@0: public final static String ELEMENT = "Element"; f@0: public static final String LABEL = "Label"; f@0: public final static String POINTS = "Points"; f@0: public final static String POINT = "Point"; f@0: public final static String ID = "id"; f@0: public final static String NEIGHBOURS = "Neighbours"; f@0: public static final String MODIFIER = "Modifier"; f@0: public static final String MODIFIERS = "Modifiers"; f@0: public static final String X = "x"; f@0: public static final String Y = "y"; f@0: public static final String BOOKMARKS = "Bookmarks"; f@0: public static final String BOOKMARK = "Bookmark"; f@0: public static final String KEY = "Key"; f@0: public static final String NOTES = "Notes"; f@0: public static final String NOTE = "Note"; f@0: public static final String CONTENT = "Content"; f@0: public static final String TREE_NODE = "TreeNode"; f@0: private static final String ROOT_AS_STRING = "-1"; f@0: }