umlet 的标记语言规范是什么?
What is the markup language specification for umlet?
我一直在整个网站、Internet 上寻找,当然还有 SO,但似乎找不到 umlet 中使用的标记语言的描述或规范。
在示例序列图中例如:
title: sample
_alpha:A~id1_|_beta:B~id2_|_gamma:G~id3_
id1->>id2:id1,id2
id2-/>id1:async Msg.
id3->>>id1:id1,id3
id1.>id3:id1,id3:async return Msg
id1->id1:id1:self
iframe{:interaction frame
id2->id3:id1,id3:async Msg.
iframe}
->、--> 等很明显,但是冒号是做什么的?
为什么需要下划线等。好问的人想知道,因为这看起来像一个有用的素描工具。
找到 an annotated script(从 14.2 开始似乎无效):
//UML2 style titles are optional.
title:sequence diagram
//the participating objects are separated using the pipe symbol "|". Their names may be underlined using underscores.
_alpha:A_|_beta:B_|_gamma:G_
//The following line describes a (synchonuous) message originating from the first object sent to the second object, while both objects are active.
1->>2:1,2
//This message is asynchronous being sent from the second object to the first object. The messages is named.
2-/>1:async Msg.
//A message from the third object to the first object; both objects are active.
3->>>1:1,3
//A named asynchronous return message in UML2 style (using a stick arrow head).
1.>3:1,3:async return Msg
//This is a self call of the first object.
1->1:1:self
//Interaction frames may be used to show optional or conditional blocks within sequence diagrams.
iframe{:interaction frame
2->3:2,3:async Msg.
iframe}
RE: BNF,来源中有umlet-elements/src/main/javacc/SequenceAllInOneParser.jj
:
options {
static = false;
IGNORE_CASE = false;
JAVA_UNICODE_ESCAPE = true;
JDK_VERSION = "1.6";
// Not yet detected as a valid option by the Eclipse plugin, but works nonetheless. This is essential for GWT compatible generated code.
JAVA_TEMPLATE_TYPE = "modern";
}
PARSER_BEGIN(SequenceAllInOneParser)
package com.baselet.element.facet.specific.sequence_aio.gen;
import org.slf4j.Logger;import org.slf4j.LoggerFactory;
import com.baselet.control.enums.LineType;
import com.baselet.element.facet.specific.sequence_aio.Lifeline;
import com.baselet.element.facet.specific.sequence_aio.Message.ArrowType;
import com.baselet.element.facet.specific.sequence_aio.SequenceDiagramBuilder;
import com.baselet.element.facet.specific.sequence_aio.SequenceDiagramException;
public class SequenceAllInOneParser {
private static final Logger log = LoggerFactory.getLogger(SequenceAllInOneParser.class);
private boolean autoTick;
/**
* Replaces "\\" with "\" in the output so that any further specified match replace pair can't match the replaced "\".
* @param matchReplacePairs
* @return the string with the replacements
*/
public static String backslashReplace(String input, String... matchReplacePairs) {
if (matchReplacePairs.length % 2 == 1) {
throw new IllegalArgumentException("matchReplacePairs must have an even number of elements.");
}
String split = "\\";
StringBuilder strBuilder = new StringBuilder(input.length());
int firstIndex = 0;
// use the indexOf function instead of a split, because split uses regex and regex is not 100% supported by GWT
int foundIndex = input.indexOf(split, firstIndex);
while (firstIndex < input.length()) {
int lastIndex = foundIndex == -1 ? input.length() : foundIndex;
String tmp = input.substring(firstIndex, lastIndex);
for (int j = 0; j < matchReplacePairs.length - 1; j += 2) {
tmp = tmp.replace(matchReplacePairs[j], matchReplacePairs[j + 1]);
}
strBuilder.append(tmp);
if (foundIndex != -1) {
strBuilder.append('\');
}
firstIndex = lastIndex + split.length();
foundIndex = input.indexOf(split, firstIndex);
}
return strBuilder.toString();
}
/**
* Small data container to pass all informations.
*/
private class MessageArrowInfo {
boolean fromLeftToRight;
LineType lineType;
ArrowType arrowType;
}
private class InteractionConstraint {
private String lifelineId = null;
private String text = "";
}
private class LifelineInterval {
private String startId;
private String endId;
}
}
PARSER_END(SequenceAllInOneParser)
/* defines input to be ignored */
< * > SKIP:
{
" "
| "\t"
}
< DIAGRAM_DEF_TEXT > SKIP:
{
"\n" : DEFAULT
| "\r\n" : DEFAULT
| "\r" : DEFAULT
}
< DEFAULT > TOKEN:
{
< DIAGRAM_OPTION_OVERRIDE_ID :"overrideIds=" >
| < DIAGRAM_OPTION_AUTO_TICK: "autoTick=" >
| < TRUE: "true" >
| < FALSE: "false" >
| < DIAGRAM_TITLE: "title=" > : DIAGRAM_DEF_TEXT
| < DIAGRAM_DESC: "desc=" > : DIAGRAM_DEF_TEXT
| < LIFELINE_DEFINITIONS: "obj=" > : LIFELINE_DEF
}
< DIAGRAM_DEF_TEXT > TOKEN:
{
< DIAGRAM_DEFINITION_TEXT:
(
~ ["\","\r","\n"]
| ("\" ["\", "n"] )
)+> : DIAGRAM_DEF_TEXT
}
< LIFELINE_DEF > TOKEN :
{
< LL_DEF_NEW_LINE: "\n" | "\r\n" | "\r" > : DEFAULT
| < LIFELINE_DEF_DELIMITER: "|" >
/*| < LIFELINE_TITLE_DELIMITER: "~" > integrated in the title */
| < LIFELINE_ACTOR: "ACTOR" >
| < LIFELINE_ACTIVE: "ACTIVE" >
| < LIFELINE_CREATED_LATER :"CREATED_LATER" >
| < LIFELINE_EXEC_SPEC_FROM_START: "EXECUTION" >
| < LIFELINE_TITLE: /* since | and ~ are special characters which delimit the title they need to be escaped */
(
~["~","\","\n","\r","|"]
| ("\" ["\","~","|","n"])
)+
"~" >
}
< DEFAULT > TOKEN :
{
< TEXT_DELIMITER: ":" > : DIAGRAM_SEQ_TEXT
| < LIST_DELIMITER: "," >
| < OPEN_CURLY_BRACKET: "{" >
| < CLOSE_CURLY_BRACKET: "}" >
| < COMMAND_DELIMITER: ";" >
| < T_DASH: "-" >
| < T_DOT: "." >
| < T_DDOT: ".." >
| < T_EQ: "=" >
| < MESSAGE_ARROW_LEFT_OPEN: "<" >
| < MESSAGE_ARROW_LEFT_FILLED: "<<<" >
| < MESSAGE_ARROW_RIGHT_OPEN: ">" >
| < MESSAGE_ARROW_RIGHT_FILLED: ">>>" >
| < MESSAGE_DURATION_INC: "+" >
| < LOST: "lost" >
| < FOUND: "found" >
| < GATE: "gate" >
| < START_COREGION: "coregionStart=" >
| < END_COREGION: "coregionEnd=" >
| < INVARIANT: "invariant=" >
| < STATE_INVARIANT: "stateInvariant=" >
| < EXEC_SPEC_START: "on=" >
| < EXEC_SPEC_END: "off=" >
| < LL_DESTROY: "destroy=" >
| < REF: "ref=" >
| < CONTINUATION: "continuation=" >
| < TEXT_ON_LIFELINE: "text=" >
| < TICK: "tick=" >
| < COMBINED_FRAGMENT: "combinedFragment=" > : CF_OPERATOR
| < INTERACTION_CONSTRAINT: "constraint=" >
| < UNSIGNED_INT_CONSTANT: ( ["0" - "9"] ) + >
| < DEFAULT_NEW_LINE: "\n" | "\r\n" | "\r" >
}
< LIFELINE_DEF, DEFAULT > TOKEN :
{
< LIFELINE_ID: ["a"-"z","A"-"Z"] (["a"-"z","A"-"Z","0"-"9"])* >
}
< DIAGRAM_SEQ_TEXT > TOKEN :
{
< TEXT_UNTIL_NEXT_COMMAND : ( /* since ; is special characters which delimit the text it must be escaped */
~["\","\n","\r",";"]
| ("\" ["\",";","n"])
)+ > : DEFAULT
}
< CF_OPERATOR > TOKEN :
{
< COMBINED_FRAGMENT_OPERATOR: /* since ; and ~ are special characters which delimit the operator they need to be escaped */
(
~["~","\","\n","\r",";"]
| ("\" ["\","~","|","n",";"])
)+
"~" > : DEFAULT
}
/* general Tokens */
/* skip comments */
< * > SKIP :
{
<"//"(~["\n","\r"])*>
}
< * > TOKEN :
{
<LAST_LINE_COMMENT: "//"(~["\n","\r"])*>
}
/**
* The main function which parses the whole diagram.
* Line comments are skipped (see Tokens)
*/
SequenceDiagramBuilder start() :
{
String titleText = "";
String descText = "";
SequenceDiagramBuilder diagram = new SequenceDiagramBuilder();
autoTick = true;
}
{
(
(
titleText=DiagramTitle() /* NL skip and change state to DEFAULT */
| descText = DiagramDescription() /* NL skip and change state to DEFAULT */
| Option(diagram) < DEFAULT_NEW_LINE >
| LifelineDefinitions(diagram) /* NL part of the nonterminal */
)+//diagram definition as new NT followed by NL alternating with sequence so that the sequence= can be removed
Sequence(diagram)
)
(< LAST_LINE_COMMENT >)?
<EOF>
{
diagram.setTitle(titleText);
diagram.setText(descText);
return diagram;
}
}
String DiagramTitle() :
{
String text = "";
}
{
< DIAGRAM_TITLE >
(
< DIAGRAM_DEFINITION_TEXT > { text = backslashReplace(token.image, "\n","\n");}
)?
{return text;}
}
String DiagramDescription() :
{
String desc = "";
}
{
< DIAGRAM_DESC >
< DIAGRAM_DEFINITION_TEXT > { desc = backslashReplace(token.image, "\n","\n");}
{return desc;}
}
/**
* Options for the whole diagram
*/
void Option(SequenceDiagramBuilder diagram) :
{
boolean overrideIds;
}
{
< DIAGRAM_OPTION_OVERRIDE_ID > overrideIds = booleanConstant()
{
diagram.setOverrideDefaultIds(overrideIds);
}
| < DIAGRAM_OPTION_AUTO_TICK > autoTick = booleanConstant()
}
/**
* Defines all Lifelines
*/
void LifelineDefinitions(SequenceDiagramBuilder diagram) :
{}
{
< LIFELINE_DEFINITIONS > LifelineDef(diagram) (< LIFELINE_DEF_DELIMITER > LifelineDef(diagram))* < LL_DEF_NEW_LINE >
}
/**
* Defines one Lifeline, the id can't be LIFELINE_ACTOR, LIFELINE_ACTIVE
* or LIFELINE_CREATED_LATER because these are keywords.
*/
void LifelineDef(SequenceDiagramBuilder diagram) :
{
String name = "";
String id = null;
boolean createdOnStart = true;
Lifeline.LifelineHeadType headType = Lifeline.LifelineHeadType.STANDARD;
boolean execSpecFromStart = false;
}
{
name = LifelineDefTitleText() (id=LifelineId())?
(
< LIFELINE_ACTOR > { headType = Lifeline.LifelineHeadType.ACTOR; }
| < LIFELINE_ACTIVE > { headType = Lifeline.LifelineHeadType.ACTIVE_CLASS; }
| < LIFELINE_CREATED_LATER > { createdOnStart = false; }
| < LIFELINE_EXEC_SPEC_FROM_START > { execSpecFromStart = true; }
) *
{
if("lost".equals(id) || "found".equals(id)) {
throw new SequenceDiagramException("'lost' and 'found' are keywords and can not be used as lifeline identifiers.");
}
diagram.addLiveline(name, id, headType, createdOnStart, execSpecFromStart);
}
}
/** can could be multiple lines */
String LifelineDefTitleText() :
{}
{
< LIFELINE_TITLE >
{
/* remove trailing ~ and handle the escaping of \, |, n and ~ */
return backslashReplace(token.image.substring(0, token.image.length() - 1), "\n", "\n", "\~", "~", "\|", "|");
}
}
/**
* Can't be one of the following, because these are keywords in the lifeline definition!
* < LIFELINE_ACTOR: "ACTOR" >
* < LIFELINE_ACTIVE: "ACTIVE" >
* < LIFELINE_CREATED_LATER :"CREATED_LATER" >
*/
String LifelineId() :
{}
{
< LIFELINE_ID >
{
return token.image;
}
}
void Sequence(SequenceDiagramBuilder diagram) :
{}
{
(
SequenceTick(diagram) < DEFAULT_NEW_LINE >
| (
SequenceElement(diagram)
( LOOKAHEAD(2) < COMMAND_DELIMITER > SequenceElement(diagram))*
(< COMMAND_DELIMITER >)?
< DEFAULT_NEW_LINE >
{ if(autoTick) { diagram.tick(); } }
)
| < DEFAULT_NEW_LINE >
)*
}
void SequenceTick(SequenceDiagramBuilder diagram) :
{
int tickCount = 1;
}
{
< TICK > (tickCount = unsignedIntConstant())?
{ diagram.tick(tickCount); }
}
void SequenceElement(SequenceDiagramBuilder diagram) :
{}
{
(
MessageOrGeneralOrderingOrText(diagram) /* Message, GeneralOrdering have a common prefix for more clarity a new nonterminal was created*/
| Coregion(diagram)
| DestroyLL(diagram)
| ExecutionSpecification(diagram)
| StateInvariant(diagram)
//| (diagram) //general ordering, TimeConstraint TimeObservation and DurationConstraint Duration Observation missing
| InteractionUse(diagram)
| Continuation(diagram)
| CombinedFragment(diagram)
)
}
void MessageOrGeneralOrderingOrText(SequenceDiagramBuilder diagram) :
{}
{
/*
* (Message and GeneralOrdering) A common prefix is: <LIFELINE_ID> "." <LIFELINE_ID> "<"
* All three have <LIFELINE_ID > as common prefix
* therefore use LOOKAHEAD(2) if it is a text, if not use a LOOKAHEAD(5) to determine if it is a message or a general ordering
*/
LOOKAHEAD(2) TextOnLifeline(diagram)
| LOOKAHEAD(5) Message(diagram)
| GeneralOrdering(diagram)
}
void CombinedFragment(SequenceDiagramBuilder diagram) :
{
LifelineInterval interval = new LifelineInterval();
String operator = "";
String id = null;
}
{
(
< COMBINED_FRAGMENT >
(
< COMBINED_FRAGMENT_OPERATOR >
{ operator = backslashReplace(token.image.substring(0, token.image.length() - 1), "\n", "\n", "\;", ";", "\~", "~"); }
)
[
id = LifelineId()
[interval = LifelineInterval()]
]
{ diagram.beginCombinedFragment(interval.startId, interval.endId, id, operator); }
)
| (
< T_DASH > < T_DASH > (< T_EQ > id=LifelineId())?
{ diagram.endCombinedFragment(id); }
)
| (
< T_DDOT > (< T_EQ > id=LifelineId())?
{ diagram.endAndBeginOperand(id); }
)
}
void Message(SequenceDiagramBuilder diagram) :
{
String leftLifelineId = null;
String rightLifelineId = null;
String leftLifelineLocalId = null;
String rightLifelineLocalId = null;
MessageArrowInfo messageArrowInfo;
String msgText = "";
int lostCount = 0;
int foundCount = 0;
int gateCount = 0;
int duration = 0;
}
{
(
(
leftLifelineId = LifelineId() [LOOKAHEAD(2)< T_DOT > leftLifelineLocalId = LifelineId()]
| < LOST > { leftLifelineId = "lost"; lostCount++; }
| < FOUND > { leftLifelineId = "found"; foundCount++; }
| < GATE > { leftLifelineId = "gate"; gateCount++; }
)
messageArrowInfo = MessageArrow()
(
rightLifelineId = LifelineId() [< T_DOT > rightLifelineLocalId = LifelineId()]
| < LOST > { rightLifelineId = "lost"; lostCount++; }
| < FOUND > { rightLifelineId = "found"; foundCount++; }
| < GATE > { rightLifelineId = "gate"; gateCount++; }
)
(duration = MessageDuration())?
(msgText = TextUntilNewLine())?
)
{
if(lostCount + foundCount + gateCount > 1) {
throw new SequenceDiagramException("Error: 'lost', 'found' and 'gate' can only occur once per message.");
}
String send;
String receive;
String sendLocalId;
String receiveLocalId;
if(messageArrowInfo.fromLeftToRight) {
send = leftLifelineId;
receive = rightLifelineId;
sendLocalId = leftLifelineLocalId;
receiveLocalId = rightLifelineLocalId;
}
else {
send = rightLifelineId;
receive = leftLifelineId;
sendLocalId = rightLifelineLocalId;
receiveLocalId = leftLifelineLocalId;
}
if(gateCount > 0) {
if(duration != 0) {
throw new SequenceDiagramException("Error: a messages with a gate can only have a duration of 0, but the duration was " + duration + ".");
}
if(send.equals("gate")) {
diagram.addSendGateMessage(receive, msgText, messageArrowInfo.lineType, messageArrowInfo.arrowType, receiveLocalId);
}
else {
diagram.addReceiveGateMessage(send, msgText, messageArrowInfo.lineType, messageArrowInfo.arrowType, sendLocalId);
}
}
else if(send.equals("lost")) {
throw new SequenceDiagramException("Error: 'lost' can only be on the receiving end of a message.");
}
else if(send.equals("found")) {
if(duration != 0) {
throw new SequenceDiagramException("Error: 'lost' and 'found' messages can only have a duration of 0, but the duration was " + duration + ".");
}
diagram.addFoundMessage(receive, msgText, messageArrowInfo.lineType, messageArrowInfo.arrowType, receiveLocalId);
}
else
{
if(receive.equals("lost")) {
if(duration != 0) {
throw new SequenceDiagramException("Error: 'lost' and 'found' messages can only have a duration of 0, but the duration was " + duration + ".");
}
diagram.addLostMessage(send, msgText, messageArrowInfo.lineType, messageArrowInfo.arrowType, sendLocalId);
}
else if(receive.equals("found")) {
throw new SequenceDiagramException("Error: 'found' can only be on the sending end of a message.");
}
else {
diagram.addMessage(send, receive, duration, msgText, messageArrowInfo.lineType, messageArrowInfo.arrowType, sendLocalId, receiveLocalId);
}
}
}
}
MessageArrowInfo MessageArrow():
{
MessageArrowInfo messageArrowInfo = new MessageArrowInfo();
}
{
(
(
messageArrowInfo.lineType = MessageArrowLineType()
(
< MESSAGE_ARROW_RIGHT_OPEN > { messageArrowInfo.arrowType = ArrowType.OPEN; }
| < MESSAGE_ARROW_RIGHT_FILLED > { messageArrowInfo.arrowType = ArrowType.FILLED; }
)
)
{
messageArrowInfo.fromLeftToRight = true;
}
| (
(
< MESSAGE_ARROW_LEFT_OPEN > { messageArrowInfo.arrowType = ArrowType.OPEN; }
| < MESSAGE_ARROW_LEFT_FILLED > { messageArrowInfo.arrowType = ArrowType.FILLED; }
)
messageArrowInfo.lineType = MessageArrowLineType()
)
{
messageArrowInfo.fromLeftToRight = false;
}
)
{ return messageArrowInfo; }
}
LineType MessageArrowLineType() :
{
LineType lineType;
}
{
(
< T_DASH > { lineType = LineType.SOLID; }
| < T_DOT > { lineType = LineType.DASHED; }
)
{
return lineType;
}
}
int MessageDuration() :
{
int duration;
}
{
(
(
< MESSAGE_DURATION_INC > { duration = 1; }
(
duration = unsignedIntConstant()
| (< MESSAGE_DURATION_INC > { duration++; })*
)
)
| (
< T_DASH > { duration = -1; }
(
duration = unsignedIntConstant() { duration = -duration; }
| (< T_DASH > { duration--; })*
)
)
)
{
return duration;
}
}
void GeneralOrdering(SequenceDiagramBuilder diagram):
{
String leftLifelineId = null;
String rightLifelineId = null;
String leftLifelineLocalId = null;
String rightLifelineLocalId = null;
boolean leftEarlier;
}
{
leftLifelineId = LifelineId()
< T_DOT >
leftLifelineLocalId = LifelineId()
(
< MESSAGE_ARROW_RIGHT_OPEN > { leftEarlier = true; }
| < MESSAGE_ARROW_LEFT_OPEN > { leftEarlier = false; }
)
rightLifelineId = LifelineId()
< T_DOT >
rightLifelineLocalId = LifelineId()
{
if(leftEarlier) {
diagram.addGeneralOrdering(leftLifelineId, leftLifelineLocalId, rightLifelineId, rightLifelineLocalId);
}
else {
diagram.addGeneralOrdering(rightLifelineId, rightLifelineLocalId, leftLifelineId, leftLifelineLocalId);
}
}
}
void Coregion(SequenceDiagramBuilder diagram) :
{
String lifelineId;
boolean start;
}
{
(
< START_COREGION > { start = true; }
| < END_COREGION > { start = false; }
)
lifelineId = LifelineId()
{
diagram.addCoregion(lifelineId, start);
}
}
void DestroyLL(SequenceDiagramBuilder diagram) :
{
String lifelineId;
}
{
< LL_DESTROY >
lifelineId = LifelineId()
{
diagram.destroyLifeline(lifelineId);
}
}
void ExecutionSpecification(SequenceDiagramBuilder diagram) :
{
String lifelineId;
boolean on;
}
{
(
< EXEC_SPEC_START > { on = true; }
| < EXEC_SPEC_END > { on = false; }
)
lifelineId = LifelineId()
{
diagram.changeExecutionSpecification(lifelineId, on);
}
( (< LIST_DELIMITER >)? lifelineId = LifelineId()
{
diagram.changeExecutionSpecification(lifelineId, on);
}
)*
}
void StateInvariant(SequenceDiagramBuilder diagram) :
{
String lifelineId;
String text = "";
boolean stateStyle;
}
{
(
< INVARIANT > { stateStyle = false; }
| < STATE_INVARIANT > { stateStyle = true; }
)
lifelineId = LifelineId()
(text = TextUntilNewLine())?
{
diagram.addStateInvariant(lifelineId, text, stateStyle);
}
}
void TextOnLifeline(SequenceDiagramBuilder diagram) :
{
String lifelineId;
String text = "";
}
{
[< TEXT_ON_LIFELINE >]
lifelineId = LifelineId()
text = TextUntilNewLine() /* text is mandatory, if not lookahead with message and general ordering would not work*/
{
diagram.addTextOnLifeline(lifelineId, text);
}
}
String TextUntilNewLine() :
{
}
{
< TEXT_DELIMITER >
< TEXT_UNTIL_NEXT_COMMAND >
{
return backslashReplace(token.image, "\n", "\n", "\;", ";");
}
}
void InteractionUse(SequenceDiagramBuilder diagram):
{
LifelineInterval interval;
String text = "";
}
{
(
< REF >
interval = LifelineInterval()
(text = TextUntilNewLine())?
)
{
diagram.addInteractionUse(interval.startId, interval.endId, text);
}
}
void Continuation(SequenceDiagramBuilder diagram):
{
LifelineInterval interval;
String text = "";
}
{
(
< CONTINUATION >
interval = LifelineInterval()
(text = TextUntilNewLine())?
)
{
diagram.addContinuation(interval.startId, interval.endId, text);
}
}
/**
* @return the start and end of the interval.
*/
LifelineInterval LifelineInterval():
{
LifelineInterval interval = new LifelineInterval();
}
{
interval.startId = LifelineId()
(< LIST_DELIMITER >)?
interval.endId = LifelineId()
{
return interval;
}
}
boolean booleanConstant() :
{ boolean value;}
{
(
<FALSE> { value = false;}
| <TRUE> { value = true;}
) {return value; }
}
int unsignedIntConstant() :
{}
{
<UNSIGNED_INT_CONSTANT> {
int value;
try {
value = Integer.parseInt(token.image);
} catch(NumberFormatException e) {
// only digits are accepted by the gramer, so the only reason for a NumberFormatException should be that the number is too big for int
throw (ParseException) new ParseException("Error: The string '" + token.image + "' couldn't be parsed as integer. The most probable reason is that the number is too big.").initCause(e);
}
return value;
}
}
我一直在整个网站、Internet 上寻找,当然还有 SO,但似乎找不到 umlet 中使用的标记语言的描述或规范。
在示例序列图中例如:
title: sample
_alpha:A~id1_|_beta:B~id2_|_gamma:G~id3_
id1->>id2:id1,id2
id2-/>id1:async Msg.
id3->>>id1:id1,id3
id1.>id3:id1,id3:async return Msg
id1->id1:id1:self
iframe{:interaction frame
id2->id3:id1,id3:async Msg.
iframe}
->、--> 等很明显,但是冒号是做什么的?
为什么需要下划线等。好问的人想知道,因为这看起来像一个有用的素描工具。
找到 an annotated script(从 14.2 开始似乎无效):
//UML2 style titles are optional.
title:sequence diagram
//the participating objects are separated using the pipe symbol "|". Their names may be underlined using underscores.
_alpha:A_|_beta:B_|_gamma:G_
//The following line describes a (synchonuous) message originating from the first object sent to the second object, while both objects are active.
1->>2:1,2
//This message is asynchronous being sent from the second object to the first object. The messages is named.
2-/>1:async Msg.
//A message from the third object to the first object; both objects are active.
3->>>1:1,3
//A named asynchronous return message in UML2 style (using a stick arrow head).
1.>3:1,3:async return Msg
//This is a self call of the first object.
1->1:1:self
//Interaction frames may be used to show optional or conditional blocks within sequence diagrams.
iframe{:interaction frame
2->3:2,3:async Msg.
iframe}
RE: BNF,来源中有umlet-elements/src/main/javacc/SequenceAllInOneParser.jj
:
options {
static = false;
IGNORE_CASE = false;
JAVA_UNICODE_ESCAPE = true;
JDK_VERSION = "1.6";
// Not yet detected as a valid option by the Eclipse plugin, but works nonetheless. This is essential for GWT compatible generated code.
JAVA_TEMPLATE_TYPE = "modern";
}
PARSER_BEGIN(SequenceAllInOneParser)
package com.baselet.element.facet.specific.sequence_aio.gen;
import org.slf4j.Logger;import org.slf4j.LoggerFactory;
import com.baselet.control.enums.LineType;
import com.baselet.element.facet.specific.sequence_aio.Lifeline;
import com.baselet.element.facet.specific.sequence_aio.Message.ArrowType;
import com.baselet.element.facet.specific.sequence_aio.SequenceDiagramBuilder;
import com.baselet.element.facet.specific.sequence_aio.SequenceDiagramException;
public class SequenceAllInOneParser {
private static final Logger log = LoggerFactory.getLogger(SequenceAllInOneParser.class);
private boolean autoTick;
/**
* Replaces "\\" with "\" in the output so that any further specified match replace pair can't match the replaced "\".
* @param matchReplacePairs
* @return the string with the replacements
*/
public static String backslashReplace(String input, String... matchReplacePairs) {
if (matchReplacePairs.length % 2 == 1) {
throw new IllegalArgumentException("matchReplacePairs must have an even number of elements.");
}
String split = "\\";
StringBuilder strBuilder = new StringBuilder(input.length());
int firstIndex = 0;
// use the indexOf function instead of a split, because split uses regex and regex is not 100% supported by GWT
int foundIndex = input.indexOf(split, firstIndex);
while (firstIndex < input.length()) {
int lastIndex = foundIndex == -1 ? input.length() : foundIndex;
String tmp = input.substring(firstIndex, lastIndex);
for (int j = 0; j < matchReplacePairs.length - 1; j += 2) {
tmp = tmp.replace(matchReplacePairs[j], matchReplacePairs[j + 1]);
}
strBuilder.append(tmp);
if (foundIndex != -1) {
strBuilder.append('\');
}
firstIndex = lastIndex + split.length();
foundIndex = input.indexOf(split, firstIndex);
}
return strBuilder.toString();
}
/**
* Small data container to pass all informations.
*/
private class MessageArrowInfo {
boolean fromLeftToRight;
LineType lineType;
ArrowType arrowType;
}
private class InteractionConstraint {
private String lifelineId = null;
private String text = "";
}
private class LifelineInterval {
private String startId;
private String endId;
}
}
PARSER_END(SequenceAllInOneParser)
/* defines input to be ignored */
< * > SKIP:
{
" "
| "\t"
}
< DIAGRAM_DEF_TEXT > SKIP:
{
"\n" : DEFAULT
| "\r\n" : DEFAULT
| "\r" : DEFAULT
}
< DEFAULT > TOKEN:
{
< DIAGRAM_OPTION_OVERRIDE_ID :"overrideIds=" >
| < DIAGRAM_OPTION_AUTO_TICK: "autoTick=" >
| < TRUE: "true" >
| < FALSE: "false" >
| < DIAGRAM_TITLE: "title=" > : DIAGRAM_DEF_TEXT
| < DIAGRAM_DESC: "desc=" > : DIAGRAM_DEF_TEXT
| < LIFELINE_DEFINITIONS: "obj=" > : LIFELINE_DEF
}
< DIAGRAM_DEF_TEXT > TOKEN:
{
< DIAGRAM_DEFINITION_TEXT:
(
~ ["\","\r","\n"]
| ("\" ["\", "n"] )
)+> : DIAGRAM_DEF_TEXT
}
< LIFELINE_DEF > TOKEN :
{
< LL_DEF_NEW_LINE: "\n" | "\r\n" | "\r" > : DEFAULT
| < LIFELINE_DEF_DELIMITER: "|" >
/*| < LIFELINE_TITLE_DELIMITER: "~" > integrated in the title */
| < LIFELINE_ACTOR: "ACTOR" >
| < LIFELINE_ACTIVE: "ACTIVE" >
| < LIFELINE_CREATED_LATER :"CREATED_LATER" >
| < LIFELINE_EXEC_SPEC_FROM_START: "EXECUTION" >
| < LIFELINE_TITLE: /* since | and ~ are special characters which delimit the title they need to be escaped */
(
~["~","\","\n","\r","|"]
| ("\" ["\","~","|","n"])
)+
"~" >
}
< DEFAULT > TOKEN :
{
< TEXT_DELIMITER: ":" > : DIAGRAM_SEQ_TEXT
| < LIST_DELIMITER: "," >
| < OPEN_CURLY_BRACKET: "{" >
| < CLOSE_CURLY_BRACKET: "}" >
| < COMMAND_DELIMITER: ";" >
| < T_DASH: "-" >
| < T_DOT: "." >
| < T_DDOT: ".." >
| < T_EQ: "=" >
| < MESSAGE_ARROW_LEFT_OPEN: "<" >
| < MESSAGE_ARROW_LEFT_FILLED: "<<<" >
| < MESSAGE_ARROW_RIGHT_OPEN: ">" >
| < MESSAGE_ARROW_RIGHT_FILLED: ">>>" >
| < MESSAGE_DURATION_INC: "+" >
| < LOST: "lost" >
| < FOUND: "found" >
| < GATE: "gate" >
| < START_COREGION: "coregionStart=" >
| < END_COREGION: "coregionEnd=" >
| < INVARIANT: "invariant=" >
| < STATE_INVARIANT: "stateInvariant=" >
| < EXEC_SPEC_START: "on=" >
| < EXEC_SPEC_END: "off=" >
| < LL_DESTROY: "destroy=" >
| < REF: "ref=" >
| < CONTINUATION: "continuation=" >
| < TEXT_ON_LIFELINE: "text=" >
| < TICK: "tick=" >
| < COMBINED_FRAGMENT: "combinedFragment=" > : CF_OPERATOR
| < INTERACTION_CONSTRAINT: "constraint=" >
| < UNSIGNED_INT_CONSTANT: ( ["0" - "9"] ) + >
| < DEFAULT_NEW_LINE: "\n" | "\r\n" | "\r" >
}
< LIFELINE_DEF, DEFAULT > TOKEN :
{
< LIFELINE_ID: ["a"-"z","A"-"Z"] (["a"-"z","A"-"Z","0"-"9"])* >
}
< DIAGRAM_SEQ_TEXT > TOKEN :
{
< TEXT_UNTIL_NEXT_COMMAND : ( /* since ; is special characters which delimit the text it must be escaped */
~["\","\n","\r",";"]
| ("\" ["\",";","n"])
)+ > : DEFAULT
}
< CF_OPERATOR > TOKEN :
{
< COMBINED_FRAGMENT_OPERATOR: /* since ; and ~ are special characters which delimit the operator they need to be escaped */
(
~["~","\","\n","\r",";"]
| ("\" ["\","~","|","n",";"])
)+
"~" > : DEFAULT
}
/* general Tokens */
/* skip comments */
< * > SKIP :
{
<"//"(~["\n","\r"])*>
}
< * > TOKEN :
{
<LAST_LINE_COMMENT: "//"(~["\n","\r"])*>
}
/**
* The main function which parses the whole diagram.
* Line comments are skipped (see Tokens)
*/
SequenceDiagramBuilder start() :
{
String titleText = "";
String descText = "";
SequenceDiagramBuilder diagram = new SequenceDiagramBuilder();
autoTick = true;
}
{
(
(
titleText=DiagramTitle() /* NL skip and change state to DEFAULT */
| descText = DiagramDescription() /* NL skip and change state to DEFAULT */
| Option(diagram) < DEFAULT_NEW_LINE >
| LifelineDefinitions(diagram) /* NL part of the nonterminal */
)+//diagram definition as new NT followed by NL alternating with sequence so that the sequence= can be removed
Sequence(diagram)
)
(< LAST_LINE_COMMENT >)?
<EOF>
{
diagram.setTitle(titleText);
diagram.setText(descText);
return diagram;
}
}
String DiagramTitle() :
{
String text = "";
}
{
< DIAGRAM_TITLE >
(
< DIAGRAM_DEFINITION_TEXT > { text = backslashReplace(token.image, "\n","\n");}
)?
{return text;}
}
String DiagramDescription() :
{
String desc = "";
}
{
< DIAGRAM_DESC >
< DIAGRAM_DEFINITION_TEXT > { desc = backslashReplace(token.image, "\n","\n");}
{return desc;}
}
/**
* Options for the whole diagram
*/
void Option(SequenceDiagramBuilder diagram) :
{
boolean overrideIds;
}
{
< DIAGRAM_OPTION_OVERRIDE_ID > overrideIds = booleanConstant()
{
diagram.setOverrideDefaultIds(overrideIds);
}
| < DIAGRAM_OPTION_AUTO_TICK > autoTick = booleanConstant()
}
/**
* Defines all Lifelines
*/
void LifelineDefinitions(SequenceDiagramBuilder diagram) :
{}
{
< LIFELINE_DEFINITIONS > LifelineDef(diagram) (< LIFELINE_DEF_DELIMITER > LifelineDef(diagram))* < LL_DEF_NEW_LINE >
}
/**
* Defines one Lifeline, the id can't be LIFELINE_ACTOR, LIFELINE_ACTIVE
* or LIFELINE_CREATED_LATER because these are keywords.
*/
void LifelineDef(SequenceDiagramBuilder diagram) :
{
String name = "";
String id = null;
boolean createdOnStart = true;
Lifeline.LifelineHeadType headType = Lifeline.LifelineHeadType.STANDARD;
boolean execSpecFromStart = false;
}
{
name = LifelineDefTitleText() (id=LifelineId())?
(
< LIFELINE_ACTOR > { headType = Lifeline.LifelineHeadType.ACTOR; }
| < LIFELINE_ACTIVE > { headType = Lifeline.LifelineHeadType.ACTIVE_CLASS; }
| < LIFELINE_CREATED_LATER > { createdOnStart = false; }
| < LIFELINE_EXEC_SPEC_FROM_START > { execSpecFromStart = true; }
) *
{
if("lost".equals(id) || "found".equals(id)) {
throw new SequenceDiagramException("'lost' and 'found' are keywords and can not be used as lifeline identifiers.");
}
diagram.addLiveline(name, id, headType, createdOnStart, execSpecFromStart);
}
}
/** can could be multiple lines */
String LifelineDefTitleText() :
{}
{
< LIFELINE_TITLE >
{
/* remove trailing ~ and handle the escaping of \, |, n and ~ */
return backslashReplace(token.image.substring(0, token.image.length() - 1), "\n", "\n", "\~", "~", "\|", "|");
}
}
/**
* Can't be one of the following, because these are keywords in the lifeline definition!
* < LIFELINE_ACTOR: "ACTOR" >
* < LIFELINE_ACTIVE: "ACTIVE" >
* < LIFELINE_CREATED_LATER :"CREATED_LATER" >
*/
String LifelineId() :
{}
{
< LIFELINE_ID >
{
return token.image;
}
}
void Sequence(SequenceDiagramBuilder diagram) :
{}
{
(
SequenceTick(diagram) < DEFAULT_NEW_LINE >
| (
SequenceElement(diagram)
( LOOKAHEAD(2) < COMMAND_DELIMITER > SequenceElement(diagram))*
(< COMMAND_DELIMITER >)?
< DEFAULT_NEW_LINE >
{ if(autoTick) { diagram.tick(); } }
)
| < DEFAULT_NEW_LINE >
)*
}
void SequenceTick(SequenceDiagramBuilder diagram) :
{
int tickCount = 1;
}
{
< TICK > (tickCount = unsignedIntConstant())?
{ diagram.tick(tickCount); }
}
void SequenceElement(SequenceDiagramBuilder diagram) :
{}
{
(
MessageOrGeneralOrderingOrText(diagram) /* Message, GeneralOrdering have a common prefix for more clarity a new nonterminal was created*/
| Coregion(diagram)
| DestroyLL(diagram)
| ExecutionSpecification(diagram)
| StateInvariant(diagram)
//| (diagram) //general ordering, TimeConstraint TimeObservation and DurationConstraint Duration Observation missing
| InteractionUse(diagram)
| Continuation(diagram)
| CombinedFragment(diagram)
)
}
void MessageOrGeneralOrderingOrText(SequenceDiagramBuilder diagram) :
{}
{
/*
* (Message and GeneralOrdering) A common prefix is: <LIFELINE_ID> "." <LIFELINE_ID> "<"
* All three have <LIFELINE_ID > as common prefix
* therefore use LOOKAHEAD(2) if it is a text, if not use a LOOKAHEAD(5) to determine if it is a message or a general ordering
*/
LOOKAHEAD(2) TextOnLifeline(diagram)
| LOOKAHEAD(5) Message(diagram)
| GeneralOrdering(diagram)
}
void CombinedFragment(SequenceDiagramBuilder diagram) :
{
LifelineInterval interval = new LifelineInterval();
String operator = "";
String id = null;
}
{
(
< COMBINED_FRAGMENT >
(
< COMBINED_FRAGMENT_OPERATOR >
{ operator = backslashReplace(token.image.substring(0, token.image.length() - 1), "\n", "\n", "\;", ";", "\~", "~"); }
)
[
id = LifelineId()
[interval = LifelineInterval()]
]
{ diagram.beginCombinedFragment(interval.startId, interval.endId, id, operator); }
)
| (
< T_DASH > < T_DASH > (< T_EQ > id=LifelineId())?
{ diagram.endCombinedFragment(id); }
)
| (
< T_DDOT > (< T_EQ > id=LifelineId())?
{ diagram.endAndBeginOperand(id); }
)
}
void Message(SequenceDiagramBuilder diagram) :
{
String leftLifelineId = null;
String rightLifelineId = null;
String leftLifelineLocalId = null;
String rightLifelineLocalId = null;
MessageArrowInfo messageArrowInfo;
String msgText = "";
int lostCount = 0;
int foundCount = 0;
int gateCount = 0;
int duration = 0;
}
{
(
(
leftLifelineId = LifelineId() [LOOKAHEAD(2)< T_DOT > leftLifelineLocalId = LifelineId()]
| < LOST > { leftLifelineId = "lost"; lostCount++; }
| < FOUND > { leftLifelineId = "found"; foundCount++; }
| < GATE > { leftLifelineId = "gate"; gateCount++; }
)
messageArrowInfo = MessageArrow()
(
rightLifelineId = LifelineId() [< T_DOT > rightLifelineLocalId = LifelineId()]
| < LOST > { rightLifelineId = "lost"; lostCount++; }
| < FOUND > { rightLifelineId = "found"; foundCount++; }
| < GATE > { rightLifelineId = "gate"; gateCount++; }
)
(duration = MessageDuration())?
(msgText = TextUntilNewLine())?
)
{
if(lostCount + foundCount + gateCount > 1) {
throw new SequenceDiagramException("Error: 'lost', 'found' and 'gate' can only occur once per message.");
}
String send;
String receive;
String sendLocalId;
String receiveLocalId;
if(messageArrowInfo.fromLeftToRight) {
send = leftLifelineId;
receive = rightLifelineId;
sendLocalId = leftLifelineLocalId;
receiveLocalId = rightLifelineLocalId;
}
else {
send = rightLifelineId;
receive = leftLifelineId;
sendLocalId = rightLifelineLocalId;
receiveLocalId = leftLifelineLocalId;
}
if(gateCount > 0) {
if(duration != 0) {
throw new SequenceDiagramException("Error: a messages with a gate can only have a duration of 0, but the duration was " + duration + ".");
}
if(send.equals("gate")) {
diagram.addSendGateMessage(receive, msgText, messageArrowInfo.lineType, messageArrowInfo.arrowType, receiveLocalId);
}
else {
diagram.addReceiveGateMessage(send, msgText, messageArrowInfo.lineType, messageArrowInfo.arrowType, sendLocalId);
}
}
else if(send.equals("lost")) {
throw new SequenceDiagramException("Error: 'lost' can only be on the receiving end of a message.");
}
else if(send.equals("found")) {
if(duration != 0) {
throw new SequenceDiagramException("Error: 'lost' and 'found' messages can only have a duration of 0, but the duration was " + duration + ".");
}
diagram.addFoundMessage(receive, msgText, messageArrowInfo.lineType, messageArrowInfo.arrowType, receiveLocalId);
}
else
{
if(receive.equals("lost")) {
if(duration != 0) {
throw new SequenceDiagramException("Error: 'lost' and 'found' messages can only have a duration of 0, but the duration was " + duration + ".");
}
diagram.addLostMessage(send, msgText, messageArrowInfo.lineType, messageArrowInfo.arrowType, sendLocalId);
}
else if(receive.equals("found")) {
throw new SequenceDiagramException("Error: 'found' can only be on the sending end of a message.");
}
else {
diagram.addMessage(send, receive, duration, msgText, messageArrowInfo.lineType, messageArrowInfo.arrowType, sendLocalId, receiveLocalId);
}
}
}
}
MessageArrowInfo MessageArrow():
{
MessageArrowInfo messageArrowInfo = new MessageArrowInfo();
}
{
(
(
messageArrowInfo.lineType = MessageArrowLineType()
(
< MESSAGE_ARROW_RIGHT_OPEN > { messageArrowInfo.arrowType = ArrowType.OPEN; }
| < MESSAGE_ARROW_RIGHT_FILLED > { messageArrowInfo.arrowType = ArrowType.FILLED; }
)
)
{
messageArrowInfo.fromLeftToRight = true;
}
| (
(
< MESSAGE_ARROW_LEFT_OPEN > { messageArrowInfo.arrowType = ArrowType.OPEN; }
| < MESSAGE_ARROW_LEFT_FILLED > { messageArrowInfo.arrowType = ArrowType.FILLED; }
)
messageArrowInfo.lineType = MessageArrowLineType()
)
{
messageArrowInfo.fromLeftToRight = false;
}
)
{ return messageArrowInfo; }
}
LineType MessageArrowLineType() :
{
LineType lineType;
}
{
(
< T_DASH > { lineType = LineType.SOLID; }
| < T_DOT > { lineType = LineType.DASHED; }
)
{
return lineType;
}
}
int MessageDuration() :
{
int duration;
}
{
(
(
< MESSAGE_DURATION_INC > { duration = 1; }
(
duration = unsignedIntConstant()
| (< MESSAGE_DURATION_INC > { duration++; })*
)
)
| (
< T_DASH > { duration = -1; }
(
duration = unsignedIntConstant() { duration = -duration; }
| (< T_DASH > { duration--; })*
)
)
)
{
return duration;
}
}
void GeneralOrdering(SequenceDiagramBuilder diagram):
{
String leftLifelineId = null;
String rightLifelineId = null;
String leftLifelineLocalId = null;
String rightLifelineLocalId = null;
boolean leftEarlier;
}
{
leftLifelineId = LifelineId()
< T_DOT >
leftLifelineLocalId = LifelineId()
(
< MESSAGE_ARROW_RIGHT_OPEN > { leftEarlier = true; }
| < MESSAGE_ARROW_LEFT_OPEN > { leftEarlier = false; }
)
rightLifelineId = LifelineId()
< T_DOT >
rightLifelineLocalId = LifelineId()
{
if(leftEarlier) {
diagram.addGeneralOrdering(leftLifelineId, leftLifelineLocalId, rightLifelineId, rightLifelineLocalId);
}
else {
diagram.addGeneralOrdering(rightLifelineId, rightLifelineLocalId, leftLifelineId, leftLifelineLocalId);
}
}
}
void Coregion(SequenceDiagramBuilder diagram) :
{
String lifelineId;
boolean start;
}
{
(
< START_COREGION > { start = true; }
| < END_COREGION > { start = false; }
)
lifelineId = LifelineId()
{
diagram.addCoregion(lifelineId, start);
}
}
void DestroyLL(SequenceDiagramBuilder diagram) :
{
String lifelineId;
}
{
< LL_DESTROY >
lifelineId = LifelineId()
{
diagram.destroyLifeline(lifelineId);
}
}
void ExecutionSpecification(SequenceDiagramBuilder diagram) :
{
String lifelineId;
boolean on;
}
{
(
< EXEC_SPEC_START > { on = true; }
| < EXEC_SPEC_END > { on = false; }
)
lifelineId = LifelineId()
{
diagram.changeExecutionSpecification(lifelineId, on);
}
( (< LIST_DELIMITER >)? lifelineId = LifelineId()
{
diagram.changeExecutionSpecification(lifelineId, on);
}
)*
}
void StateInvariant(SequenceDiagramBuilder diagram) :
{
String lifelineId;
String text = "";
boolean stateStyle;
}
{
(
< INVARIANT > { stateStyle = false; }
| < STATE_INVARIANT > { stateStyle = true; }
)
lifelineId = LifelineId()
(text = TextUntilNewLine())?
{
diagram.addStateInvariant(lifelineId, text, stateStyle);
}
}
void TextOnLifeline(SequenceDiagramBuilder diagram) :
{
String lifelineId;
String text = "";
}
{
[< TEXT_ON_LIFELINE >]
lifelineId = LifelineId()
text = TextUntilNewLine() /* text is mandatory, if not lookahead with message and general ordering would not work*/
{
diagram.addTextOnLifeline(lifelineId, text);
}
}
String TextUntilNewLine() :
{
}
{
< TEXT_DELIMITER >
< TEXT_UNTIL_NEXT_COMMAND >
{
return backslashReplace(token.image, "\n", "\n", "\;", ";");
}
}
void InteractionUse(SequenceDiagramBuilder diagram):
{
LifelineInterval interval;
String text = "";
}
{
(
< REF >
interval = LifelineInterval()
(text = TextUntilNewLine())?
)
{
diagram.addInteractionUse(interval.startId, interval.endId, text);
}
}
void Continuation(SequenceDiagramBuilder diagram):
{
LifelineInterval interval;
String text = "";
}
{
(
< CONTINUATION >
interval = LifelineInterval()
(text = TextUntilNewLine())?
)
{
diagram.addContinuation(interval.startId, interval.endId, text);
}
}
/**
* @return the start and end of the interval.
*/
LifelineInterval LifelineInterval():
{
LifelineInterval interval = new LifelineInterval();
}
{
interval.startId = LifelineId()
(< LIST_DELIMITER >)?
interval.endId = LifelineId()
{
return interval;
}
}
boolean booleanConstant() :
{ boolean value;}
{
(
<FALSE> { value = false;}
| <TRUE> { value = true;}
) {return value; }
}
int unsignedIntConstant() :
{}
{
<UNSIGNED_INT_CONSTANT> {
int value;
try {
value = Integer.parseInt(token.image);
} catch(NumberFormatException e) {
// only digits are accepted by the gramer, so the only reason for a NumberFormatException should be that the number is too big for int
throw (ParseException) new ParseException("Error: The string '" + token.image + "' couldn't be parsed as integer. The most probable reason is that the number is too big.").initCause(e);
}
return value;
}
}