/**
* Copyright (c) 2010-2016 by the respective copyright holders.
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*/
package org.openhab.binding.insteonplm.internal.device;
import java.util.HashMap;
import java.util.PriorityQueue;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Class that manages all the per-device request queues using a single thread.
*
* - Each device has its own request queue, and the RequestQueueManager keeps a
* queue of queues.
* - Each entry in m_requestQueues corresponds to a single device's request queue.
* A device should never be more than once in m_requestQueues.
* - A hash map (m_requestQueueHash) is kept in sync with m_requestQueues for
* faster lookup in case a request queue is modified and needs to be
* rescheduled.
*
* @author Bernd Pfrommer
* @since 1.6.0
*/
public class RequestQueueManager {
private static RequestQueueManager s_instance = null;
private static final Logger logger = LoggerFactory.getLogger(RequestQueueManager.class);
private Thread m_queueThread = null;
private PriorityQueue<RequestQueue> m_requestQueues = new PriorityQueue<RequestQueue>();
private HashMap<InsteonDevice, RequestQueue> m_requestQueueHash = new HashMap<InsteonDevice, RequestQueue>();
private boolean m_keepRunning = true;
private RequestQueueManager() {
m_queueThread = new Thread(new RequestQueueReader());
m_queueThread.start();
}
/**
* Add device to global request queue.
*
* @param dev the device to add
* @param time the time when the queue should be processed
*/
public void addQueue(InsteonDevice dev, long time) {
synchronized (m_requestQueues) {
RequestQueue q = m_requestQueueHash.get(dev);
if (q == null) {
logger.trace("scheduling request for device {} in {} msec", dev.getAddress(),
time - System.currentTimeMillis());
q = new RequestQueue(dev, time);
} else {
logger.trace("queue for dev {} is already scheduled in {} msec", dev.getAddress(),
q.getExpirationTime() - System.currentTimeMillis());
if (!m_requestQueues.remove(q)) {
logger.error("queue for {} should be there, report as bug!", dev);
}
m_requestQueueHash.remove(dev);
}
long expTime = q.getExpirationTime();
if (expTime > time) {
q.setExpirationTime(time);
}
// add the queue back in after (maybe) having modified
// the expiration time
m_requestQueues.add(q);
m_requestQueueHash.put(dev, q);
m_requestQueues.notify();
}
}
/**
* Stops request queue thread
*/
private void stopThread() {
logger.debug("stopping thread");
if (m_queueThread != null) {
synchronized (m_requestQueues) {
m_keepRunning = false;
m_requestQueues.notifyAll();
}
try {
logger.debug("waiting for thread to join");
m_queueThread.join();
logger.debug("request queue thread exited!");
} catch (InterruptedException e) {
logger.error("got interrupted waiting for thread exit ", e);
}
m_queueThread = null;
}
}
class RequestQueueReader implements Runnable {
@Override
public void run() {
logger.debug("starting request queue thread");
synchronized (m_requestQueues) {
while (m_keepRunning) {
try {
while (m_keepRunning && !m_requestQueues.isEmpty()) {
RequestQueue q = m_requestQueues.peek();
long now = System.currentTimeMillis();
long expTime = q.getExpirationTime();
InsteonDevice dev = q.getDevice();
if (expTime > now) {
//
// The head of the queue is not up for processing yet, wait().
//
logger.trace("request queue head: {} must wait for {} msec", dev.getAddress(),
expTime - now);
m_requestQueues.wait(expTime - now);
//
// note that the wait() can also return because of changes to
// the queue, not just because the time expired!
//
continue;
}
//
// The head of the queue has expired and can be processed!
//
q = m_requestQueues.poll(); // remove front element
m_requestQueueHash.remove(dev); // and remove from hash map
long nextExp = dev.processRequestQueue(now);
if (nextExp > 0) {
q = new RequestQueue(dev, nextExp);
m_requestQueues.add(q);
m_requestQueueHash.put(dev, q);
logger.trace("device queue for {} rescheduled in {} msec", dev.getAddress(),
nextExp - now);
} else {
// remove from hash since queue is no longer scheduled
logger.debug("device queue for {} is empty!", dev.getAddress());
}
}
logger.trace("waiting for request queues to fill");
m_requestQueues.wait();
} catch (InterruptedException e) {
logger.error("request queue thread got interrupted, breaking..", e);
break;
}
}
}
logger.debug("exiting request queue thread!");
}
}
public static class RequestQueue implements Comparable<RequestQueue> {
private InsteonDevice m_device = null;
private long m_expirationTime = 0L;
RequestQueue(InsteonDevice dev, long expirationTime) {
m_device = dev;
m_expirationTime = expirationTime;
}
public InsteonDevice getDevice() {
return m_device;
}
public long getExpirationTime() {
return m_expirationTime;
}
public void setExpirationTime(long t) {
m_expirationTime = t;
}
@Override
public int compareTo(RequestQueue a) {
return (int) (m_expirationTime - a.m_expirationTime);
}
}
public static synchronized RequestQueueManager s_instance() {
if (s_instance == null) {
s_instance = new RequestQueueManager();
}
return (s_instance);
}
public static void s_destroyInstance() {
if (s_instance != null) {
s_instance.stopThread();
s_instance = null;
}
}
}