// Copyright (c) 2006 - 2008, Markus Strauch.
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are met:
//
// * Redistributions of source code must retain the above copyright notice,
// this list of conditions and the following disclaimer.
// * Redistributions in binary form must reproduce the above copyright notice,
// this list of conditions and the following disclaimer in the documentation
// and/or other materials provided with the distribution.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
// THE POSSIBILITY OF SUCH DAMAGE.
package net.sf.sdedit.diagram;
import java.util.LinkedList;
import net.sf.sdedit.error.SemanticError;
import net.sf.sdedit.message.Answer;
import net.sf.sdedit.message.BroadcastMessage;
import net.sf.sdedit.message.ConstructorMessage;
import net.sf.sdedit.message.ForwardMessage;
import net.sf.sdedit.message.Message;
import net.sf.sdedit.message.MessageToSelf;
import net.sf.sdedit.message.Primitive;
/**
* This class implements most of the abstract sequence generation logic,
* abstract in the sense that here appropriate caller and callee lifelines are
* computed and answers are sent back, but nothing is known about graphical
* drawing.
*
* @author Markus Strauch
*
*/
final class MessageProcessor {
private final Diagram diagram;
private final DiagramDataProvider provider;
/*
* The following attributes are valid during one single call of
* processMessage(...).
*/
private MessageData data;
/**
* The root lifeline of the caller. Is invariantly non-null.
*/
private Lifeline rootCaller;
/**
* The root lifeline of the callee. Is null iff the message is a primitive.
*/
private Lifeline rootCallee;
/**
* The number of the thread where the message is sent.
*
* <ul>
* <li>When no threading is used, it is invariantly 0.</li>
* <li>Ordinary messages are sent on a certain thread >=0 where the
* caller must be active.</li>
* <li>Spawning messages, whether sent by an actor or by an object,
* including messages to active objects, are sent on the newly spawned
* thread.</li>
* <li>Messages exchanged between actors are sent on the special thread -1.</li>
* </ul>
*/
private int callerThread = 0;
private int calleeThread = 0;
private Lifeline caller;
private Lifeline callee;
private Answer answer;
private boolean calleeIsActiveObject() {
return rootCallee != null && rootCallee.isActiveObject();
}
private boolean callerIsActor() {
return rootCaller.isAlwaysActive();
}
private boolean calleeIsActor() {
return rootCallee != null && rootCallee.isAlwaysActive();
}
private boolean isPrimitiveMessage() {
return rootCallee == null;
}
MessageProcessor(final Diagram diagram) {
this.diagram = diagram;
this.provider = diagram.getDataProvider();
}
ForwardMessage processMessage(final MessageData theData)
throws SemanticError {
this.data = theData;
initMessage();
theData.getBroadcastType();
return _processMessage(theData.getBroadcastType() < 2);
}
private ForwardMessage _processMessage(boolean openFragments)
throws SemanticError {
checkSemantics();
if (diagram.isThreaded()) {
findThreadNumbers();
diagram.setCallerThread(callerThread);
} else {
noThreadingChecks();
}
caller = findCaller();
if (!isPrimitiveMessage()) {
callee = findCallee();
} else {
callee = null;
}
ForwardMessage message = getMessage();
// fixes bug 2019720
if (openFragments) {
diagram.getFragmentManager().openFragments();
}
// execute(message);
return message;
}
private void noThreadingChecks() throws SemanticError {
if (rootCallee != null && rootCallee.isActiveObject()) {
throw new SemanticError(provider,
"Active objects are not permitted when multithreading is not enabled.");
}
if (data.isSpawnMessage()) {
throw new SemanticError(provider,
"Threads cannot be spawned when multithreading is not enabled.");
}
if (data.returnsInstantly()) {
throw new SemanticError(provider,
"Instant return must not be specified when multithreading is not enabled.");
}
if (data.getThread() != -1) {
throw new SemanticError(provider,
"A thread number must not be specified when multithreading is not enabled.");
}
if (data.getBroadcastType() != 0) {
throw new SemanticError(provider,
"Broadcast messages can only be sent when multithreading is enabled");
}
}
private void checkSemantics() throws SemanticError {
if (callerIsActor() && data.getCaller().equals(data.getCallee())) {
throw new SemanticError(provider,
"an actor cannot send a message to itself");
}
if (callerIsActor() && data.getAnswer().length() > 0) {
throw new SemanticError(
provider,
"An actor cannot receive an answer automatically. "
+ "This should be done by means of an explicit message!");
}
if (calleeIsActor() && data.getAnswer().length() > 0) {
throw new SemanticError(
provider,
"There are no automatic answers to messages that reach actors."
+ " This should be done by means of an explicit message!");
}
if (diagram.isThreaded() && data.isSpawnMessage()
&& data.getAnswer().length() > 0) {
throw new SemanticError(
provider,
"There are no automatic answers to messages that spawn threads."
+ " This should be done by means of an explicit message!");
}
if (rootCallee != null) {
if (!rootCallee.isAlive() && !data.isNewMessage()) {
throw new SemanticError(provider, data.getMessage() + ": "
+ data.getCallee() + " must be created first");
}
if (rootCallee.isAlive() && data.isNewMessage()) {
throw new SemanticError(provider, data.getMessage() + ": "
+ data.getCallee() + " has already been created");
}
}
if (diagram.isThreaded() && callerIsActor() && data.isSpawnMessage()) {
throw new SemanticError(provider,
"Actor messages are spawning by default in threaded mode");
}
if (diagram.isThreaded() && calleeIsActor()
&& data.getBroadcastType() == 0 && data.isSpawnMessage()) {
throw new SemanticError(provider,
"Messages sent to actors must not be spawning");
}
}
private void initMessage() throws SemanticError {
rootCaller = diagram.getLifeline(data.getCaller());
if (rootCaller == null) {
throw new SemanticError(provider, data.getCaller()
+ " does not exist");
}
if (!data.getCallee().equals("")) {
rootCallee = diagram.getLifeline(data.getCallee());
if (rootCallee == null) {
throw new SemanticError(provider, data.getCallee()
+ " does not exist");
}
} else {
rootCallee = null;
}
}
/**
*
* @pre threading is used
* @post callerThread and calleeThread are set
*
* @throws SemanticError
*/
private void findThreadNumbers() throws SemanticError {
if (callerIsActor() && (isPrimitiveMessage() || calleeIsActor())) {
callerThread = -1;
calleeThread = -1;
return;
}
if (callerIsActor()) {
callerThread = -1;
if (!data.returnsInstantly()) {
calleeThread = diagram.spawnThread();
}
return;
}
// The caller is not an actor.
if (diagram.noThreadIsSpawned()) {
callerThread = diagram.spawnThread();
} else {
Lifeline lineToBeFound = null;
if (!data.getCallerMnemonic().equals("")) {
lineToBeFound = diagram.getLifelineByMnemonic(data.getCaller(),
data.getCallerMnemonic());
if (lineToBeFound == null) {
throw new SemanticError(provider,
"There is no lifeline named \"" + data.getCaller()
+ "\" associated to mnemonic \""
+ data.getCallerMnemonic() + "\"");
}
callerThread = lineToBeFound.getThread();
} else {
/*
* If the MessageData specifies a thread number, we'll take it
* as the current thread number, otherwise we check if the
* caller object (represented by callerRoot) is just by only a
* single thread.
*/
if (data.getThread() >= 0) {
if (data.getThread() >= diagram.getNumberOfThreads()) {
throw new SemanticError(provider,
"Illegal thread number: " + data.getThread());
}
callerThread = data.getThread();
} else {
final int uniqueThread = rootCaller.getUniqueThread();
if (uniqueThread >= 0) {
callerThread = uniqueThread;
} else {
if (calleeThread == -1) {
throw new SemanticError(provider,
"Explicit thread number required.");
}
callerThread = calleeThread;
}
}
}
}
boolean spawning = data.isSpawnMessage();
if (spawning) {
if (data.getBroadcastType() > 0) {
spawning = !calleeIsActor();
}
}
if ((spawning || calleeIsActiveObject()) && !data.returnsInstantly()) {
calleeThread = diagram.spawnThread();
} else {
calleeThread = callerThread;
}
}
/**
* Returns the Lifeline representation of the caller as specified by the
* given MessageData and sets the current thread to the thread that was used
* in order to activate the Lifeline.
*
* @param data
* @return the Lifeline representation of the caller
* @throws SemanticError
* @precondition caller is not an actor
*/
private Lifeline findCaller() throws SemanticError {
final String callerName = data.getCaller();
if (callerIsActor()) {
/*
* When multithreading is disabled and an actor sends a message,
* finish all ongoing activities.
*/
if (!diagram.isThreaded()) {
diagram.finish();
}
return rootCaller;
}
final String mnemonic = data.getCallerMnemonic();
/*
* If lineToBeFound is not null, we just look for it, ignoring level and
* thread specification of the MessageData.
*/
Lifeline lineToBeFound = null;
/* TODO: prevent mnemonics for actors */
if (!mnemonic.equals("")) {
lineToBeFound = diagram.getLifelineByMnemonic(data.getCaller(),
mnemonic);
if (lineToBeFound == null) {
throw new SemanticError(provider,
"There is no lifeline named \"" + data.getCaller()
+ "\" associated to mnemonic \"" + mnemonic
+ "\"");
}
}
final LinkedList<Message> currentStack = diagram.currentStack();
if (currentStack == null) {
throw new SemanticError(provider, "Thread " + callerThread
+ " has died");
}
if (callerThread == 0 && currentStack.isEmpty()
&& diagram.firstCaller() == null) {
/*
* The message described by MessageData is the first that occurs on
* the thread 0, no object is yet active. So set the caller active
* and declare it the first caller.
*/
diagram.setFirstCaller(rootCaller);
rootCaller.setActive(true);
return rootCaller;
}
/*
* This number counts how often we have seen a lifeline with the same
* name, but with a wrong level (or not equal to lineToBeFound), as
* senders of answers on the stack.
*/
int occured = 0;
while (!currentStack.isEmpty()) {
final Message theAnswer = currentStack.getLast();
if (lineToBeFound != null) {
if (theAnswer.getCaller() == lineToBeFound) {
return lineToBeFound;
}
} else if (theAnswer.getCaller().getName().equals(data.getCaller())) {
// An answer with the right level and the actual caller
// as a caller is not sent. It can only be sent when
// the current message has been executed (including its
// followers)
if (occured == data.getLevel()) {
return theAnswer.getCaller();
}
occured++;
}
/*
* All answers that have non matching lifelines as senders are
* popped from the stack and the answer is added to the diagram,
* thus, an arrow representation will appear.
*/
currentStack.removeLast();
diagram.sendAnswer(theAnswer);
}
if (diagram.firstCaller() != null
&& diagram.firstCaller().getName().equals(callerName)) {
/*
* We have not yet seen the lifeline we are looking for, the last
* chance is that it is the first caller on the thread.
*/
if (occured == data.getLevel()) {
return diagram.firstCaller();
}
/*
* This chance was also missed, we add 1 to occured in order to
* count the occurence as first caller.
*/
occured++;
}
throw objectNotFound(occured, lineToBeFound);
}
private SemanticError objectNotFound(final int occured,
final Lifeline lineToBeFound) {
final String msg;
if (lineToBeFound != null) {
msg = data.getCaller() + "[" + data.getCallerMnemonic()
+ "] is not active";
} else if (occured == 0) {
msg = data.getCaller() + " is not active at all";
} else if (occured == 1) {
msg = data.getCaller() + "[" + data.getLevel() + "]"
+ "is not active, but " + data.getCaller() + "[0] is";
} else {
msg = data.getCaller() + "[" + data.getLevel() + "]"
+ "is not active, but " + data.getCaller() + "[0]"
+ (occured == 2 ? ", " : " - ") + data.getCaller() + "["
+ (occured - 1) + "] are";
}
return new SemanticError(provider, msg);
}
/**
* If the callee, as described in the <tt>data</tt>, is not active on the
* given thread, this method returns the root lifeline of the callee. If
* there is already a callee lifeline for the thread, this lifeline is
* returned - for creating a higher-level activity via
* {@linkplain Lifeline#addActivity(Lifeline, int)}.
*
* @param data
* @param thread
* @return
* @throws SemanticError
*/
private Lifeline findCallee() throws SemanticError {
if (calleeIsActor()) {
return rootCallee;
}
Lifeline theCallee = rootCallee.getLastInThread(calleeThread);
if (theCallee == null) {
theCallee = rootCallee;
}
if (!data.returnsInstantly() || !callerIsActor()
&& !data.isSpawnMessage()) {
if (!theCallee.isAlive() && data.isNewMessage()) {
theCallee.setThread(calleeThread);
} else if (theCallee.isActive()) {
theCallee = theCallee.addActivity(caller, calleeThread);
} else { // theCallee == rootCallee
theCallee.setThread(calleeThread);
}
}
final String calleeMnemonic = data.getCalleeMnemonic();
if (!calleeMnemonic.equals("")) {
diagram.associateLifeline(data.getCallee(), calleeMnemonic,
theCallee);
theCallee.setMnemonic(calleeMnemonic);
}
return theCallee;
}
private ForwardMessage getMessage() throws SemanticError {
if (data.isDestroyMessage() && rootCallee != null
&& rootCallee.isActive()) {
throw new SemanticError(provider, "cannot destroy active object");
}
if (isPrimitiveMessage()) {
return new Primitive(caller, diagram, data);
}
if (data.getBroadcastType() != 0) {
return new BroadcastMessage(caller, callee, diagram, data);
}
if (!callee.isAlive() && data.isNewMessage()) {
return new ConstructorMessage(caller, callee, diagram, data);
}
if (caller.getName().equals(callee.getName())) {
return new MessageToSelf(caller, callee, diagram, data);
}
return new ForwardMessage(caller, callee, diagram, data);
}
void execute(final ForwardMessage message) throws SemanticError {
// if (message.getCaller().isWaiting()) {
// if (!(message instanceof Primitive) || !((Primitive)
// message).isSynchronizing()) {
// throw new SemanticError (provider, "the thread is blocked");
// }
// }
if (message instanceof Primitive && diagram.isThreaded()
&& message.getText().equals("stop") && !caller.isAlwaysActive()) {
diagram.finish(diagram.getCallerThread());
diagram.setThreadState("dead");
caller.finish();
diagram.deleteStack();
return;
}
message.updateView();
if (provider.getState() != null) {
diagram.addToStateMap(message.getArrow(), provider.getState());
}
answer = message.getAnswerMessage();
diagram.setCallerThread(calleeThread);
if (diagram.isThreaded()
&& !calleeIsActor()
&& !data.returnsInstantly()
&& !(message instanceof Primitive)
&& (calleeIsActiveObject() || data.isSpawnMessage() || caller
.isAlwaysActive())) {
diagram.setFirstCaller(callee);
diagram.setThreadState("running");
}
if (answer != null) {
if (data.returnsInstantly()) {
diagram.sendAnswer(answer);
} else {
diagram.currentStack().add(answer);
}
}
int dnum = data.getNoteNumber();
if (dnum > 0) {
diagram.associateMessage(dnum, message);
}
dnum = data.getAnswerNoteNumber();
if (dnum > 0) {
if (answer == null) {
throw new SemanticError(provider,
"You cannot associate a note to an answer when there is none.");
}
diagram.associateMessage(dnum, answer);
}
}
}