/* $Id: AbstractMessageNotationUml.java 17828 2010-01-12 18:55:12Z linus $
*****************************************************************************
* Copyright (c) 2009 Contributors - see below
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* mvw
*****************************************************************************
*
* Some portions of this file was previously release using the BSD License:
*/
// Copyright (c) 2009 The Regents of the University of California. All
// Rights Reserved. Permission to use, copy, modify, and distribute this
// software and its documentation without fee, and without a written
// agreement is hereby granted, provided that the above copyright notice
// and this paragraph appear in all copies. This software program and
// documentation are copyrighted by The Regents of the University of
// California. The software program and documentation are supplied "AS
// IS", without any accompanying services from The Regents. The Regents
// does not warrant that the operation of the program will be
// uninterrupted or error-free. The end-user understands that the program
// was developed for research purposes and is advised not to rely
// exclusively on the program for any reason. IN NO EVENT SHALL THE
// UNIVERSITY OF CALIFORNIA BE LIABLE TO ANY PARTY FOR DIRECT, INDIRECT,
// SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES, INCLUDING LOST PROFITS,
// ARISING OUT OF THE USE OF THIS SOFTWARE AND ITS DOCUMENTATION, EVEN IF
// THE UNIVERSITY OF CALIFORNIA HAS BEEN ADVISED OF THE POSSIBILITY OF
// SUCH DAMAGE. THE UNIVERSITY OF CALIFORNIA SPECIFICALLY DISCLAIMS ANY
// WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE SOFTWARE
// PROVIDED HEREUNDER IS ON AN "AS IS" BASIS, AND THE UNIVERSITY OF
// CALIFORNIA HAS NO OBLIGATIONS TO PROVIDE MAINTENANCE, SUPPORT,
// UPDATES, ENHANCEMENTS, OR MODIFICATIONS.
package org.argouml.notation.providers.uml;
import java.text.ParseException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
import java.util.List;
import java.util.NoSuchElementException;
import org.apache.log4j.Logger;
import org.argouml.application.events.ArgoEventPump;
import org.argouml.application.events.ArgoEventTypes;
import org.argouml.application.events.ArgoHelpEvent;
import org.argouml.i18n.Translator;
import org.argouml.kernel.ProjectManager;
import org.argouml.model.Model;
import org.argouml.notation.providers.MessageNotation;
import org.argouml.util.CustomSeparator;
import org.argouml.util.MyTokenizer;
/**
* This abstract class provides the common functionality for
* the UML notation of messages.<br/>
* It is extended by {@link MessageNotationUml}, with the
* notation of messages as seen in collaboration diagrams,
* and {@link SDMessageNotationUml}, with the notation of
* messages as seen in sequence diagrams.<p>
*
* The Message notation syntax is a line of the following form,
* which we can generate and parse: <p>
*
* <pre>
* intno := integer|name
* seq := intno ['.' intno]*
* recurrence := '*'['//'] | '*'['//']'[' <i>iteration </i>']' | '['
* <i>condition </i>']'
* seqelem := {[intno] ['['recurrence']']}
* seq_expr := seqelem ['.' seqelem]*
* ret_list := lvalue [',' lvalue]*
* arg_list := rvalue [',' rvalue]*
* predecessor := seq [',' seq]* '/'
* message := [predecessor] seq_expr ':' [ret_list :=] name ([arg_list])
* </pre>
*
* Which is rather complex, so a few examples:<ul>
* <li> 2: display(x, y)
* <li> 1.3.1: p := find(specs)
* <li> [x < 0] 4: invert(color)
* <li> A3, B4/ C3.1*: update()
* </ul>
*
* This syntax is compatible with the UML 1.4.2 specification.<p>
*
* Actually, only a subset of this syntax is currently supported, and some
* is not even planned to be supported. The exceptions are intno, which only
* allows a number possibly followed by a sequence of letters in the range
* 'a' - 'z', seqelem, which does not allow a recurrence, and message, which
* does allow one recurrence near seq_expr. <p>
*
* (formerly, the supported syntax was: name: action ) <p>
*
* Generating a string from the model has some extra functionality:
* If obtaining the Script of the Action returns an empty string,
* then an alternative representation is given:
* If the action is a CallAction, use the name of its Operation,
* and if it is a SendAction, the name of its Signal.
* If also this returns no string, then we display the name of the Message. <p>
*
* Rationale:
* This allows ArgoUML to show something on the diagram with older projects,
* which only had the name of the Message filled in by the user.
* This also may improve the diagrams for imported XMI.<p>
*
* Parsing a text that is generated by one of the backup scenarios,
* causes it to be written back in the script of the Action.
* Hence, editing the text on the diagram only once
* causes the Action Script to be used from then on. <p>
*
* Supported operations for the parser: <p>
* <ul>
* <li>Locating an Operation by name and the number of arguments -
* the operation is hooked to the CallAction of the Message.
* <li>Create an Operation with given name (no arguments).
* <li>Change the order of messages (predecessor/successor).
* <li>Reverting the direction of a message.
* <li>etc.
* </ul>
*
* @see MessageNotationUml
* @see SDMessageNotationUml
* @since 0.28.alpha1
* @author penyaskito
*/
public abstract class AbstractMessageNotationUml extends MessageNotation {
private static final Logger LOG =
Logger.getLogger(AbstractMessageNotationUml.class);
/**
* The list of CustomSeparators to use when tokenizing parameters.
*/
private final List<CustomSeparator> parameterCustomSep;
/**
* An object containing an UML Message object.
*/
protected static class MsgPtr {
/**
* The message pointed to.
*/
Object message;
}
/**
* @param umlMessage the UML Message object
*/
public AbstractMessageNotationUml(Object umlMessage) {
super(umlMessage);
parameterCustomSep = initParameterSeparators();
}
protected String toString(final Object umlMessage,
boolean showSequenceNumbers) {
Iterator it;
Collection umlPredecessors;
Object umlAction;
Object umlActivator; // this is a Message UML object
MsgPtr ptr;
int lpn;
/* Supported format:
* predecessors number ":" action
* The 3 parts of the string to generate: */
StringBuilder predecessors = new StringBuilder(); // includes the "/"
String number; // the "seq_expr" from the header javadoc
// the ":" is not included in "number" - it is always present
String action = "";
if (umlMessage == null) {
return "";
}
ptr = new MsgPtr();
lpn = recCountPredecessors(umlMessage, ptr) + 1;
umlActivator = Model.getFacade().getActivator(umlMessage);
umlPredecessors = Model.getFacade().getPredecessors(umlMessage);
it = (umlPredecessors != null) ? umlPredecessors.iterator() : null;
if (it != null && it.hasNext()) {
MsgPtr ptr2 = new MsgPtr();
int precnt = 0;
while (it.hasNext()) {
Object msg = /*(MMessage)*/ it.next();
int mpn = recCountPredecessors(msg, ptr2) + 1;
if (mpn == lpn - 1
&& umlActivator == Model.getFacade().getActivator(msg)
&& Model.getFacade().getPredecessors(msg).size() < 2
&& (ptr2.message == null
|| countSuccessors(ptr2.message) < 2)) {
continue;
}
if (predecessors.length() > 0) {
predecessors.append(", ");
}
predecessors.append(
generateMessageNumber(msg, ptr2.message, mpn));
precnt++;
}
if (precnt > 0) {
predecessors.append(" / ");
}
}
number = generateMessageNumber(umlMessage, ptr.message, lpn);
umlAction = Model.getFacade().getAction(umlMessage);
if (umlAction != null) {
if (Model.getFacade().getRecurrence(umlAction) != null) {
number = generateRecurrence(
Model.getFacade().getRecurrence(umlAction))
+ " "
+ number;
/* TODO: The recurrence goes in front of the action?
* Does this not contradict the header JavaDoc? */
}
}
action = NotationUtilityUml.generateActionSequence(umlAction);
if ("".equals(action) || action.trim().startsWith("(")) {
/* If the script of the Action is empty,
* (or only specifies arguments and no method name)
* then we generate a string based on
* a different model element: */
action = getInitiatorOfAction(umlAction);
if ("".equals(action)) {
// This may return null:
String n = Model.getFacade().getName(umlMessage);
if (n != null) {
action = n;
}
}
}
else if (!action.endsWith(")")) {
/* Dirty fix for issue 1758 (Needs to be amended
* when we start supporting parameters):
*/
action = action + "()";
}
if (!showSequenceNumbers) {
return action;
}
return predecessors + number + " : " + action;
}
protected String getInitiatorOfAction(Object umlAction) {
String result = "";
if (Model.getFacade().isACallAction(umlAction)) {
Object umlOperation = Model.getFacade().getOperation(umlAction);
if (Model.getFacade().isAOperation(umlOperation)) {
StringBuilder sb = new StringBuilder(
Model.getFacade().getName(umlOperation));
if (sb.length() > 0) {
sb.append("()");
result = sb.toString();
}
}
} else if (Model.getFacade().isASendAction(umlAction)) {
Object umlSignal = Model.getFacade().getSignal(umlAction);
if (Model.getFacade().isASignal(umlSignal)) {
String n = Model.getFacade().getName(umlSignal);
if (n != null) {
result = n;
}
}
}
return result;
}
protected List<CustomSeparator> initParameterSeparators() {
List<CustomSeparator> separators = new ArrayList<CustomSeparator>();
separators.add(MyTokenizer.SINGLE_QUOTED_SEPARATOR);
separators.add(MyTokenizer.DOUBLE_QUOTED_SEPARATOR);
separators.add(MyTokenizer.PAREN_EXPR_STRING_SEPARATOR);
return separators;
}
public void parse(final Object umlMessage, final String text) {
try {
parseMessage(umlMessage, text);
} catch (ParseException pe) {
final String msg = "statusmsg.bar.error.parsing.message";
final Object[] args = {pe.getLocalizedMessage(),
Integer.valueOf(pe.getErrorOffset()), };
ArgoEventPump.fireEvent(new ArgoHelpEvent(
ArgoEventTypes.HELP_CHANGED, this,
Translator.messageFormat(msg, args)));
}
}
public String getParsingHelp() {
return "parsing.help.fig-message";
}
/**
* Generate the "intno" of the given Message. <p>
*
* If the predecessor of the given message has only one successor, then
* we return the string representation of the given integer. <p>
* If the predecessor of the given message has more than one successor, then
* this is a case of parallel execution of messages, e.g.
* Message 3.1a and Message 3.1b are concurrent within activation 3.1.
* Hence In this case we use a syntax like: 1a, 1b, 1c.
*
* This means that the first successor
* in the ordered list of successors that has more than one entry
* will get the postfix a, the second b, etc.
*
* TODO: Document exceptional behaviour.
*
* @param umlMessage the UML message object to generate
* the sequence number for
* @param umlPredecessor the immediate predecessor message (UML object)
* that has the given message as successor
* @param position the integer position of the given message
* within its sequence
* @return the generated sequence expression string,
* or null if the given Message was null
*/
protected String generateMessageNumber(Object umlMessage,
Object umlPredecessor,
int position) {
Iterator it;
String activatorIntNo = "";
Object umlActivator;
int subpos = 0, submax = 1;
if (umlMessage == null) {
return null;
}
umlActivator = Model.getFacade().getActivator(umlMessage);
if (umlActivator != null) {
activatorIntNo = generateMessageNumber(umlActivator);
// activatorIntNo is now guaranteed not null
}
if (umlPredecessor != null) {
// get the ordered list of immediate successors:
Collection c = Model.getFacade().getSuccessors(umlPredecessor);
submax = c.size();
it = c.iterator();
while (it.hasNext() && it.next() != umlMessage) {
subpos++;
}
}
StringBuilder result = new StringBuilder(activatorIntNo);
if (activatorIntNo.length() > 0) {
result.append(".");
}
result.append(position);
if (submax > 1) {
result.append((char) ('a' + subpos));
}
return result.toString();
}
/**
* Finds the break between message number and (possibly) message order.
*
* @return The position of the end of the number.
*/
private static int findMsgOrderBreak(String s) {
int i, t;
t = s.length();
for (i = 0; i < t; i++) {
char c = s.charAt(i);
if (c < '0' || c > '9') {
break;
}
}
return i;
}
/**
* Generates the textual number of a given Message, called seq_expr.
* The seq_expr is a string of numbers separated by points
* which describes the message's order
* and level in a collaboration.<p>
*
* If you plan to modify this seq_expr, make sure that
* the parsing of the Message is adapted accordingly to the change.
*
* @param message A Message to generate the seq_expr for
* @return A String with the seq_expr of the given message,
* or null if the given message was null
*/
private String generateMessageNumber(Object message) {
MsgPtr ptr = new MsgPtr();
int pos = recCountPredecessors(message, ptr) + 1;
return generateMessageNumber(message, ptr.message, pos);
}
/**
* Count the number of successors of the given Message. <p>
*
* Successors have the same Activator as the given message.
* This Activator may be null.
*
* @param message the UML Message object
* @return the number of successors: 0..n
*/
protected int countSuccessors(Object message) {
int count = 0;
final Object activator = Model.getFacade().getActivator(message);
final Collection successors = Model.getFacade().getSuccessors(message);
if (successors != null) {
for (Object suc : successors) {
if (Model.getFacade().getActivator(suc) != activator) {
continue;
}
count++;
}
}
return count;
}
/**
* Generates a textual description of an IterationExpression.
*
* @param expr the given UML expression object or null
* @return the string
*/
protected String generateRecurrence(Object expr) {
if (expr == null) {
return "";
}
return Model.getFacade().getBody(expr).toString();
}
/**
* Parse a Message textual description.<p>
*
* TODO: - This method is too complex, lets break it up. <p>
*
* @param umlMessage the UML Message object to apply any changes to
* @param s the String to parse
* @throws ParseException
* when it detects an error in the attribute string. See also
* ParseError.getErrorOffset().
*/
protected void parseMessage(Object umlMessage, String s)
throws ParseException {
String fname = null;
// the condition or iteration expression (recurrence):
StringBuilder guard = null;
String paramExpr = null;
String token;
StringBuilder varname = null;
List<List> predecessors = new ArrayList<List>();
List<Integer> seqno = null;
List<Integer> currentseq = new ArrayList<Integer>();
// List<String> args = null;
boolean mustBePre = false;
boolean mustBeSeq = false;
boolean parallell = false;
boolean iterative = false;
boolean mayDeleteExpr = false;
boolean refindOperation = false;
boolean hasPredecessors = false;
currentseq.add(null);
currentseq.add(null);
try {
MyTokenizer st = new MyTokenizer(s, " ,\t,*,[,],.,:,=,/,\\,",
MyTokenizer.PAREN_EXPR_STRING_SEPARATOR);
while (st.hasMoreTokens()) {
token = st.nextToken();
if (" ".equals(token) || "\t".equals(token)) {
if (currentseq == null) {
if (varname != null && fname == null) {
varname.append(token);
}
}
} else if ("[".equals(token)) {
if (mustBePre) {
String msg = "parsing.error.message.pred-unqualified";
throw new ParseException(Translator.localize(msg),
st.getTokenIndex());
}
mustBeSeq = true;
if (guard != null) {
String msg = "parsing.error.message.several-specs";
throw new ParseException(Translator.localize(msg),
st.getTokenIndex());
}
guard = new StringBuilder();
while (true) {
token = st.nextToken();
if ("]".equals(token)) {
break;
}
guard.append(token);
}
} else if ("*".equals(token)) {
if (mustBePre) {
String msg = "parsing.error.message.pred-unqualified";
throw new ParseException(Translator.localize(msg),
st.getTokenIndex());
}
mustBeSeq = true;
if (currentseq != null) {
iterative = true;
}
} else if (".".equals(token)) {
if (currentseq == null) {
String msg = "parsing.error.message.unexpected-dot";
throw new ParseException(Translator.localize(msg),
st.getTokenIndex());
}
if (currentseq.get(currentseq.size() - 2) != null
|| currentseq.get(currentseq.size() - 1) != null) {
currentseq.add(null);
currentseq.add(null);
}
} else if (":".equals(token)) {
if (st.hasMoreTokens()) {
String t = st.nextToken();
if ("=".equals(t)) {
st.putToken(":=");
continue;
}
st.putToken(t);
}
if (mustBePre) {
String msg = "parsing.error.message.pred-colon";
throw new ParseException(Translator.localize(msg),
st.getTokenIndex());
}
if (currentseq != null) {
if (currentseq.size() > 2
&& currentseq.get(currentseq.size() - 2) == null
&& currentseq.get(currentseq.size() - 1) == null) {
currentseq.remove(currentseq.size() - 1);
currentseq.remove(currentseq.size() - 1);
}
seqno = currentseq;
currentseq = null;
mayDeleteExpr = true;
}
} else if ("/".equals(token)) {
if (st.hasMoreTokens()) {
String t = st.nextToken();
if ("/".equals(t)) {
st.putToken("//");
continue;
}
st.putToken(t);
}
if (mustBeSeq) {
String msg = "parsing.error.message.sequence-slash";
throw new ParseException(Translator.localize(msg),
st.getTokenIndex());
}
mustBePre = false;
mustBeSeq = true;
if (currentseq.size() > 2
&& currentseq.get(currentseq.size() - 2) == null
&& currentseq.get(currentseq.size() - 1) == null) {
currentseq.remove(currentseq.size() - 1);
currentseq.remove(currentseq.size() - 1);
}
if (currentseq.get(currentseq.size() - 2) != null
|| currentseq.get(currentseq.size() - 1) != null) {
predecessors.add(currentseq);
currentseq = new ArrayList<Integer>();
currentseq.add(null);
currentseq.add(null);
}
hasPredecessors = true;
} else if ("//".equals(token)) {
if (mustBePre) {
String msg = "parsing.error.message.pred-parallelized";
throw new ParseException(Translator.localize(msg),
st.getTokenIndex());
}
mustBeSeq = true;
if (currentseq != null) {
parallell = true;
}
} else if (",".equals(token)) {
if (currentseq != null) {
if (mustBeSeq) {
String msg = "parsing.error.message.many-numbers";
throw new ParseException(Translator.localize(msg),
st.getTokenIndex());
}
mustBePre = true;
if (currentseq.size() > 2
&& currentseq.get(currentseq.size() - 2) == null
&& currentseq.get(currentseq.size() - 1) == null) {
currentseq.remove(currentseq.size() - 1);
currentseq.remove(currentseq.size() - 1);
}
if (currentseq.get(currentseq.size() - 2) != null
|| currentseq.get(currentseq.size() - 1) != null) {
predecessors.add(currentseq);
currentseq = new ArrayList<Integer>();
currentseq.add(null);
currentseq.add(null);
}
hasPredecessors = true;
} else {
if (varname == null && fname != null) {
varname = new StringBuilder(fname + token);
fname = null;
} else if (varname != null && fname == null) {
varname.append(token);
} else {
String msg = "parsing.error.message.found-comma";
throw new ParseException(
Translator.localize(msg),
st.getTokenIndex());
}
}
} else if ("=".equals(token) || ":=".equals(token)) {
if (currentseq == null) {
if (varname == null) {
varname = new StringBuilder(fname);
fname = "";
} else {
fname = "";
}
}
} else if (currentseq == null) {
if (paramExpr == null && token.charAt(0) == '(') {
if (token.charAt(token.length() - 1) != ')') {
String msg =
"parsing.error.message.malformed-parameters";
throw new ParseException(Translator.localize(msg),
st.getTokenIndex());
}
if (fname == null || "".equals(fname)) {
String msg =
"parsing.error.message.function-not-found";
throw new ParseException(Translator.localize(msg),
st.getTokenIndex());
}
if (varname == null) {
varname = new StringBuilder();
}
paramExpr = token.substring(1, token.length() - 1);
} else if (varname != null && fname == null) {
varname.append(token);
} else if (fname == null || fname.length() == 0) {
fname = token;
} else {
String msg = "parsing.error.message.unexpected-token";
Object[] parseExcArgs = {token};
throw new ParseException(
Translator.localize(msg, parseExcArgs),
st.getTokenIndex());
}
} else {
boolean hasVal =
currentseq.get(currentseq.size() - 2) != null;
boolean hasOrd =
currentseq.get(currentseq.size() - 1) != null;
boolean assigned = false;
int bp = findMsgOrderBreak(token);
if (!hasVal && !assigned && bp == token.length()) {
try {
currentseq.set(
currentseq.size() - 2, Integer.valueOf(
token));
assigned = true;
} catch (NumberFormatException nfe) { }
}
if (!hasOrd && !assigned && bp == 0) {
try {
currentseq.set(
currentseq.size() - 1, Integer.valueOf(
parseMsgOrder(token)));
assigned = true;
} catch (NumberFormatException nfe) { }
}
if (!hasVal && !hasOrd && !assigned && bp > 0
&& bp < token.length()) {
Integer nbr, ord;
try {
nbr = Integer.valueOf(token.substring(0, bp));
ord = Integer.valueOf(
parseMsgOrder(token.substring(bp)));
currentseq.set(currentseq.size() - 2, nbr);
currentseq.set(currentseq.size() - 1, ord);
assigned = true;
} catch (NumberFormatException nfe) { }
}
if (!assigned) {
String msg = "parsing.error.message.unexpected-token";
Object[] parseExcArgs = {token};
throw new ParseException(
Translator.localize(msg, parseExcArgs),
st.getTokenIndex());
}
}
}
} catch (NoSuchElementException nsee) {
String msg = "parsing.error.message.unexpected-end-message";
throw new ParseException(Translator.localize(msg), s.length());
} catch (ParseException pre) {
throw pre;
}
List<String> args = parseArguments(paramExpr, mayDeleteExpr);
printDebugInfo(s, fname, guard, paramExpr, varname, predecessors,
seqno, parallell, iterative);
/* Now apply the changes to the model: */
buildAction(umlMessage);
handleGuard(umlMessage, guard, parallell, iterative);
fname = fillBlankFunctionName(umlMessage, fname, mayDeleteExpr);
varname = fillBlankVariableName(umlMessage, varname, mayDeleteExpr);
refindOperation = handleFunctionName(umlMessage, fname, varname,
refindOperation);
refindOperation = handleArguments(umlMessage, args, refindOperation);
refindOperation = handleSequenceNumber(umlMessage, seqno,
refindOperation);
handleOperation(umlMessage, fname, refindOperation);
handlePredecessors(umlMessage, predecessors, hasPredecessors);
}
private void printDebugInfo(String s, String fname, StringBuilder guard,
String paramExpr, StringBuilder varname, List<List> predecessors,
List<Integer> seqno, boolean parallell, boolean iterative) {
if (LOG.isDebugEnabled()) {
StringBuffer buf = new StringBuffer();
buf.append("ParseMessage: " + s + "\n");
buf.append("Message: ");
for (int i = 0; seqno != null && i + 1 < seqno.size(); i += 2) {
if (i > 0) {
buf.append(", ");
}
buf.append(seqno.get(i) + " (" + seqno.get(i + 1) + ")");
}
buf.append("\n");
buf.append("predecessors: " + predecessors.size() + "\n");
for (int i = 0; i < predecessors.size(); i++) {
int j;
List v = predecessors.get(i);
buf.append(" Predecessor: ");
for (j = 0; v != null && j + 1 < v.size(); j += 2) {
if (j > 0) {
buf.append(", ");
}
buf.append(v.get(j) + " (" + v.get(j + 1) + ")");
}
}
buf.append("guard: " + guard + " it: " + iterative + " pl: "
+ parallell + "\n");
buf.append(varname + " := " + fname + " ( " + paramExpr + " )"
+ "\n");
LOG.debug(buf);
}
}
/**
* @param paramExpr
* @param mayDeleteExpr
* @return
*/
protected List<String> parseArguments(String paramExpr,
boolean mayDeleteExpr) {
String token;
List<String> args = null;
if (paramExpr != null) {
MyTokenizer st = new MyTokenizer(paramExpr, "\\,",
parameterCustomSep);
args = new ArrayList<String>();
while (st.hasMoreTokens()) {
token = st.nextToken();
if (",".equals(token)) {
if (args.size() == 0) {
args.add(null);
}
args.add(null);
} else {
if (args.size() == 0) {
if (token.trim().length() == 0) {
continue;
}
args.add(null);
}
String arg = args.get(args.size() - 1);
if (arg != null) {
arg = arg + token;
} else {
arg = token;
}
args.set(args.size() - 1, arg);
}
}
} else if (mayDeleteExpr) {
args = new ArrayList<String>();
}
return args;
}
/**
* Set the predecessors of the given Message.
*
* @param umlMessage the given UML Message object to be adapted
* @param predecessors the given predecessors as parsed
* @param hasPredecessors true if there are some, if false we do nothing
* @throws ParseException if something is wrong with the predecessor text
*/
protected void handlePredecessors(Object umlMessage,
List<List> predecessors, boolean hasPredecessors)
throws ParseException {
// Predecessors used to be not implemented, because it
// caused some problems that I've not found an easy way to handle yet,
// d00mst. The specific problem is that the notation currently is
// ambiguous on second message after a thread split.
// Why not implement it anyway? d00mst
// TODO: Document this ambiguity and the choice made.
if (hasPredecessors) {
Collection roots =
findCandidateRoots(
Model.getFacade().getMessages(
Model.getFacade().getInteraction(umlMessage)),
null,
null);
List<Object> pre = new ArrayList<Object>();
predfor:
for (int i = 0; i < predecessors.size(); i++) {
for (Object root : roots) {
Object msg =
walkTree(root, predecessors.get(i));
if (msg != null && msg != umlMessage) {
if (isBadPreMsg(umlMessage, msg)) {
String parseMsg = "parsing.error.message.one-pred";
throw new ParseException(
Translator.localize(parseMsg), 0);
}
pre.add(msg);
continue predfor;
}
}
String parseMsg = "parsing.error.message.pred-not-found";
throw new ParseException(Translator.localize(parseMsg), 0);
}
MsgPtr ptr = new MsgPtr();
recCountPredecessors(umlMessage, ptr);
if (ptr.message != null && !pre.contains(ptr.message)) {
pre.add(ptr.message);
}
Model.getCollaborationsHelper().setPredecessors(umlMessage, pre);
}
}
/**
* Update the model with the operation name. <p>
*
* The given operation name is located on the receiver of the given message.
* If an operation with the given name
* and a matching number of arguments is located,
* then the CallAction of the message is adapted accordingly.
*
* @param umlMessage the message of which the CallAction is to be adapted
* @param fname the name of the operation to be used
* @param refindOperation true if we have to set the operation
* of the CallAction
* @throws ParseException if the operation syntax can not be parsed
*/
protected void handleOperation(Object umlMessage, String fname,
boolean refindOperation) throws ParseException {
if (fname != null && refindOperation) {
Object role = Model.getFacade().getReceiver(umlMessage);
List ops =
getOperation(
Model.getFacade().getBases(role),
fname.trim(),
Model.getFacade().getActualArguments(
Model.getFacade().getAction(umlMessage)).size());
Object callAction = Model.getFacade().getAction(umlMessage);
if (Model.getFacade().isACallAction(callAction)) {
if (ops.size() > 0) {
// If there are more than one suitable operation,
// then we pick the first one.
Model.getCommonBehaviorHelper().setOperation(callAction,
ops.get(0));
} else {
Model.getCommonBehaviorHelper().setOperation(
callAction, null);
}
}
}
}
/**
* @param umlMessage
* @param seqno
* @param refindOperation
* @return
* @throws ParseException
*/
protected boolean handleSequenceNumber(Object umlMessage,
List<Integer> seqno, boolean refindOperation) throws ParseException {
int i;
if (seqno != null) {
Object/* MMessage */root;
// Find the preceding message, if any, on either end of the
// association.
StringBuilder pname = new StringBuilder();
StringBuilder mname = new StringBuilder();
String gname = generateMessageNumber(umlMessage);
boolean swapRoles = false;
int majval = 0;
if (seqno.get(seqno.size() - 2) != null) {
majval =
Math.max((seqno.get(seqno.size() - 2)).intValue()
- 1,
0);
}
int minval = 0;
if (seqno.get(seqno.size() - 1) != null) {
minval =
Math.max((seqno.get(seqno.size() - 1)).intValue(),
0);
}
for (i = 0; i + 1 < seqno.size(); i += 2) {
int bv = 1;
if (seqno.get(i) != null) {
bv = Math.max((seqno.get(i)).intValue(), 1);
}
int sv = 0;
if (seqno.get(i + 1) != null) {
sv = Math.max((seqno.get(i + 1)).intValue(), 0);
}
if (i > 0) {
mname.append(".");
}
mname.append(Integer.toString(bv) + (char) ('a' + sv));
if (i + 3 < seqno.size()) {
if (i > 0) {
pname.append(".");
}
pname.append(Integer.toString(bv) + (char) ('a' + sv));
}
}
root = null;
if (pname.length() > 0) {
root = findMsg(Model.getFacade().getSender(umlMessage),
pname.toString());
if (root == null) {
root = findMsg(Model.getFacade().getReceiver(umlMessage),
pname.toString());
if (root != null) {
swapRoles = true;
}
}
} else if (!hasMsgWithActivator(Model.getFacade().getSender(umlMessage),
null)
&& hasMsgWithActivator(Model.getFacade().getReceiver(umlMessage),
null)) {
swapRoles = true;
}
if (compareMsgNumbers(mname.toString(), gname.toString())) {
// Do nothing
} else if (isMsgNumberStartOf(gname.toString(), mname.toString())) {
String msg = "parsing.error.message.subtree-rooted-self";
throw new ParseException(Translator.localize(msg), 0);
} else if (Model.getFacade().getPredecessors(umlMessage).size() > 1
&& Model.getFacade().getSuccessors(umlMessage).size() > 1) {
String msg = "parsing.error.message.start-end-many-threads";
throw new ParseException(Translator.localize(msg), 0);
} else if (root == null && pname.length() > 0) {
String msg = "parsing.error.message.activator-not-found";
throw new ParseException(Translator.localize(msg), 0);
} else if (swapRoles
&& Model.getFacade().getActivatedMessages(umlMessage).size() > 0
&& (Model.getFacade().getSender(umlMessage)
!= Model.getFacade().getReceiver(umlMessage))) {
String msg = "parsing.error.message.reverse-direction-message";
throw new ParseException(Translator.localize(msg), 0);
} else {
/* Disconnect the message from the call graph
* Make copies of returned live collections
* since we're modifying
*/
Collection c = new ArrayList(
Model.getFacade().getPredecessors(umlMessage));
Collection c2 = new ArrayList(
Model.getFacade().getSuccessors(umlMessage));
Iterator it;
it = c2.iterator();
while (it.hasNext()) {
Model.getCollaborationsHelper().removeSuccessor(umlMessage,
it.next());
}
it = c.iterator();
while (it.hasNext()) {
Iterator it2 = c2.iterator();
Object pre = /* (MMessage) */it.next();
Model.getCollaborationsHelper().removePredecessor(umlMessage, pre);
while (it2.hasNext()) {
Model.getCollaborationsHelper().addPredecessor(
it2.next(), pre);
}
}
// Connect the message at a new spot
Model.getCollaborationsHelper().setActivator(umlMessage, root);
if (swapRoles) {
Object/* MClassifierRole */r =
Model.getFacade().getSender(umlMessage);
Model.getCollaborationsHelper().setSender(umlMessage,
Model.getFacade().getReceiver(umlMessage));
Model.getCommonBehaviorHelper().setReceiver(umlMessage, r);
}
if (root == null) {
c =
filterWithActivator(
Model.getFacade().getSentMessages(
Model.getFacade().getSender(umlMessage)),
null);
} else {
c = Model.getFacade().getActivatedMessages(root);
}
c2 = findCandidateRoots(c, root, umlMessage);
it = c2.iterator();
// If c2 is empty, then we're done (or there is a
// cycle in the message graph, which would be bad) If
// c2 has more than one element, then the model is
// crappy, but we'll just use one of them anyway
if (majval <= 0) {
while (it.hasNext()) {
Model.getCollaborationsHelper().addSuccessor(umlMessage,
/* (MMessage) */it.next());
}
} else if (it.hasNext()) {
Object/* MMessage */pre =
walk(/* (MMessage) */it.next(), majval - 1, false);
Object/* MMessage */post = successor(pre, minval);
if (post != null) {
Model.getCollaborationsHelper()
.removePredecessor(post, pre);
Model.getCollaborationsHelper()
.addPredecessor(post, umlMessage);
}
insertSuccessor(pre, umlMessage, minval);
}
refindOperation = true;
}
}
return refindOperation;
}
/**
* @param umlMessage
* @param args
* @param refindOperation
* @return
*/
protected boolean handleArguments(Object umlMessage, List<String> args,
boolean refindOperation) {
if (args != null) {
Collection c = new ArrayList(
Model.getFacade().getActualArguments(
Model.getFacade().getAction(umlMessage)));
Iterator it = c.iterator();
int ii;
for (ii = 0; ii < args.size(); ii++) {
Object umlArgument = (it.hasNext() ? it.next() : null);
if (umlArgument == null) {
umlArgument = Model.getCommonBehaviorFactory()
.createArgument();
Model.getCommonBehaviorHelper().addActualArgument(
Model.getFacade().getAction(umlMessage), umlArgument);
refindOperation = true;
}
if (Model.getFacade().getValue(umlArgument) == null
|| !args.get(ii).equals(
Model.getFacade().getBody(
Model.getFacade().getValue(umlArgument)))) {
String value = (args.get(ii) != null ? args.get(ii)
: "");
Object umlExpression =
Model.getDataTypesFactory().createExpression(
getExpressionLanguage(),
value.trim());
Model.getCommonBehaviorHelper().setValue(umlArgument, umlExpression);
}
}
while (it.hasNext()) {
Model.getCommonBehaviorHelper()
.removeActualArgument(Model.getFacade().getAction(umlMessage),
it.next());
refindOperation = true;
}
}
return refindOperation;
}
/**
* Store the given function name and return variable name
* in the script of the action of the given message. <p>
*
* Constraint: the given Message shall have an Action.
*
* @param umlMessage the given UML Message object to adapt
* @param fname the name of the function
* @param varname the return variable name
* @param refindOperation if false, then we may return true or false.
* If true, we return true.
* @return true if we stored the fname and varname
* in the Action of the Message
*/
protected boolean handleFunctionName(Object umlMessage, String fname,
StringBuilder varname, boolean refindOperation) {
if (fname != null) {
String expr = fname.trim();
if (varname.length() > 0) {
expr = varname.toString().trim() + " := " + expr;
}
Object action = Model.getFacade().getAction(umlMessage);
assert action != null;
Object script = Model.getFacade().getScript(action);
if (script == null
|| !expr.equals(Model.getFacade().getBody(script))) {
Object newActionExpression =
Model.getDataTypesFactory()
.createActionExpression(
getExpressionLanguage(),
expr.trim());
Model.getCommonBehaviorHelper().setScript(
action, newActionExpression);
refindOperation = true;
}
}
return refindOperation;
}
/**
* Fill in the variable name if it is blank. <p>
* The variable name is the part in front of the assignment operator.
*
* @param umlMessage the given message to fill the variable name for
* @param varname if null, then we get the variable name from the model.
* @param mayDeleteExpr if true, then we may delete the variable,
* and hence we return an empty string
* @return the original variable name, or if it was null,
* a variable name generated from the model
*/
protected StringBuilder fillBlankVariableName(Object umlMessage,
StringBuilder varname, boolean mayDeleteExpr) {
/* If no variable name was given, then retain the one in the model. */
if (varname == null) {
Object script = Model.getFacade().getScript(
Model.getFacade().getAction(umlMessage));
if (!mayDeleteExpr && script != null) {
String body =
(String) Model.getFacade().getBody(script);
int idx = body.indexOf(":=");
if (idx < 0) {
idx = body.indexOf("=");
}
if (idx >= 0) {
varname = new StringBuilder(body.substring(0, idx));
} else {
varname = new StringBuilder();
}
} else {
varname = new StringBuilder();
}
}
return varname;
}
/**
* Fill in the function name if it is blank. <p>
*
* The fname is the part of the script after the assignment operator.
*
* @param umlMessage the given message to fill the fname for
* @param fname if null, then we get the fname from the model.
* @param mayDeleteExpr if true, then we may delete the function,
* and hence we return an empty string
* @return the original fname, or if it was null,
* a fname generated from the model
*/
protected String fillBlankFunctionName(Object umlMessage, String fname,
boolean mayDeleteExpr) {
/* If no function-name was given, then retain the one in the model. */
if (fname == null) {
Object script = Model.getFacade().getScript(
Model.getFacade().getAction(umlMessage));
if (!mayDeleteExpr && script != null) {
String body =
(String) Model.getFacade().getBody(script);
int idx = body.indexOf(":=");
if (idx >= 0) {
idx++;
} else {
idx = body.indexOf("=");
}
if (idx >= 0) {
fname = body.substring(idx + 1);
} else {
fname = body;
}
} else {
fname = "";
}
}
return fname;
}
/**
* Store the parsed guard in the UML objects related to the given Message.
*
* @param umlMessage the UML Message object
* @param guard the guard expression string
* @param parallell true if parallel execution was indicated
* @param iterative true if this is an iterative expression,
* as opposed to a condition
*/
protected void handleGuard(Object umlMessage, StringBuilder guard,
boolean parallell, boolean iterative) {
/* Store the guard, i.e. condition or iteration expression,
* in the recurrence field of the Action: */
if (guard != null) {
guard = new StringBuilder("[" + guard.toString().trim() + "]");
if (iterative) {
if (parallell) {
guard = guard.insert(0, "*//");
} else {
guard = guard.insert(0, "*");
}
}
Object expr =
Model.getDataTypesFactory().createIterationExpression(
getExpressionLanguage(), guard.toString());
Model.getCommonBehaviorHelper().setRecurrence(
Model.getFacade().getAction(umlMessage), expr);
}
}
/**
* Build a CallAction for the given UML Message
* if it did not have an Action yet.
*
* @param umlMessage the UML Message object to create an Action for
*/
protected void buildAction(Object umlMessage) {
if (Model.getFacade().getAction(umlMessage) == null) {
/* If there was no Action yet, create a CallAction: */
Object a = Model.getCommonBehaviorFactory()
.createCallAction();
Model.getCoreHelper().addOwnedElement(Model.getFacade().getContext(
Model.getFacade().getInteraction(umlMessage)), a);
Model.getCollaborationsHelper().setAction(umlMessage, a);
}
}
/**
* TODO: This name of the expression language should be
* configurable by the user. <p>
*
* According to the UML standard,
* the expression language should be the same
* for all elements in one diagram. <p>
*
* UML is not a sensible default - usually this is some pseudo-language.
*
* @return the name of the expression language
*/
private String getExpressionLanguage() {
return "";
}
/**
* Walks a call tree from a <code>root</code> node
* following the directions given in <code>path</code>
* to a destination node. If the destination node cannot be reached, then
* null is returned.
*
* @param root The root of the call tree.
* @param path The path to walk in the call tree.
* @return The message at the end of path, or <code>null</code>.
*/
private Object walkTree(Object root, List path) {
int i;
for (i = 0; i + 1 < path.size(); i += 2) {
int bv = 0;
if (path.get(i) != null) {
bv = Math.max(((Integer) path.get(i)).intValue() - 1, 0);
}
int sv = 0;
if (path.get(i + 1) != null) {
sv = Math.max(((Integer) path.get(i + 1)).intValue(), 0);
}
root = walk(root, bv - 1, true);
if (root == null) {
return null;
}
if (bv > 0) {
root = successor(root, sv);
if (root == null) {
return null;
}
}
if (i + 3 < path.size()) {
Iterator it =
findCandidateRoots(
Model.getFacade().getActivatedMessages(root),
root,
null).iterator();
// Things are strange if there are more than one candidate root,
// it has no obvious interpretation in terms of a call tree.
if (!it.hasNext()) {
return null;
}
root = /* (MMessage) */it.next();
}
}
return root;
}
/**
* Finds the steps'th successor of r in the sense that it is a successor of
* a successor of r (steps times). The first successor with the same
* activator as r is used in each step. If there are not enough successors,
* then struct determines the result. If struct is true, then null is
* returned, otherwise the last successor found.
*/
private Object walk(Object/* MMessage */r, int steps, boolean strict) {
Object/* MMessage */act = Model.getFacade().getActivator(r);
while (steps > 0) {
Iterator it = Model.getFacade().getSuccessors(r).iterator();
do {
if (!it.hasNext()) {
return (strict ? null : r);
}
r = /* (MMessage) */it.next();
} while (Model.getFacade().getActivator(r) != act);
steps--;
}
return r;
}
/**
* Finds the root candidates in a collection c, ie the messages in c that
* has the activator a (may be null) and has no predecessor with the same
* activator. If veto isn't null, then the message in veto will not be
* included in the Collection of candidates.
*
* @param c The collection of UML Message objects.
* @param a The message.
* @param veto The excluded message.
* @return The found roots.
*/
private Collection findCandidateRoots(Collection c, Object a, Object veto) {
List<Object> candidates = new ArrayList<Object>();
for (Object message : c) {
if (message == veto) {
continue;
}
if (Model.getFacade().getActivator(message) != a) {
continue;
}
Collection predecessors =
Model.getFacade().getPredecessors(message);
boolean isCandidate = true;
for (Object predecessor : predecessors) {
if (Model.getFacade().getActivator(predecessor) == a) {
isCandidate = false;
}
}
if (isCandidate) {
candidates.add(message);
}
}
return candidates;
}
/**
* Finds the steps'th successor of message r in the sense that it is a
* direct successor of r. Returns null if r has fewer successors.
*/
private Object successor(Object/* MMessage */r, int steps) {
Iterator it = Model.getFacade().getSuccessors(r).iterator();
while (it.hasNext() && steps > 0) {
it.next();
steps--;
}
if (it.hasNext()) {
return /* (MMessage) */it.next();
}
return null;
}
/**
* Compares two message numbers n1, n2 with each other to determine if n1
* specifies a the same position as n2 in a call tree or n1 specifies a
* position that is a father of the position specified by n2.
*/
private boolean isMsgNumberStartOf(String n1, String n2) {
int i, j, len, jlen;
len = n1.length();
jlen = n2.length();
i = 0;
j = 0;
for (; i < len;) {
int ibv, isv;
int jbv, jsv;
ibv = 0;
for (; i < len; i++) {
char c = n1.charAt(i);
if (c < '0' || c > '9') {
break;
}
ibv *= 10;
ibv += c - '0';
}
isv = 0;
for (; i < len; i++) {
char c = n1.charAt(i);
if (c < 'a' || c > 'z') {
break;
}
isv *= 26;
isv += c - 'a';
}
jbv = 0;
for (; j < jlen; j++) {
char c = n2.charAt(j);
if (c < '0' || c > '9') {
break;
}
jbv *= 10;
jbv += c - '0';
}
jsv = 0;
for (; j < jlen; j++) {
char c = n2.charAt(j);
if (c < 'a' || c > 'z') {
break;
}
jsv *= 26;
jsv += c - 'a';
}
if (ibv != jbv || isv != jsv) {
return false;
}
if (i < len && n1.charAt(i) != '.') {
return false;
}
i++;
if (j < jlen && n2.charAt(j) != '.') {
return false;
}
j++;
}
return true;
}
/**
* Compares two message numbers with each other to see if they are equal, in
* the sense that they refer to the same position in a call tree.
*
* @param n1 The first number.
* @param n2 The second number.
* @return <code>true</code> if they are the same.
*/
private boolean compareMsgNumbers(String n1, String n2) {
return isMsgNumberStartOf(n1, n2) && isMsgNumberStartOf(n2, n1);
}
/**
* Parses a message order specification.
*/
private static int parseMsgOrder(String s) {
int i, t;
int v = 0;
t = s.length();
for (i = 0; i < t; i++) {
char c = s.charAt(i);
if (c < 'a' || c > 'z') {
throw new NumberFormatException();
}
v *= 26;
v += c - 'a';
}
return v;
}
/**
* Finds the message in ClassifierRole r that has the message number written
* in n. If it isn't found, null is returned.
*/
private Object findMsg(Object/* MClassifierRole */r, String n) {
Collection c = Model.getFacade().getReceivedMessages(r);
Iterator it = c.iterator();
while (it.hasNext()) {
Object msg = /* (MMessage) */it.next();
String gname = generateMessageNumber(msg);
if (compareMsgNumbers(gname, n)) {
return msg;
}
}
return null;
}
/**
* Examines a collection to see if any message has the given message as an
* activator.
*
* @param r
* MClassifierRole
* @param m
* MMessage
*/
private boolean hasMsgWithActivator(Object r, Object m) {
Iterator it = Model.getFacade().getSentMessages(r).iterator();
while (it.hasNext()) {
if (Model.getFacade().getActivator(it.next()) == m) {
return true;
}
}
return false;
}
/**
* Examines the call tree from chld to see if ans is an ancestor.
*
* @param ans
* MMessage
* @param chld
* MMessage
*/
private boolean isBadPreMsg(Object ans, Object chld) {
while (chld != null) {
if (ans == chld) {
return true;
}
if (isPredecessorMsg(ans, chld, 100)) {
return true;
}
chld = Model.getFacade().getActivator(chld);
}
return false;
}
/**
* Examines the call tree from suc to see if pre is a predecessor. This
* function is recursive and md specifies the maximum level of recursions
* allowed.
*
* @param pre
* MMessage
* @param suc
* MMessage
*/
private boolean isPredecessorMsg(Object pre, Object suc, int md) {
Iterator it = Model.getFacade().getPredecessors(suc).iterator();
while (it.hasNext()) {
Object m = /* (MMessage) */it.next();
if (m == pre) {
return true;
}
if (md > 0 && isPredecessorMsg(pre, m, md - 1)) {
return true;
}
}
return false;
}
/**
* Finds the messages in Collection c that has message a as activator.
*/
private Collection<Object> filterWithActivator(Collection c,
Object/*MMessage*/a) {
List<Object> v = new ArrayList<Object>();
for (Object msg : c) {
if (Model.getFacade().getActivator(msg) == a) {
v.add(msg);
}
}
return v;
}
/**
* Inserts message s as the p'th successor of message m.
*
* @param m
* MMessage
* @param s
* MMessage
*/
private void insertSuccessor(Object m, Object s, int p) {
List<Object> successors =
new ArrayList<Object>(Model.getFacade().getSuccessors(m));
if (successors.size() > p) {
successors.add(p, s);
} else {
successors.add(s);
}
Model.getCollaborationsHelper().setSuccessors(m, successors);
}
/**
* Finds all operations in a given collection of classifiers
* with the given name
* and the given number of parameters.
* If no operation is found, one is created in the first given Classifier.
* The applicable operations are returned.
*
* @param classifiers the collection of classifiers to search for operations
* @param name the name of the operation to be found
* @param params the number of parameters of the operation to be found
* @return a list of the sought operations
* @throws ParseException if the operation syntax can not be parsed
*/
private List getOperation(Collection classifiers, String name, int params)
throws ParseException {
List<Object> operations = new ArrayList<Object>();
if (name == null || name.length() == 0) {
return operations;
}
for (Object clf : classifiers) {
Collection oe = Model.getFacade().getFeatures(clf);
for (Object operation : oe) {
if (!(Model.getFacade().isAOperation(operation))) {
continue;
}
if (!name.equals(Model.getFacade().getName(operation))) {
continue;
}
if (params != countParameters(operation)) {
continue;
}
operations.add(operation);
}
}
if (operations.size() > 0) {
return operations;
}
Iterator it = classifiers.iterator();
if (it.hasNext()) {
StringBuilder expr = new StringBuilder(name + "(");
int i;
for (i = 0; i < params; i++) {
if (i > 0) {
expr.append(", ");
}
expr.append("param" + (i + 1));
}
expr.append(")");
// Jaap Branderhorst 2002-23-09 added next lines to link
// parameters and operations to the figs that represent
// them
Object cls = it.next();
Object returnType =
ProjectManager.getManager()
.getCurrentProject().getDefaultReturnType();
Object op = Model.getCoreFactory().buildOperation(cls, returnType);
new OperationNotationUml(op).parseOperation(
expr.toString(), op);
operations.add(op);
}
return operations;
}
/**
* Counts the number of parameters that are not return values.
*/
private int countParameters(Object bf) {
int count = 0;
for (Object parameter : Model.getFacade().getParameters(bf)) {
if (!Model.getFacade().isReturn(parameter)) {
count++;
}
}
return count;
}
/**
* Recursively count the number of predecessors of the given Message,
* and return (a pointer to) the first Message in the chain.
*
* @param umlMessage the UML Message to count the predecessors for
* @param ptr an object to contain the returned first Message
* @return the number of messages in the chain
*/
protected int recCountPredecessors(Object umlMessage, MsgPtr ptr) {
int pre = 0;
int local = 0;
Object/*MMessage*/ maxmsg = null;
Object activator;
if (umlMessage == null) {
ptr.message = null;
return 0;
}
activator = Model.getFacade().getActivator(umlMessage);
for (Object predecessor
: Model.getFacade().getPredecessors(umlMessage)) {
if (Model.getFacade().getActivator(predecessor)
!= activator) {
continue;
}
int p = recCountPredecessors(predecessor, null) + 1;
if (p > pre) {
pre = p;
maxmsg = predecessor;
}
local++;
}
if (ptr != null) {
ptr.message = maxmsg;
}
return Math.max(pre, local);
}
}