Issue 14547: Structured activity node execution model needs to be corrected (fuml-ftf) Source: Model Driven Solutions (Mr. Ed Seidewitz, ed-s(at)modeldriven.com) Nature: Uncategorized Issue Severity: Summary: Specification: Semantics of a Foundational Subset for Executable UML Models (ptc/2008-11-03) Subclause: 8.5.3 Complete Structured Activities, 8.5.4 Extra Structured Activities The execution model for structured activity nodes has never been tested and thus likely contains errors. At the very least, the specifications for ConditionalNodeActivation and LoopNodeActivation need to be corrected to reflect the fact that the test and body properties only reference executable nodes, not all the nodes that might be contained in the structured activity node and the specification for StructuredActivityNode needs to handle input and output pins. Resolution: The execution model for structured activity nodes, conditional nodes, loop nodes and expansion regions has now been updated and tested, per the proposed revisions to the specification text. Revised Text: In Subclause 8.5.2.2.5, ActivityNodeActivationGroup: · Replace the code for the getNodeActivation operation with: // Return the node activation (if any) in this group, // or any nested group, corresponding to the given activity node. // If this is a group for a structured activity node activation, // also include the pin activations for that node activation. ActivityNodeActivation activation = null; if (this.containingNodeActivation != null && node instanceof Pin) { activation = this.containingNodeActivation.getPinActivation((Pin)node); } } if (activation == null) { int i = 1; while (activation == null & i <= this.nodeActivations.size()) { activation = this.nodeActivations.getValue(i-1).getNodeActivation(node); i = i + 1; } } return activation; In Subclause 8.5.3.2.1, ClauseActivation: · Replace the code for the isReady operation with: // Test if all predecessors to this clause activation have failed. ClauseActivationList predecessors = this.getPredecessors(); boolean ready = true; int i = 1; while (ready & i <= predecessors.size()) { ClauseActivation predecessor = predecessors.getValue(i-1); BooleanValue decisionValue = predecessor.getDecision(); // Note that the decision will be null if the predecessor clause has not run yet. if (decisionValue == null) { ready = false; } else { ready = !decisionValue.value; } i = i + 1; } return ready; In Subclause 8.5.3.2.2, ConditionalNodeActivation: · In the doStructuredActivity operation, at the beginning add: // Run all the non-executable nodes in the conditional node. After the statement "ConditionalNode node = (ConditionalNode)(this.node);", add: ActivityNodeActivationList nodeActivations = this.activationGroup.nodeActivations; ActivityNodeActivationList nonExecutableNodeActivations = new ActivityNodeActivationList(); for (int i = 0; i < nodeActivations.size(); i++) { ActivityNodeActivation nodeActivation = nodeActivations.getValue(i); if (!(nodeActivation.node instanceof ExecutableNode)) { nonExecutableNodeActivations.addValue(nodeActivation); } } this.activationGroup.run(nonExecutableNodeActivations); After the statement "clauseActivation.clause = clause;", add: clauseActivation.conditionalNodeActivation = this; Move the statement "this.activationGroup.terminateAll();" to the end of the code. After the statement "Clause selectedClause = this.selectedClauses.getValue(i-1);", add: for (int j = 0; j < clauses.size(); j++) { Clause clause = clauses.getValue(i); if (clause != selectedClause) { ExecutableNodeList nodes = clause.test; for (int k = 0; k < nodes.size; k++) { ExecutableNode node = nodes.getValue(k); this.activationGroup.getNodeActivation(node).terminate(); } } } In Subclause 8.5.3.2.3, LoopNodeActivation: · Replace the code for the doStructuredActivity operation with: // Set the initial values for the body outputs to the values of the loop variable input pins. // If isTestedFirst is true, then repeatedly run the test part and the body part of the loop, copying values from the body outputs to the loop variables. // If isTestedFirst is false, then repeatedly run the body part and the test part of the loop, copying values from the body outputs to the loop variables. // When the test fails, copy the values of the body outputs to the loop outputs. // [Note: The body outputs are used for the loop outputs, rather than the loop variables, since values on the loop variables may be consumed when running the test for the last time.] LoopNode loopNode = (LoopNode)(this.node); InputPinList loopVariableInputs = loopNode.loopVariableInput; OutputPinList loopVariables = loopNode.loopVariable; OutputPinList resultPins = loopNode.result; ValuesList bodyOutputLists = this.bodyOutputLists; for (int i = 0; i < loopVariableInputs.size(); i++) { InputPin loopVariableInput = loopVariableInputs.getValue(i); Values bodyOutputList = new Values(); bodyOutputList.values = this.takeTokens(loopVariableInput); this.bodyOutputLists.addValue(bodyOutputList); } boolean continuing = true; do { // Set loop variable values this.runLoopVariables(); for (int i = 0; i < loopVariables.size(); i++) { OutputPin loopVariable = loopVariables.getValue(i); Values bodyOutputList = bodyOutputLists.getValue(i); ValueList values = bodyOutputList.values; this.putPinValues(loopVariable, values); ((OutputPinActivation)this.activationGroup.getNodeActivation(loopVariable)). sendUnofferedTokens(); } // Run all the non-executable, non-pin nodes in the conditional node. ActivityNodeActivationList nodeActivations = this.activationGroup.nodeActivations; ActivityNodeActivationList nonExecutableNodeActivations = new ActivityNodeActivationList(); for (int i = 0; i < nodeActivations.size(); i++) { ActivityNodeActivation nodeActivation = nodeActivations.getValue(i); if (!(nodeActivation.node instanceof ExecutableNode | nodeActivation.node instanceof Pin)) { nonExecutableNodeActivations.addValue(nodeActivation); } } this.activationGroup.run(nonExecutableNodeActivations); // Run the loop if (loopNode.isTestedFirst) { continuing = this.runTest(); if (continuing) { this.runBody(); } } else { this.runBody(); continuing = this.runTest(); } this.activationGroup.terminateAll(); } while (continuing); for (int i = 0; i < bodyOutputLists.size(); i++) { Values bodyOutputList = bodyOutputLists.getValue(i); OutputPin resultPin = resultPins.getValue(i); this.putTokens(resultPin, bodyOutputList.values); } · In the runTest operation, replace the statement return ((BooleanValue)(this.getPinValues(loopNode.decider).getValue(0))).value; with: ValueList values = this.getPinValues(loopNode.decider); // If there is no decider value, treat it as false. boolean decision = false; if (values.size() > 0) { decision = ((BooleanValue)(values.getValue(0))).value; } return decision; · Replace the code for the runBody operation with: // Run the body part of the loop node for this node activation and save the body outputs. LoopNode loopNode = (LoopNode)this.node; this.activationGroup.runNodes(this.makeActivityNodeList(loopNode.bodyPart)); OutputPinList bodyOutputs = loopNode.bodyOutput; ValuesList bodyOutputLists = this.bodyOutputLists; for (int i = 0; i < bodyOutputs.size(); i++) { OutputPin bodyOutput = bodyOutputs.getValue(i); Values bodyOutputList = bodyOutputLists.getValue(i); bodyOutputList.values = this.getPinValues(bodyOutput); } In Subclause 8.5.3.2.4, StructuredActivityNodeActivation: · At the beginning of the doStructuredActivity operation, add: Action action = (Action)(this.node); // *** Concurrently send offers from all input pins. *** InputPinList inputPins = action.input; for (Iterator i = inputPins.iterator(); i.hasNext();) { InputPin inputPin = (InputPin)i.next(); PinActivation pinActivation = this.getPinActivation(inputPin); pinActivation.sendUnofferedTokens(); } · In the getNodeActivation operation, replace: ActivityNodeActivation activation; if (thisActivation != null) { activation = thisActivation; } else { activation = this.activationGroup.getNodeActivation(node); } with: ActivityNodeActivation activation = null; if (thisActivation != null) { activation = thisActivation; } else if (this.activationGroup != null) { activation = this.activationGroup.getNodeActivation(node); } · At the beginning of the createNodeActivations operation, add: super.createNodeActivations(); · Replace the code for makeActivityNodeList with: // Return an activity node list containing the given list of executable nodes // and any pins that they own. ActivityNodeList activityNodes = new ActivityNodeList(); for (int i = 0; i < nodes.size(); i++) { ActivityNode node = nodes.getValue(i); activityNodes.addValue(node); if (node instanceof Action) { Action action = (Action)node; InputPinList inputPins = action.input; for (int j = 0; j < inputPins.size(); j++) { InputPin inputPin = inputPins.getValue(j); activityNodes.addValue(inputPin); } OutputPinList outputPins = action.output; for (int j = 0; j < outputPins.size(); j++) { OutputPin inputPin = outputPins.getValue(j); activityNodes.addValue(outputPin); } } } return activityNodes; In Figure 72 and Subclause 8.5.4.2.1, for class ExpansionRegionActivationGroup: · Add the following description for the attribute regionActivation The expansion region activation this activation group is for. · Add the attribute index: Integer with the description: The index (starting at 1) of this activation group in the list held by the expansion region activation. · Add the operation getActivityExecution() with the code: // Get the activity execution that contains the expansion region activation for this activation group. return this.regionActivation.getActivityExecution(); In Subclause 8.5.4.2.3, ExpansionRegionActivation: · Add the following association descriptions: activationGroups The set of expansion activation groups for this expansion region activation. One activation group is created corresponding to each token held by the first input expansion node activation for the expansion region. inputExpansionTokens The tokens taken from each of the input expansion node activations for this expansion region activation. These are preserved for initializing the group input of each of the activation groups. inputTokens The tokens taken from each of the input pin activations for this expansion region activation. These are preserved for initializing the region inputs of each of the activation groups. · In the takeOfferedTokens operation: Replace the initial comment with "// Take the tokens from the input pin and input expansion node activations and save them." Remove all the code after the second for loop and before the return statement. · Replace the code for the doStructuredActivity operation with: // Create a number of expansion region activation groups equal to the number of values expanded in the region, // setting the region inputs and group inputs for each group. // Run the body of the region in each group, either iteratively or in parallel. // Add the outputs of each activation group to the corresonding output expansion node activations. ExpansionRegion region = (ExpansionRegion)this.node; InputPinList inputPins = region.input; ExpansionNodeList inputElements = region.inputElement; ExpansionNodeList outputElements = region.outputElement; int n = this.inputExpansionTokens.getValue(0).tokens.size(); int k = 1; while (k <= n) { ExpansionActivationGroup activationGroup = new ExpansionActivationGroup(); activationGroup.regionActivation = this; activationGroup.index = k; int j = 1; while (j <= inputPins.size()) { OutputPinActivation regionInput = new OutputPinActivation(); regionInput.run(); activationGroup.regionInputs.addValue(regionInput); j = j + 1; } j = 1; while (j <= inputElements.size()) { OutputPinActivation groupInput = new OutputPinActivation(); groupInput.run(); activationGroup.groupInputs.addValue(groupInput); j = j + 1; } j = 1; while (j <= outputElements.size()) { OutputPinActivation groupOutput = new OutputPinActivation(); groupOutput.run(); activationGroup.groupOutputs.addValue(new OutputPinActivation()); j = j + 1; } activationGroup.createNodeActivations(region.node); activationGroup.createEdgeInstances(region.edge); this.activationGroups.addValue(activationGroup); k = k + 1; } ExpansionActivationGroupList activationGroups = this.activationGroups; if (region.mode == ExpansionKind.iterative) { for (int i = 0; i < activationGroups.size(); i++) { ExpansionActivationGroup activationGroup = activationGroups.getValue(i); this.runGroup(activationGroup); } } else if (region.mode == ExpansionKind.parallel) { // *** Activate all groups concurrently. *** for (Iterator i = activationGroups.iterator(); i.hasNext();) { ExpansionActivationGroup activationGroup = (ExpansionActivationGroup)i.next(); this.runGroup(activationGroup); } } for (int i = 0; i < activationGroups.size(); i++) { ExpansionActivationGroup activationGroup = activationGroups.getValue(i); OutputPinActivationList groupOutputs = activationGroup.groupOutputs; for (int j = 0; j < groupOutputs.size(); j++) { OutputPinActivation groupOutput = groupOutputs.getValue(j); ExpansionNode outputElement = outputElements.getValue(j); this.getExpansionNodeActivation(outputElement). addTokens(groupOutput.takeTokens()); } } · In the isReady operation, replace boolean ready = false; if (super.isReady()) { … With: boolean ready = super.isReady(); if (ready) { … · In Figure 72 and Subclause 8.5.4.2.3, rename the activateGroup operation to runGroup and replace its code with: // Set up the inputs for the group with the given index, run the group and then fire the group outputs. TokenSetList inputTokens = this.inputTokens; for (int j = 0; j < inputTokens.size(); j++) { TokenSet tokenSet = inputTokens.getValue(j); OutputPinActivation regionInput = activationGroup.regionInputs.getValue(j); regionInput.clearTokens(); regionInput.addTokens(tokenSet.tokens); regionInput.sendUnofferedTokens(); } TokenSetList inputExpansionTokens = this.inputExpansionTokens; for (int j = 0; j < inputExpansionTokens.size(); j++) { TokenSet tokenSet = inputExpansionTokens.getValue(j); OutputPinActivation groupInput = activationGroup.groupInputs.getValue(j); groupInput.clearTokens(); groupInput.addToken(tokenSet.tokens.getValue(activationGroup.index-1)); regionInput.sendUnofferedTokens(); } activationGroup.run(activationGroup.nodeActivations); OutputPinActivationList groupOutputs = activationGroup.groupOutputs; for (int i = 0; i < groupOutputs.size(); i++) { OutputPinActivation groupOutput = groupOutputs.getValue(i); groupOutput.fire(groupOutput.takeOfferedTokens()); } activationGroup.terminateAll(); In Subclause 8.6.2.2.1, ActionActivation: · In the SendOffers operation, replace: // *** Fire all output pins concurrently. *** with: // *** Send offers from all output pins concurrently. *** And replace: pinActivation.fire(pinActivation.takeOfferedTokens()); with: pinActivation.sendUnofferedTokens(); In subclause 8.5.4.2.4, TokenSet: · Add the class description: A set of tokens taken from an input pin activation or input expansion node activation for an expansion region. · Add the association description tokens The set of tokens in this token set. In Subclause 8.6.2.2.7, OutputPinActivation, remove the isReady and fire operations. In Figure 74 and Subclause 8.6.2.2.8, change the multiplicity of PinActivation::actionActivation to 0..1. Actions taken: October 8, 2009: received issue July 23, 2010: closed issue Discussion: End of Annotations:===== ubject: Structured activity node execution model needs to be corrected Date: Thu, 8 Oct 2009 16:22:03 -0400 X-MS-Has-Attach: X-MS-TNEF-Correlator: Thread-Topic: Structured activity node execution model needs to be corrected thread-index: AcpIVP9XC5NqdTkoR82FaXiVl4SMMQ== From: "Ed Seidewitz" To: Specification: Semantics of a Foundational Subset for Executable UML Models (ptc/2008-11-03) Subclause: 8.5.3 Complete Structured Activities, 8.5.4 Extra Structured Activities The execution model for structured activity nodes has never been tested and thus likely contains errors. At the very least, the specifications for ConditionalNodeActivation and LoopNodeActivation need to be corrected to reflect the fact that the test and body properties only reference executable nodes, not all the nodes that might be contained in the structured activity node and the specification for StructuredActivityNode needs to handle input and output pins.