view java/src/uk/ac/qmul/eecs/ccmi/network/OscProtocol.java @ 0:78b7fc5391a2

first import, outcome of NIME 2014 hackaton
author Fiore Martin <f.martin@qmul.ac.uk>
date Tue, 08 Jul 2014 16:28:59 +0100
parents
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.io.IOException;
import java.net.SocketException;
import java.nio.ByteBuffer;
import java.nio.channels.SocketChannel;
import java.util.ResourceBundle;

import uk.ac.qmul.eecs.ccmi.gui.DiagramEventSource;

import de.sciss.net.OSCBundle;
import de.sciss.net.OSCMessage;

/*
 * An implementation of the Protocol interface which uses OSC messages 
 * streamed on a TCP connection. 
 *
 */
class OscProtocol implements Protocol {

	OscProtocol(){
		buffer = ByteBuffer.allocate(DEFAULT_CAPACITY);
		codec = new CCmIOSCPacketCodec();
	}
	
	@Override
	public void send(SocketChannel channel, Command cmd) throws IOException {
		/* OSC message args = [diagram, cmd.arg1, cmd.arg2, amd.arg2, ... , cmd.argN  ] */
		bundle = new CCmIOSCBundle(cmd.getTimestamp());
		Object[] args = new Object[2+cmd.getArgNum()];
		args[0] = cmd.getDiagram();
		args[1] = cmd.getSource().toString();
		for(int i=0; i<cmd.getArgNum();i++)
			args[i+2] = cmd.getArgAt(i);
		
		bundle.addPacket(new OSCMessage(OSC_NAME_PREFIX+cmd.getName().toString(),args));
		try{
			writeBundle(channel);
		}catch(IOException u){
			throw new IOException(
					/* give a more user friendly message */
					ResourceBundle.getBundle(Server.class.getName()).getString("dialog.error.no_send"),u);
		}
	}
	
	@Override
	public void send(SocketChannel channel, Reply reply) throws IOException {
		/* OSC message args = [messageLen, diagram, message ] */
		bundle = new CCmIOSCBundle(reply.getTimestamp());
		bundle.addPacket(new OSCMessage(
				OSC_NAME_PREFIX+reply.getName().toString(), 
				new Object[] {reply.getMessageLen(), reply.getDiagram() ,reply.getMessage(),reply.getSource().toString()}
				));
		try{
			writeBundle(channel);
		}catch(IOException u){
			throw new IOException(
					/* give a more user friendly message */
					ResourceBundle.getBundle(Server.class.getName()).getString("dialog.error.no_send"),u);
		}
	}
	
	@Override
	public void send(SocketChannel channel, LockMessage lockMessage) throws IOException {
		/* OSC message args = [diagram, source, lock.arg1, lock.arg2, lock.arg2, ... , lock.argN  ] */
		bundle = new CCmIOSCBundle(lockMessage.getTimestamp());
		Object[] args = new Object[2+lockMessage.getArgNum()];
		args[0] = lockMessage.getDiagram();
		args[1] = lockMessage.getSource().toString();
		for(int i=0; i<lockMessage.getArgNum();i++)
			args[i+2] = lockMessage.getArgAt(i);
		
		bundle.addPacket(new OSCMessage(
				OSC_NAME_PREFIX+lockMessage.getName().toString(), 
				args));
		try{
			writeBundle(channel);
		}catch(IOException u){
			throw new IOException(
					/* give a more user friendly message */
					ResourceBundle.getBundle(Server.class.getName()).getString("dialog.error.no_send"),u);
		}
	}
	
