/*
* This file is part of jNAT-PMPlib.
*
* jNAT-PMPlib is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* jNAT-PMPlib is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with jNAT-PMPlib. If not, see <http://www.gnu.org/licenses/>.
*/
package net.tomp2p.natpmp;
import java.net.InetAddress;
import java.util.LinkedList;
import java.util.logging.Level;
import java.util.logging.Logger;
import net.tomp2p.utils.Timings;
/**
* MessageQueue manages a queue of {@link Message}s and ensures that only one
* message at time is sent to the NAT-PMP device. To use the MessageQueue,
* simply call {@link createMesageQueue()}. This will create and return a
* MessageQueue. {@link shutdown()} must be called when the queue is no longer
* needed. This class is thread-safe.
*
* @author flszen
*/
class MessageQueue implements Runnable {
private InetAddress gatewayIP;
private Thread thread;
private LinkedList<Message> queue;
private final Object queueLock = new Object();
private final Object messageLock = new Object();
private boolean shutdown = false;
private final Object shutdownLock = new Object();
/**
* Constructs a MessageQueue.
*/
private MessageQueue(InetAddress gatewayIP) {
// Localize.
this.gatewayIP = gatewayIP;
// Prepare the thread.
thread = new Thread(this, "MessageQueue");
thread.setDaemon(false);
// Prepare the queue.
queue = new LinkedList<Message>();
}
/**
* Creates and starts a {@link MessageQueue}. The created MesageQueue is
* returned. To use the queue, add {@link Message}s through the {@link
* enqueueMessage(Message)} method.
*
* @return The creates MessageQueue.
*/
static MessageQueue createMessageQueue(InetAddress gatewayIP) {
// Create and start the queue.
MessageQueue messageQueue = new MessageQueue(gatewayIP);
messageQueue.thread.start();
// Allow the thread that we just started to run to its wait().
while (messageQueue.thread.getState() != Thread.State.TIMED_WAITING) {
Thread.yield();
}
// Return the queue.
return messageQueue;
}
/**
* Enqueues a {@link Message} in the queue.
*
* @param message
* The {@link Message} to enqueue.
*/
void enqueueMessage(Message message) {
synchronized (queueLock) {
queue.add(message);
queueLock.notify();
}
}
/**
* Clears all messages in the queue. If a message is currently being
* processed, it is not interrupted.
*/
void clearQueue() {
synchronized (queueLock) {
queue.clear();
queueLock.notify();
}
}
/**
* Shuts down this messageQueue. This method is synchronous. The queue is
* cleared and it waits for the last item to be processed.
*/
void shutdown() {
// Signal shutdown.
synchronized (shutdownLock) {
shutdown = true;
}
// Clear the queue.
clearQueue();
// Wait for the shutdown to complete.
while (thread.isAlive()) {
try {
Timings.sleep(25);
} catch (InterruptedException ex) {
Logger.getLogger(MessageQueue.class.getName()).log(Level.SEVERE, null, ex);
break;
}
}
}
/**
* Flag indicates that this MessageQueue is shutdown.
*
* @return True if the MessageQueue is shut down, false if it is not.
*/
boolean isShutdown() {
synchronized (shutdownLock) {
return shutdown;
}
}
/**
* Causes the current thread to wait until the queue is empty.
*/
void waitUntilQueueEmpty() {
int size = 0;
synchronized (messageLock) {
size = queue.size();
// Wait in quarter-second intervals until the size is zero.
while (size > 0) {
// Release the messageLock for a 1/4 second. This prevents
// deadlock with run().
try {
messageLock.wait(250);
} catch (InterruptedException ex) {
Logger.getLogger(MessageQueue.class.getName()).log(Level.SEVERE, null, ex);
}
size = queue.size();
}
}
}
public void run() {
// Loop while running.
while (!shutdown) {
try {
// Get a Message from the queue, if one is available.
Message message = null;
synchronized (messageLock) {
synchronized (queueLock) {
// Loop until a message is received.
while (message == null && !shutdown) {
if (queue.size() > 0) {
// Get the message to send.
message = queue.removeFirst();
} else {
// Wait for a message for up to 1/4 second.
queueLock.wait(250);
// Release messageLock briefly. This prevents
// deadlock during waitUntilQueueEmpty().
messageLock.wait(1);
}
}
}
// If shutdown is true, we may end up here. Continue the
// loop.
if (shutdown) {
continue;
}
// Send the message outside of the queueLock context.
message.sendMessage(gatewayIP);
// Notify the listener about the repsonse.
message.notifyListener();
}
} catch (InterruptedException ex) {
// If the thread is interrupted, it is silently dropped.
// Interruptions should not be received with this arrangement
// anyway.
}
}
}
}