/**
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
*/
package com.ubergeek42.weechat.relay.connection;
import com.ubergeek42.weechat.relay.RelayMessage;
import com.ubergeek42.weechat.relay.protocol.Data;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.concurrent.LinkedBlockingQueue;
import static org.junit.Assert.*;
public abstract class AbstractConnection implements Connection {
protected final static boolean DEBUG = false;
protected static Logger logger = LoggerFactory.getLogger("AbstractConnection");
private static int iteration = 0;
private volatile STATE state = STATE.UNKNOWN;
private Observer observer = null;
protected OutputStream out = null;
protected InputStream in = null;
protected Thread connector = null;
protected Thread reader = null;
protected Thread writer = null;
//////////////////////////////////////////////////////////////////////////////////////////////// interface
@Override public STATE getState() {
return state;
}
@Override public void connect() {
assertEquals(state, STATE.UNKNOWN);
assertNull(connector);
final int i = iteration++;
state = STATE.CONNECTING;
observer.onStateChanged(STATE.CONNECTING);
connector = new Thread(new Runnable() {@Override public void run() {hi(); connectOnce(i); bye();}});
connector.setName("con" + i);
connector.start();
}
@Override synchronized public void disconnect() {
logger.debug("disconnect()");
assertNotEquals(state, STATE.UNKNOWN);
assertNotNull(connector);
if (state == STATE.DISCONNECTED) return;
state = STATE.DISCONNECTED;
observer.onStateChanged(STATE.DISCONNECTED);
doDisconnect();
}
@Override public void setObserver(Observer observer) {
this.observer = observer;
}
//////////////////////////////////////////////////////////////////////////////////////////////// override me!
protected abstract void doConnect() throws Exception;
protected void doDisconnect() {
connector.interrupt();
if (reader != null) reader.interrupt();
if (writer != null) writer.interrupt();
}
protected void startReader(final int i) {
reader = new Thread(new Runnable() {@Override public void run() {hi(); readLoop(); bye();}});
reader.setName("r" + i);
reader.start();
}
protected void startWriter(final int i) {
writer = new Thread(new Runnable() {@Override public void run() {hi(); writeLoop(); bye();}});
writer.setName("wr" + i);
writer.start();
}
@Override public void sendMessage(String string) {
outbox.add(string);
}
//////////////////////////////////////////////////////////////////////////////////////////////// connect
private void connectOnce(final int i) {
try {
doConnect();
state = STATE.CONNECTED;
} catch (Exception e) {
if (state != STATE.DISCONNECTED) {
logger.error("connectOnce(): exception while state == " + state, e);
observer.onException(e);
}
disconnect();
return;
}
observer.onStateChanged(STATE.CONNECTED);
startReader(i);
startWriter(i);
}
//////////////////////////////////////////////////////////////////////////////////////////////// send
private LinkedBlockingQueue<String> outbox = new LinkedBlockingQueue<>();
private void writeLoop() {
try {
//noinspection InfiniteLoopStatement
while (true) {
out.write(outbox.take().getBytes());
}
} catch (InterruptedException | IOException e) {
if (state == STATE.DISCONNECTED) return;
logger.error("writeLoop(): exception while state == " + state, e);
} finally {
try {out.close();}
catch (IOException ignored) {}
}
}
//////////////////////////////////////////////////////////////////////////////////////////////// receive
private final static int HEADER_LENGTH = 4;
private void readLoop() {
byte[] data;
try {
//noinspection InfiniteLoopStatement
while (true) {
data = new byte[HEADER_LENGTH];
readAll(data, 0);
data = enlarge(data, new Data(data).getUnsignedInt());
readAll(data, HEADER_LENGTH);
observer.onMessage(new RelayMessage(data));
}
} catch (IOException | StreamClosed e) {
if (state == STATE.DISCONNECTED) return;
logger.error("readLoop(): exception while state == " + state, e);
observer.onException(e);
} finally {
try {in.close();}
catch (IOException ignored) {}
disconnect();
}
}
public static class StreamClosed extends Exception {}
private void readAll(byte[] data, int startAt) throws IOException, StreamClosed {
for (int pos = startAt; pos != data.length;) {
int read = in.read(data, pos, data.length - pos);
if (read == -1) throw new StreamClosed();
pos += read;
}
}
private static byte[] enlarge(byte[] in, int size) {
byte[] out = new byte[size];
System.arraycopy(in, 0, out, 0, in.length);
return out;
}
//////////////////////////////////////////////////////////////////////////////////////////////// receive
private void hi() {logger.debug("hi");}
private void bye() {logger.debug("bye");}
}