package desmoj.extensions.crossbar;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import desmoj.core.report.FlexReporterBuilder;
import desmoj.core.report.FlexReporterBuilder.Row;
import desmoj.core.report.Reporter;
import desmoj.core.simulator.DelayedInterruptException;
import desmoj.core.simulator.ExternalEvent;
import desmoj.core.simulator.Model;
import desmoj.core.simulator.ProcessQueue;
import desmoj.core.simulator.Reportable;
import desmoj.core.simulator.SimProcess;
import desmoj.core.simulator.TimeInstant;
import desmoj.core.simulator.TimeOperations;
import desmoj.core.simulator.TimeSpan;
import desmoj.extensions.crossbar.CrossbarMessage.DistributionMode;
/**
* The MessageCrossbar is a higher modeling construct used to synchronize
* processes.<br />
* <br />
* The MessageCrossbare permits the creation of any number of MessageChannels on
* it. Over these channels messages can be sent from one sender process to any
* number of receiver processes. To receive a message a process has to wait for
* it on a message channel. Once a process starts waiting on a channel it is
* automatically passivated and blocked. If a process sends a message to a
* channel all process waiting there will be reactivated and unregistered from
* the message channel and the MessageCrossbar.<br />
* <br />
* It is possible for a process to wait on several message channels
* simultaneously. It will be activated and removed from all channels if a
* message is received on any of those channels. In parallel it is also possible
* for a process to send messages to several channels simultaneously.
*
* @author Malte Unkrig
*
* @see MessageChannel
* @see CrossbarMessage
*
*/
public class MessageCrossbar<T extends SimProcess> extends Reportable {
/**
* The map in which the MessageCrossbar stores all its individual
* {@link MessageChannel}s
*/
private final Map<String, MessageChannel<T>> messageChannelsByName;
/**
* A map matching sent messages to their receivers.
*/
private final Map<T, CrossbarMessage> messagesByProcesses;
/**
* A queue containing all process currently waiting on the MessageCrossbar.
*/
private final ProcessQueue<T> passivatedProcessesQueue;
/**
* A counter keeping track of the processes which were interrupted and
* removed from this channel because of their max waiting time on the
* MessageCrossbar was reached.
*/
private long interruptedWaits;
/**
* A counter keeping track of the messages which were successfully delivered
* to a waiting process. A message is considered delivered if it was
* forwarded to at least one waiting process.
*/
private long deliveredMessages;
/**
* A counter keeping track of the messages which could not be delivered to a
* waiting process. A message is considered lost if it could not be
* forwarded to any receiver because at the time it was received there was
* no process waiting for messages.
*/
private long lostMessages;
/**
* Creates a MessageCrossbar object with all parameters required. The
* MessageCrossbar registers itself at the given model
*
* @param name
* java.lang.String : The name of this reportable
* @param owner
* Model : The model this reportable is associated to
* @param showInReport
* boolean : Flag for showing the report Set it to
* <code>true</code> if reportable should show up in report. Set
* it to <code>false</code> if reportable should not be shown in
* report.
* @param showInTrace
* boolean : Flag for showing this reportable in trace files. Set
* it to <code>true</code> if reportable should show up in trace.
* Set it to <code>false</code> if reportable should not be shown
* in trace.
*/
public MessageCrossbar(Model owner, String name, boolean showInReport, boolean showInTrace) {
super(owner, name, showInReport, showInTrace);
messageChannelsByName = new LinkedHashMap<String, MessageChannel<T>>();
messagesByProcesses = new HashMap<T, CrossbarMessage>();
passivatedProcessesQueue = new ProcessQueue<T>(owner, name + "Queue", false, false);
}
/**
* Checks whether the SimProcess (in it's current state) can wait at the
* message crossbar.
*
* @return Whether the SimProcess is valid or not.
* @param process
* The SimProcess to check
* @param where
* The Method from which this method is called
*/
private boolean canProcessWaitAtMessageCrossbar(SimProcess process, String where) {
if (process.isBlocked()) {
sendWarning("Can't wait at MessageCrossbar! Command ignored.", "SimProcess : " + getName() + " Method: "
+ where, "SimProcess is blocked. Blocked SimProcesses cannot wait at a MessageCrossbar.",
"You can check if a SimProcess is blocked using the method " + "isBlocked().");
return false;
}
/*
* TODO checken: Prozess wird gecancled und kann somit an der Crossbar
* warten
*/
// if (process.isScheduled()) {
// sendWarning("Can't wait at MessageCrossbar! Command ignored.",
// "SimProcess : " + getName() + " Method: "
// + where, "The SimProcess to be scheduled is already scheduled.",
// "Make sure the SimProcess is not sheduled before trying to wait at the MessageCrossbar.");
// return false;
//
// }
if (process.isTerminated()) {
sendWarning("Can't wait at MessageCrossbar! Command ignored.", "SimProcess : " + getName() + " Method: "
+ where, "SimProcess is terminated. Terminated SimProcesses cannot be interrupted.",
"You can check if a SimProcess is terminated using the method " + "isTerminated().");
return false;
}
return true;
}
/**
* Creates a new MessageChannel with is automatically associated with the
* MessageCrossbar. If a channel with the specified name already exists it
* will be returned instead of a new channel.
*
* @param channelName
* String : The name of the new channel
*
* @return The new MessageChannel instance
*/
public MessageChannel<T> createMessageChannel(String channelName) {
MessageChannel<T> messageChannel;
if (channelName == null || channelName.isEmpty()) {
sendWarning(
"Invalid MessageChannel name",
"MessageCrossbar : "
+ getName()
+ " Method: MessageChannel<T> createMessageChannel(String channelName, boolean showInReport, boolean showInTrace)",
"The name of the message channel may not be null or \"\". It was: \"" + channelName + "\".",
"Please use a valid channel name");
return null;
}
if (!messageChannelsByName.containsKey(channelName)) {
messageChannel = new MessageChannel<T>(getModel(), channelName, this);
messageChannelsByName.put(channelName, messageChannel);
}
return messageChannelsByName.get(channelName);
}
/**
* Creates a new MessageChannel with is automatically associated with the
* MessageCrossbar. If a channel with the specified name already exists it
* will be returned instead of a new channel.<br />
* <br />
* By specifying the generic type of the {@link MessageChannel} this method
* allows the creation of MessageChannels which are more specifically typed
* than the MessageCrossbar. In this way on a generally typed
* MessageCrossbar< SimProcess> a MessageChannel< Truck> (where Truck
* extends SimProcess) can be created.
*
* @param channelName
* String : The name of the new channel
* @param desiredGenericType
* Class<ST> : The generic typ of the channel.
* @param showInReport
* boolean : Flag for showing the report Set it to
* <code>true</code> if reportable should show up in report. Set
* it to <code>false</code> if reportable should not be shown in
* report.
* @param showInTrace
* boolean : Flag for showing this reportable in trace files. Set
* it to <code>true</code> if reportable should show up in trace.
* Set it to <code>false</code> if reportable should not be shown
* in trace.
*
* @return The new MessageChannel instance
*/
@SuppressWarnings("unchecked")
public <ST extends T> MessageChannel<ST> createMessageChannel(String channelName, Class<ST> desiredGenericType,
boolean showInReport, boolean showInTrace) {
return (MessageChannel<ST>) createMessageChannel(channelName);
}
/**
* {@inheritDoc}
*/
@Override
public Reporter createReporter() {
FlexReporterBuilder builder;
Row row;
builder = new FlexReporterBuilder("MessageCrossbars", 6100);
row = builder.openRow();
row.addHeadingCell("Title");
row.addHeadingCell("");
row.addHeadingCell("(Re)set");
row.addHeadingCell("MsgsRec");
row.addHeadingCell("MsgsDeli");
row.addHeadingCell("MsgsLost");
row.addHeadingCell("Activated directly");
row.addHeadingCell("Activated indirectly");
row.addHeadingCell("Aborted");
row.addHeadingCell("Obs");
row.addHeadingCell("Qmax");
row.addHeadingCell("Qnow");
row.addHeadingCell("Qavg");
row.addHeadingCell("max.Wait");
row.addHeadingCell("avg.Wait");
row.closeRow();
row = builder.openRow();
row.addCell(getName());
row.addCell("Global:");
row.addCell(resetAt());
row.addCell(getReceivedMessages());
row.addCell(getDeliveredMessages());
row.addCell(getLostMessages());
row.addCell(getProcessActivations());
row.addCell("n/a");
row.addCell(getInterruptedWaits());
row.addCell(getObservations());
row.addCell(passivatedProcessesQueue.maxLength());
row.addCell(passivatedProcessesQueue.length());
row.addCell(passivatedProcessesQueue.averageLength());
row.addCell(passivatedProcessesQueue.maxWaitTime());
row.addCell(passivatedProcessesQueue.averageWaitTime());
row.closeRow();
row = builder.openRow();
row.addCell("").addHeadingCell("Message channels").addCell("").addCell("").addCell("").addCell("").addCell("")
.addCell("").addCell("").addCell("").addCell("").addCell("").addCell("").addCell("").addCell("")
.closeRow();
for (MessageChannel<T> ch : messageChannelsByName.values()) {
row = builder.openRow();
row.addCell("");
row.addCell(ch.getName());
row.addCell(ch.resetAt());
row.addCell(ch.getReceivedMessages());
row.addCell(ch.getDeliveredMessages());
row.addCell(ch.getLostMessages());
row.addCell(ch.getDirectProcessActivations());
row.addCell(ch.getIndirectProcessActivations());
row.addCell(ch.getInterruptedWaits());
row.addCell(ch.getObservations());
row.addCell(ch.getPassivatedProcessesQueue().maxLength());
row.addCell(ch.getPassivatedProcessesQueue().length());
row.addCell(ch.getPassivatedProcessesQueue().averageLength());
row.addCell(ch.getPassivatedProcessesQueue().maxWaitTime());
row.addCell(ch.getPassivatedProcessesQueue().averageWaitTime()).closeRow();
}
return builder.build();
}
/**
*
* @return The number of delivered messages. A message is considered
* delivered if it was forwarded to at least one waiting process.
*/
public long getDeliveredMessages() {
return deliveredMessages;
}
/**
*
* @return The number of process which were interrupted while waiting on the
* MessageCrossbar because their max wait time was exceeded.
*/
public long getInterruptedWaits() {
return interruptedWaits;
}
/**
*
* @return The number of message which were sent to this MessageCrossbar but
* couldn't be forwarded to a receiver because no process was
* waiting on the MessageCrossbar at the time the message was
* received.
*/
public long getLostMessages() {
return lostMessages;
}
/**
* Returns the {@link MessageChannel} identified by the given name.
*
* @param channelName
* The name of the channel
*
* @return The {@link MessageChannel} identified by the given name, or
* <code>null</code> if no channel with that name is managed by the
* MessageCrossbar.
*/
public MessageChannel<T> getMessageChannel(String channelName) {
return messageChannelsByName.get(channelName);
}
/**
* Method that returns the message which is to be distributed to the
* receiver. This can either be the original message itself if the
* {@link DistributionMode} of the message is
* {@link DistributionMode#DISTRIBUTE_BY_REFERENCE} or a copy if the
* DistributionMode is {@link DistributionMode#DISTRIBUTE_AS_COPY}
*
* @param message
* The original message
* @return The message object to distribute
*/
private CrossbarMessage getMessageToDistributeToReceiverProcess(CrossbarMessage message) {
CrossbarMessage messageToDistribute;
switch (message.getDistributionMode()) {
case DISTRIBUTE_AS_COPY:
try {
messageToDistribute = message.clone();
} catch (CloneNotSupportedException e) {
throw new RuntimeException(e);
}
break;
case DISTRIBUTE_BY_REFERENCE:
messageToDistribute = message;
break;
default:
throw new RuntimeException("Unhandled DistributionMode: " + message.getDistributionMode());
}
return messageToDistribute;
}
/**
* @return The number of processes which have been handled by this message
* MessageCrossbar (meaning processes which entered the channel and
* also left it again).
*/
@Override
public long getObservations() {
return getPassivatedProcessesQueue().getObservations();
}
/**
*
* @return The Queue in which all passivated processes are stored.
*/
ProcessQueue<T> getPassivatedProcessesQueue() {
return passivatedProcessesQueue;
}
/**
*
* @return The number of process that were successfully activated by a
* delivered message.
*/
public long getProcessActivations() {
return getObservations() - interruptedWaits;
}
/**
*
* @return The number of messages which were sent to this channel.
*/
public long getReceivedMessages() {
return deliveredMessages + lostMessages;
}
/**
* Checks whether the given list of message channels is valid.
*
* @param channels
* The list of channels to check
* @param where
* The caller method
* @return True if the list of channels is valid, false otherwise
*/
private boolean isChannelListValid(List<MessageChannel<T>> channels, String where) {
String channelsNullOrEmpty = null;
if (channels == null) {
channelsNullOrEmpty = "null";
} else if (channels.isEmpty()) {
channelsNullOrEmpty = "empty";
}
if (channelsNullOrEmpty != null) {
sendWarning("Invalid parameter. The attempted action is ignored!", "MessageCrossbar: " + getName()
+ " Method: " + where, "The list of channels given as a parameter is " + channelsNullOrEmpty + ".",
"Make sure you pass a valid list of channels to the method.");
return false;
}
for (MessageChannel<T> channel : channels) {
if (!messageChannelsByName.containsValue(channel)) {
sendWarning("Invalid parameter. The attempted action is ignored!", "MessageCrossbar: " + getName()
+ " Method: " + where, "The channel named \"" + channel.getName()
+ "\" does not belong to the MessageCrossbar \"" + getName() + "\"",
"Make sure to only pass channels to this method which belong to the MessageCrossbar.");
return false;
}
}
return true;
}
/**
* Checks whether the given CrossbarMessage is valid.
*
* @param message
* The message to check
* @param where
* The caller method
* @return True if the message is valid, false otherwise
*/
private boolean isMessageValid(CrossbarMessage message, String where) {
if (message == null) {
sendWarning("Invalid parameter. The attempted action is ignored!", "MessageCrossbar: " + getName()
+ " Method: " + where, "The message is only a null pointer.",
"Make sure that only valid CrossbarMessages are passed to this method.");
return false;
}
return true;
}
/**
* Checks whether the SimProcess using the MessageCrossbar is a valid
* process.
*
* @return Whether the SimProcess can currently wait at the crossbar.
* @param process
* The SimProcess to check
* @param where
* The Method from which this method is called
*/
private boolean isProcessValid(SimProcess process, String where) {
if (process == null) {
sendWarning("A non existing process is trying to use the MessageCrossbar. "
+ "The attempted action is ignored!", "MessageCrossbar: " + getName() + " Method: " + where,
"The process is only a null pointer.", "Make sure that only real SimProcesses are using Stocks.");
return false;
}
if (!isModelCompatible(process)) {
sendWarning("The process trying to use the MessageCrossbar does not "
+ "belong to this model. The attempted action is ignored!", "Stock: " + getName() + " Method: "
+ where, "The process is not modelcompatible.",
"Make sure that processes are using only MessageCrossbars within their model.");
return false;
}
return true;
}
/**
* Checks whether the given TimeInstant is valid
*
* @param waitUntil
* The TimeInstant to check
* @param where
* The caller method
* @return True if the TimeInstant is valid, false otherwise
*/
private boolean isWaitUntilValid(TimeInstant waitUntil, String where) {
if (waitUntil != null && TimeInstant.isBefore(waitUntil, presentTime())) {
sendWarning("Cannot wait at the MessageCrossbar! Command Ignored.", "MessageCrossbar: " + getName()
+ " Method " + where, "The parameter waitUntil is before the current simulation time.",
"Please pass a valid TimeInstant to this method.");
return false;
}
return true;
}
/**
* {@inheritDoc}
*/
@Override
public void reset() {
getPassivatedProcessesQueue().reset();
for (MessageChannel<?> messageChannel : messageChannelsByName.values()) {
messageChannel.reset();
}
deliveredMessages = 0;
lostMessages = 0;
interruptedWaits = 0;
}
/**
* {@inheritDoc}
*/
@Override
public TimeInstant resetAt() {
return passivatedProcessesQueue.resetAt();
}
/**
* Sends the given {@link CrossbarMessage} to the specified message
* channels, thereby activating all processes that are waiting on these
* channel.
*
*
* @param message
* The message to send to the specified channels
*
* @param channelsToSendTo
* The channels to which the message will be sent
*
*/
public void sendMessage(CrossbarMessage message, List<MessageChannel<T>> channelsToSendTo) {
String where = "void sendMessage(CrossbarMessage message, List<MessageChannel<T>> channelsToSendTo)";
Set<T> processesToActivate;
SimProcess currentSimProcess;
currentSimProcess = currentSimProcess();
// Check if the process is valid
if (!isProcessValid(currentSimProcess, where)) {
return;
}
// Check if the channelsToSendTo are valid
if (!isChannelListValid(channelsToSendTo, where)) {
return;
}
// Check if the message is valid
if (!isMessageValid(message, where)) {
return;
}
// TODO Bitte pr�fen ob hier checks fehlen
// Set the sender of the message
message.setSender(currentSimProcess);
// Determine all process that are to be activated...
processesToActivate = new HashSet<T>();
for (MessageChannel<T> channel : channelsToSendTo) {
// ..and in doing so also remove those processes from the channels
// they are waiting on
processesToActivate.addAll(channel.receiveMessageAndRemoveWaitingProcesses());
}
// check whether processes are to be activated
if (!processesToActivate.isEmpty()) {
// This message activates at least on process so it counts as
// delivered
deliveredMessages++;
// Allthough we have removed all processes from the channels the
// message was sent to, the processes might wait on other channels
// ion this
// crossbar to which the message wasnt sent to. Wen need to remove
// the
// processes from these channels too.
for (MessageChannel<T> channel : messageChannelsByName.values()) {
channel.removePassivatedProcessesThatWereActivatedByAMessageOnAnotherChannel(processesToActivate);
}
// for each process which is to be activated
for (T process : processesToActivate) {
// accociate the proccess with the message which activated it
messagesByProcesses.put(process, getMessageToDistributeToReceiverProcess(message));
skipTraceNote();
getPassivatedProcessesQueue().remove(process);
process.setBlocked(false);// unblock it
skipTraceNote();
process.activateAfter(current());// activate it
}
} else {
// the list of processes to be activated is empty. Since the message
// doesn't lead to any process activations it is considered
// "lost". Therefore the lostMessages counter needs to be
// incremented
lostMessages++;
}
}
/**
* Wait for a {@link CrossbarMessage} on the specified message channels,
* thereby passivating the current process. The process is not reactivated
* until a message is received.
*
*
* @param channelsToWaitOn
* The channels on which to wait for a message
*
* @return The CorssbarMessage received after waiting
*/
public CrossbarMessage waitForMessage(List<MessageChannel<T>> channelsToWaitOn) {
return waitForMessage(channelsToWaitOn, (TimeInstant) null);
}
/**
* Wait for a {@link CrossbarMessage} on the specified message channels,
* thereby passivating the current process. The process is not reactivated
* until a message is received. The parameter waitUntil specifies the point
* in time to which the current process will wait for a message. If no
* message is received before that time, the process will be interrupted and
* a {@link DelayedInterruptException} will be thrown.
*
*
* @param channelsToWaitOn
* The channels on which to wait for a message
* @param waitUntil
* The point in time at which the waiting will be interrupted
* @return The CorssbarMessage received after waiting
* @throws DelayedInterruptException
* An exception indicating that the point in time specified by
* the waitUntil parameter has passed and the waiting has been
* interrupted.
*/
@SuppressWarnings("unchecked")
public CrossbarMessage waitForMessage(List<MessageChannel<T>> channelsToWaitOn, TimeInstant waitUntil)
throws DelayedInterruptException {
final T current;
String where = "CrossbarMessage waitForMessage(List<MessageChannel<T>> channelsToWaitOn, TimeInstant waitUntil)";
ExternalEvent delayedInterruptEvent;
ExternalEvent processUnblockingEvent = null;
current = (T) currentSimProcess();// get the current SimProcess
// perform all necessary checks
if (!isProcessValid(current, where)) {
return null;
}
if (!isChannelListValid(channelsToWaitOn, where)) {
return null;
}
if (!isWaitUntilValid(waitUntil, where)) {
return null;
}
if (!canProcessWaitAtMessageCrossbar(current, where)) {
return null;
}
// TODO Bitte pr�fen ob hier weitere checks fehlen
// If a wait time limit is set (waitUntil!=null) schedule an delayed
// interrupt of this process, so it can leave the
// crossbar after the specified wait time
// Since blocked processes cannot be interrupted schedule an
// ExternalEvent that unblocks the process just before it is
// interrupted.
if (waitUntil != null) {
skipTraceNote();
delayedInterruptEvent = current.interruptDelayed(waitUntil);
processUnblockingEvent = new ExternalEvent(getModel(), "MessageCrossbarProcessUnblockingEvent", true) {
@Override
public void eventRoutine() {
current.setBlocked(false);
}
};
skipTraceNote();
processUnblockingEvent.scheduleBefore(delayedInterruptEvent);
}
current.setBlocked(true); // block the process
if (current.isScheduled()) {
current.cancel();
}
for (MessageChannel<T> channel : channelsToWaitOn) {
// Let the channels keep track of the processes which are waiting on
// them
channel.addWaitingProcess(current);
}
// let the crossbar track all passivated process
skipTraceNote();
getPassivatedProcessesQueue().insert(current);
if (currentlySendTraceNotes()) {
String messageChannelNames = "";
Iterator<MessageChannel<T>> messageChannelsIterator = channelsToWaitOn.iterator();
while (messageChannelsIterator.hasNext()) {
messageChannelNames += messageChannelsIterator.next().getName();
if (messageChannelsIterator.hasNext()) {
messageChannelNames += ", ";
}
}
sendTraceNote("Waiting for a message at the MessageCrosbar " + getName() + " on the message channels "
+ messageChannelNames + ". Process is passivated.");
}
try {
skipTraceNote(); // dont tell the user that...
current.passivate();// ...the process is passivated here.
} catch (DelayedInterruptException ex) {
// If the process is interrupted by the delayed interupt, remove it
// from the crossbar queue and from all channels it might still be
// waiting on
skipTraceNote();
getPassivatedProcessesQueue().remove(current);
interruptedWaits++; // increment the number of interrupted waits
for (MessageChannel<T> channel : messageChannelsByName.values()) {
channel.abortWaiting(current);
}
// Finally re-throw the DelayedInterruptException so a process using
// the message crossbar can handle the interrupt.
throw ex;
}
// The process has been activated normally. Therefore the delayed
// interrupt and the processUnblockingEvent have to be unscheduled. Of
// course this only applies if a wait time limit has been set and a
// delayed interrupt has been scheduled.
if (waitUntil != null) {
skipTraceNote(2);
current.cancelInterruptDelayed();
processUnblockingEvent.cancel();
}
// Now after the process is active again get the message that activated
// the process and return it. Additionally remove the process from the
// list of passivated processes.
return messagesByProcesses.remove(current);
}
/**
* Wait for a {@link CrossbarMessage} on the specified message channels,
* thereby passivating the current process. The process is not reactivated
* until a message is received. The parameter maxWaitTime specifies maximum
* time the current process will wait for a message. If no message is
* received before this amount of time has elapsed, the process will be
* interrupted and a {@link DelayedInterruptException} will be thrown.
*
* @param channelsToWaitOn
* The channels on which to wait for a message
* @param maxWaitTime
* The maximum amount of time until the waiting will be
* interrupted
* @return The received message
* @throws DelayedInterruptException
* An exception indicating that the maximum wait time specified
* by the maxWaitTime parameter has passed and that the waiting
* has been interrupted
*/
public CrossbarMessage waitForMessage(List<MessageChannel<T>> channelsToWaitOn, TimeSpan maxWaitTime)
throws DelayedInterruptException {
TimeInstant waitUntil;
if (maxWaitTime != null) {
waitUntil = TimeOperations.add(presentTime(), maxWaitTime);
} else {
waitUntil = null;
}
return waitForMessage(channelsToWaitOn, waitUntil);
}
}