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