	@Override
	public void send(SocketChannel channel, AwarenessMessage awareMsg) throws IOException {
		/* OSC message args = [diagram name, siagram event action source]*/
		bundle = new CCmIOSCBundle(awareMsg.getTimestamp());
		Object[] args = new Object[2];
		args[0] = awareMsg.getDiagram();
		args[1] = awareMsg.getSource().toString();
		
		bundle.addPacket(new OSCMessage(
				OSC_NAME_PREFIX+awareMsg.getName().toString(),
				args));
		try{
			writeBundle(channel);
		}catch(IOException u){
			throw new IOException(
					/* give a more user friendly message */
					ResourceBundle.getBundle(Server.class.getName()).getString("dialog.error.no_send"),u);
		}
	}

//	@Override
//	public Command receiveCommand(SocketChannel channel) throws IOException {
//		OSCBundle bundle = readBundle(channel);
//		OSCMessage oscMessage = (OSCMessage)bundle.getPacket(0);
//		String name = oscMessage.getName();
//		assert(name.startsWith(""+OSC_NAME_PREFIX));
//		name = name.substring(1); // chop off the trailing '/'
//		Object args[] = new Object[oscMessage.getArgCount()-CMD_OFFSET];			 
//		for(int i=0; i< args.length;i++)
//			args[i] = oscMessage.getArg(i+CMD_OFFSET);
//		return new Command(
//				Command.valueOf(name),
//				(String)oscMessage.getArg(CMD_DIAGRAM_INDEX),
//				args,
//				bundle.getTimeTag(),
//				DiagramEventSource.valueOf((String)oscMessage.getArg(CMD_SOURCE_INDEX))
//		);
//	}
	
	@Override
	public Reply receiveReply(SocketChannel channel) throws IOException {
		OSCBundle bundle = readBundle(channel);
		OSCMessage oscMessage = (OSCMessage)bundle.getPacket(0);
		String name = oscMessage.getName();
		assert(name.startsWith(""+OSC_NAME_PREFIX));
		name = name.substring(1); // chop off the trailing '/'
		@SuppressWarnings("unused")
		Integer len = (Integer)oscMessage.getArg(REPLY_LEN_INDEX); // of no use at the moment
		return new Reply(
				Reply.valueOf(name),
				(String)oscMessage.getArg(REPLY_DIAGRAM_INDEX),
				(String)oscMessage.getArg(REPLY_MESSAGE_INDEX),
				bundle.getTimeTag(),
				DiagramEventSource.valueOf((String)oscMessage.getArg(REPLY_SOURCE_INDEX)));
	}
	
	@Override
	public LockMessage receiveLockMessage(SocketChannel channel) throws IOException {
		OSCBundle bundle = readBundle(channel);
		OSCMessage oscMessage = (OSCMessage)bundle.getPacket(0);
		String name = oscMessage.getName();
		name = name.substring(1); // chop off the trailing '/'
		Object args[] = new Object[oscMessage.getArgCount()-LOCK_OFFSET];			 
		for(int i=0; i< args.length;i++)
			args[i] = oscMessage.getArg(i+LOCK_OFFSET);
		return new LockMessage(
				LockMessage.valueOf(name),
				bundle.getTimeTag(),
				(String)oscMessage.getArg(LOCK_DIAGRAM_INDEX),
				args,
				DiagramEventActionSource.valueOf((String)oscMessage.getArg(LOCK_SOURCE_INDEX))
				);
	}
	
