You are viewing a plain text version of this content. The canonical link for it is here.
Posted to dev@tomcat.apache.org by re...@apache.org on 2026/10/18 15:33:02 UTC

(tomcat) 16/17: Follow the EL specification for substream with a negative start

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

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

commit 7619dc385255ead96fd7e49284c035f034d7ba26
Author: opencode <re...@apache.org>
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 4f3a01dee5..692fb3e886 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 c21d78d83d..c828d7c985 100644
--- a/test/org/apache/el/stream/TestCollectionOperations.java
+++ b/test/org/apache/el/stream/TestCollectionOperations.java
@@ -325,6 +325,79 @@ public class TestCollectionOperations extends ELBaseTest {
     }
 
 
+    @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: dev-unsubscribe@tomcat.apache.org
For additional commands, e-mail: dev-help@tomcat.apache.org