/* $Id$ */
package ibis.ipl.impl.multi;
import ibis.ipl.Ibis;
import ibis.ipl.MessageUpcall;
import ibis.ipl.NoSuchPropertyException;
import ibis.ipl.PortType;
import ibis.ipl.ReadMessage;
import ibis.ipl.ReceivePort;
import ibis.ipl.ReceivePortConnectUpcall;
import ibis.ipl.ReceivePortIdentifier;
import ibis.ipl.ReceiveTimedOutException;
import ibis.ipl.SendPortIdentifier;
import ibis.util.ThreadPool;
import java.io.IOException;
import java.io.PrintStream;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.Properties;
// import org.slf4j.Logger;
// import org.slf4j.LoggerFactory;
public class MultiReceivePort implements ReceivePort {
// private Logger logger = LoggerFactory.getLogger(MultiReceivePort.class);
private final Map<String, ReceivePort>subPortMap = Collections.synchronizedMap(new HashMap<String, ReceivePort>());
private final MultiReceivePortIdentifier id;
private final ManageableMapper ManageableMapper;
private final PortType portType;
private final ArrayList<MultiReadMessage>messageQueue = new ArrayList<MultiReadMessage>();
private final ArrayList<IOException>exceptionQueue = new ArrayList<IOException>();
private final ArrayList<DowncallHandler>handlers = new ArrayList<DowncallHandler>();
private final MultiIbis ibis;
private boolean handlersStarted;
private final class DowncallHandler implements Runnable {
private final ReceivePort subPort;
private final String ibisName;
boolean running = false;
public DowncallHandler(ReceivePort subPort, String ibisName) {
this.subPort = subPort;
this.ibisName = ibisName;
}
public void run() {
running = true;
while (running) {
// logger.debug("Reading message");
ReadMessage message = null;
IOException exception = null;
try {
message = subPort.receive();
} catch (IOException e) {
exception = e;
}
// logger.debug("Run Locking Message Queue");
synchronized (messageQueue) {
// logger.debug("Run setting result: " + message + " : " + exception);
if (message != null || exception != null) {
if (message != null) {
messageQueue.add(new MultiReadMessage(message, ibis.receivePortMap.get(subPort)));
}
else if (exception != null) {
exceptionQueue.add(exception);
}
else {
exceptionQueue.add(new ReceiveTimedOutException("Timeout waiting for message."));
}
// logger.debug("Run notifying");
messageQueue.notifyAll();
}
}
}
// logger.debug("Handler exiting");
}
}
/**
* This class forwards upcalls with the proper receive port.
*/
private final class ConnectUpcaller
implements ReceivePortConnectUpcall {
final MultiReceivePort port;
final ReceivePortConnectUpcall upcaller;
final String ibisName;
public ConnectUpcaller(String ibisName, MultiReceivePort port,
ReceivePortConnectUpcall upcaller) {
this.port = port;
this.upcaller = upcaller;
this.ibisName = ibisName;
}
public boolean gotConnection(ReceivePort me,
SendPortIdentifier applicant) {
return upcaller.gotConnection(port, applicant);
}
public void lostConnection(ReceivePort me,
SendPortIdentifier johnDoe, Throwable reason) {
try {
upcaller.lostConnection(port, ibis.mapSendPortIdentifier(johnDoe, ibisName), reason);
} catch (IOException e) {
// TODO What the hell to do here?
}
}
}
/**
* This class forwards message upcalls with the proper message.
*/
private static final class Upcaller implements MessageUpcall {
MessageUpcall upcaller;
MultiReceivePort port;
public Upcaller(MessageUpcall upcaller, MultiReceivePort port) {
this.upcaller = upcaller;
this.port = port;
}
public void upcall(ReadMessage m) throws IOException, ClassNotFoundException {
upcaller.upcall(new MultiReadMessage(m, port));
}
}
@SuppressWarnings({ "unchecked", "rawtypes" })
public MultiReceivePort(PortType type, MultiIbis ibis,
String name, MessageUpcall upcall, ReceivePortConnectUpcall connectUpcall,
Properties properties)
throws IOException {
this.id = new MultiReceivePortIdentifier(ibis.identifier(), name);
// Wrap the upcaller if there is one
if (upcall != null) {
upcall = new Upcaller(upcall, this);
}
for (String ibisName:ibis.subIbisMap.keySet()) {
Ibis subIbis = ibis.subIbisMap.get(ibisName);
ConnectUpcaller upcaller = null;
if (connectUpcall != null) {
upcaller = new ConnectUpcaller(ibisName, this, connectUpcall);
}
ReceivePort subPort = subIbis.createReceivePort(type, name, upcall, upcaller, properties);
subPortMap.put(ibisName, subPort);
ibis.receivePortMap.put(subPort, this);
id.addSubId(ibisName, subPort.identifier());
if (type.hasCapability(PortType.RECEIVE_EXPLICIT) ||
type.hasCapability(PortType.RECEIVE_POLL) ||
type.hasCapability(PortType.RECEIVE_POLL_UPCALLS) ||
type.hasCapability(PortType.RECEIVE_TIMEOUT)) {
DowncallHandler handler = new DowncallHandler(subPort, ibisName);
handlers.add(handler);
}
}
this.ManageableMapper = new ManageableMapper((Map)subPortMap);
this.portType = type;
this.ibis = ibis;
}
public synchronized void close() throws IOException {
for(DowncallHandler handler:handlers) {
handler.running = false;
// TODO connect to wake? Or will close wake?
}
for(ReceivePort port:subPortMap.values()) {
try {
port.close();
}
catch (IOException e) {
// TODO Bundle up exceptions
}
}
ibis.closeReceivePort(this);
}
public synchronized void close(long timeoutMillis) throws IOException {
// TODO Should we kick off threads to close with timeout?
long timeout = timeoutMillis / subPortMap.size();
for (ReceivePort port:subPortMap.values()) {
try {
port.close(timeout);
}
catch (IOException e) {
// TODO Bundle up exceptions
}
}
}
public synchronized SendPortIdentifier[] connectedTo() {
HashMap<SendPortIdentifier, String>connectedTo = new HashMap<SendPortIdentifier,String>();
for (String ibisName:subPortMap.keySet()) {
ReceivePort port = subPortMap.get(ibisName);
SendPortIdentifier[] ids = port.connectedTo();
for (int i=0; i<ids.length;i++) {
try {
connectedTo.put(ibis.mapSendPortIdentifier(ids[i], ibisName), ibisName);
} catch (IOException e) {
// TODO What the hell to do here?
}
}
}
return connectedTo.keySet().toArray(new SendPortIdentifier[connectedTo.size()]);
}
public synchronized void disableConnections() {
for (ReceivePort port:subPortMap.values()) {
port.disableConnections();
}
}
public synchronized void disableMessageUpcalls() {
for (ReceivePort port:subPortMap.values()) {
port.disableMessageUpcalls();
}
}
public synchronized void enableConnections() {
for (ReceivePort port:subPortMap.values()) {
port.enableConnections();
}
}
public synchronized void enableMessageUpcalls() {
for (ReceivePort port:subPortMap.values()) {
port.enableMessageUpcalls();
}
}
public synchronized PortType getPortType() {
return portType;
}
public ReceivePortIdentifier identifier() {
return id;
}
public synchronized SendPortIdentifier[] lostConnections() {
HashMap<SendPortIdentifier, String>connectedTo = new HashMap<SendPortIdentifier,String>();
for (String ibisName:subPortMap.keySet()) {
ReceivePort port = subPortMap.get(ibisName);
SendPortIdentifier[] ids = port.lostConnections();
for (int i=0; i<ids.length;i++) {
try {
connectedTo.put(ibis.mapSendPortIdentifier(ids[i], ibisName), ibisName);
}
catch (IOException e) {
// TODO: What the hell to do here?
}
}
}
return connectedTo.keySet().toArray(new SendPortIdentifier[connectedTo.size()]);
}
public String name() {
return id.name();
}
public synchronized SendPortIdentifier[] newConnections() {
HashMap<SendPortIdentifier, String>connectedTo = new HashMap<SendPortIdentifier,String>();
for (String ibisName:subPortMap.keySet()) {
ReceivePort port = subPortMap.get(ibisName);
SendPortIdentifier[] ids = port.newConnections();
for (int i=0; i<ids.length;i++) {
try {
connectedTo.put(ibis.mapSendPortIdentifier(ids[i], ibisName), ibisName);
} catch (IOException e) {
// TODO What the hell to do here?
}
}
}
return connectedTo.keySet().toArray(new SendPortIdentifier[connectedTo.size()]);
}
public synchronized ReadMessage poll() throws IOException {
ReadMessage result = null;
synchronized (messageQueue) {
if (messageQueue.size() == 0) {
// Poll all subports for fairness
for (ReceivePort port:subPortMap.values()) {
ReadMessage message = port.poll();
if (message != null) {
messageQueue.add(new MultiReadMessage(message, this));
}
}
}
if (messageQueue.size() > 0) {
result = messageQueue.remove(0);
}
}
return result;
}
public ReadMessage receive() throws IOException {
return receive(0);
}
public synchronized ReadMessage receive(long timeoutMillis) throws IOException {
if (handlers.size() == 0) {
throw new IOException("Downcalls Not Configured!");
}
// logger.debug("> receive");
// TODO: What is the cost of this lazy init?
if (!handlersStarted) {
// logger.debug("Starting handlers.");
for (DowncallHandler handler:handlers) {
ThreadPool.createNew(handler, "Handler Thread: "+handler.ibisName);
}
handlersStarted = true;
}
ReadMessage ret = null;
synchronized (messageQueue) {
// logger.debug(">> messageQueue locked");
if (messageQueue.size() == 0) {
do {
try {
// logger.debug("Waiting for message.");
if (timeoutMillis > 0) {
messageQueue.wait(timeoutMillis);
}
else {
messageQueue.wait();
}
// logger.debug("Woke from wait.");
}
catch (InterruptedException e) {
// Ignore
}
}
while (messageQueue.size() == 0 && exceptionQueue.size() == 0);
if (messageQueue.size() == 0) {
// logger.debug("Throwing exception!");
IOException e = exceptionQueue.remove(0);
exceptionQueue.clear();
throw e; // Added (nothing was done with it) --Ceriel
}
}
ret = messageQueue.remove(0);
}
// logger.debug("< receive");
return ret;
}
public String getManagementProperty(String key)
throws NoSuchPropertyException {
return ManageableMapper.getManagementProperty(key);
}
public Map<String, String> managementProperties() {
return ManageableMapper.managementProperties();
}
public void printManagementProperties(PrintStream stream) {
ManageableMapper.printManagementProperties(stream);
}
public void setManagementProperties(Map<String, String> properties)
throws NoSuchPropertyException {
ManageableMapper.setManagementProperties(properties);
}
public void setManagementProperty(String key, String value)
throws NoSuchPropertyException {
ManageableMapper.setManagementProperty(key, value);
}
}