package rocks.inspectit.shared.all.kryonet;
import static com.esotericsoftware.minlog.Log.DEBUG;
import static com.esotericsoftware.minlog.Log.ERROR;
import static com.esotericsoftware.minlog.Log.debug;
import static com.esotericsoftware.minlog.Log.error;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
/**
* Used to be notified about connection events.
* <p>
* <b>IMPORTANT:</b> The class code is copied/taken/based from
* <a href="https://github.com/EsotericSoftware/kryonet">kryonet</a>. Original author is Nathan
* Sweet. License info can be found
* <a href="https://github.com/EsotericSoftware/kryonet/blob/master/license.txt">here</a>.
*/
@SuppressWarnings({ "all", "unchecked" })
// NOCHKALL
public class Listener {
/**
* Called when the remote end has been connected. This will be invoked before any objects are
* received by {@link #received(Connection, Object)}. This will be invoked on the same thread as
* {@link Client#update(int)} and {@link Server#update(int)}. This method should not block for
* long periods as other network activity will not be processed until it returns.
*/
public void connected(Connection connection) {
}
/**
* Called when the remote end is no longer connected. There is no guarantee as to what thread
* will invoke this method.
*/
public void disconnected(Connection connection) {
}
/**
* Called when an object has been received from the remote end of the connection. This will be
* invoked on the same thread as {@link Client#update(int)} and {@link Server#update(int)}. This
* method should not block for long periods as other network activity will not be processed
* until it returns.
*/
public void received(Connection connection, Object object) {
}
/**
* Called when the connection is below the {@link Connection#setIdleThreshold(float) idle
* threshold}.
*/
public void idle(Connection connection) {
}
/**
* Uses reflection to called "received(Connection, XXX)" on the listener, where XXX is the
* received object type. Note this class uses a HashMap lookup and (cached) reflection, so is
* not as efficient as writing a series of "instanceof" statements.
*/
static public class ReflectionListener extends Listener {
private final HashMap<Class, Method> classToMethod = new HashMap();
@Override
public void received(Connection connection, Object object) {
Class type = object.getClass();
Method method = classToMethod.get(type);
if (method == null) {
if (classToMethod.containsKey(type)) {
return; // Only fail on the first attempt to find the method.
}
try {
method = getClass().getMethod("received", new Class[] { Connection.class, type });
} catch (SecurityException ex) {
if (ERROR) {
error("kryonet", "Unable to access method: received(Connection, " + type.getName() + ")", ex);
}
return;
} catch (NoSuchMethodException ex) {
if (DEBUG) {
debug("kryonet", "Unable to find listener method: " + getClass().getName() + "#received(Connection, " + type.getName() + ")");
}
return;
} finally {
classToMethod.put(type, method);
}
}
try {
method.invoke(this, connection, object);
} catch (Throwable ex) {
if ((ex instanceof InvocationTargetException) && (ex.getCause() != null)) {
ex = ex.getCause();
}
if (ex instanceof RuntimeException) {
throw (RuntimeException) ex;
}
throw new RuntimeException("Error invoking method: " + getClass().getName() + "#received(Connection, " + type.getName() + ")", ex);
}
}
}
/**
* Wraps a listener and queues notifications as {@link Runnable runnables}. This allows the
* runnables to be processed on a different thread, preventing the connection's update thread
* from being blocked.
*/
static public abstract class QueuedListener extends Listener {
final Listener listener;
public QueuedListener(Listener listener) {
if (listener == null) {
throw new IllegalArgumentException("listener cannot be null.");
}
this.listener = listener;
}
@Override
public void connected(final Connection connection) {
queue(new Runnable() {
@Override
public void run() {
listener.connected(connection);
}
});
}
@Override
public void disconnected(final Connection connection) {
queue(new Runnable() {
@Override
public void run() {
listener.disconnected(connection);
}
});
}
@Override
public void received(final Connection connection, final Object object) {
queue(new Runnable() {
@Override
public void run() {
listener.received(connection, object);
}
});
}
@Override
public void idle(final Connection connection) {
queue(new Runnable() {
@Override
public void run() {
listener.idle(connection);
}
});
}
abstract protected void queue(Runnable runnable);
}
/** Wraps a listener and processes notification events on a separate thread. */
static public class ThreadedListener extends QueuedListener {
protected final ExecutorService threadPool;
/** Creates a single thread to process notification events. */
public ThreadedListener(Listener listener) {
this(listener, Executors.newFixedThreadPool(1));
}
/** Uses the specified threadPool to process notification events. */
public ThreadedListener(Listener listener, ExecutorService threadPool) {
super(listener);
if (threadPool == null) {
throw new IllegalArgumentException("threadPool cannot be null.");
}
this.threadPool = threadPool;
}
@Override
public void queue(Runnable runnable) {
threadPool.execute(runnable);
}
}
/**
* Delays the notification of the wrapped listener to simulate lag on incoming objects.
* Notification events are processed on a separate thread after a delay. Note that only incoming
* objects are delayed. To delay outgoing objects, use a LagListener at the other end of the
* connection.
*/
static public class LagListener extends QueuedListener {
private final ScheduledExecutorService threadPool;
private final int lagMillisMin, lagMillisMax;
final LinkedList<Runnable> runnables = new LinkedList();
public LagListener(int lagMillisMin, int lagMillisMax, Listener listener) {
super(listener);
this.lagMillisMin = lagMillisMin;
this.lagMillisMax = lagMillisMax;
threadPool = Executors.newScheduledThreadPool(1);
}
@Override
public void queue(Runnable runnable) {
synchronized (runnables) {
runnables.addFirst(runnable);
}
int lag = lagMillisMin + (int) (Math.random() * (lagMillisMax - lagMillisMin));
threadPool.schedule(new Runnable() {
@Override
public void run() {
Runnable runnable;
synchronized (runnables) {
runnable = runnables.removeLast();
}
runnable.run();
}
}, lag, TimeUnit.MILLISECONDS);
}
}
}