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