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 extends PrototypePersistenceDelegate> 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 extends PrototypePersistenceDelegate> 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: }