This is an automated email from the ASF dual-hosted git repository.
yesamer pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/incubator-kie.git
The following commit(s) were added to refs/heads/main by this push:
new 1d8242b8fbb [incubator-kie#7229] DMN: Fix nested list results from
Context expressions in for loops (#7230)
1d8242b8fbb is described below
commit 1d8242b8fbbad36b3a03fb5ad1b7f7754d27d18b
Author: Alisha Mohamed Ali <[email protected]>
AuthorDate: Thu Oct 29 15:35:10 2026 +0530
[incubator-kie#7229] DMN: Fix nested list results from Context expressions
in for loops (#7230)
* [incubator-kie#7229] DMN: do not coerce the result of a nested context to
the type of the enclosing decision
* [incubator-kie#7229] Shorten comments and reduce the tests
* [incubator-kie#7229] Use Optional in nested-context detection and unit
test the helpers
* [incubator-kie#7229] Rename the node expression helper and test a
two-level nesting
---------
Co-authored-by: Alisha Mohamed Ali <[email protected]>
---
.../dmn/core/compiler/DMNEvaluatorCompiler.java | 67 ++-
.../dmn/core/DMNContextResultTypeCoercionTest.java | 241 +++++++++++
.../core/compiler/DMNEvaluatorCompilerTest.java | 200 +++++++++
.../DMNv1_6/ContextResultTypeCoercion.dmn | 458 +++++++++++++++++++++
4 files changed, 962 insertions(+), 4 deletions(-)
diff --git
a/kie-dmn/kie-dmn-core/src/main/java/org/kie/dmn/core/compiler/DMNEvaluatorCompiler.java
b/kie-dmn/kie-dmn-core/src/main/java/org/kie/dmn/core/compiler/DMNEvaluatorCompiler.java
index 33a7eae6d16..af623a1bb92 100644
---
a/kie-dmn/kie-dmn-core/src/main/java/org/kie/dmn/core/compiler/DMNEvaluatorCompiler.java
+++
b/kie-dmn/kie-dmn-core/src/main/java/org/kie/dmn/core/compiler/DMNEvaluatorCompiler.java
@@ -30,6 +30,7 @@ import java.util.Objects;
import java.util.Optional;
import java.util.UUID;
import java.util.stream.Collectors;
+import java.util.stream.Stream;
import javax.xml.namespace.QName;
@@ -78,6 +79,7 @@ import org.kie.dmn.feel.runtime.functions.BaseFEELFunction;
import org.kie.dmn.feel.runtime.functions.DTInvokerFunction;
import org.kie.dmn.model.api.Binding;
import org.kie.dmn.model.api.BusinessKnowledgeModel;
+import org.kie.dmn.model.api.ChildExpression;
import org.kie.dmn.model.api.Conditional;
import org.kie.dmn.model.api.Context;
import org.kie.dmn.model.api.ContextEntry;
@@ -97,6 +99,7 @@ import org.kie.dmn.model.api.InputClause;
import org.kie.dmn.model.api.Invocation;
import org.kie.dmn.model.api.Iterator;
import org.kie.dmn.model.api.LiteralExpression;
+import org.kie.dmn.model.api.NamedElement;
import org.kie.dmn.model.api.OutputClause;
import org.kie.dmn.model.api.Quantified;
import org.kie.dmn.model.api.Relation;
@@ -376,10 +379,13 @@ public class DMNEvaluatorCompiler implements
DMNDecisionLogicCompiler {
ContextEntry parentEntry = (ContextEntry)
ctxDef.getParent();
type = compiler.resolveTypeRef(model,
parentEntry.getVariable(), parentEntry.getVariable(),
parentEntry.getVariable().getTypeRef());
- } else if (node instanceof BusinessKnowledgeModelNode) {
- type = ((BusinessKnowledgeModelNode)
node).getResultType();
- } else if (node instanceof DecisionNode) {
- type = ((DecisionNode) node).getResultType();
+ } else if (isResultOfDecisionOrBKM(ctxDef, node)) {
+ // a context nested in another expression (e.g. the
`return` of a `for`) yields just a part of the node result
+ if (node instanceof BusinessKnowledgeModelNode) {
+ type = ((BusinessKnowledgeModelNode)
node).getResultType();
+ } else if (node instanceof DecisionNode) {
+ type = ((DecisionNode) node).getResultType();
+ }
}
ctxEval.addEntry(
DMNContextEvaluator.RESULT_ENTRY,
@@ -394,6 +400,59 @@ public class DMNEvaluatorCompiler implements
DMNDecisionLogicCompiler {
return ctxEval;
}
+ /**
+ * Checks if the given <code>Context</code> produces the result of the
<code>Decision</code> or <code>BusinessKnowledgeModel</code>:
+ * it is the expression of the node itself, or a
<code>then</code>/<code>else</code> branch of it when that is a
<code>Conditional</code>.
+ * A context nested deeper (e.g. in a <code>for</code>, a list or another
context) is not.
+ * The model elements are compared, since the parent links are populated
only when a model is unmarshalled.
+ * @param ctxDef the context being compiled
+ * @param node the node the context belongs to
+ * @return <code>true</code> if the value of the context is the result of
the node
+ */
+ static boolean isResultOfDecisionOrBKM(Context ctxDef, DMNBaseNode node) {
+ return getDecisionBKMNodeExpression(node)
+ .map(root -> isSameOrConditionalBranchOf(root, ctxDef))
+ .orElse(false);
+ }
+
+ /**
+ * @param node the Decision or BusinessKnowledgeModel node
+ * @return the expression of the Decision, or the body of the function
definition of the BKM; empty if not available
+ */
+ static Optional<Expression> getDecisionBKMNodeExpression(DMNBaseNode node)
{
+ NamedElement source = node.getSource();
+ if (source instanceof Decision decision) {
+ return Optional.ofNullable(decision.getExpression());
+ }
+ if (source instanceof BusinessKnowledgeModel bkm) {
+ return
Optional.ofNullable(bkm.getEncapsulatedLogic()).map(FunctionDefinition::getExpression);
+ }
+ return Optional.empty();
+ }
+
+ /**
+ * @param root the root expression of a node
+ * @param expression the expression to look for
+ * @return <code>true</code> if <code>expression</code> is the very same
instance (not just an equal one) of <code>root</code>,
+ * or of a <code>then</code>/<code>else</code> branch of
<code>root</code>, when it is a <code>Conditional</code>
+ */
+ static boolean isSameOrConditionalBranchOf(Expression root, Expression
expression) {
+ if (root == expression) {
+ return true;
+ }
+ if (root instanceof Conditional conditional) {
+ return Stream.of(conditional.getThen(), conditional.getElse())
+ .map(DMNEvaluatorCompiler::getChildExpression)
+ .flatMap(Optional::stream)
+ .anyMatch(branch -> isSameOrConditionalBranchOf(branch,
expression));
+ }
+ return false;
+ }
+
+ static Optional<Expression> getChildExpression(ChildExpression child) {
+ return Optional.ofNullable(child).map(ChildExpression::getExpression);
+ }
+
private DMNExpressionEvaluator
compileFunctionDefinition(DMNCompilerContext ctx, DMNModelImpl model,
DMNBaseNode node,
String functionName,
FunctionDefinition funcDef) {
diff --git
a/kie-dmn/kie-dmn-core/src/test/java/org/kie/dmn/core/DMNContextResultTypeCoercionTest.java
b/kie-dmn/kie-dmn-core/src/test/java/org/kie/dmn/core/DMNContextResultTypeCoercionTest.java
new file mode 100644
index 00000000000..39352f8ad1c
--- /dev/null
+++
b/kie-dmn/kie-dmn-core/src/test/java/org/kie/dmn/core/DMNContextResultTypeCoercionTest.java
@@ -0,0 +1,241 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.kie.dmn.core;
+
+import java.io.InputStreamReader;
+import java.nio.charset.StandardCharsets;
+import java.util.Collections;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.MethodSource;
+import org.junit.jupiter.params.provider.ValueSource;
+import org.kie.dmn.api.core.DMNMessage;
+import org.kie.dmn.api.core.DMNMessageType;
+import org.kie.dmn.api.core.DMNModel;
+import org.kie.dmn.api.core.DMNResult;
+import org.kie.dmn.api.core.DMNRuntime;
+import org.kie.dmn.backend.marshalling.v1x.DMNMarshallerFactory;
+import org.kie.dmn.core.api.DMNFactory;
+import org.kie.dmn.core.compiler.DMNCompilerImpl;
+import org.kie.dmn.core.compiler.RuntimeTypeCheckOption;
+import org.kie.dmn.core.impl.DMNRuntimeImpl;
+import org.kie.dmn.core.impl.VoidDMNRuntimeKB;
+import org.kie.dmn.core.util.DMNRuntimeUtil;
+import org.kie.dmn.model.api.Context;
+import org.kie.dmn.model.api.DMNModelInstrumentedBase;
+import org.kie.dmn.model.api.Definitions;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+class DMNContextResultTypeCoercionTest extends BaseInterpretedVsCompiledTest {
+
+ private static final String MODEL =
"valid_models/DMNv1_6/ContextResultTypeCoercion.dmn";
+ private static final String NAMESPACE =
"https://github.com/kiegroup/drools/kie-dmn";
+ private static final String NAME = "ContextResultTypeCoercion";
+
+ private static final List<String> DOUBLED = List.of("aa", "bb", "cc");
+
+ private DMNResult evaluate(String decisionName, boolean typeCheck) {
+ final DMNRuntime runtime = DMNRuntimeUtil.createRuntime(MODEL,
this.getClass());
+ ((DMNRuntimeImpl) runtime).setOption(new
RuntimeTypeCheckOption(typeCheck));
+ final DMNModel dmnModel = runtime.getModel(NAMESPACE, NAME);
+ assertThat(dmnModel).isNotNull();
+
assertThat(dmnModel.hasErrors()).as(DMNRuntimeUtil.formatMessages(dmnModel.getMessages())).isFalse();
+ return runtime.evaluateByName(dmnModel, DMNFactory.newContext(),
decisionName);
+ }
+
+ private Object resultOf(String decisionName, boolean typeCheck) {
+ final DMNResult dmnResult = evaluate(decisionName, typeCheck);
+
assertThat(dmnResult.hasErrors()).as(DMNRuntimeUtil.formatMessages(dmnResult.getMessages())).isFalse();
+ return dmnResult.getDecisionResultByName(decisionName).getResult();
+ }
+
+ // a decision typed as a collection, with a for returning a context: the
elements must not be wrapped in a list
+
+ @ParameterizedTest
+ @ValueSource(booleans = {true, false})
+ void forReturnContextEntry(boolean typeCheck) {
+ init(false);
+ assertThat(resultOf("For Return Context Entry",
typeCheck)).isEqualTo(DOUBLED);
+ }
+
+ @ParameterizedTest
+ @MethodSource("params")
+ void forReturnContextGetValue(boolean useExecModelCompiler) {
+ init(useExecModelCompiler);
+ assertThat(resultOf("For Return Context Get Value",
true)).isEqualTo(DOUBLED);
+ }
+
+ @ParameterizedTest
+ @MethodSource("params")
+ void forReturnContextStructEntry(boolean useExecModelCompiler) {
+ init(useExecModelCompiler);
+ assertThat(resultOf("For Return Context Struct Entry", true))
+ .isEqualTo(List.of(Map.of("name", "a"), Map.of("name", "b"),
Map.of("name", "c")));
+ }
+
+ @ParameterizedTest
+ @MethodSource("params")
+ void forContextInTypedEntry(boolean useExecModelCompiler) {
+ init(useExecModelCompiler);
+ assertThat(resultOf("For Context In Typed Entry",
true)).isEqualTo(DOUBLED);
+ }
+
+ @ParameterizedTest
+ @MethodSource("params")
+ void bkmForReturnContext(boolean useExecModelCompiler) {
+ init(useExecModelCompiler);
+ assertThat(resultOf("Call For Context BKM", true)).isEqualTo(DOUBLED);
+ }
+
+ @ParameterizedTest
+ @ValueSource(booleans = {true, false})
+ void forReturnContextWrongType(boolean typeCheck) {
+ init(false);
+ // the elements are strings, while the decision is typed as a
collection of numbers
+ final DMNResult dmnResult = evaluate("For Return Context Wrong Type",
typeCheck);
+ if (typeCheck) {
+ assertThat(dmnResult.getMessages(DMNMessage.Severity.ERROR))
+ .anyMatch(m -> m.getMessageType() ==
DMNMessageType.ERROR_EVAL_NODE);
+ assertThat(dmnResult.getDecisionResultByName("For Return Context
Wrong Type").getResult()).isNull();
+ } else {
+ assertThat(dmnResult.hasErrors()).isFalse();
+ assertThat(dmnResult.getDecisionResultByName("For Return Context
Wrong Type").getResult()).isEqualTo(List.of("a", "b"));
+ }
+ }
+
+ // other nested contexts
+
+ @ParameterizedTest
+ @MethodSource("params")
+ void listOfContexts(boolean useExecModelCompiler) {
+ init(useExecModelCompiler);
+ assertThat(resultOf("List Of Contexts", true)).isEqualTo(List.of("a",
"b"));
+ }
+
+ @ParameterizedTest
+ @MethodSource("params")
+ void contextAsInvocationBinding(boolean useExecModelCompiler) {
+ init(useExecModelCompiler);
+ assertThat(resultOf("Context As Invocation Binding",
true)).isEqualTo(List.of("a!"));
+ }
+
+ @ParameterizedTest
+ @MethodSource("params")
+ void contextInNestedFunctionBody(boolean useExecModelCompiler) {
+ init(useExecModelCompiler);
+ assertThat(resultOf("Context In Nested Function Body",
true)).isEqualTo(List.of("a!"));
+ }
+
+ // a context nested two levels deep
+
+ @ParameterizedTest
+ @MethodSource("params")
+ void forInForReturnContext(boolean useExecModelCompiler) {
+ init(useExecModelCompiler);
+ assertThat(resultOf("Nested For In For Context", true))
+ .isEqualTo(List.of(List.of("a1", "a2"), List.of("b1", "b2")));
+ }
+
+ // the context produces the result of the node: the conversion to a
singleton list is preserved
+
+ @ParameterizedTest
+ @MethodSource("params")
+ void rootContext(boolean useExecModelCompiler) {
+ init(useExecModelCompiler);
+ assertThat(resultOf("Root Context", true)).isEqualTo(List.of("a"));
+ }
+
+ @ParameterizedTest
+ @MethodSource("params")
+ void rootConditionalThenContext(boolean useExecModelCompiler) {
+ init(useExecModelCompiler);
+ assertThat(resultOf("Root Conditional Of Contexts",
true)).isEqualTo(List.of("a"));
+ }
+
+ @ParameterizedTest
+ @MethodSource("params")
+ void rootConditionalElseContext(boolean useExecModelCompiler) {
+ init(useExecModelCompiler);
+ assertThat(resultOf("Root Conditional Else Context",
true)).isEqualTo(List.of("a"));
+ }
+
+ @ParameterizedTest
+ @MethodSource("params")
+ void typedEntryContext(boolean useExecModelCompiler) {
+ init(useExecModelCompiler);
+ // the context takes the type of the entry
+ assertThat(resultOf("Typed Entry Context",
true)).isEqualTo(List.of("b"));
+ }
+
+ @ParameterizedTest
+ @MethodSource("params")
+ void bkmRootContext(boolean useExecModelCompiler) {
+ init(useExecModelCompiler);
+ assertThat(resultOf("Call Root Context BKM",
true)).isEqualTo(List.of("a"));
+ }
+
+ @ParameterizedTest
+ @MethodSource("params")
+ void bkmRootConditionalOfContexts(boolean useExecModelCompiler) {
+ init(useExecModelCompiler);
+ assertThat(resultOf("Call Root Conditional BKM",
true)).isEqualTo(List.of("a"));
+ }
+
+ // the result type of a context must not depend on its parent link
+
+ @ParameterizedTest
+ @MethodSource("params")
+ void contextsWithoutParentLink(boolean useExecModelCompiler) {
+ init(useExecModelCompiler);
+ final Definitions definitions =
DMNMarshallerFactory.newDefaultMarshaller()
+ .unmarshal(new
InputStreamReader(getClass().getClassLoader().getResourceAsStream(MODEL),
StandardCharsets.UTF_8));
+
definitions.getDrgElement().forEach(DMNContextResultTypeCoercionTest::removeParentLinkOfContexts);
+ final DMNModel dmnModel = new DMNCompilerImpl().compile(definitions,
Collections.emptyList());
+
assertThat(dmnModel.hasErrors()).as(DMNRuntimeUtil.formatMessages(dmnModel.getMessages())).isFalse();
+ final DMNRuntimeImpl runtime = new DMNRuntimeImpl(new
VoidDMNRuntimeKB());
+ runtime.setOption(new RuntimeTypeCheckOption(true));
+
+ final Map<String, Object> expected = new LinkedHashMap<>();
+ expected.put("For Return Context Entry", DOUBLED);
+ expected.put("List Of Contexts", List.of("a", "b"));
+ expected.put("Root Context", List.of("a"));
+ expected.put("Root Conditional Of Contexts", List.of("a"));
+ expected.put("Call Root Context BKM", List.of("a"));
+ expected.put("Call Root Conditional BKM", List.of("a"));
+ expected.put("Call For Context BKM", DOUBLED);
+ expected.forEach((decisionName, expectedResult) -> {
+ final DMNResult dmnResult = runtime.evaluateByName(dmnModel,
DMNFactory.newContext(), decisionName);
+ assertThat(dmnResult.hasErrors()).as(decisionName + " " +
DMNRuntimeUtil.formatMessages(dmnResult.getMessages())).isFalse();
+
assertThat(dmnResult.getDecisionResultByName(decisionName).getResult()).as(decisionName).isEqualTo(expectedResult);
+ });
+ }
+
+ private static void removeParentLinkOfContexts(DMNModelInstrumentedBase
element) {
+ element.getChildren().forEach(child -> {
+ removeParentLinkOfContexts(child);
+ if (child instanceof Context) {
+ child.setParent(null);
+ }
+ });
+ }
+}
diff --git
a/kie-dmn/kie-dmn-core/src/test/java/org/kie/dmn/core/compiler/DMNEvaluatorCompilerTest.java
b/kie-dmn/kie-dmn-core/src/test/java/org/kie/dmn/core/compiler/DMNEvaluatorCompilerTest.java
index de2c7a12846..26bdfa07359 100644
---
a/kie-dmn/kie-dmn-core/src/test/java/org/kie/dmn/core/compiler/DMNEvaluatorCompilerTest.java
+++
b/kie-dmn/kie-dmn-core/src/test/java/org/kie/dmn/core/compiler/DMNEvaluatorCompilerTest.java
@@ -30,16 +30,32 @@ import org.kie.dmn.api.core.DMNRuntime;
import org.kie.dmn.api.core.DMNType;
import org.kie.dmn.api.core.DMNVersion;
import org.kie.dmn.core.api.DMNExpressionEvaluator;
+import org.kie.dmn.core.ast.BusinessKnowledgeModelNodeImpl;
import org.kie.dmn.core.ast.DMNBaseNode;
import org.kie.dmn.core.ast.DMNConditionalEvaluator;
+import org.kie.dmn.core.ast.DecisionNodeImpl;
+import org.kie.dmn.core.ast.InputDataNodeImpl;
import org.kie.dmn.core.impl.DMNModelImpl;
import org.kie.dmn.core.internal.utils.DMNRuntimeBuilder;
import org.kie.dmn.feel.FEEL;
import org.kie.dmn.feel.lang.FEELDialect;
import org.kie.dmn.feel.lang.impl.FEELImpl;
+import org.kie.dmn.model.api.ChildExpression;
import org.kie.dmn.model.api.Conditional;
+import org.kie.dmn.model.api.Context;
+import org.kie.dmn.model.api.Expression;
+import org.kie.dmn.model.api.FunctionDefinition;
import org.kie.dmn.model.api.DMNModelInstrumentedBase;
import org.kie.dmn.model.api.LiteralExpression;
+import org.kie.dmn.model.v1_5.TBusinessKnowledgeModel;
+import org.kie.dmn.model.v1_5.TChildExpression;
+import org.kie.dmn.model.v1_5.TConditional;
+import org.kie.dmn.model.v1_5.TContext;
+import org.kie.dmn.model.v1_5.TDecision;
+import org.kie.dmn.model.v1_5.TFor;
+import org.kie.dmn.model.v1_5.TFunctionDefinition;
+import org.kie.dmn.model.v1_5.TInputData;
+import org.kie.dmn.model.v1_5.TList;
import org.kie.dmn.model.v1_5.TLiteralExpression;
import org.kie.internal.io.ResourceFactory;
@@ -49,7 +65,11 @@ import static
org.kie.dmn.core.ast.DMNConditionalEvaluator.EvaluatorType.ELSE;
import static org.kie.dmn.core.ast.DMNConditionalEvaluator.EvaluatorType.IF;
import static org.kie.dmn.core.ast.DMNConditionalEvaluator.EvaluatorType.THEN;
import static
org.kie.dmn.core.compiler.DMNEvaluatorCompiler.getEvaluatorIdentifier;
+import static
org.kie.dmn.core.compiler.DMNEvaluatorCompiler.getChildExpression;
import static
org.kie.dmn.core.compiler.DMNEvaluatorCompiler.getEvaluatorIdentifierMap;
+import static
org.kie.dmn.core.compiler.DMNEvaluatorCompiler.getDecisionBKMNodeExpression;
+import static
org.kie.dmn.core.compiler.DMNEvaluatorCompiler.isResultOfDecisionOrBKM;
+import static
org.kie.dmn.core.compiler.DMNEvaluatorCompiler.isSameOrConditionalBranchOf;
class DMNEvaluatorCompilerTest {
@@ -227,6 +247,186 @@ class DMNEvaluatorCompilerTest {
assertThat(result).isInstanceOf(DMNConditionalEvaluator.class);
}
+ @Test
+ void getDecisionBKMNodeExpressionOfDecision() {
+ Expression expression = new TContext();
+
assertThat(getDecisionBKMNodeExpression(decisionNode(expression))).containsSame(expression);
+ }
+
+ @Test
+ void getDecisionBKMNodeExpressionOfDecisionWithoutExpression() {
+ assertThat(getDecisionBKMNodeExpression(decisionNode(null))).isEmpty();
+ }
+
+ @Test
+ void getDecisionBKMNodeExpressionOfBKM() {
+ Expression body = new TContext();
+
assertThat(getDecisionBKMNodeExpression(bkmNode(functionDefinition(body)))).containsSame(body);
+ }
+
+ @Test
+ void getDecisionBKMNodeExpressionOfBKMWithoutEncapsulatedLogic() {
+ assertThat(getDecisionBKMNodeExpression(bkmNode(null))).isEmpty();
+ }
+
+ @Test
+ void getDecisionBKMNodeExpressionOfBKMWithoutBody() {
+
assertThat(getDecisionBKMNodeExpression(bkmNode(functionDefinition(null)))).isEmpty();
+ }
+
+ @Test
+ void getDecisionBKMNodeExpressionOfOtherNode() {
+ assertThat(getDecisionBKMNodeExpression(new InputDataNodeImpl(new
TInputData()))).isEmpty();
+ }
+
+ @Test
+ void getChildExpressionOfNull() {
+ assertThat(getChildExpression(null)).isEmpty();
+ }
+
+ @Test
+ void getChildExpressionWithExpression() {
+ Expression expression = new TContext();
+
assertThat(getChildExpression(childExpression(expression))).containsSame(expression);
+ }
+
+ @Test
+ void getChildExpressionWithoutExpression() {
+ assertThat(getChildExpression(new TChildExpression())).isEmpty();
+ }
+
+ @Test
+ void isSameOrConditionalBranchOfSameInstance() {
+ Expression expression = new TContext();
+ assertThat(isSameOrConditionalBranchOf(expression,
expression)).isTrue();
+ }
+
+ @Test
+ void isSameOrConditionalBranchOfAnotherInstance() {
+ // two contexts that look the same are different expressions, only the
very same instance is accepted
+ assertThat(isSameOrConditionalBranchOf(new TContext(), new
TContext())).isFalse();
+ }
+
+ @Test
+ void isSameOrConditionalBranchOfThenBranch() {
+ Expression expression = new TContext();
+ assertThat(isSameOrConditionalBranchOf(conditional(new TContext(),
expression, new TContext()), expression)).isTrue();
+ }
+
+ @Test
+ void isSameOrConditionalBranchOfElseBranch() {
+ Expression expression = new TContext();
+ assertThat(isSameOrConditionalBranchOf(conditional(new TContext(), new
TContext(), expression), expression)).isTrue();
+ }
+
+ @Test
+ void isSameOrConditionalBranchOfNestedConditionalBranch() {
+ Expression expression = new TContext();
+ Conditional nested = conditional(new TContext(), expression, new
TContext());
+ assertThat(isSameOrConditionalBranchOf(conditional(new TContext(), new
TContext(), nested), expression)).isTrue();
+ }
+
+ @Test
+ void isSameOrConditionalBranchOfIfCondition() {
+ Expression expression = new TContext();
+ assertThat(isSameOrConditionalBranchOf(conditional(expression, new
TContext(), new TContext()), expression)).isFalse();
+ }
+
+ @Test
+ void isSameOrConditionalBranchOfConditionalWithoutBranches() {
+ assertThat(isSameOrConditionalBranchOf(conditional(new TContext(),
null, null), new TContext())).isFalse();
+ }
+
+ @Test
+ void isSameOrConditionalBranchOfNotConditional() {
+ Expression expression = new TContext();
+ TList list = new TList();
+ list.getExpression().add(expression);
+ assertThat(isSameOrConditionalBranchOf(list, expression)).isFalse();
+ }
+
+ @Test
+ void isResultOfDecisionOrBKMDecisionRootContext() {
+ Context context = new TContext();
+ assertThat(isResultOfDecisionOrBKM(context,
decisionNode(context))).isTrue();
+ }
+
+ @Test
+ void isResultOfDecisionOrBKMDecisionNestedContext() {
+ Context context = new TContext();
+ TList list = new TList();
+ list.getExpression().add(context);
+ assertThat(isResultOfDecisionOrBKM(context,
decisionNode(list))).isFalse();
+ }
+
+ @Test
+ void isResultOfDecisionOrBKMDecisionWithoutExpression() {
+ assertThat(isResultOfDecisionOrBKM(new TContext(),
decisionNode(null))).isFalse();
+ }
+
+ @Test
+ void isResultOfDecisionOrBKMBKMRootContext() {
+ Context context = new TContext();
+ assertThat(isResultOfDecisionOrBKM(context,
bkmNode(functionDefinition(context)))).isTrue();
+ }
+
+ @Test
+ void isResultOfDecisionOrBKMBKMConditionalBranchContext() {
+ Context context = new TContext();
+ Conditional conditional = conditional(new TContext(), context, new
TContext());
+ assertThat(isResultOfDecisionOrBKM(context,
bkmNode(functionDefinition(conditional)))).isTrue();
+ }
+
+ @Test
+ void isResultOfDecisionOrBKMBKMWithoutEncapsulatedLogic() {
+ assertThat(isResultOfDecisionOrBKM(new TContext(),
bkmNode(null))).isFalse();
+ }
+
+ @Test
+ void isResultOfDecisionOrBKMDecisionContextNestedInTwoFors() {
+ Context context = new TContext();
+ TFor inner = new TFor();
+ inner.setReturn(childExpression(context));
+ TFor outer = new TFor();
+ outer.setReturn(childExpression(inner));
+ assertThat(isResultOfDecisionOrBKM(context,
decisionNode(outer))).isFalse();
+ }
+
+ private static DMNBaseNode decisionNode(Expression expression) {
+ TDecision decision = new TDecision();
+ decision.setExpression(expression);
+ return new DecisionNodeImpl(decision);
+ }
+
+ private static DMNBaseNode bkmNode(FunctionDefinition encapsulatedLogic) {
+ TBusinessKnowledgeModel bkm = new TBusinessKnowledgeModel();
+ bkm.setEncapsulatedLogic(encapsulatedLogic);
+ return new BusinessKnowledgeModelNodeImpl(bkm);
+ }
+
+ private static FunctionDefinition functionDefinition(Expression body) {
+ FunctionDefinition toReturn = new TFunctionDefinition();
+ toReturn.setExpression(body);
+ return toReturn;
+ }
+
+ private static ChildExpression childExpression(Expression expression) {
+ if (expression == null) {
+ return null;
+ }
+ ChildExpression toReturn = new TChildExpression();
+ toReturn.setExpression(expression);
+ return toReturn;
+ }
+
+ private static Conditional conditional(Expression ifExpression, Expression
thenExpression, Expression elseExpression) {
+ Conditional toReturn = new TConditional();
+ toReturn.setIf(childExpression(ifExpression));
+ toReturn.setThen(childExpression(thenExpression));
+ toReturn.setElse(childExpression(elseExpression));
+ return toReturn;
+ }
+
private LiteralExpression getLiteralExpression(String expressionLanguage) {
LiteralExpression toReturn = new TLiteralExpression();
toReturn.setExpressionLanguage(expressionLanguage);
diff --git
a/kie-dmn/kie-dmn-test-resources/src/test/resources/valid_models/DMNv1_6/ContextResultTypeCoercion.dmn
b/kie-dmn/kie-dmn-test-resources/src/test/resources/valid_models/DMNv1_6/ContextResultTypeCoercion.dmn
new file mode 100644
index 00000000000..56db5285635
--- /dev/null
+++
b/kie-dmn/kie-dmn-test-resources/src/test/resources/valid_models/DMNv1_6/ContextResultTypeCoercion.dmn
@@ -0,0 +1,458 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ ~ Licensed to the Apache Software Foundation (ASF) under one
+ ~ or more contributor license agreements. See the NOTICE file
+ ~ distributed with this work for additional information
+ ~ regarding copyright ownership. The ASF licenses this file
+ ~ to you under the Apache License, Version 2.0 (the
+ ~ "License"); you may not use this file except in compliance
+ ~ with the License. You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing,
+ ~ software distributed under the License is distributed on an
+ ~ "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ ~ KIND, either express or implied. See the License for the
+ ~ specific language governing permissions and limitations
+ ~ under the License.
+ -->
+<semantic:definitions xmlns="https://github.com/kiegroup/drools/kie-dmn"
+ xmlns:dc="http://www.omg.org/spec/DMN/20180521/DC/"
+ xmlns:di="http://www.omg.org/spec/DMN/20180521/DI/"
+
xmlns:dmndi="https://www.omg.org/spec/DMN/20230324/DMNDI/"
+ xmlns:feel="https://www.omg.org/spec/DMN/20240513/FEEL/"
+
xmlns:semantic="https://www.omg.org/spec/DMN/20240513/MODEL/"
+ id="_ContextResultTypeCoercion"
+ name="ContextResultTypeCoercion"
+ namespace="https://github.com/kiegroup/drools/kie-dmn">
+ <semantic:itemDefinition name="tStrList" isCollection="true">
+ <semantic:typeRef>string</semantic:typeRef>
+ </semantic:itemDefinition>
+ <semantic:itemDefinition name="tNumList" isCollection="true">
+ <semantic:typeRef>number</semantic:typeRef>
+ </semantic:itemDefinition>
+ <semantic:itemDefinition name="tAttribute" isCollection="false">
+ <semantic:itemComponent name="name" isCollection="false">
+ <semantic:typeRef>string</semantic:typeRef>
+ </semantic:itemComponent>
+ </semantic:itemDefinition>
+ <semantic:itemDefinition name="tAttributeList" isCollection="true">
+ <semantic:typeRef>tAttribute</semantic:typeRef>
+ </semantic:itemDefinition>
+ <semantic:itemDefinition name="tKeyValue" isCollection="false">
+ <semantic:itemComponent name="k" isCollection="false">
+ <semantic:typeRef>string</semantic:typeRef>
+ </semantic:itemComponent>
+ </semantic:itemDefinition>
+ <semantic:itemDefinition name="tStrListList" isCollection="true">
+ <semantic:typeRef>tStrList</semantic:typeRef>
+ </semantic:itemDefinition>
+ <semantic:decision id="d_ForReturnContextEntry" name="For Return Context
Entry">
+ <semantic:variable name="For Return Context Entry" typeRef="tStrList"/>
+ <semantic:for iteratorVariable="x">
+ <semantic:in>
+ <semantic:literalExpression>
+ <semantic:text>["a", "b", "c"]</semantic:text>
+ </semantic:literalExpression>
+ </semantic:in>
+ <semantic:return>
+ <semantic:context>
+ <semantic:contextEntry>
+ <semantic:variable name="d" typeRef="string"/>
+ <semantic:literalExpression>
+ <semantic:text>x + x</semantic:text>
+ </semantic:literalExpression>
+ </semantic:contextEntry>
+ <semantic:contextEntry>
+ <semantic:literalExpression>
+ <semantic:text>d</semantic:text>
+ </semantic:literalExpression>
+ </semantic:contextEntry>
+ </semantic:context>
+ </semantic:return>
+ </semantic:for>
+ </semantic:decision>
+ <semantic:decision id="d_ForReturnContextGetValue" name="For Return Context
Get Value">
+ <semantic:variable name="For Return Context Get Value"
typeRef="tStrList"/>
+ <semantic:for iteratorVariable="x">
+ <semantic:in>
+ <semantic:literalExpression>
+ <semantic:text>["a", "b", "c"]</semantic:text>
+ </semantic:literalExpression>
+ </semantic:in>
+ <semantic:return>
+ <semantic:context>
+ <semantic:contextEntry>
+ <semantic:variable name="c" typeRef="tKeyValue"/>
+ <semantic:literalExpression>
+ <semantic:text>{k: x + x}</semantic:text>
+ </semantic:literalExpression>
+ </semantic:contextEntry>
+ <semantic:contextEntry>
+ <semantic:literalExpression>
+ <semantic:text>get value(c, "k")</semantic:text>
+ </semantic:literalExpression>
+ </semantic:contextEntry>
+ </semantic:context>
+ </semantic:return>
+ </semantic:for>
+ </semantic:decision>
+ <semantic:decision id="d_ForReturnContextStructEntry" name="For Return
Context Struct Entry">
+ <semantic:variable name="For Return Context Struct Entry"
typeRef="tAttributeList"/>
+ <semantic:for iteratorVariable="x">
+ <semantic:in>
+ <semantic:literalExpression>
+ <semantic:text>["a", "b", "c"]</semantic:text>
+ </semantic:literalExpression>
+ </semantic:in>
+ <semantic:return>
+ <semantic:context>
+ <semantic:contextEntry>
+ <semantic:variable name="attribute" typeRef="tAttribute"/>
+ <semantic:literalExpression>
+ <semantic:text>{name: x}</semantic:text>
+ </semantic:literalExpression>
+ </semantic:contextEntry>
+ <semantic:contextEntry>
+ <semantic:literalExpression>
+ <semantic:text>attribute</semantic:text>
+ </semantic:literalExpression>
+ </semantic:contextEntry>
+ </semantic:context>
+ </semantic:return>
+ </semantic:for>
+ </semantic:decision>
+ <semantic:decision id="d_ForReturnContextWrongType" name="For Return
Context Wrong Type">
+ <semantic:variable name="For Return Context Wrong Type"
typeRef="tNumList"/>
+ <semantic:for iteratorVariable="x">
+ <semantic:in>
+ <semantic:literalExpression>
+ <semantic:text>["a", "b"]</semantic:text>
+ </semantic:literalExpression>
+ </semantic:in>
+ <semantic:return>
+ <semantic:context>
+ <semantic:contextEntry>
+ <semantic:literalExpression>
+ <semantic:text>x</semantic:text>
+ </semantic:literalExpression>
+ </semantic:contextEntry>
+ </semantic:context>
+ </semantic:return>
+ </semantic:for>
+ </semantic:decision>
+ <semantic:decision id="d_ListOfContexts" name="List Of Contexts">
+ <semantic:variable name="List Of Contexts" typeRef="tStrList"/>
+ <semantic:list>
+ <semantic:context>
+ <semantic:contextEntry>
+ <semantic:literalExpression>
+ <semantic:text>"a"</semantic:text>
+ </semantic:literalExpression>
+ </semantic:contextEntry>
+ </semantic:context>
+ <semantic:context>
+ <semantic:contextEntry>
+ <semantic:literalExpression>
+ <semantic:text>"b"</semantic:text>
+ </semantic:literalExpression>
+ </semantic:contextEntry>
+ </semantic:context>
+ </semantic:list>
+ </semantic:decision>
+ <semantic:businessKnowledgeModel id="k_Echo" name="Echo">
+ <semantic:variable name="Echo" typeRef="string"/>
+ <semantic:encapsulatedLogic>
+ <semantic:formalParameter name="p" typeRef="Any"/>
+ <semantic:literalExpression>
+ <semantic:text>p + "!"</semantic:text>
+ </semantic:literalExpression>
+ </semantic:encapsulatedLogic>
+ </semantic:businessKnowledgeModel>
+ <semantic:decision id="d_ContextAsInvocationBinding" name="Context As
Invocation Binding">
+ <semantic:variable name="Context As Invocation Binding"
typeRef="tStrList"/>
+ <semantic:knowledgeRequirement>
+ <semantic:requiredKnowledge href="#k_Echo"/>
+ </semantic:knowledgeRequirement>
+ <semantic:invocation>
+ <semantic:literalExpression>
+ <semantic:text>Echo</semantic:text>
+ </semantic:literalExpression>
+ <semantic:binding>
+ <semantic:parameter name="p"/>
+ <semantic:context>
+ <semantic:contextEntry>
+ <semantic:literalExpression>
+ <semantic:text>"a"</semantic:text>
+ </semantic:literalExpression>
+ </semantic:contextEntry>
+ </semantic:context>
+ </semantic:binding>
+ </semantic:invocation>
+ </semantic:decision>
+ <semantic:decision id="d_ContextInNestedFunctionBody" name="Context In
Nested Function Body">
+ <semantic:variable name="Context In Nested Function Body"
typeRef="tStrList"/>
+ <semantic:context>
+ <semantic:contextEntry>
+ <semantic:variable name="f" typeRef="function"/>
+ <semantic:functionDefinition kind="FEEL">
+ <semantic:formalParameter name="x" typeRef="string"/>
+ <semantic:context>
+ <semantic:contextEntry>
+ <semantic:literalExpression>
+ <semantic:text>x</semantic:text>
+ </semantic:literalExpression>
+ </semantic:contextEntry>
+ </semantic:context>
+ </semantic:functionDefinition>
+ </semantic:contextEntry>
+ <semantic:contextEntry>
+ <semantic:literalExpression>
+ <semantic:text>f("a") + "!"</semantic:text>
+ </semantic:literalExpression>
+ </semantic:contextEntry>
+ </semantic:context>
+ </semantic:decision>
+ <semantic:decision id="d_ForContextInTypedEntry" name="For Context In Typed
Entry">
+ <semantic:variable name="For Context In Typed Entry" typeRef="tStrList"/>
+ <semantic:context>
+ <semantic:contextEntry>
+ <semantic:variable name="doubled" typeRef="tStrList"/>
+ <semantic:for iteratorVariable="x">
+ <semantic:in>
+ <semantic:literalExpression>
+ <semantic:text>["a", "b", "c"]</semantic:text>
+ </semantic:literalExpression>
+ </semantic:in>
+ <semantic:return>
+ <semantic:context>
+ <semantic:contextEntry>
+ <semantic:variable name="d" typeRef="string"/>
+ <semantic:literalExpression>
+ <semantic:text>x + x</semantic:text>
+ </semantic:literalExpression>
+ </semantic:contextEntry>
+ <semantic:contextEntry>
+ <semantic:literalExpression>
+ <semantic:text>d</semantic:text>
+ </semantic:literalExpression>
+ </semantic:contextEntry>
+ </semantic:context>
+ </semantic:return>
+ </semantic:for>
+ </semantic:contextEntry>
+ <semantic:contextEntry>
+ <semantic:literalExpression>
+ <semantic:text>doubled</semantic:text>
+ </semantic:literalExpression>
+ </semantic:contextEntry>
+ </semantic:context>
+ </semantic:decision>
+ <semantic:decision id="d_RootContext" name="Root Context">
+ <semantic:variable name="Root Context" typeRef="tStrList"/>
+ <semantic:context>
+ <semantic:contextEntry>
+ <semantic:literalExpression>
+ <semantic:text>"a"</semantic:text>
+ </semantic:literalExpression>
+ </semantic:contextEntry>
+ </semantic:context>
+ </semantic:decision>
+ <semantic:decision id="d_RootConditionalOfContexts" name="Root Conditional
Of Contexts">
+ <semantic:variable name="Root Conditional Of Contexts"
typeRef="tStrList"/>
+ <semantic:conditional>
+ <semantic:if>
+ <semantic:literalExpression>
+ <semantic:text>true</semantic:text>
+ </semantic:literalExpression>
+ </semantic:if>
+ <semantic:then>
+ <semantic:context>
+ <semantic:contextEntry>
+ <semantic:literalExpression>
+ <semantic:text>"a"</semantic:text>
+ </semantic:literalExpression>
+ </semantic:contextEntry>
+ </semantic:context>
+ </semantic:then>
+ <semantic:else>
+ <semantic:context>
+ <semantic:contextEntry>
+ <semantic:literalExpression>
+ <semantic:text>"b"</semantic:text>
+ </semantic:literalExpression>
+ </semantic:contextEntry>
+ </semantic:context>
+ </semantic:else>
+ </semantic:conditional>
+ </semantic:decision>
+ <semantic:decision id="d_RootConditionalElseContext" name="Root Conditional
Else Context">
+ <semantic:variable name="Root Conditional Else Context"
typeRef="tStrList"/>
+ <semantic:conditional>
+ <semantic:if>
+ <semantic:literalExpression>
+ <semantic:text>false</semantic:text>
+ </semantic:literalExpression>
+ </semantic:if>
+ <semantic:then>
+ <semantic:literalExpression>
+ <semantic:text>"b"</semantic:text>
+ </semantic:literalExpression>
+ </semantic:then>
+ <semantic:else>
+ <semantic:context>
+ <semantic:contextEntry>
+ <semantic:literalExpression>
+ <semantic:text>"a"</semantic:text>
+ </semantic:literalExpression>
+ </semantic:contextEntry>
+ </semantic:context>
+ </semantic:else>
+ </semantic:conditional>
+ </semantic:decision>
+ <semantic:decision id="d_TypedEntryContext" name="Typed Entry Context">
+ <semantic:variable name="Typed Entry Context" typeRef="tStrList"/>
+ <semantic:context>
+ <semantic:contextEntry>
+ <semantic:variable name="inner" typeRef="tStrList"/>
+ <semantic:context>
+ <semantic:contextEntry>
+ <semantic:literalExpression>
+ <semantic:text>"b"</semantic:text>
+ </semantic:literalExpression>
+ </semantic:contextEntry>
+ </semantic:context>
+ </semantic:contextEntry>
+ <semantic:contextEntry>
+ <semantic:literalExpression>
+ <semantic:text>inner</semantic:text>
+ </semantic:literalExpression>
+ </semantic:contextEntry>
+ </semantic:context>
+ </semantic:decision>
+ <semantic:businessKnowledgeModel id="k_RootContextBKM" name="Root Context
BKM">
+ <semantic:variable name="Root Context BKM" typeRef="tStrList"/>
+ <semantic:encapsulatedLogic>
+ <semantic:context>
+ <semantic:contextEntry>
+ <semantic:literalExpression>
+ <semantic:text>"a"</semantic:text>
+ </semantic:literalExpression>
+ </semantic:contextEntry>
+ </semantic:context>
+ </semantic:encapsulatedLogic>
+ </semantic:businessKnowledgeModel>
+ <semantic:decision id="d_CallRootContextBKM" name="Call Root Context BKM">
+ <semantic:variable name="Call Root Context BKM" typeRef="Any"/>
+ <semantic:knowledgeRequirement>
+ <semantic:requiredKnowledge href="#k_RootContextBKM"/>
+ </semantic:knowledgeRequirement>
+ <semantic:literalExpression>
+ <semantic:text>Root Context BKM()</semantic:text>
+ </semantic:literalExpression>
+ </semantic:decision>
+ <semantic:businessKnowledgeModel id="k_RootConditionalBKM" name="Root
Conditional BKM">
+ <semantic:variable name="Root Conditional BKM" typeRef="tStrList"/>
+ <semantic:encapsulatedLogic>
+ <semantic:conditional>
+ <semantic:if>
+ <semantic:literalExpression>
+ <semantic:text>true</semantic:text>
+ </semantic:literalExpression>
+ </semantic:if>
+ <semantic:then>
+ <semantic:context>
+ <semantic:contextEntry>
+ <semantic:literalExpression>
+ <semantic:text>"a"</semantic:text>
+ </semantic:literalExpression>
+ </semantic:contextEntry>
+ </semantic:context>
+ </semantic:then>
+ <semantic:else>
+ <semantic:context>
+ <semantic:contextEntry>
+ <semantic:literalExpression>
+ <semantic:text>"b"</semantic:text>
+ </semantic:literalExpression>
+ </semantic:contextEntry>
+ </semantic:context>
+ </semantic:else>
+ </semantic:conditional>
+ </semantic:encapsulatedLogic>
+ </semantic:businessKnowledgeModel>
+ <semantic:decision id="d_CallRootConditionalBKM" name="Call Root
Conditional BKM">
+ <semantic:variable name="Call Root Conditional BKM" typeRef="Any"/>
+ <semantic:knowledgeRequirement>
+ <semantic:requiredKnowledge href="#k_RootConditionalBKM"/>
+ </semantic:knowledgeRequirement>
+ <semantic:literalExpression>
+ <semantic:text>Root Conditional BKM()</semantic:text>
+ </semantic:literalExpression>
+ </semantic:decision>
+ <semantic:businessKnowledgeModel id="k_ForContextBKM" name="For Context
BKM">
+ <semantic:variable name="For Context BKM" typeRef="tStrList"/>
+ <semantic:encapsulatedLogic>
+ <semantic:for iteratorVariable="x">
+ <semantic:in>
+ <semantic:literalExpression>
+ <semantic:text>["a", "b", "c"]</semantic:text>
+ </semantic:literalExpression>
+ </semantic:in>
+ <semantic:return>
+ <semantic:context>
+ <semantic:contextEntry>
+ <semantic:variable name="d" typeRef="string"/>
+ <semantic:literalExpression>
+ <semantic:text>x + x</semantic:text>
+ </semantic:literalExpression>
+ </semantic:contextEntry>
+ <semantic:contextEntry>
+ <semantic:literalExpression>
+ <semantic:text>d</semantic:text>
+ </semantic:literalExpression>
+ </semantic:contextEntry>
+ </semantic:context>
+ </semantic:return>
+ </semantic:for>
+ </semantic:encapsulatedLogic>
+ </semantic:businessKnowledgeModel>
+ <semantic:decision id="d_CallForContextBKM" name="Call For Context BKM">
+ <semantic:variable name="Call For Context BKM" typeRef="Any"/>
+ <semantic:knowledgeRequirement>
+ <semantic:requiredKnowledge href="#k_ForContextBKM"/>
+ </semantic:knowledgeRequirement>
+ <semantic:literalExpression>
+ <semantic:text>For Context BKM()</semantic:text>
+ </semantic:literalExpression>
+ </semantic:decision>
+ <semantic:decision id="d_NestedForInForContext" name="Nested For In For
Context">
+ <semantic:variable name="Nested For In For Context"
typeRef="tStrListList"/>
+ <semantic:for iteratorVariable="x">
+ <semantic:in>
+ <semantic:literalExpression>
+ <semantic:text>["a", "b"]</semantic:text>
+ </semantic:literalExpression>
+ </semantic:in>
+ <semantic:return>
+ <semantic:for iteratorVariable="y">
+ <semantic:in>
+ <semantic:literalExpression>
+ <semantic:text>["1", "2"]</semantic:text>
+ </semantic:literalExpression>
+ </semantic:in>
+ <semantic:return>
+ <semantic:context>
+ <semantic:contextEntry>
+ <semantic:literalExpression>
+ <semantic:text>x + y</semantic:text>
+ </semantic:literalExpression>
+ </semantic:contextEntry>
+ </semantic:context>
+ </semantic:return>
+ </semantic:for>
+ </semantic:return>
+ </semantic:for>
+ </semantic:decision>
+</semantic:definitions>
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]