package sk.stuba.fiit.perconik.activity.listeners;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.TimeUnit;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.annotation.Nullable;
import javax.annotation.concurrent.GuardedBy;
import com.google.common.base.MoreObjects;
import com.google.common.base.MoreObjects.ToStringHelper;
import com.google.common.base.Stopwatch;
import com.google.common.collect.LinkedListMultimap;
import com.google.common.collect.ListMultimap;
import org.eclipse.swt.widgets.Display;
import org.eclipse.ui.IWorkbench;
import org.eclipse.ui.IWorkbenchListener;
import sk.stuba.fiit.perconik.activity.probes.Probe;
import sk.stuba.fiit.perconik.core.Listener;
import sk.stuba.fiit.perconik.data.events.Event;
import sk.stuba.fiit.perconik.eclipse.swt.widgets.DisplayTask;
import sk.stuba.fiit.perconik.utilities.concurrent.NamedRunnable;
import static java.util.Collections.emptyList;
import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.base.Preconditions.checkNotNull;
import static com.google.common.base.Preconditions.checkState;
import static com.google.common.collect.Lists.newLinkedList;
import static sk.stuba.fiit.perconik.activity.listeners.AbstractListener.RegistrationHook.POST_UNREGISTER;
import static sk.stuba.fiit.perconik.eclipse.ui.Workbenches.getWorkbench;
import static sk.stuba.fiit.perconik.eclipse.ui.Workbenches.waitForWorkbench;
/**
* TODO
*
* @author Pavol Zbell
* @since 1.0
*/
public abstract class AbstractListener implements Listener {
final ListMultimap<RegistrationHook, Runnable> registerHooks;
/**
* Constructor for use by subclasses.
*/
protected AbstractListener() {
this.registerHooks = LinkedListMultimap.create(RegistrationHook.values().length);
Disposer.OnWorkbenchShutdown.activate(this);
Disposer.OnFinalUnregistration.activate(this);
}
protected abstract <V> V execute(final DisplayTask<V> task);
protected abstract void execute(final Runnable command);
public enum RegistrationHook {
PRE_REGISTER {
@Override
void handle(final AbstractListener listener, final Runnable task, final Exception failure) {
listener.preRegisterFailure(task, failure);
}
},
POST_REGISTER {
@Override
void handle(final AbstractListener listener, final Runnable task, final Exception failure) {
listener.postRegisterFailure(task, failure);
}
},
PRE_UNREGISTER {
@Override
void handle(final AbstractListener listener, final Runnable task, final Exception failure) {
listener.preUnregisterFailure(task, failure);
}
},
POST_UNREGISTER {
@Override
void handle(final AbstractListener listener, final Runnable task, final Exception failure) {
listener.postUnregisterFailure(task, failure);
}
};
void on(final AbstractListener listener) {
for (Runnable task: listener.registerHooks.get(this)) {
try {
task.run();
} catch (Exception failure) {
this.handle(listener, task, failure);
}
}
}
abstract void handle(AbstractListener listener, Runnable task, Exception failure);
public boolean add(final AbstractListener listener, final Runnable task) {
return listener.registerHooks.put(this, checkNotNull(task));
}
public boolean remove(final AbstractListener listener, final Runnable task) {
return listener.registerHooks.remove(this, checkNotNull(task));
}
@Override
public String toString() {
return this.name().toLowerCase();
}
}
@Override
public final void preRegister() {
RegistrationHook.PRE_REGISTER.on(this);
}
protected abstract void preRegisterFailure(Runnable task, Exception failure);
@Override
public final void postRegister() {
RegistrationHook.POST_REGISTER.on(this);
}
protected abstract void postRegisterFailure(Runnable task, Exception failure);
@Override
public final void preUnregister() {
RegistrationHook.PRE_UNREGISTER.on(this);
}
protected abstract void preUnregisterFailure(Runnable task, Exception failure);
@Override
public final void postUnregister() {
RegistrationHook.POST_UNREGISTER.on(this);
}
protected abstract void postUnregisterFailure(Runnable task, Exception failure);
protected abstract class InternalProbe<T> implements Probe<T> {
/**
* Constructor for use by subclasses.
*/
protected InternalProbe() {}
}
protected abstract Map<String, InternalProbe<?>> internalProbeMappings();
protected static abstract class ContinuousEvent<E> {
private static final long UNSET = -1L;
private final Object lock = new Object();
@GuardedBy("lock")
private final Stopwatch watch;
@GuardedBy("lock")
private List<E> sequence;
@GuardedBy("lock")
private long total;
final long pause;
final long window;
final TimeUnit unit;
protected ContinuousEvent(final Stopwatch watch, final long pause, final long window, final TimeUnit unit) {
this.watch = checkNotNull(watch);
this.sequence = newLinkedList();
this.total = UNSET;
checkArgument(pause >= 0L);
checkArgument(window >= 0L);
this.pause = pause;
this.window = window;
this.unit = checkNotNull(unit);
}
@GuardedBy("lock")
private void startWatchAndClearContinuousEvents() {
assert !this.watch.isRunning() && this.sequence.isEmpty() && this.total == UNSET;
this.sequence = newLinkedList();
this.total = 0L;
this.watch.reset().start();
}
@GuardedBy("lock")
private void stopWatchAndProcessContinuousEvents() {
assert this.watch.isRunning() && !this.sequence.isEmpty() && this.total != UNSET;
this.process(newLinkedList(this.sequence));
this.sequence = emptyList();
this.total = UNSET;
this.watch.stop();
}
@GuardedBy("lock")
private void restartWatch() {
assert this.watch.isRunning();
this.watch.reset().start();
}
public abstract void push(E event);
public abstract void flush();
final void synchronizedPush(final E event) {
synchronized (this.lock) {
LinkedList<E> sequence = newLinkedList(this.sequence);
if (!this.accept(sequence, event)) {
return;
}
if (this.watch.isRunning() && !this.continuous(sequence, event)) {
this.watchRunningButEventsNotContinouous();
this.stopWatchAndProcessContinuousEvents();
}
if (!this.watch.isRunning()) {
this.watchNotRunning();
this.startWatchAndClearContinuousEvents();
}
long delta = this.watch.elapsed(this.unit);
long total = this.total += delta;
this.sequence.add(event);
if (delta < this.pause && total < this.window) {
this.watchTimeNotElapsed(delta);
this.restartWatch();
return;
}
this.watchTimeElapsedAndAboutToProcess(delta);
this.stopWatchAndProcessContinuousEvents();
}
}
final void synchronizedFlush() {
synchronized (this.lock) {
if (this.watch.isRunning()) {
this.stopWatchAndProcessContinuousEvents();
}
}
}
public final LinkedList<E> sequence() {
synchronized (this.lock) {
return newLinkedList(this.sequence);
}
}
/**
* Returns maximal between continuous event pause.
*/
public final long pause() {
return this.pause;
}
/**
* Returns maximal continuous event time window.
*/
public final long window() {
return this.window;
}
/**
* Returns common time unit for both pause and window.
*/
public final TimeUnit unit() {
return this.unit;
}
/**
* Determines whether specified event is suitable for further processing.
*
* @param sequence possibly empty continuous event sequence
* @param event potential event to further process
*/
protected abstract boolean accept(LinkedList<E> sequence, E event);
/**
* Determines whether specified event is continuous to the event sequence.
*
* @param sequence non-empty continuous event sequence
* @param event potential event sequence candidate
*/
protected abstract boolean continuous(LinkedList<E> sequence, E event);
/**
* Processes continuous event sequence.
*
* @param sequence non-empty continuous event sequence
*/
protected abstract void process(LinkedList<E> sequence);
/**
* Returns total elapsed window time.
*
* @throws IllegalStateException if watch not running
*/
protected final long total() {
long total = this.total;
checkState(total >= 0L);
return total;
}
/**
* Invoked when watch running but events not continuous so about to be processed.
*/
protected void watchRunningButEventsNotContinouous() {}
/**
* Invoked when watch not running and about to start.
*/
protected void watchNotRunning() {}
/**
* Invoked when watch time not elapsed and about to wait for next event push.
*
* @param delta elapsed time delta lesser than pause
*/
protected void watchTimeNotElapsed(final long delta) {}
/**
* Invoked when watch time elapsed and continuous events about to be processed.
*
* @param delta elapsed time delta greater or equal to pause
*/
protected void watchTimeElapsedAndAboutToProcess(final long delta) {}
@Override
public String toString() {
return this.toStringHelper().toString();
}
protected ToStringHelper toStringHelper() {
ToStringHelper helper = MoreObjects.toStringHelper(this);
helper.add("pause", this.pause);
helper.add("window", this.window);
helper.add("unit", this.unit);
return helper;
}
}
protected abstract void inject(String path, Event data) throws Exception;
protected abstract void validate(String path, Event data) throws Exception;
protected abstract void persist(String path, Event data) throws Exception;
protected final void send(final String path, final Event data) {
this.preSend(path, data);
try {
this.inject(path, data);
this.validate(path, data);
this.persist(path, data);
} catch (Exception failure) {
this.sendFailure(path, data, failure);
}
this.postSend(path, data);
}
protected final void send(final String path, final Iterable<Event> batch) {
this.send(path, batch.iterator());
}
protected final void send(final String path, final Iterator<Event> batch) {
while (batch.hasNext()) {
this.send(path, batch.next());
}
}
protected abstract void sendFailure(String path, Event data, Exception failure);
abstract void preSend(String path, Event data);
abstract void postSend(String path, Event data);
@Override
public final boolean equals(@Nullable final Object object) {
return super.equals(object);
}
@Override
public final int hashCode() {
return super.hashCode();
}
/**
* Always throws {@code CloneNotSupportedException}.
*/
@Override
protected final Object clone() throws CloneNotSupportedException {
throw new CloneNotSupportedException();
}
/**
* Listeners should not depend on Java finalization.
*/
@Override
protected final void finalize() {}
private static abstract class Disposer {
private static final Logger logger = Logger.getLogger(Disposer.class.getName());
final AbstractListener listener;
Disposer(final AbstractListener listener) {
this.listener = checkNotNull(listener);
}
private static final class OnWorkbenchShutdown extends Disposer implements IWorkbenchListener {
private OnWorkbenchShutdown(final AbstractListener listener) {
super(listener);
}
static void activate(final AbstractListener listener) {
final OnWorkbenchShutdown disposer = new OnWorkbenchShutdown(listener);
final Runnable activation = new NamedRunnable(OnWorkbenchShutdown.class) {
public void run() {
waitForWorkbench().addWorkbenchListener(disposer);
}
};
Display.getDefault().asyncExec(activation);
}
public boolean preShutdown(final IWorkbench workbench, final boolean forced) {
return true;
}
public void postShutdown(final IWorkbench workbench) {
this.safeDispose(workbench);
}
@Override
void unsafeDispose() throws Exception {
this.listener.onWorkbenchShutdown();
}
}
private static final class OnFinalUnregistration extends Disposer {
private OnFinalUnregistration(final AbstractListener listener) {
super(listener);
}
static void activate(final AbstractListener listener) {
final OnFinalUnregistration disposer = new OnFinalUnregistration(listener);
final Runnable activation = new NamedRunnable(OnFinalUnregistration.class) {
public void run() {
disposer.safeDispose(getWorkbench());
}
};
POST_UNREGISTER.add(listener, activation);
}
@Override
void unsafeDispose() throws Exception {
this.listener.onFinalUnregistration();
}
}
void safeDispose(@Nullable final IWorkbench workbench) {
if (workbench != null && workbench.isClosing()) {
try {
this.unsafeDispose();
} catch (Exception failure) {
logger.log(Level.INFO, "Internal failure on " + this.listener + " disposal", failure);
}
}
}
abstract void unsafeDispose() throws Exception;
}
/**
* Invoked automatically on workbench shutdown.
*
* <p><b>Warning:</b> users should not invoke this method directly.
*/
protected abstract void onWorkbenchShutdown() throws Exception;
/**
* Invoked automatically on final unregistration when workbench is closing.
*
* <p><b>Warning:</b> users should not invoke this method directly.
*/
protected abstract void onFinalUnregistration() throws Exception;
@Override
public String toString() {
return this.getClass().getName();
}
}