Specification: Semantics of a Foundation Subset for Executable UML Models, FTF Beta 1 (ptc/08-11-03)
Section: 8.6.2.2.1 ActionActivation, 8.6.3.2.4 CreateLinkActionActivation and 8.6.3.2.8 LinkActionActivation
Depends on: http://www.omg.org/issues/fuml-ftf.html#Issue13544
Summary:
Part of CreateLinkActionActivation.doAction() entails destroying all links that have a value for any end for which isReplaceAll is true. (see fUML 1.0, page 238). This involves the following chain of invocations:
- fUML.Semantics.Actions.IntermediateActions.LinkActionActivation.endMatchesEndData(Link, LinkEndData) see fUML 1.0, page 238;
- fUML.Semantics.Actions.BasicActions.ActionActivation.getTokens(InputPin) – see fUML 1.0, page 233;
- fUML.Semantics.Activities.IntermediateActivities.ActivityNodeActivation.takeTokens() see fUML 1.0, p. 215
By taking the tokens from the pin activation corresponding to the input pin, LinkActionActivation.endMatchesEndData() induces side effects on the state of the execution engine each time it is called.
Proposed Resolution:
Resolving this issue entails 3 things:
a) Defining a read-only operation to read the tokens available on an input pin:
fUML.Semantics.Actions.BasicActions.ActionActivation.readTokens(InputPin)
/**
- NFR 04/16/2009 For LinkActionActivation.endMatchesEndData(Link, LinkEndData),
- we need a mechanism to test whether the available tokens match the link end data
- and if they do, then actually remove them.
- readTokens() is the same as getTokens() except that:
- getTokens() removes the tokens on the pin activation, i.e, pinActivation.takeTokens()
- readTokens() leaves the tokens on the pin activation, i.e., pinActivation.getTokens()
*/
public fUML.Semantics.Classes.Kernel.ValueList readTokens(
fUML.Syntax.Actions.BasicActions.InputPin pin) {
// Precondition: The action execution has fired and the given pin is
// owned by the action of the action execution.
// Take any tokens held by the pin activation corresponding to the given
// input pin and return them.
// NFR 04/16/2009
Debug.println("[readTokens] node = " + this.node.name + " (pin: " + pin.name + ")");
PinActivation pinActivation = this.getPinActivation(pin);
TokenList tokens = pinActivation.getTokens();
ValueList values = new ValueList();
for (int i = 0; i < tokens.size(); i++) {
Token token = tokens.getValue;
Value value = ((ObjectToken) token).value;
if (value != null)
{
values.addValue(value);
}
}
return values;
}
b) With the above operation, LinkActionActivation.endMatchesEndData() can be fixed as follows:
public boolean endMatchesEndData(fUML.Semantics.Classes.Kernel.Link link,
fUML.Syntax.Actions.IntermediateActions.LinkEndData endData) {
// Test whether the appropriate end of the given link matches the given
// end data.
boolean matches = false;
if (endData.value == null)
{
matches = true;
}
else {
Property end = endData.end;
FeatureValue linkFeatureValue = link.getFeatureValue(end);
Value endValue = this.readTokens(endData.value).getValue(0);
if (endData instanceof LinkEndDestructionData) {
if (!((LinkEndDestructionData) endData).isDestroyDuplicates
& !end.multiplicityElement.isUnique & end.multiplicityElement.isOrdered)
{
int destroyAt = ((UnlimitedNaturalValue) (this
.readTokens(((LinkEndDestructionData) endData).destroyAt).getValue(0))).value.naturalValue;
matches = linkFeatureValue.values.getValue(0).equals(endValue)
&& linkFeatureValue.position == destroyAt;
}
else
{
matches = linkFeatureValue.values.getValue(0).equals(endValue);
}
} else
{
matches = linkFeatureValue.values.getValue(0).equals(endValue);
}
}
return matches;
}
c) All other actions/activity behaviors which directly or indirectly invoked LinkActionActivation.endMatchesEndData() need to be reviewed to make sure that the tokens will be consumed. There are only 3 cases to consider:
fUML.Semantics.Actions.IntermediateActions.CreateLinkActionActivation.doAction()
fUML.Semantics.Actions.IntermediateActions.DestroyLinkActionActivation.doAction()
fUML.Semantics.Actions.IntermediateActions.ReadLinkActionActivation.doAction()
CreateLinkActionActivation.doAction() already consumes the tokens when creating the new link instance.
DestroyLinkActionActivation.doAction() needs extra logic to delete the tokens from the input pins after determining the matching links to destroy, if any. This requires modifying this operation as follows:
public void doAction() {
// Get the extent, at the current execution locus, of the association
// for which links are being destroyed.
// Destroy all links that match the given link end destruction data.
// For unique ends, or non-unique ends for which isDestroyDuplicates is
// true, match links with a matching value for that end.
// For non-unique, ordered ends for which isDestroyDuplicates is false,
// match links with an end value at the given destroyAt position. [Must
// a value be given, too, in this case?]
// For non-unique, non-ordered ends for which isDestroyDuplicates is
// false, pick one matching link (if any) non-deterministically. [The
// semantics of this case is not clear from the current spec.]
DestroyLinkAction action = (DestroyLinkAction) (this.node);
LinkEndDestructionDataList destructionDataList = action.endData;
boolean destroyOnlyOne = false;
int j = 1;
while (!destroyOnlyOne & j <= destructionDataList.size())
{
LinkEndDestructionData endData = destructionDataList.getValue(j - 1);
destroyOnlyOne = !endData.end.multiplicityElement.isUnique
& !endData.end.multiplicityElement.isOrdered & !endData.isDestroyDuplicates;
j = j + 1;
}
LinkEndDataList endDataList = new LinkEndDataList();
for (int i = 0; i < destructionDataList.size(); i++)
{
LinkEndDestructionData endData = destructionDataList.getValue(i);
endDataList.addValue(endData);
}
ExtensionalValueList extent = this.getExecutionLocus().getExtent(this.getAssociation());
ExtensionalValueList matchingLinks = new ExtensionalValueList();
for (int i = 0; i < extent.size(); i++) {
ExtensionalValue value = extent.getValue;
Link link = (Link) value;
if (this.linkMatchesEndData(link, endDataList))
{
matchingLinks.addValue(link);
}
}
/*
- Now that we know which matching links to destroy,
- we need to ensure all of the tokens on the input pins
- of this action are consumed.
*/
for (int i = 0; i < destructionDataList.size(); i++) {
LinkEndDestructionData endData = destructionDataList.getValue;
Property end = endData.end;
if (!endData.isDestroyDuplicates
& !end.multiplicityElement.isUnique & end.multiplicityElement.isOrdered)
{
this.getTokens(endData.destroyAt);
}
this.getTokens(endData.value);
}
if (destroyOnlyOne) {
// *** If there is more than one matching link,
// non-deterministically choose one. ***
if (matchingLinks.size() > 0)
{
int i = ((ChoiceStrategy) this.getExecutionLocus().factory.getStrategy("choice"))
.choose(matchingLinks.size());
matchingLinks.getValue(i - 1).destroy();
}
} else {
for (int i = 0; i < matchingLinks.size(); i++)
{
ExtensionalValue matchingLink = matchingLinks.getValue(i);
matchingLink.destroy();
}
}
}
ReadLinkActionActivation.doAction() needs extra logic to consume the tokens after matching the data against all of the links in the extension.
public void doAction() {
// Get the extent, at the current execution locus, of the association to
// which the action applies.
// For all links that match the link end data, place the value of the
// remaining "open" end on the result pin.
ReadLinkAction action = (ReadLinkAction) (this.node);
LinkEndDataList endDataList = action.endData;
LinkEndData openEnd = null;
int i = 1;
while ((openEnd == null) & i <= endDataList.size()) {
if (endDataList.getValue(i-1).value == null)
{
openEnd = endDataList.getValue(i-1);
}
i = i + 1;
}
ExtensionalValueList extent = this.getExecutionLocus().getExtent(this.getAssociation());
for (int j = 0; j < extent.size(); j++) {
ExtensionalValue value = extent.getValue(j);
Link link = (Link) value;
if (this.linkMatchesEndData(link, endDataList))
{
Value resultValue = link.getFeatureValue(openEnd.end).values.getValue(0);
this.putToken(action.result, resultValue);
}
}
/*
- Now that we have checked all links for possible matches,
- we have to consume the tokens on the input pins corresponding to the non-open link ends.
*/
for (int k=0; k<endDataList.size(); k++) {
LinkEndData endData = endDataList.getValue(k);
if (endData.value != null)
{
this.getTokens(endData.value);
}
}
}