/**
* Copyright (c) 2013, Will Szumski
*
* This file is part of formicidae.
*
* formicidae is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* formicidae 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with formicidae. If not, see <http://www.gnu.org/licenses/>.
*/
package org.cowboycoders.ant.events;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
import java.util.logging.Logger;
import org.cowboycoders.ant.interfaces.AntChipInterface;
import org.cowboycoders.ant.messages.AntMessageFactory;
import org.cowboycoders.ant.messages.MessageException;
import org.cowboycoders.ant.messages.MessageMetaWrapper;
import org.cowboycoders.ant.messages.StandardMessage;
public class EventMachine {
/**
* guards list of received messages
*/
//private final Lock rxQueueLock = new ReentrantLock();
//private Condition rxQueueChanged = rxQueueLock.newCondition();
//private boolean rxQueueEmpty = true;
private AntChipInterface chipInterface;
public final static Logger LOGGER = Logger.getLogger(EventMachine.class .getName());
private BroadcastMessenger<byte []> rawMessenger;
private BroadcastMessenger<StandardMessage> convertedMessenger;
private boolean running = false;
private class EventPump implements BroadcastListener<byte []> {
@Override
public void receiveMessage(byte[] message) {
StandardMessage msg = null;
try {
msg = AntMessageFactory.createMessage(message);
} catch (MessageException e) {
LOGGER.warning("Error converting raw data to type StandardMessage");
}
if(msg != null) {
LOGGER.finer("received :" + msg.getClass());
convertedMessenger.sendMessage(msg);
} else {
LOGGER.warning("Ignoring data packet");
}
}
}
private class IncomingMessageListener implements BroadcastListener<StandardMessage> {
private MessageMetaWrapper<StandardMessage> wrappedMessage;
private Lock messageUpdateLock;
private Condition replyRecieved;
private MessageCondition condition;
public IncomingMessageListener(Lock messageUpdateLock, MessageCondition condition){
this.condition = condition;
this.messageUpdateLock = messageUpdateLock;
this.replyRecieved = messageUpdateLock.newCondition();
}
@Override
public void receiveMessage(StandardMessage message) {
//FIXME: unregister this after first success, so we don't process subsequent messages
try {
if(!condition.test(message)) return;
} catch (Exception e) {
// we re-test the condition later back on calling thread
}
MessageMetaWrapper<StandardMessage> wrappedMessage =
new MessageMetaWrapper<StandardMessage>(message);
try {
messageUpdateLock.lock();
//FIXME: check flag to make sure we have sent message to node
this.wrappedMessage = wrappedMessage;
replyRecieved.signalAll();
} finally {
messageUpdateLock.unlock();
}
}
public MessageMetaWrapper<StandardMessage> getReply(Long timeout, TimeUnit timeoutUnit) throws InterruptedException, TimeoutException {
final long timeoutNano = timeout != null ? TimeUnit.NANOSECONDS.convert(timeout, timeoutUnit) : 0L;
final long initialTimeStamp = MessageMetaWrapper.getCurrentTimestamp();
try {
messageUpdateLock.lock();
while (wrappedMessage == null) {
if (timeout != null) {
long timeoutRemaining = timeoutNano - (MessageMetaWrapper.getCurrentTimestamp() - initialTimeStamp);
if(!replyRecieved.await(timeoutRemaining, TimeUnit.NANOSECONDS)) {
throw new TimeoutException("timeout waiting for message");
}
} else {
replyRecieved.await();
}
}
} finally {
messageUpdateLock.unlock();
}
// retest to throw an exception on calling thread
condition.test(wrappedMessage.unwrap());
return wrappedMessage;
}
}
public EventMachine(AntChipInterface chipInterface) {
this.chipInterface = chipInterface;
this.rawMessenger = new BroadcastMessenger<byte []>();
this.convertedMessenger = new BroadcastMessenger<StandardMessage>();
rawMessenger.addBroadcastListener(new EventPump());
}
public void registerRxListener(BroadcastListener<StandardMessage> listener) {
convertedMessenger.addBroadcastListener(listener);
}
public void removeRxListener(BroadcastListener<StandardMessage> listener) {
convertedMessenger.removeBroadcastListener(listener);
}
public static Logger getLogger() {
return LOGGER;
}
public MessageMetaWrapper<StandardMessage> waitForCondition(
MessageCondition msgCondition,
Long timeout, TimeUnit timeoutUnit, LockExchangeContainer lockExchanger)
throws InterruptedException, TimeoutException {
Lock msgLock = new ReentrantLock();
try {
msgLock.lock();
IncomingMessageListener listener = new IncomingMessageListener(msgLock, msgCondition);
registerRxListener(listener);
if(lockExchanger != null) {
try {
lockExchanger.lock.lock();
lockExchanger.returnLock = msgLock;
lockExchanger.lockAvailable.signalAll();
} finally {
lockExchanger.lock.unlock();
}
}
MessageMetaWrapper<StandardMessage> rtn = null;
// don't leave extraneous listeners if an exception is thrown
try {
rtn = listener.getReply(timeout, timeoutUnit);
} finally {
removeRxListener(listener);
}
return rtn;
} finally {
msgLock.unlock();
}
}
public synchronized void start() {
if (running) return;
chipInterface.registerRxMessenger(rawMessenger);
chipInterface.start();
running = true;
}
public synchronized void stop() {
if (!running) return;
chipInterface.stop();
chipInterface.unregisterRxMessenger(rawMessenger);
running = false;
}
public synchronized boolean isRunning() {
return running;
}
}