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

rmaucher pushed a commit to branch 9.0.x
in repository https://gitbox.apache.org/repos/asf/tomcat.git

commit 7920df4629957a0211dbbf80868f08ceac77df9a
Author: opencode <[email protected]>
AuthorDate: Sun Oct 18 16:56:58 2026 +0200

    Follow the EL specification for substream with a negative start
    
    The two argument form of the substream stream operation worked with
    absolute positions: it consumed elements until the read position
    reached the start position and then emitted elements until the read
    position reached the end position. For a start greater than zero that
    matches the specification, because the number of elements emitted is
    the end position minus the start position. For a start that is less
    than or equal to zero it did not. The specification skips elements only
    when the start is greater than zero and always includes the next
    (end minus start) elements, so substream(-2, 3) of five elements had to
    include all five but returned only the first three, and substream(-2,
    0) returned nothing where the specification includes two elements.
    
    Skip the greater of the start and zero elements, and include the next
    end minus start elements. The count is calculated as a long because the
    single argument form requests the rest of the stream by passing an end
    of the largest int value, and that minus a negative start does not fit
    in an int. The behaviour for a start that is greater than or equal to
    zero, which covers the limit operation that delegates to this one, is
    unchanged.
    
    Adds tests for a negative start on both forms, with a positive and a
    zero end, and for a source with fewer elements than the number to
    skip. The two tests with the negative start and an end report the
    truncated results against the previous implementation.
---
 java/org/apache/el/stream/Stream.java              | 36 +++++++----
 .../apache/el/stream/TestCollectionOperations.java | 73 ++++++++++++++++++++++
 2 files changed, 96 insertions(+), 13 deletions(-)

diff --git a/java/org/apache/el/stream/Stream.java 
b/java/org/apache/el/stream/Stream.java
index 28cd3a93da..37a9e71988 100644
--- a/java/org/apache/el/stream/Stream.java
+++ b/java/org/apache/el/stream/Stream.java
@@ -291,10 +291,11 @@ public class Stream {
 
 
     /**
-     * Returns a stream consisting of the elements of this stream from the
-     * specified start position to the end.
+     * Returns a stream consisting of the elements of this stream after
+     * skipping the first {@code start} elements. No elements are skipped if
+     * {@code start} is less than or equal to zero.
      *
-     * @param start the start position (inclusive)
+     * @param start the number of elements to skip
      * @return the new substream
      */
     public Stream substream(final Number start) {
@@ -302,10 +303,12 @@ public class Stream {
     }
 
     /**
-     * Returns a stream consisting of the elements of this stream from the
-     * specified start position up to, but not including, the specified end 
position.
+     * Returns a stream consisting of the elements of this stream, skipping
+     * the first {@code start} elements and including the next
+     * {@code end - start} elements. No elements are skipped if {@code start}
+     * is less than or equal to zero.
      *
-     * @param start the start position (inclusive)
+     * @param start the number of elements to skip
      * @param end the end position (exclusive)
      * @return the new substream
      */
@@ -313,18 +316,25 @@ public class Stream {
 
         Iterator<Object> downStream = new OpIterator() {
 
-            private final int startPos = start.intValue();
-            private final int endPos = end.intValue();
-            private int itemCount = 0;
+            // The specification counts the elements to include from the
+            // position at which the start elements were skipped, so a start
+            // that is less than or equal to zero, which skips no elements,
+            // includes end minus start elements. The count is calculated as
+            // a long because the single argument form passes an end of
+            // Integer.MAX_VALUE.
+            private final int skipCount = Math.max(start.intValue(), 0);
+            private final long includeCount = (long) end.intValue() - 
start.intValue();
+            private int skipped = 0;
+            private long included = 0;
 
             @Override
             protected void findNext() {
-                while (itemCount < startPos && iterator.hasNext()) {
+                while (skipped < skipCount && iterator.hasNext()) {
                     iterator.next();
-                    itemCount++;
+                    skipped++;
                 }
-                if (itemCount < endPos && iterator.hasNext()) {
-                    itemCount++;
+                if (skipped == skipCount && included < includeCount && 
iterator.hasNext()) {
+                    included++;
                     next = iterator.next();
                     foundNext = true;
                 }
diff --git a/test/org/apache/el/stream/TestCollectionOperations.java 
b/test/org/apache/el/stream/TestCollectionOperations.java
index b2a04926be..efce1345d6 100644
--- a/test/org/apache/el/stream/TestCollectionOperations.java
+++ b/test/org/apache/el/stream/TestCollectionOperations.java
@@ -324,6 +324,79 @@ public class TestCollectionOperations {
     }
 
 
+    @Test
+    public void testSubstreamStart02() {
+        // A start that is less than or equal to zero skips no elements
+        ELProcessor processor = new ELProcessor();
+
+        Object result = processor.getValue(
+                "[1, 2, 3, 4, 5].stream().substream(-2).toList()",
+                Object.class);
+
+        List<Long> expected = new ArrayList<>(5);
+        expected.add(1L);
+        expected.add(2L);
+        expected.add(3L);
+        expected.add(4L);
+        expected.add(5L);
+
+        Assert.assertEquals(expected, result);
+    }
+
+
+    @Test
+    public void testSubstreamStartEnd02() {
+        // A start that is less than or equal to zero skips no elements and
+        // the number of elements included is end minus start
+        ELProcessor processor = new ELProcessor();
+
+        Object result = processor.getValue(
+                "[1, 2, 3, 4, 5].stream().substream(-2,3).toList()",
+                Object.class);
+
+        List<Long> expected = new ArrayList<>(5);
+        expected.add(1L);
+        expected.add(2L);
+        expected.add(3L);
+        expected.add(4L);
+        expected.add(5L);
+
+        Assert.assertEquals(expected, result);
+    }
+
+
+    @Test
+    public void testSubstreamStartEnd03() {
+        // The zero end position still includes end minus start elements when
+        // the start is negative
+        ELProcessor processor = new ELProcessor();
+
+        Object result = processor.getValue(
+                "[1, 2, 3, 4, 5].stream().substream(-2,0).toList()",
+                Object.class);
+
+        List<Long> expected = new ArrayList<>(2);
+        expected.add(1L);
+        expected.add(2L);
+
+        Assert.assertEquals(expected, result);
+    }
+
+
+    @Test
+    public void testSubstreamStartEnd04() {
+        // A source with fewer elements than the number to skip includes
+        // nothing
+        ELProcessor processor = new ELProcessor();
+
+        Object result = processor.getValue(
+                "[1].stream().substream(2,3).toList()",
+                Object.class);
+
+        Assert.assertEquals(new ArrayList<Long>(), result);
+    }
+
+
     @Test
     public void testToArray01() {
         ELProcessor processor = new ELProcessor();


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

Reply via email to