/*
* Copyright (c) 2014 tabletoptool.com team.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the GNU Public License v3.0
* which accompanies this distribution, and is available at
* http://www.gnu.org/licenses/gpl.html
*
* Contributors:
* rptools.com team - initial implementation
* tabletoptool.com team - further development
*/
package com.t3.clientserver.connection;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.CopyOnWriteArrayList;
import com.t3.clientserver.connection.ActivityListener.Direction;
import com.t3.clientserver.connection.ActivityListener.State;
import com.t3.clientserver.handler.DisconnectHandler;
import com.t3.clientserver.handler.MessageHandler;
/**
* @author drice
*
* TODO To change the template for this generated type comment go to
* Window - Preferences - Java - Code Style - Code Templates
*/
public abstract class AbstractConnection {
// We don't need to make each list synchronized since the class is synchronized
protected Map<Object, List<byte[]>> outQueueMap = new HashMap<Object, List<byte[]>>();
protected List<List<byte[]>> outQueueList = new LinkedList<List<byte[]>>();
protected List<MessageHandler> messageHandlers = new CopyOnWriteArrayList<MessageHandler>();
protected List<ActivityListener> listeners = new CopyOnWriteArrayList<ActivityListener>();
protected List<DisconnectHandler> disconnectHandlers = new CopyOnWriteArrayList<DisconnectHandler>();
public final void addMessageHandler(MessageHandler handler) {
messageHandlers.add(handler);
}
public final void removeMessageHandler(MessageHandler handler) {
messageHandlers.remove(handler);
}
protected final void dispatchMessage(String id, byte[] message) {
for (MessageHandler handler : messageHandlers) {
handler.handleMessage(id, message);
}
}
public synchronized void addMessage(byte[] message) {
addMessage(null, message);
}
public synchronized void addMessage(Object channel, byte[] message) {
List<byte[]> queue = getOutQueue(channel);
queue.add(message);
// Queue up for sending
outQueueList.add(queue);
}
protected List<byte[]> getOutQueue(Object channel) {
// Ordinarily I would synchronize this method, but I imagine the channels will be initialized once
// at the beginning of execution. Thus get(channel) will only return once right at the beginning
// no sense incurring the cost of synchronizing the method on the class for that.
List<byte[]> queue = outQueueMap.get(channel);
if (queue == null) {
queue = Collections.synchronizedList(new ArrayList<byte[]>());
outQueueMap.put(channel, queue);
}
return queue;
}
public synchronized boolean hasMoreMessages() {
return !outQueueList.isEmpty();
}
public synchronized byte[] nextMessage() {
if (!hasMoreMessages()) {
return null;
}
List<byte[]> queue = outQueueList.remove(0);
byte[] message = queue.remove(0);
if (!queue.isEmpty()) {
outQueueList.add(queue);
}
return message;
}
public final void fireDisconnect() {
for (DisconnectHandler handler : disconnectHandlers) {
handler.handleDisconnect(this);
}
}
public final void addActivityListener(ActivityListener listener) {
listeners.add(listener);
}
public final void removeActivityListener(ActivityListener listener) {
listeners.remove(listener);
}
public final void addDisconnectHandler(DisconnectHandler handler) {
disconnectHandlers.add(handler);
}
public final void removeDisconnectHandler(DisconnectHandler handler) {
disconnectHandlers.remove(handler);
}
protected final void notifyListeners(Direction direction, State state, int totalTransferSize, int currentTransferSize) {
for (ActivityListener listener : listeners) {
listener.notify(direction, state, totalTransferSize, currentTransferSize);
}
}
///////////////////////////////////////////////////////////////////////////
// static helper methods
///////////////////////////////////////////////////////////////////////////
protected final void writeMessage(OutputStream out, byte[] message) throws IOException {
int length = message.length;
notifyListeners(Direction.Outbound, State.Start, length, 0);
out.write(length >> 24);
out.write(length >> 16);
out.write(length >> 8);
out.write(length);
for (int i = 0; i < message.length; i++) {
out.write(message[i]);
if (i != 0 && i % ActivityListener.CHUNK_SIZE == 0) {
notifyListeners(Direction.Outbound, State.Progress, length, i);
}
}
out.flush();
notifyListeners(Direction.Outbound, State.Complete, length, length);
}
protected final byte[] readMessage(InputStream in) throws IOException {
int b32 = in.read();
int b24 = in.read();
int b16 = in.read();
int b8 = in.read();
if (b32 < 0) {
throw new IOException ("Stream closed");
}
int length = (b32 << 24) + (b24 << 16) + (b16 << 8) + b8;
notifyListeners(Direction.Inbound, State.Start, length, 0);
byte[] ret = new byte[length];
for (int i = 0; i < length; i++) {
ret[i] = (byte) in.read();
if (i != 0 && i % ActivityListener.CHUNK_SIZE == 0) {
notifyListeners(Direction.Inbound, State.Progress, length, i);
}
}
notifyListeners(Direction.Inbound, State.Complete, length, length);
return ret;
}
}