/*
* Copyright: Almende B.V. (2014), Rotterdam, The Netherlands
* License: The Apache Software License, Version 2.0
*/
package com.almende.eve.scheduling.clock;
import java.util.ArrayList;
import java.util.List;
import java.util.NavigableMap;
import java.util.concurrent.ConcurrentSkipListMap;
import java.util.concurrent.Executor;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.ReentrantLock;
import java.util.logging.Logger;
import org.joda.time.DateTime;
import org.joda.time.Interval;
import com.almende.util.threads.ThreadPool;
/**
* The Class RunnableClock.
*/
public class RunnableClock implements Runnable, Clock {
private static final Logger LOG = Logger.getLogger(RunnableClock.class
.getName());
protected final NavigableMap<ClockEntry, ClockEntry> TIMELINE = new ConcurrentSkipListMap<ClockEntry, ClockEntry>();
protected static final ScheduledExecutorService SCHEDULER = ThreadPool
.getScheduledPool();
protected static final Executor RUNNER = ThreadPool
.getPool();
protected ScheduledFuture<?> future = null;
protected ReentrantLock futureLock = new ReentrantLock();
/*
* Non locking task scheduling
*/
@Override
public void run() {
final List<Runnable> toRun = new ArrayList<Runnable>();
synchronized (TIMELINE) {
while (!TIMELINE.isEmpty()) {
final ClockEntry ce = TIMELINE.firstEntry().getValue();
final DateTime now = DateTime.now();
if (ce.getDue().isEqual(now) || ce.getDue().isBefore(now)) {
TIMELINE.remove(ce);
toRun.add(ce.getCallback());
continue;
}
final long interval = new Interval(now, ce.getDue())
.toDurationMillis();
if (interval <= 0) {
continue;
}
if (future == null
|| future.getDelay(TimeUnit.MILLISECONDS) != interval) {
if (future != null) {
future.cancel(false);
}
future = SCHEDULER.schedule(this, interval,
TimeUnit.MILLISECONDS);
}
break;
}
if (future == null && !TIMELINE.isEmpty()) {
LOG.warning("Lost trigger, should never happen!");
}
}
for (Runnable run : toRun) {
RUNNER.execute(run);
}
}
/*
* (non-Javadoc)
* @see
* com.almende.eve.scheduler.clock.Clock#requestTrigger(java.lang.String,
* org.joda.time.DateTime, java.lang.Runnable)
*/
@Override
public void requestTrigger(final String triggerId, final DateTime due,
final Runnable callback) {
final ClockEntry ce = new ClockEntry(triggerId, due, callback);
final ClockEntry oldVal = TIMELINE.get(ce);
if (oldVal == null || oldVal.getDue().isAfter(due)) {
TIMELINE.put(ce, ce);
} else {
LOG.warning(ce.getTriggerId()
+ ": Skip adding ce, because has old value earlier than current. "
+ oldVal.getTriggerId());
}
RUNNER.execute(this);
}
/*
* (non-Javadoc)
* @see com.almende.eve.scheduling.clock.Clock#cancel(java.lang.String)
*/
@Override
public void cancel(final String triggerId) {
final ClockEntry ce = new ClockEntry(triggerId, null, null);
TIMELINE.remove(ce);
}
/*
* (non-Javadoc)
* @see com.almende.eve.scheduling.clock.Clock#clear()
*/
@Override
public void clear() {
TIMELINE.clear();
futureLock.lock();
if (future != null) {
future.cancel(false);
future = null;
}
futureLock.unlock();
}
@Override
public DateTime nowDateTime() {
// Nothing todo, time progresses by itself:)
return DateTime.now();
}
@Override
public long now() {
return nowDateTime().getMillis();
}
@Override
public void start() {
// Nothing todo, time progresses by itself:)
}
@Override
public void stop() {
// Nothing todo, time progresses by itself:)
}
@Override
public void done(final String triggerId) {
// Nothing todo, timeprogresses by itself:)
}
}