package com.dronecontrol.socketcontrol.input.socket;
import com.google.common.collect.Sets;
import org.apache.log4j.Logger;
import org.xsocket.connection.IConnectHandler;
import org.xsocket.connection.IDataHandler;
import org.xsocket.connection.IDisconnectHandler;
import org.xsocket.connection.INonBlockingConnection;
import org.xsocket.connection.NonBlockingConnection;
import java.io.IOException;
import java.nio.BufferUnderflowException;
import java.util.Set;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
import static com.google.common.base.Preconditions.checkState;
public class SocketClient implements IConnectHandler, IDisconnectHandler, IDataHandler
{
private static final int RECONNECT_TIMEOUT_SECONDS = 5;
private static final String LINE_TERMINATION_STRING = "\n";
private static final String LINE_TERMINATION_PATTERN = "\\n";
private final Logger logger = Logger.getLogger(SocketClient.class);
private final ScheduledExecutorService worker;
private final Set<SocketClientDataListener> dataListeners;
private INonBlockingConnection socketConnection;
private String hostName;
private int port;
private boolean connected;
public SocketClient()
{
worker = Executors.newSingleThreadScheduledExecutor();
dataListeners = Sets.newCopyOnWriteArraySet();
}
public void connect(String hostName, int port)
{
checkState(!connected, "The socket client is already connected");
this.hostName = hostName;
this.port = port;
connected = false;
startConnecting();
}
private void startConnecting()
{
if (connected)
{
return;
}
try
{
logger.info(String.format("Connecting to socket server at %s:%d", hostName, port));
socketConnection = new NonBlockingConnection(hostName, port, this);
} catch (IOException e)
{
logger.info(String.format("Connection to socket server failed - retry in %d seconds", RECONNECT_TIMEOUT_SECONDS));
tryReconnect();
}
}
public void tryReconnect()
{
Runnable task = new Runnable()
{
public void run()
{
startConnecting();
}
};
worker.schedule(task, RECONNECT_TIMEOUT_SECONDS, TimeUnit.SECONDS);
}
@Override
public boolean onConnect(INonBlockingConnection iNonBlockingConnection) throws IOException, BufferUnderflowException
{
logger.info("Connection to socket server established");
connected = true;
return true;
}
@Override
public boolean onDisconnect(INonBlockingConnection iNonBlockingConnection) throws IOException
{
logger.info(String.format("Connection to socket server disconnected - retrying connect in %d seconds", RECONNECT_TIMEOUT_SECONDS));
connected = false;
tryReconnect();
return true;
}
@Override
public boolean onData(INonBlockingConnection connection)
{
String[] messages = getMessagesFromConnection(connection);
if (messages != null)
{
invokeCallbacksForMessages(messages);
}
return true;
}
private String[] getMessagesFromConnection(INonBlockingConnection connection)
{
String[] messages = null;
try
{
String content = connection.readStringByDelimiter(LINE_TERMINATION_STRING);
messages = content.split(LINE_TERMINATION_PATTERN);
} catch (Exception e)
{
logger.warn(String.format("Receiving data failed: %s", e.getMessage()));
}
return messages;
}
private void invokeCallbacksForMessages(String[] messages)
{
for (String message : messages)
{
for (SocketClientDataListener callback : dataListeners)
{
callback.OnData(message);
}
}
}
@SuppressWarnings("UnusedDeclaration")
public void send(String content)
{
try
{
socketConnection.write(content + LINE_TERMINATION_STRING);
} catch (IOException e)
{
logger.warn(String.format("Sending data failed: %s", e.getMessage()));
}
}
public void disconnect()
{
try
{
if (!connected)
{
return;
}
socketConnection.close();
} catch (IOException e)
{
logger.warn(String.format("Disconnecting from socket server failed: %s", e.getMessage()));
}
}
public synchronized void addDataListener(SocketClientDataListener callback)
{
dataListeners.add(callback);
}
public synchronized void removeDataListener(SocketClientDataListener callback)
{
dataListeners.remove(callback);
}
public boolean isConnected()
{
return connected;
}
}