Mercurial > hg > accesspd
view java/src/uk/ac/qmul/eecs/ccmi/network/NetDiagram.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.network; import java.awt.geom.Point2D; import java.awt.geom.Rectangle2D; import java.io.IOException; import java.nio.channels.SocketChannel; import java.util.Queue; import java.util.Set; import javax.swing.tree.TreeNode; import uk.ac.qmul.eecs.ccmi.diagrammodel.CollectionModel; import uk.ac.qmul.eecs.ccmi.diagrammodel.DiagramElement; import uk.ac.qmul.eecs.ccmi.diagrammodel.DiagramTreeNode; import uk.ac.qmul.eecs.ccmi.diagrammodel.NodeProperties; 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.DiagramModelUpdater; import uk.ac.qmul.eecs.ccmi.gui.Edge; import uk.ac.qmul.eecs.ccmi.gui.GraphElement; import uk.ac.qmul.eecs.ccmi.gui.Lock; import uk.ac.qmul.eecs.ccmi.gui.Node; import uk.ac.qmul.eecs.ccmi.gui.awareness.AwarenessPanel; import uk.ac.qmul.eecs.ccmi.gui.persistence.PrototypePersistenceDelegate; import uk.ac.qmul.eecs.ccmi.network.ClientConnectionManager.LockAnswer; import uk.ac.qmul.eecs.ccmi.utils.ExceptionHandler; /** * * A NetDiagram is a diagram that is shared by connecting to a server on either a remote or local host. * That means that other users from other computers can modify the diagram model through the server. * A NetDiagram is created by wrapping a local diagram (a diagram open in the local editor) into a NetDiagram class. * The wrapped diagram works as a delegate. What Really changes between a local diagram and a network diagram is * that the modelUpdater will directly affect the diagram model for the former and exchange messages with the server * for the latter. In the case of a network diagram the changes to the model are actually made by a {@link ClientConnectionManager} * thread upon receiving a message from the server. * */ public abstract class NetDiagram extends Diagram { private NetDiagram(Diagram delegateDiagram){ this.delegateDiagram = delegateDiagram; innerModelUpdater = new InnerModelUpdater(); } public static NetDiagram wrapRemoteHost(Diagram diagram, ClientConnectionManager connectionManager,SocketChannel channel){ return new RemoteHostDiagram(diagram,connectionManager,channel); } public static NetDiagram wrapLocalHost(Diagram diagram, SocketChannel channel, Queue<DiagramElement> dElements, ExceptionHandler handler){ return new LocalHostDiagram(diagram,channel,dElements,handler); } @Override public String getName(){ return delegateDiagram.getName(); } @Override public String toString(){ return getName(); } @Override public PrototypePersistenceDelegate getPrototypePersistenceDelegate(){ return delegateDiagram.getPrototypePersistenceDelegate(); } @Override public TreeModel<Node,Edge> getTreeModel(){ return delegateDiagram.getTreeModel(); } @Override public CollectionModel<Node,Edge> getCollectionModel(){ return delegateDiagram.getCollectionModel(); } @Override public void setName(String name) { delegateDiagram.setName(name); } @Override public Node[] getNodePrototypes() { return delegateDiagram.getNodePrototypes(); } @Override public Edge[] getEdgePrototypes() { return delegateDiagram.getEdgePrototypes(); } @Override public DiagramModelUpdater getModelUpdater(){ return innerModelUpdater; } public Diagram getDelegate(){ return delegateDiagram; } public abstract void enableAwareness(AwarenessPanel panel); public abstract void disableAwareness(AwarenessPanel panel); public abstract SocketChannel getSocketChannel(); protected abstract void send(Command cmd, DiagramElement element); protected abstract void send(Command cmd, DiagramTreeNode treeNode); protected abstract void send(LockMessage lockMessage); protected abstract void send(AwarenessMessage awMsg); protected abstract boolean receiveLockAnswer(); private Diagram delegateDiagram; private InnerModelUpdater innerModelUpdater; public static String LOCALHOST_STRING = " @ localhost"; private class InnerModelUpdater implements DiagramModelUpdater { @Override public boolean getLock(DiagramTreeNode treeNode, Lock lock, DiagramEventActionSource actionSource) { try { sendLockMessage(treeNode,lock,true,actionSource); }catch(IllegalArgumentException iae){ return false; } return receiveLockAnswer(); } @Override public void yieldLock(DiagramTreeNode treeNode, Lock lock, DiagramEventActionSource actionSource) { try { sendLockMessage(treeNode,lock,false,actionSource); }catch(IllegalArgumentException iae) {} } @Override public void sendAwarenessMessage(AwarenessMessage.Name awMsgName, Object source){ if(source instanceof DiagramEventActionSource) send(new AwarenessMessage(awMsgName,getName(),(DiagramEventActionSource)source)); else if(source instanceof String){ send(new AwarenessMessage(awMsgName,getName(),(String)source)); } } private void sendLockMessage(DiagramTreeNode treeNode, Lock lock, boolean isGettingLock, DiagramEventActionSource source){ TreeNode[] path = treeNode.getPath(); Object[] args = new Object[path.length-1]; if(args.length == 0 && !treeNode.isRoot()) throw new IllegalArgumentException("it's a node no longer connected with the tree"); for(int i=0;i<path.length-1;i++){ args[i] = delegateDiagram.getTreeModel().getIndexOfChild(path[i], path[i+1]); } send(new LockMessage( LockMessageConverter.getLockMessageNamefromLock(lock,isGettingLock), delegateDiagram.getName(), args, source )); } @Override public void insertInCollection(DiagramElement element,DiagramEventSource source) { boolean isNode = false; if(element instanceof Node) isNode = true; Command cmd = null; if(isNode){ Rectangle2D bounds = ((Node)element).getBounds(); cmd = new Command( Command.Name.INSERT_NODE, delegateDiagram.getName(), new Object[] {element.getType(),bounds.getX(),bounds.getY()}, makeRemote(source) ); }else{ Edge edge = (Edge)element; Object args[] = new Object[1+edge.getNodesNum()]; args[0] = edge.getType(); /* the args of the command will be the id's of the connected edges */ for(int i = 1; i< args.length; i++) args[i] = edge.getNodeAt(i-1).getId(); cmd = new Command( Command.Name.INSERT_EDGE, delegateDiagram.getName(), args, makeRemote(source) ); } send(cmd,element); } @Override public void insertInTree(DiagramElement element) { insertInCollection(element,DiagramEventSource.TREE); } @Override public void takeOutFromCollection(DiagramElement element,DiagramEventSource source) { boolean isNode = false; if(element instanceof Node) isNode = true; Command cmd = new Command( isNode ? Command.Name.REMOVE_NODE : Command.Name.REMOVE_EDGE, delegateDiagram.getName(), new Object[] {element.getId()}, makeRemote(source) ); send(cmd,element); } @Override public void takeOutFromTree(DiagramElement element) { takeOutFromCollection(element,DiagramEventSource.TREE); } @Override public void setName(DiagramElement element, String name, DiagramEventSource source) { send(new Command( element instanceof Node ? Command.Name.SET_NODE_NAME : Command.Name.SET_EDGE_NAME, delegateDiagram.getName(), new Object[] {element.getId(), name}, makeRemote(source)), element); } @Override public void setNotes(DiagramTreeNode treeNode, String notes, DiagramEventSource source) { TreeNode[] path = treeNode.getPath(); Object[] args = new Object[path.length]; for(int i=0;i<path.length-1;i++){ args[i] = delegateDiagram.getTreeModel().getIndexOfChild(path[i], path[i+1]); } args[args.length-1] = notes; Command cmd = new Command(Command.Name.SET_NOTES, delegateDiagram.getName(),args,makeRemote(source)); send(cmd,treeNode); } @Override public void setProperty(Node node, String type, int index, String value, DiagramEventSource source) { send(new Command(Command.Name.SET_PROPERTY, delegateDiagram.getName(), new Object[] {node.getId(),type,index,value}, makeRemote(source)), node ); } @Override public void setProperties(Node node, NodeProperties properties, DiagramEventSource source) { send(new Command(Command.Name.SET_PROPERTIES, delegateDiagram.getName(), new Object[] {node.getId(),properties.toString()}, makeRemote(source)), node ); } @Override public void clearProperties(Node node, DiagramEventSource source) { send(new Command(Command.Name.CLEAR_PROPERTIES, delegateDiagram.getName(), new Object[] {node.getId()}, makeRemote(source)), node ); } @Override public void addProperty(Node node, String type, String value, DiagramEventSource source) { send(new Command(Command.Name.ADD_PROPERTY, delegateDiagram.getName(), new Object[] {node.getId(),type,value}, makeRemote(source)), node ); } @Override public void removeProperty(Node node, String type, int index, DiagramEventSource source) { send(new Command(Command.Name.REMOVE_PROPERTY, delegateDiagram.getName(), new Object[] {node.getId(),type,index}, makeRemote(source)), node ); } @Override public void setModifiers(Node node, String type, int index, Set<Integer> modifiers, DiagramEventSource source) { Object args[] = new Object[modifiers.size()+3]; args[0] = node.getId(); args[1] = type; args[2] = index; int i = 0; for(Integer I : modifiers){ args[i+3] = I; i++; } send(new Command(Command.Name.SET_MODIFIERS, delegateDiagram.getName(),args,makeRemote(source)),node); } @Override public void setEndLabel(Edge edge, Node node, String label, DiagramEventSource source) { send(new Command(Command.Name.SET_ENDLABEL, delegateDiagram.getName(), new Object[] {edge.getId(), node.getId(), label},makeRemote(source)), edge ); } @Override public void setEndDescription(Edge edge, Node node, int index, DiagramEventSource source) { send(new Command(Command.Name.SET_ENDDESCRIPTION, delegateDiagram.getName(), new Object[] {edge.getId(), node.getId(), index},makeRemote(source)), edge ); } @Override public void translate(GraphElement ge, Point2D p, double dx, double dy, DiagramEventSource source) { double px = 0; double py = 0; if(p != null){ px = p.getX(); py = p.getY(); } if(ge instanceof Node){ Node n = (Node)ge; send(new Command(Command.Name.TRANSLATE_NODE, delegateDiagram.getName(), new Object[] {n.getId(), px, py, dx,dy},makeRemote(source) ),n); }else{ Edge e = (Edge)ge; send(new Command(Command.Name.TRANSLATE_EDGE, delegateDiagram.getName(), new Object[] {e.getId(), px, py, dx,dy},makeRemote(source) ),e); } } @Override public void startMove(GraphElement ge, Point2D p, DiagramEventSource source) { /* Store internally the point the motion started from and send a unique message * * to the server when the edge is actually bended. This is because the lock will be * * asked only when the mouse motion actually starts, whereas this call is done when * * the edge is clicked down. So this variable is non null only when the first * * bend-message is sent */ edgeStartMovePoint = p; } @Override public void bend(Edge edge, Point2D p, DiagramEventSource source) { /* send informations about the starting point only at the first time */ if(edgeStartMovePoint == null) send(new Command(Command.Name.BEND, delegateDiagram.getName(), new Object[] {edge.getId(),p.getX(),p.getY()}, makeRemote(source)), edge); else{ send(new Command(Command.Name.BEND, delegateDiagram.getName(), new Object[] {edge.getId(),p.getX(),p.getY(), edgeStartMovePoint.getX(),edgeStartMovePoint.getY()}, makeRemote(source)), edge); edgeStartMovePoint = null; } } @Override public void stopMove(GraphElement ge, DiagramEventSource source) { if(ge instanceof Node){ Node n = (Node)ge; send(new Command(Command.Name.STOP_NODE_MOVE, delegateDiagram.getName(), new Object[] {n.getId()},makeRemote(source)), n); }else{ Edge e = (Edge)ge; send(new Command(Command.Name.STOP_EDGE_MOVE, delegateDiagram.getName(), new Object[] {e.getId()},makeRemote(source)), e); } } /* source passed as argument to the updater methods have are local and with no id * since this source has to be sent to the server, it must be set as non local * (constructor will do) and the is must be set as well */ private DiagramEventSource makeRemote(DiagramEventSource src){ return new DiagramEventSource(src); } private Point2D edgeStartMovePoint; } private static class RemoteHostDiagram extends NetDiagram{ /** * This class wraps an existing diagram into a network diagram. * The network diagrams returns a TreeModelNetWrap and a CollectionModelNetWrap * when the relative getters are called * * @param diagram the diagram to wrap * @param connectionManager a connected socket channel */ private RemoteHostDiagram(Diagram diagram, ClientConnectionManager connectionManager,SocketChannel channel){ super(diagram); this.channel = channel; this.connectionManager = connectionManager; } @Override protected void send(Command cmd, DiagramElement element) { connectionManager.addRequest(new ClientConnectionManager.SendCmdRequest(cmd, channel, element )); } @Override protected void send(Command cmd, DiagramTreeNode treeNode) { connectionManager.addRequest(new ClientConnectionManager.SendTreeCmdRequest(cmd, channel, treeNode )); } @Override protected void send(LockMessage lockMessage){ connectionManager.addRequest(new ClientConnectionManager.SendLockRequest(channel, lockMessage)); } @Override protected void send(AwarenessMessage awMsg){ connectionManager.addRequest(new ClientConnectionManager.SendAwarenessRequest(channel,awMsg)); } @Override protected boolean receiveLockAnswer(){ ClientConnectionManager.Answer answer = connectionManager.getAnswer(); /* diagram has been reverted while waiting for a lock answer : the answer is gonna be yes * * then, as the client is no longer connected to the server and there is no more locking in place */ if(answer instanceof ClientConnectionManager.RevertedDiagramAnswer) return true; LockMessage.Name name = ((LockAnswer)answer).message.getName(); switch(name){ case YES_L : return true; case NO_L : return false; default : throw new RuntimeException("message not recognized: "+name.toString()); } } @Override public String getLabel(){ return new StringBuilder(getName()) .append(' ').append('@').append(' ') .append(channel.socket().getInetAddress().getHostAddress()) .toString(); } @Override public SocketChannel getSocketChannel(){ return channel; } @Override public void enableAwareness(AwarenessPanel panel){ connectionManager.getAwarenessPanelEditor().addAwarenessPanel(panel); } @Override public void disableAwareness(AwarenessPanel panel){ connectionManager.getAwarenessPanelEditor().removeAwarenessPanel(panel); } @Override public Object clone(){ throw new UnsupportedOperationException(); } private SocketChannel channel; private ClientConnectionManager connectionManager; } private static class LocalHostDiagram extends NetDiagram { private LocalHostDiagram(Diagram diagram, SocketChannel channel, Queue<DiagramElement> diagramElements, ExceptionHandler handler) { super(diagram); this.channel = channel; this.diagramElements = diagramElements; this.exceptionHandler = handler; this.protocol = ProtocolFactory.newInstance(); } @Override protected void send(Command cmd, DiagramElement element){ switch(cmd.getName()){ case INSERT_NODE : case INSERT_EDGE : case REMOVE_NODE : case REMOVE_EDGE : diagramElements.add(element); break; } try{ protocol.send(channel, cmd); }catch(IOException ioe){ switch(cmd.getName()){ case INSERT_NODE : case INSERT_EDGE : case REMOVE_NODE : case REMOVE_EDGE : diagramElements.remove(element); break; } exceptionHandler.handleException(ioe); } } @Override protected void send(LockMessage lockMessage) { try { protocol.send(channel, lockMessage); } catch (IOException ioe) { exceptionHandler.handleException(ioe); } } @Override protected void send(AwarenessMessage awMsg){ try { protocol.send(channel, awMsg); } catch (IOException ioe) { exceptionHandler.handleException(ioe); } } @Override protected boolean receiveLockAnswer(){ LockMessage answer; try { answer = protocol.receiveLockMessage(channel); } catch (IOException ioe) { exceptionHandler.handleException(ioe); return false; } switch((LockMessage.Name)answer.getName()){ case YES_L : return true; case NO_L : return false; default : throw new RuntimeException("message not recognized: "+answer.getName().toString()); } } @Override protected void send(Command cmd , DiagramTreeNode treeNode){ try { protocol.send(channel, cmd); } catch (IOException ioe) { exceptionHandler.handleException(ioe); } } @Override public String getLabel(){ return getName()+LOCALHOST_STRING; } @Override public SocketChannel getSocketChannel(){ return channel; } @Override public void enableAwareness(AwarenessPanel panel){ Server.getServer().getAwarenessPanelEditor().addAwarenessPanel(panel); } @Override public void disableAwareness(AwarenessPanel panel){ Server.getServer().getAwarenessPanelEditor().removeAwarenessPanel(panel); } private SocketChannel channel; private Queue<DiagramElement> diagramElements; private Protocol protocol; private ExceptionHandler exceptionHandler; } }