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