	public Message receiveMessage(SocketChannel channel) throws IOException {
		OSCBundle bundle = readBundle(channel);
		OSCMessage oscMessage = (OSCMessage)bundle.getPacket(0);
		String name = oscMessage.getName();
		assert(name.startsWith(""+OSC_NAME_PREFIX));
		name = name.substring(1); // chop off the trailing '/'
		if(name.endsWith(Reply.NAME_POSTFIX)){ // it's a reply 
			@SuppressWarnings("unused")
			Integer len = (Integer)oscMessage.getArg(REPLY_LEN_INDEX); // of no use at the moment
			Reply reply = new Reply(
					Reply.valueOf(name),
					(String)oscMessage.getArg(REPLY_DIAGRAM_INDEX),
					(String)oscMessage.getArg(REPLY_MESSAGE_INDEX),
					bundle.getTimeTag(),
					DiagramEventSource.valueOf((String)oscMessage.getArg(REPLY_SOURCE_INDEX)));
			return reply;
		}else if (name.endsWith(LockMessage.NAME_POSTFIX)){
			Object args[] = new Object[oscMessage.getArgCount()-LOCK_OFFSET];			 
			for(int i=0; i< args.length;i++)
				args[i] = oscMessage.getArg(i+LOCK_OFFSET);
			return new LockMessage(
					LockMessage.valueOf(name),
					bundle.getTimeTag(),
					(String)oscMessage.getArg(LOCK_DIAGRAM_INDEX),
					args,
					DiagramEventActionSource.valueOf((String)oscMessage.getArg(LOCK_SOURCE_INDEX)) 
					);
		}else if(name.endsWith(AwarenessMessage.NAME_POSTFIX)){ // it's an awareness message 
			AwarenessMessage.Name awName = AwarenessMessage.valueOf(name);
			if(awName == AwarenessMessage.Name.USERNAME_A || awName == AwarenessMessage.Name.ERROR_A){
				return new AwarenessMessage(awName,
						(String)oscMessage.getArg(AWAR_DIAGRAM_INDEX),
						(String)oscMessage.getArg(AWAR_SOURCE_INDEX)
					);
			}else {
				return new AwarenessMessage(awName,
						(String)oscMessage.getArg(AWAR_DIAGRAM_INDEX),
						DiagramEventActionSource.valueOf((String)oscMessage.getArg(AWAR_SOURCE_INDEX))
					);
			}
		}else{ // it's a command
			Object args[] = new Object[oscMessage.getArgCount()-CMD_OFFSET];
			for(int i=0; i< args.length;i++)
				args[i] = oscMessage.getArg(i+CMD_OFFSET);
			return new Command(
					Command.valueOf(name),
					(String)oscMessage.getArg(CMD_DIAGRAM_INDEX),
					args,
					bundle.getTimeTag(),
					DiagramEventSource.valueOf((String)oscMessage.getArg(CMD_SOURCE_INDEX))
			);
		}
	}
	
	private OSCBundle readBundle(SocketChannel channel) throws IOException{
		/* read the size of the OSC packet, first 4 bytes according to OSC specs */
		buffer.rewind().limit(4);
		while( buffer.hasRemaining() )
			if( channel.read( buffer ) == -1 )
				throw new SocketException(ResourceBundle.getBundle(Server.class.getName()).getString("error.connection_close"));
			
		buffer.rewind();
		int packetSize = buffer.getInt();
		assert(packetSize > 0 );
		ByteBuffer b = buffer;
		/* if the packet is very big we must allocate an ad hoc temporary big big buffer */
		if(packetSize <= DEFAULT_CAPACITY)
			b.rewind().limit(packetSize);
		else	
			b = ByteBuffer.allocate(packetSize);
		/* read the packet, it must be a bundle containing only one message */
		while( b.hasRemaining() )
			if( channel.read( b ) == -1 ) 
				throw new SocketException(ResourceBundle.getBundle(Server.class.getName()).getString("error.connection_close"));
		b.rewind();
		return (OSCBundle)codec.decode(b);
	}
	
	private void writeBundle(SocketChannel channel) throws IOException{
		ByteBuffer b = buffer;
		buffer.clear();
		if(bundle.getSize() + 4 > DEFAULT_CAPACITY){
			b = ByteBuffer.allocate(bundle.getSize() + 4);
		}
		b.position(4);
		bundle.encode(codec,b);
		int len = b.position() - 4;
		b.putInt(0, len);
		b.flip();
		channel.write(b);
	}
	
	ByteBuffer buffer;
	CCmIOSCBundle bundle;
	CCmIOSCPacketCodec codec;
	
	private static final int DEFAULT_CAPACITY = 1024;
	private static final char OSC_NAME_PREFIX = '/';
	
	
	private static final int REPLY_LEN_INDEX = 0;
	/* position of the diagram Name in the OSC message */
	private static final int REPLY_DIAGRAM_INDEX = 1;
	private static final int REPLY_SOURCE_INDEX = 3;
	private static final int CMD_DIAGRAM_INDEX = 0;
	private static final int CMD_SOURCE_INDEX = 1;
	private static final int CMD_OFFSET = 2;
	private static final int LOCK_DIAGRAM_INDEX = 0;
	private static final int LOCK_SOURCE_INDEX = 1;
	private static final int LOCK_OFFSET = 2;
	private static final int AWAR_DIAGRAM_INDEX = 0;
	private static final int AWAR_SOURCE_INDEX = 1;
	/* ------------------------------------------------*/
	private static final int REPLY_MESSAGE_INDEX = 2;
}