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