This is an automated email from the ASF dual-hosted git repository.

gitgabrio pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/incubator-kie-drools.git


The following commit(s) were added to refs/heads/main by this push:
     new 3f8d93f15d [incubator-kie issues#1794] Fix range' related TCKs 
failures (#6244)
3f8d93f15d is described below

commit 3f8d93f15db370af1334ae131916d957dd5a4795
Author: Gabriele Cardosi <[email protected]>
AuthorDate: Wed Feb 5 17:32:07 2025 +0100

    [incubator-kie issues#1794] Fix range' related TCKs failures (#6244)
    
    * [incubator-kie-issues#1794] Descending ranges should return null (#1795)
    
    * [incubator-kie-issues#1794] Range with both endpoint null should return 
null (#1796)
    
    * [incubator-kie-issues#1794] Range with mismatching null endpoint and open 
closed flag should return null (#1797)
    
    * [incubator-kie-issues#1794] Range with whitespaces should be correctly 
parsed (#1798)
    
    ---------
    
    Co-authored-by: Gabriele-Cardosi <[email protected]>
---
 .../NullContentInsideForIterationException.java    |  27 ++++++
 .../kie/dmn/feel/lang/ast/ForExpressionNode.java   |  11 ++-
 .../java/org/kie/dmn/feel/lang/ast/RangeNode.java  |  11 ++-
 .../dmn/feel/runtime/functions/RangeFunction.java  |  44 +++++----
 .../dmn/feel/lang/ast/ForExpressionNodeTest.java   |  16 +++-
 .../org/kie/dmn/feel/lang/ast/RangeNodeTest.java   | 105 +++++++++++++++++++++
 .../feel/runtime/FEELConditionsAndLoopsTest.java   |   2 +
 .../kie/dmn/feel/runtime/FEELFunctionsTest.java    |  11 ++-
 .../org/kie/dmn/feel/runtime/FEELRangesTest.java   |   4 +-
 .../feel/runtime/functions/RangeFunctionTest.java  |  36 +++++--
 .../dmn/feel/util/ExpressionNodeFactoryUtils.java  |  22 ++++-
 11 files changed, 251 insertions(+), 38 deletions(-)

diff --git 
a/kie-dmn/kie-dmn-feel/src/main/java/org/kie/dmn/feel/exceptions/NullContentInsideForIterationException.java
 
b/kie-dmn/kie-dmn-feel/src/main/java/org/kie/dmn/feel/exceptions/NullContentInsideForIterationException.java
new file mode 100644
index 0000000000..95db8ff228
--- /dev/null
+++ 
b/kie-dmn/kie-dmn-feel/src/main/java/org/kie/dmn/feel/exceptions/NullContentInsideForIterationException.java
@@ -0,0 +1,27 @@
+/*
+ * 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.feel.exceptions;
+
+import java.io.Serial;
+
+public class NullContentInsideForIterationException extends RuntimeException {
+
+    @Serial
+    private static final long serialVersionUID = -1400399311677539243L;
+}
\ No newline at end of file
diff --git 
a/kie-dmn/kie-dmn-feel/src/main/java/org/kie/dmn/feel/lang/ast/ForExpressionNode.java
 
b/kie-dmn/kie-dmn-feel/src/main/java/org/kie/dmn/feel/lang/ast/ForExpressionNode.java
index e8b32efdbf..9b1aae2a97 100644
--- 
a/kie-dmn/kie-dmn-feel/src/main/java/org/kie/dmn/feel/lang/ast/ForExpressionNode.java
+++ 
b/kie-dmn/kie-dmn-feel/src/main/java/org/kie/dmn/feel/lang/ast/ForExpressionNode.java
@@ -1,4 +1,4 @@
-/**
+/*
  * 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
@@ -21,6 +21,7 @@ package org.kie.dmn.feel.lang.ast;
 import org.antlr.v4.runtime.ParserRuleContext;
 import org.kie.dmn.feel.exceptions.EndpointOfForIterationNotValidTypeException;
 import 
org.kie.dmn.feel.exceptions.EndpointOfForIterationDifferentTypeException;
+import org.kie.dmn.feel.exceptions.NullContentInsideForIterationException;
 import org.kie.dmn.feel.lang.EvaluationContext;
 import org.kie.dmn.feel.lang.Type;
 import org.kie.dmn.feel.lang.ast.forexpressioniterators.ForIteration;
@@ -83,7 +84,7 @@ public class ForExpressionNode
             populateToReturn(0, ctx, toReturn);
             LOG.trace("returning {}", toReturn);
             return toReturn;
-        } catch (EndpointOfForIterationNotValidTypeException | 
EndpointOfForIterationDifferentTypeException e) {
+        } catch (EndpointOfForIterationNotValidTypeException | 
EndpointOfForIterationDifferentTypeException | 
NullContentInsideForIterationException e) {
             // ast error already reported
             return null;
         } finally {
@@ -126,14 +127,16 @@ public class ForExpressionNode
         return BuiltInType.LIST;
     }
 
-    private ForIteration createForIteration(EvaluationContext ctx, 
IterationContextNode iterationContextNode) {
+    private ForIteration createForIteration(EvaluationContext ctx, 
IterationContextNode iterationContextNode) throws 
NullContentInsideForIterationException {
         LOG.trace("Creating ForIteration for {}", iterationContextNode);
         ForIteration toReturn = null;
         String name = iterationContextNode.evaluateName(ctx);
         Object result = iterationContextNode.evaluate(ctx);
         Object rangeEnd = iterationContextNode.evaluateRangeEnd(ctx);
         if (rangeEnd == null) {
-            if (result instanceof Iterable iterable) {
+            if (result == null) {
+                throw new NullContentInsideForIterationException();
+            } else if (result instanceof Iterable iterable) {
                 toReturn = new ForIteration(name, iterable);
             } else if (result instanceof Range) {
                 toReturn = getForIteration(ctx, name, ((Range) 
result).getStart(), ((Range) result).getEnd());
diff --git 
a/kie-dmn/kie-dmn-feel/src/main/java/org/kie/dmn/feel/lang/ast/RangeNode.java 
b/kie-dmn/kie-dmn-feel/src/main/java/org/kie/dmn/feel/lang/ast/RangeNode.java
index 0d44d025d7..5ef569e051 100644
--- 
a/kie-dmn/kie-dmn-feel/src/main/java/org/kie/dmn/feel/lang/ast/RangeNode.java
+++ 
b/kie-dmn/kie-dmn-feel/src/main/java/org/kie/dmn/feel/lang/ast/RangeNode.java
@@ -111,6 +111,9 @@ public class RangeNode
     public Range evaluate(EvaluationContext ctx) {
         Object s = start.evaluate( ctx );
         Object e = end.evaluate( ctx );
+        if (s == null && e == null) {
+            return null;
+        }
         
         Type sType = BuiltInType.determineTypeFromInstance(s);
         Type eType = BuiltInType.determineTypeFromInstance(e);
@@ -125,13 +128,17 @@ public class RangeNode
 
         Comparable start = s instanceof UndefinedValueComparable ? 
(Comparable) s : convertToComparable(ctx, s );
         Comparable end = e instanceof UndefinedValueComparable ? (Comparable) 
e : convertToComparable( ctx, e );
-
-        return new RangeImpl( lowerBound==IntervalBoundary.OPEN ? 
Range.RangeBoundary.OPEN : Range.RangeBoundary.CLOSED,
+        return isDescendingRange(start, end) ? null :
+        new RangeImpl( lowerBound==IntervalBoundary.OPEN ? 
Range.RangeBoundary.OPEN : Range.RangeBoundary.CLOSED,
                               start,
                               end,
                               upperBound==IntervalBoundary.OPEN ? 
Range.RangeBoundary.OPEN : Range.RangeBoundary.CLOSED );
     }
 
+    static boolean isDescendingRange(Comparable start, Comparable end) {
+        return (start == null || start instanceof UndefinedValueComparable || 
end == null || end instanceof UndefinedValueComparable) ? false : 
start.compareTo(end) > 0;
+    }
+
     private Comparable convertToComparable(EvaluationContext ctx, Object s) {
         Comparable start;
         if (s == null) {
diff --git 
a/kie-dmn/kie-dmn-feel/src/main/java/org/kie/dmn/feel/runtime/functions/RangeFunction.java
 
b/kie-dmn/kie-dmn-feel/src/main/java/org/kie/dmn/feel/runtime/functions/RangeFunction.java
index b6601c462b..fe4719167f 100644
--- 
a/kie-dmn/kie-dmn-feel/src/main/java/org/kie/dmn/feel/runtime/functions/RangeFunction.java
+++ 
b/kie-dmn/kie-dmn-feel/src/main/java/org/kie/dmn/feel/runtime/functions/RangeFunction.java
@@ -1,4 +1,4 @@
-/**
+/*
  * 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
@@ -79,33 +79,33 @@ public class RangeFunction extends BaseFEELFunction {
             object -> object instanceof OffsetTime,
             object -> object instanceof LocalTime);
 
-
     private RangeFunction() {
         super("range");
     }
 
     public FEELFnResult<Range> invoke(@ParameterName("from") String from) {
-        if (from == null || from.isEmpty() || from.isBlank()) {
+        if (from == null || from.isBlank()) {
             return FEELFnResult.ofError(new 
InvalidParametersEvent(FEELEvent.Severity.ERROR, "from", "cannot be null"));
         }
+        String fromToUse = from.trim();
         Range.RangeBoundary startBoundary;
-        if (from.startsWith("(") || from.startsWith("]")) {
+        if (fromToUse.startsWith("(") || fromToUse.startsWith("]")) {
             startBoundary = RangeBoundary.OPEN;
-        } else if (from.startsWith("[")) {
+        } else if (fromToUse.startsWith("[")) {
             startBoundary = RangeBoundary.CLOSED;
         } else {
             return FEELFnResult.ofError(new 
InvalidParametersEvent(FEELEvent.Severity.ERROR, "from", "does not start with a 
valid character"));
         }
         Range.RangeBoundary endBoundary;
-        if (from.endsWith(")") || from.endsWith("[")) {
+        if (fromToUse.endsWith(")") || fromToUse.endsWith("[")) {
             endBoundary = RangeBoundary.OPEN;
-        } else if (from.endsWith("]")) {
+        } else if (fromToUse.endsWith("]")) {
             endBoundary = RangeBoundary.CLOSED;
         } else {
             return FEELFnResult.ofError(new 
InvalidParametersEvent(FEELEvent.Severity.ERROR, "from", "does not end with a 
valid character"));
         }
 
-        String[] split = from.split("\\.\\.");
+        String[] split = fromToUse.split("\\.\\.");
         if (split.length != 2) {
             return FEELFnResult.ofError(new 
InvalidParametersEvent(FEELEvent.Severity.ERROR, "from", "does not include two 
literals separated by `..` two dots characters"));
         }
@@ -115,11 +115,11 @@ public class RangeFunction extends BaseFEELFunction {
             return FEELFnResult.ofError(new 
InvalidParametersEvent(FEELEvent.Severity.ERROR, "from", "at least one endpoint 
must not be null"));
         }
         BaseNode leftNode = parse(leftString);
-        if (!nodeIsAllowed(leftNode)) {
+        if (!nodeIsAllowed(leftNode, startBoundary)) {
             return FEELFnResult.ofError(new 
InvalidParametersEvent(FEELEvent.Severity.ERROR, "from", "left endpoint is not 
a recognised valid literal"));
         }
         BaseNode rightNode = parse(rightString);
-        if (!nodeIsAllowed(rightNode)) {
+        if (!nodeIsAllowed(rightNode, endBoundary)) {
             return FEELFnResult.ofError(new 
InvalidParametersEvent(FEELEvent.Severity.ERROR, "from", "right endpoint is not 
a recognised valid literal"));
         }
         Object left = leftNode.evaluate(getStubbed());
@@ -135,9 +135,10 @@ public class RangeFunction extends BaseFEELFunction {
         if (!nodesReturnsSameType(left, right)) {
             return FEELFnResult.ofError(new 
InvalidParametersEvent(FEELEvent.Severity.ERROR, "from", "endpoints must be of 
equivalent types"));
         }
-
-        // Boundary values need to be always defined in range string. They can 
be undefined only in unary test, that represents range, e.g. (<10).
-        return FEELFnResult.ofResult(new RangeImpl(startBoundary, (Comparable) 
left, (Comparable) right, endBoundary));
+        Range toReturn = getReturnedValue(left, right, startBoundary, 
endBoundary);
+        // Boundary values need to be always defined in range string. They can 
be undefined only in unary test, that
+        // represents range, e.g. (<10).
+        return FEELFnResult.ofResult(toReturn);
     }
 
     @Override
@@ -145,8 +146,18 @@ public class RangeFunction extends BaseFEELFunction {
         return DEFAULT_VALUE;
     }
 
-    protected boolean nodeIsAllowed(BaseNode node) {
-        return ALLOWED_NODES.stream().anyMatch(baseNodePredicate -> 
baseNodePredicate.test(node));
+    static Range getReturnedValue(Object left, Object right, 
Range.RangeBoundary startBoundary,
+                                  Range.RangeBoundary endBoundary) {
+        return (left == null && right == null) ? null :
+                new RangeImpl(startBoundary, (Comparable) left, (Comparable) 
right, endBoundary);
+    }
+
+    protected boolean nodeIsAllowed(BaseNode node, RangeBoundary boundary) {
+        if (node instanceof NullNode && boundary.equals(RangeBoundary.CLOSED)) 
{
+            return false;
+        } else {
+            return ALLOWED_NODES.stream().anyMatch(baseNodePredicate -> 
baseNodePredicate.test(node));
+        }
     }
 
     protected boolean nodeValueIsAllowed(Object value) {
@@ -171,7 +182,7 @@ public class RangeFunction extends BaseFEELFunction {
     }
 
     protected BaseNode parse(String input) {
-       return input.isEmpty() || input.isBlank() ? getNullNode() : 
parseNotEmptyInput(input);
+        return input.isEmpty() || input.isBlank() ? getNullNode() : 
parseNotEmptyInput(input);
     }
 
     protected BaseNode getNullNode() {
@@ -194,5 +205,4 @@ public class RangeFunction extends BaseFEELFunction {
         }
         return STUBBED;
     }
-
 }
diff --git 
a/kie-dmn/kie-dmn-feel/src/test/java/org/kie/dmn/feel/lang/ast/ForExpressionNodeTest.java
 
b/kie-dmn/kie-dmn-feel/src/test/java/org/kie/dmn/feel/lang/ast/ForExpressionNodeTest.java
index fc7e9b17d0..2b28ee57bf 100644
--- 
a/kie-dmn/kie-dmn-feel/src/test/java/org/kie/dmn/feel/lang/ast/ForExpressionNodeTest.java
+++ 
b/kie-dmn/kie-dmn-feel/src/test/java/org/kie/dmn/feel/lang/ast/ForExpressionNodeTest.java
@@ -1,4 +1,4 @@
-/**
+/*
  * 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
@@ -31,11 +31,12 @@ import org.kie.dmn.feel.util.EvaluationContextTestUtil;
 import org.kie.dmn.feel.lang.types.BuiltInType;
 
 import static org.assertj.core.api.Assertions.assertThat;
+import static 
org.kie.dmn.feel.util.ExpressionNodeFactoryUtils.getAtLiteralRangeNode;
 import static 
org.kie.dmn.feel.util.ExpressionNodeFactoryUtils.getIterationContextNode;
 import static org.kie.dmn.feel.util.ExpressionNodeFactoryUtils.getListNode;
 import static org.kie.dmn.feel.util.ExpressionNodeFactoryUtils.getNameRefNode;
 import static 
org.kie.dmn.feel.util.ExpressionNodeFactoryUtils.getNestedListNode;
-import static org.kie.dmn.feel.util.ExpressionNodeFactoryUtils.getRangeNode;
+import static 
org.kie.dmn.feel.util.ExpressionNodeFactoryUtils.getLocalDateRangeNode;
 
 class ForExpressionNodeTest {
 
@@ -49,6 +50,15 @@ class ForExpressionNodeTest {
                 containsExactly(BigDecimal.ONE, BigDecimal.valueOf(2), 
BigDecimal.valueOf(3), BigDecimal.valueOf(4));
     }
 
+    @Test
+    void evaluateDescendingRange() {
+        RangeNode rangeNode = 
getAtLiteralRangeNode("[@\"1980-01-03T00:00:00\"..@\"1980-01-01T00:00:00\"]", 
"1980-01-03T00:00:00", "1980-01-01T00:00:00", 
RangeNode.IntervalBoundary.CLOSED, RangeNode.IntervalBoundary.CLOSED);
+        IterationContextNode i = getIterationContextNode("i", rangeNode, "i in 
[@\"1980-01-03T00:00:00\"..@\"1980-01-01T00:00:00\"]");
+        ForExpressionNode forExpressionNode = new 
ForExpressionNode(Collections.singletonList(i), 
getNameRefNode(BuiltInType.UNKNOWN, "i"), "for i in 
[@\"1980-01-03T00:00:00\"..@\"1980-01-01T00:00:00\"] return i");
+        Object retrieved = 
forExpressionNode.evaluate(EvaluationContextTestUtil.newEmptyEvaluationContext());
+        assertThat(retrieved).isNull();
+    }
+
     @Test
     void evaluateNestedArray() {
         Map<String, List<String>> firstIterationContext = new 
LinkedHashMap<>();
@@ -65,7 +75,7 @@ class ForExpressionNodeTest {
 
     @Test
     void evaluateRange() {
-        IterationContextNode x = getIterationContextNode("x", 
getRangeNode("[1980-01-01 .. 1980-01-03]", LocalDate.of(1980, 1, 1), 
LocalDate.of(1980, 1, 3), RangeNode.IntervalBoundary.CLOSED, 
RangeNode.IntervalBoundary.CLOSED ), "x in [1980-01-01 .. 1980-01-03]");
+        IterationContextNode x = getIterationContextNode("x", 
getLocalDateRangeNode("[1980-01-01 .. 1980-01-03]", LocalDate.of(1980, 1, 1), 
LocalDate.of(1980, 1, 3), RangeNode.IntervalBoundary.CLOSED, 
RangeNode.IntervalBoundary.CLOSED ), "x in [1980-01-01 .. 1980-01-03]");
         ForExpressionNode forExpressionNode = new 
ForExpressionNode(Collections.singletonList(x), 
getNameRefNode(BuiltInType.DATE, "x"), "for x in [1980-01-01 .. 1980-01-03] 
return x");
         Object retrieved = 
forExpressionNode.evaluate(EvaluationContextTestUtil.newEmptyEvaluationContext());
         
assertThat(retrieved).isInstanceOf(List.class).asList().containsExactly(LocalDate.of(1980,
 1, 1),
diff --git 
a/kie-dmn/kie-dmn-feel/src/test/java/org/kie/dmn/feel/lang/ast/RangeNodeTest.java
 
b/kie-dmn/kie-dmn-feel/src/test/java/org/kie/dmn/feel/lang/ast/RangeNodeTest.java
new file mode 100644
index 0000000000..d7f9e7e174
--- /dev/null
+++ 
b/kie-dmn/kie-dmn-feel/src/test/java/org/kie/dmn/feel/lang/ast/RangeNodeTest.java
@@ -0,0 +1,105 @@
+/*
+ * 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.feel.lang.ast;
+
+import java.math.BigDecimal;
+import java.time.LocalDate;
+import java.util.Arrays;
+import java.util.Collection;
+
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.MethodSource;
+import org.kie.dmn.feel.runtime.impl.RangeImpl;
+import org.kie.dmn.feel.runtime.impl.UndefinedValueComparable;
+import org.kie.dmn.feel.util.EvaluationContextTestUtil;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.kie.dmn.feel.lang.ast.RangeNode.isDescendingRange;
+import static 
org.kie.dmn.feel.util.ExpressionNodeFactoryUtils.getAtLiteralRangeNode;
+import static 
org.kie.dmn.feel.util.ExpressionNodeFactoryUtils.getLocalDateRangeNode;
+import static 
org.kie.dmn.feel.util.ExpressionNodeFactoryUtils.getNumericRangeNode;
+import static 
org.kie.dmn.feel.util.ExpressionNodeFactoryUtils.getStringRangeNode;
+
+class RangeNodeTest {
+
+    @ParameterizedTest
+    @MethodSource("ascendingRanges")
+    void evaluateAscendingRanges(RangeNode toTest) {
+        assertThat(toTest).isNotNull();
+        
assertThat(toTest.evaluate(EvaluationContextTestUtil.newEmptyEvaluationContext()))
+                .isNotNull()
+                .isInstanceOf(RangeImpl.class);
+    }
+
+    @ParameterizedTest
+    @MethodSource("descendingRanges")
+    void evaluateDescendingRanges(RangeNode toTest) {
+        assertThat(toTest).isNotNull();
+        
assertThat(toTest.evaluate(EvaluationContextTestUtil.newEmptyEvaluationContext())).isNull();
+    }
+
+    @Test
+    void isDescendingRangeTest() {
+        assertThat(isDescendingRange(BigDecimal.ONE, 
BigDecimal.TEN)).isFalse();
+        assertThat(isDescendingRange(new UndefinedValueComparable(), 
BigDecimal.TEN)).isFalse();
+        assertThat(isDescendingRange(BigDecimal.ONE, new 
UndefinedValueComparable())).isFalse();
+        assertThat(isDescendingRange(new UndefinedValueComparable(), new 
UndefinedValueComparable())).isFalse();
+        assertThat(isDescendingRange(BigDecimal.ONE, 
BigDecimal.ONE)).isFalse();
+        assertThat(isDescendingRange(BigDecimal.TEN, BigDecimal.ONE)).isTrue();
+
+        assertThat(isDescendingRange(null, BigDecimal.TEN)).isFalse();
+        assertThat(isDescendingRange(BigDecimal.ONE, null)).isFalse();
+        assertThat(isDescendingRange(null, null)).isFalse();
+        assertThat(isDescendingRange(BigDecimal.ONE, 
BigDecimal.ONE)).isFalse();
+        assertThat(isDescendingRange(BigDecimal.TEN, BigDecimal.ONE)).isTrue();
+    }
+
+    @Test
+    void isNullNullRangeTest() {
+        BaseNode startNode = new NullNode("null");
+        BaseNode endNode = new NullNode("null");
+        RangeNode rangeNode = new RangeNode(RangeNode.IntervalBoundary.CLOSED, 
RangeNode.IntervalBoundary.CLOSED,
+                                            startNode, endNode, 
"[null..null]");
+        assertThat(rangeNode).isNotNull();
+        
assertThat(rangeNode.evaluate(EvaluationContextTestUtil.newEmptyEvaluationContext())).isNull();
+    }
+
+    private static Collection<RangeNode> ascendingRanges() {
+        return Arrays.asList(
+                getNumericRangeNode("[1..3]", BigDecimal.ONE, 
BigDecimal.valueOf(3), RangeNode.IntervalBoundary.CLOSED, 
RangeNode.IntervalBoundary.CLOSED),
+                getLocalDateRangeNode("[@\"1970-01-01\"..@\"1970-01-02\"]", 
LocalDate.of(1970, 1, 1), LocalDate.of(1970, 1, 2), 
RangeNode.IntervalBoundary.CLOSED, RangeNode.IntervalBoundary.CLOSED),
+                getStringRangeNode("[\"a\"..\"z\"]", "a", "z", 
RangeNode.IntervalBoundary.CLOSED, RangeNode.IntervalBoundary.CLOSED),
+                getAtLiteralRangeNode("[@\"P1D\"..@\"P2D\"]", "P1D", "P2D", 
RangeNode.IntervalBoundary.CLOSED, RangeNode.IntervalBoundary.CLOSED),
+                getAtLiteralRangeNode("[@\"P1Y\"..@\"P2Y\"]", "P1Y", "P2Y", 
RangeNode.IntervalBoundary.CLOSED, RangeNode.IntervalBoundary.CLOSED),
+                getAtLiteralRangeNode("[@\"01:00:00\"..@\"02:00:00\"]", 
"01:00:00", "02:00:00", RangeNode.IntervalBoundary.CLOSED, 
RangeNode.IntervalBoundary.CLOSED)
+        );
+    }
+
+    private static Collection<RangeNode> descendingRanges() {
+        return Arrays.asList(
+                getNumericRangeNode("[3..1]", BigDecimal.valueOf(3), 
BigDecimal.ONE, RangeNode.IntervalBoundary.CLOSED, 
RangeNode.IntervalBoundary.CLOSED),
+                getLocalDateRangeNode("[@\"1970-01-02\"..@\"1970-01-01\"]", 
LocalDate.of(1970, 1, 2), LocalDate.of(1970, 1, 1), 
RangeNode.IntervalBoundary.CLOSED, RangeNode.IntervalBoundary.CLOSED),
+                getStringRangeNode("[\"z\"..\"a\"]", "z", "a", 
RangeNode.IntervalBoundary.CLOSED, RangeNode.IntervalBoundary.CLOSED),
+                getAtLiteralRangeNode("[@\"P2D\"..@\"P1D\"]", "P2D", "P1D", 
RangeNode.IntervalBoundary.CLOSED, RangeNode.IntervalBoundary.CLOSED),
+                getAtLiteralRangeNode("[@\"P2Y\"..@\"P1Y\"]", "P2Y", "P1Y", 
RangeNode.IntervalBoundary.CLOSED, RangeNode.IntervalBoundary.CLOSED),
+                getAtLiteralRangeNode("[@\"02:00:00\"..@\"01:00:00\"]", 
"02:00:00", "01:00:00", RangeNode.IntervalBoundary.CLOSED, 
RangeNode.IntervalBoundary.CLOSED)
+        );
+    }
+}
\ No newline at end of file
diff --git 
a/kie-dmn/kie-dmn-feel/src/test/java/org/kie/dmn/feel/runtime/FEELConditionsAndLoopsTest.java
 
b/kie-dmn/kie-dmn-feel/src/test/java/org/kie/dmn/feel/runtime/FEELConditionsAndLoopsTest.java
index eca0fe4ef5..46719d5cab 100644
--- 
a/kie-dmn/kie-dmn-feel/src/test/java/org/kie/dmn/feel/runtime/FEELConditionsAndLoopsTest.java
+++ 
b/kie-dmn/kie-dmn-feel/src/test/java/org/kie/dmn/feel/runtime/FEELConditionsAndLoopsTest.java
@@ -51,6 +51,8 @@ public class FEELConditionsAndLoopsTest extends BaseFEELTest {
                 {"for x in [ 10, 20, 30 ], y in [ 1, 2, 3 ] return x * y",
                         Stream.of(10, 20, 30, 20, 40, 60, 30, 60, 90 
).map(BigDecimal::valueOf).collect(Collectors.toList() ),
                  null },
+                {"for i in 
[@\"1980-01-03T00:00:00\"..@\"1980-01-01T00:00:00\"] return i", null,
+                        null },
                 {"count( for x in [1, 2, 3] return x+1 )", BigDecimal.valueOf( 
3 ), null},
 
                 // quantified
diff --git 
a/kie-dmn/kie-dmn-feel/src/test/java/org/kie/dmn/feel/runtime/FEELFunctionsTest.java
 
b/kie-dmn/kie-dmn-feel/src/test/java/org/kie/dmn/feel/runtime/FEELFunctionsTest.java
index a199e8f1f6..08d3d22cd4 100644
--- 
a/kie-dmn/kie-dmn-feel/src/test/java/org/kie/dmn/feel/runtime/FEELFunctionsTest.java
+++ 
b/kie-dmn/kie-dmn-feel/src/test/java/org/kie/dmn/feel/runtime/FEELFunctionsTest.java
@@ -1,4 +1,4 @@
-/**
+/*
  * 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
@@ -30,6 +30,7 @@ import org.junit.jupiter.params.provider.MethodSource;
 import org.kie.dmn.api.feel.runtime.events.FEELEvent;
 import org.kie.dmn.feel.lang.FEELDialect;
 import org.kie.dmn.feel.lang.types.impl.ComparablePeriod;
+import org.kie.dmn.feel.runtime.impl.RangeImpl;
 
 public class FEELFunctionsTest extends BaseFEELTest {
 
@@ -306,7 +307,13 @@ public class FEELFunctionsTest extends BaseFEELTest {
                 {"list replace ( [2, 4, 7, 8], -3, 6)", 
Arrays.asList(BigDecimal.valueOf(2), BigDecimal.valueOf(6), 
BigDecimal.valueOf(7), BigDecimal.valueOf(8)), null},
                 {"list replace ( [2, 4, 7, 8], function(item, newItem) item + 
newItem, 6)", null , FEELEvent.Severity.ERROR},
                 {"list replace ( [\"El-1\", \"El-2\", \"El-3\", \"El-4\"], 
function(item, newItem) item = \"El-2\", null)", Arrays.asList("El-1", null, 
"El-3", "El-4"), null},
-                {"list replace ( [2, 4, 7, 8], function(item, newItem) item < 
newItem, 5)", Arrays.asList(BigDecimal.valueOf(5), BigDecimal.valueOf(5), 
BigDecimal.valueOf(7), BigDecimal.valueOf(8)), null}
+                {"list replace ( [2, 4, 7, 8], function(item, newItem) item < 
newItem, 5)", Arrays.asList(BigDecimal.valueOf(5), BigDecimal.valueOf(5), 
BigDecimal.valueOf(7), BigDecimal.valueOf(8)), null},
+
+                // ranges
+                {"range( \"[ 1 .. 3 ]\" )", new 
RangeImpl(Range.RangeBoundary.CLOSED, BigDecimal.ONE, BigDecimal.valueOf(3), 
Range.RangeBoundary.CLOSED), null},
+                {"range(\"[null..null]\")", null, FEELEvent.Severity.ERROR},
+                {"range(\"[null..2]\")", null, FEELEvent.Severity.ERROR},
+                {"range(\"[1..null]\")", null, FEELEvent.Severity.ERROR},
         };
         return addAdditionalParameters(cases, false);
     }
diff --git 
a/kie-dmn/kie-dmn-feel/src/test/java/org/kie/dmn/feel/runtime/FEELRangesTest.java
 
b/kie-dmn/kie-dmn-feel/src/test/java/org/kie/dmn/feel/runtime/FEELRangesTest.java
index 601bd26f84..506ae00c07 100644
--- 
a/kie-dmn/kie-dmn-feel/src/test/java/org/kie/dmn/feel/runtime/FEELRangesTest.java
+++ 
b/kie-dmn/kie-dmn-feel/src/test/java/org/kie/dmn/feel/runtime/FEELRangesTest.java
@@ -51,13 +51,15 @@ public class FEELRangesTest extends BaseFEELTest {
                 {"(<=1)", new RangeImpl(Range.RangeBoundary.OPEN, new 
UndefinedValueComparable(), BigDecimal.ONE, Range.RangeBoundary.CLOSED), null},
                 {"(null..10)", new RangeImpl(Range.RangeBoundary.OPEN, null, 
BigDecimal.valueOf(10), Range.RangeBoundary.OPEN), null},
 
+                {"[null..null]", null, null},
                 {"[1..2]", new RangeImpl(Range.RangeBoundary.CLOSED, 
BigDecimal.ONE, BigDecimal.valueOf(2), Range.RangeBoundary.CLOSED), null},
-                {"[2..1]", new RangeImpl(Range.RangeBoundary.CLOSED, 
BigDecimal.valueOf(2), BigDecimal.ONE, Range.RangeBoundary.CLOSED), null},
+                {"[2..1]", null, null},
                 {"[1..2)", new RangeImpl(Range.RangeBoundary.CLOSED, 
BigDecimal.ONE, BigDecimal.valueOf(2), Range.RangeBoundary.OPEN), null},
                 {"(1..2]", new RangeImpl(Range.RangeBoundary.OPEN, 
BigDecimal.ONE, BigDecimal.valueOf(2), Range.RangeBoundary.CLOSED), null},
                 {"(1..2)", new RangeImpl(Range.RangeBoundary.OPEN, 
BigDecimal.ONE, BigDecimal.valueOf(2), Range.RangeBoundary.OPEN), null},
 
                 {"[\"a\"..\"z\"]", new RangeImpl(Range.RangeBoundary.CLOSED, 
"a", "z", Range.RangeBoundary.CLOSED), null},
+                {"[\"z\"..\"a\"]", null, null},
                 {"[\"a\"..\"z\")", new RangeImpl(Range.RangeBoundary.CLOSED, 
"a", "z", Range.RangeBoundary.OPEN), null},
                 {"(\"a\"..\"z\"]", new RangeImpl(Range.RangeBoundary.OPEN, 
"a", "z", Range.RangeBoundary.CLOSED), null},
                 {"(\"a\"..\"z\")", new RangeImpl(Range.RangeBoundary.OPEN, 
"a", "z", Range.RangeBoundary.OPEN), null},
diff --git 
a/kie-dmn/kie-dmn-feel/src/test/java/org/kie/dmn/feel/runtime/functions/RangeFunctionTest.java
 
b/kie-dmn/kie-dmn-feel/src/test/java/org/kie/dmn/feel/runtime/functions/RangeFunctionTest.java
index 2ed5fc7d4a..93f05e4378 100644
--- 
a/kie-dmn/kie-dmn-feel/src/test/java/org/kie/dmn/feel/runtime/functions/RangeFunctionTest.java
+++ 
b/kie-dmn/kie-dmn-feel/src/test/java/org/kie/dmn/feel/runtime/functions/RangeFunctionTest.java
@@ -103,6 +103,10 @@ class RangeFunctionTest {
                                                     new 
ComparablePeriod(Period.parse("P2Y6M")),
                                                     Range.RangeBoundary.OPEN),
                                       from);
+        from = "[..2]";
+        FunctionTestUtil.assertResultError(rangeFunction.invoke(from),
+                                      InvalidParametersEvent.class,
+                                      from);
     }
 
     @Test
@@ -137,6 +141,10 @@ class RangeFunctionTest {
                                                     null,
                                                     Range.RangeBoundary.OPEN),
                                       from);
+        from = "[1..]";
+        FunctionTestUtil.assertResultError(rangeFunction.invoke(from),
+                                           InvalidParametersEvent.class,
+                                           from);
     }
 
     @Test
@@ -260,26 +268,40 @@ class RangeFunctionTest {
                                       from);
     }
 
+    @Test
+    void invoke_WithWhiteSpaces() {
+        String from = "[ 1 .. 3 ]";
+        FunctionTestUtil.assertResult(rangeFunction.invoke(from),
+                                      new 
RangeImpl(Range.RangeBoundary.CLOSED, BigDecimal.ONE, BigDecimal.valueOf(3), 
Range.RangeBoundary.CLOSED),
+                                      from);
+        from = " [ 1 .. 3 ] ";
+        FunctionTestUtil.assertResult(rangeFunction.invoke(from),
+                                      new 
RangeImpl(Range.RangeBoundary.CLOSED, BigDecimal.ONE, BigDecimal.valueOf(3), 
Range.RangeBoundary.CLOSED),
+                                      from);
+    }
+
     @Test
     void nodeIsAllowed_True() {
         BaseNode node = rangeFunction.getNullNode();
-        
assertThat(rangeFunction.nodeIsAllowed(node)).withFailMessage(node.getText()).isTrue();
+        assertThat(rangeFunction.nodeIsAllowed(node, 
Range.RangeBoundary.OPEN)).withFailMessage(node.getText()).isTrue();
         node = getNumberNode();
-        
assertThat(rangeFunction.nodeIsAllowed(node)).withFailMessage(node.getText()).isTrue();
+        assertThat(rangeFunction.nodeIsAllowed(node, 
Range.RangeBoundary.OPEN)).withFailMessage(node.getText()).isTrue();
         node = getStringNode();
-        
assertThat(rangeFunction.nodeIsAllowed(node)).withFailMessage(node.getText()).isTrue();
+        assertThat(rangeFunction.nodeIsAllowed(node, 
Range.RangeBoundary.OPEN)).withFailMessage(node.getText()).isTrue();
         node = getAtLiteralNode();
-        
assertThat(rangeFunction.nodeIsAllowed(node)).withFailMessage(node.getText()).isTrue();
+        assertThat(rangeFunction.nodeIsAllowed(node, 
Range.RangeBoundary.OPEN)).withFailMessage(node.getText()).isTrue();
         node = getFunctionInvocationNodeA();
-        
assertThat(rangeFunction.nodeIsAllowed(node)).withFailMessage(node.getText()).isTrue();
+        assertThat(rangeFunction.nodeIsAllowed(node, 
Range.RangeBoundary.OPEN)).withFailMessage(node.getText()).isTrue();
     }
 
     @Test
     void nodeIsAllowed_False() {
         BaseNode node = rangeFunction.parse("if(true)");
-        
assertThat(rangeFunction.nodeIsAllowed(node)).withFailMessage(node.getText()).isFalse();
+        assertThat(rangeFunction.nodeIsAllowed(node, 
Range.RangeBoundary.OPEN)).withFailMessage(node.getText()).isFalse();
         node = getBooleanNode();
-        
assertThat(rangeFunction.nodeIsAllowed(node)).withFailMessage(node.getText()).isFalse();
+        assertThat(rangeFunction.nodeIsAllowed(node, 
Range.RangeBoundary.OPEN)).withFailMessage(node.getText()).isFalse();
+        node = rangeFunction.getNullNode();
+        assertThat(rangeFunction.nodeIsAllowed(node, 
Range.RangeBoundary.CLOSED)).withFailMessage(node.getText()).isFalse();
     }
 
     @Test
diff --git 
a/kie-dmn/kie-dmn-feel/src/test/java/org/kie/dmn/feel/util/ExpressionNodeFactoryUtils.java
 
b/kie-dmn/kie-dmn-feel/src/test/java/org/kie/dmn/feel/util/ExpressionNodeFactoryUtils.java
index f7afe8f0c7..1720e061f7 100644
--- 
a/kie-dmn/kie-dmn-feel/src/test/java/org/kie/dmn/feel/util/ExpressionNodeFactoryUtils.java
+++ 
b/kie-dmn/kie-dmn-feel/src/test/java/org/kie/dmn/feel/util/ExpressionNodeFactoryUtils.java
@@ -1,4 +1,4 @@
-/**
+/*
  * 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
@@ -19,6 +19,7 @@
 package org.kie.dmn.feel.util;
 
 import org.kie.dmn.feel.lang.Type;
+import org.kie.dmn.feel.lang.ast.AtLiteralNode;
 import org.kie.dmn.feel.lang.ast.BaseNode;
 import org.kie.dmn.feel.lang.ast.FunctionInvocationNode;
 import org.kie.dmn.feel.lang.ast.IterationContextNode;
@@ -81,13 +82,30 @@ public class ExpressionNodeFactoryUtils {
         return new TemporalConstantNode(value, null, null, null);
     }
 
-    public static RangeNode getRangeNode(String text, LocalDate start, 
LocalDate end, RangeNode.IntervalBoundary lowerBound, 
RangeNode.IntervalBoundary upperBound) {
+    public static RangeNode getLocalDateRangeNode(String text, LocalDate 
start, LocalDate end, RangeNode.IntervalBoundary lowerBound, 
RangeNode.IntervalBoundary upperBound) {
         BaseNode nameRefNode = getNameRefNode(BuiltInType.DATE, "x");
         ListNode startParams = getListNode(start.toString(), 
List.of(start.toString()));
         ListNode endParams = getListNode(end.toString(), 
List.of(end.toString()));
         BaseNode startNode = new FunctionInvocationNode(nameRefNode, 
startParams, getTemporalConstantNode(start), start.toString());
         BaseNode endNode = new FunctionInvocationNode(nameRefNode, endParams, 
getTemporalConstantNode(end), end.toString());
+        return new RangeNode(lowerBound, upperBound, startNode, endNode, text);
+    }
+
+    public static RangeNode getNumericRangeNode(String text, BigDecimal start, 
BigDecimal end, RangeNode.IntervalBoundary lowerBound, 
RangeNode.IntervalBoundary upperBound) {
+        BaseNode startNode = new NumberNode(start, start.toString());
+        BaseNode endNode = new NumberNode(end, end.toString());
+        return new RangeNode(lowerBound, upperBound, startNode, endNode, text);
+    }
+
+    public static RangeNode getStringRangeNode(String text, String start, 
String end, RangeNode.IntervalBoundary lowerBound, RangeNode.IntervalBoundary 
upperBound) {
+        BaseNode startNode = new StringNode(start);
+        BaseNode endNode = new StringNode(end);
+        return new RangeNode(lowerBound, upperBound, startNode, endNode, text);
+    }
 
+    public static RangeNode getAtLiteralRangeNode(String text, String start, 
String end, RangeNode.IntervalBoundary lowerBound, RangeNode.IntervalBoundary 
upperBound) {
+        BaseNode startNode = new AtLiteralNode(new StringNode(start), start);
+        BaseNode endNode = new AtLiteralNode(new StringNode(end), end);
         return new RangeNode(lowerBound, upperBound, startNode, endNode, text);
     }
 


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]


Reply via email to