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

ahuber pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/causeway.git


The following commit(s) were added to refs/heads/master by this push:
     new 6e9f0c0051 CAUSEWAY-3404: adds negative end-index semantics to 
Can#subCan
6e9f0c0051 is described below

commit 6e9f0c00512f35b23fc0873ed038f0c2ecc01162
Author: Andi Huber <[email protected]>
AuthorDate: Wed Feb 7 10:28:52 2024 +0100

    CAUSEWAY-3404: adds negative end-index semantics to Can#subCan
---
 .../apache/causeway/commons/collections/Can.java   | 13 +++-
 .../causeway/commons/collections/Can_Empty.java    |  5 ++
 .../causeway/commons/collections/Can_Multiple.java | 12 ++-
 .../commons/collections/Can_Singleton.java         |  7 ++
 .../causeway/commons/collections/CanTest.java      | 85 ++++++++++++++++++++++
 5 files changed, 119 insertions(+), 3 deletions(-)

diff --git 
a/commons/src/main/java/org/apache/causeway/commons/collections/Can.java 
b/commons/src/main/java/org/apache/causeway/commons/collections/Can.java
index da1080ac9e..22c25d83ea 100644
--- a/commons/src/main/java/org/apache/causeway/commons/collections/Can.java
+++ b/commons/src/main/java/org/apache/causeway/commons/collections/Can.java
@@ -577,15 +577,26 @@ extends ImmutableCollection<T>, Comparable<Can<T>>, 
Serializable {
 
     // -- SUB SETS AND PARTITIONS
 
+    /**
+     * Returns a sub-{@link Can} that is made of elements from this {@link 
Can},
+     * starting with indices from {@code startInclusive}.
+     * <p>
+     * Out of bounds picking is simply ignored.
+     *
+     * @param startInclusive the (inclusive) initial index
+     */
+    Can<T> subCan(int startInclusive);
+
     /**
      * Returns a sub-{@link Can} that is made of elements from this {@link 
Can},
      * when selected by those indices,
-     * that result from given range {@code[startInclusive, endExclusive)}.
+     * that result from given range {@code [startInclusive, endExclusive]}.
      * <p>
      * Out of bounds picking is simply ignored.
      *
      * @param startInclusive the (inclusive) initial index
      * @param endExclusive the exclusive upper bound index
+     *      - if negative is interpreted as {@code this.size - 
abs(endExclusive)}
      */
     Can<T> subCan(int startInclusive, int endExclusive);
 
diff --git 
a/commons/src/main/java/org/apache/causeway/commons/collections/Can_Empty.java 
b/commons/src/main/java/org/apache/causeway/commons/collections/Can_Empty.java
index f57ed37fc8..ba2c1144e7 100644
--- 
a/commons/src/main/java/org/apache/causeway/commons/collections/Can_Empty.java
+++ 
b/commons/src/main/java/org/apache/causeway/commons/collections/Can_Empty.java
@@ -212,6 +212,11 @@ final class Can_Empty<T> implements Can<T> {
         return Can.empty();
     }
 
+    @Override
+    public Can<T> subCan(final int startInclusive) {
+        return Can.empty();
+    }
+
     @Override
     public Can<T> subCan(final int startInclusive, final int endExclusive) {
         return Can.empty();
diff --git 
a/commons/src/main/java/org/apache/causeway/commons/collections/Can_Multiple.java
 
b/commons/src/main/java/org/apache/causeway/commons/collections/Can_Multiple.java
index f0cdfb4c72..320a571631 100644
--- 
a/commons/src/main/java/org/apache/causeway/commons/collections/Can_Multiple.java
+++ 
b/commons/src/main/java/org/apache/causeway/commons/collections/Can_Multiple.java
@@ -310,12 +310,20 @@ final class Can_Multiple<T> implements Can<T> {
         return _CanFactory.ofNonNullElements(newElements);
     }
 
+    @Override
+    public Can<T> subCan(final int startInclusive) {
+        return pickByIndex(IntStream.range(startInclusive, size()));
+    }
+
     @Override
     public Can<T> subCan(final int startInclusive, final int endExclusive) {
-        if (startInclusive >= endExclusive) {
+        final int upperBoundExclusive = endExclusive < 0
+                ? size() + endExclusive
+                : endExclusive;
+        if (startInclusive >= upperBoundExclusive) {
             return Can.empty();
         }
-        return pickByIndex(IntStream.range(startInclusive, endExclusive));
+        return pickByIndex(IntStream.range(startInclusive, 
upperBoundExclusive));
     }
 
     @Override
diff --git 
a/commons/src/main/java/org/apache/causeway/commons/collections/Can_Singleton.java
 
b/commons/src/main/java/org/apache/causeway/commons/collections/Can_Singleton.java
index 1d7122ed40..49efe88903 100644
--- 
a/commons/src/main/java/org/apache/causeway/commons/collections/Can_Singleton.java
+++ 
b/commons/src/main/java/org/apache/causeway/commons/collections/Can_Singleton.java
@@ -282,6 +282,13 @@ final class Can_Singleton<T> implements Can<T> {
         return _CanFactory.ofNonNullElements(newElements);
     }
 
+    @Override
+    public Can<T> subCan(final int startInclusive) {
+        return startInclusive <= 0
+                ? this
+                : Can.empty();
+    }
+
     @Override
     public Can<T> subCan(final int startInclusive, final int endExclusive) {
         if (startInclusive >= endExclusive) {
diff --git 
a/commons/src/test/java/org/apache/causeway/commons/collections/CanTest.java 
b/commons/src/test/java/org/apache/causeway/commons/collections/CanTest.java
index d8ef8de3a6..932bd8c96a 100644
--- a/commons/src/test/java/org/apache/causeway/commons/collections/CanTest.java
+++ b/commons/src/test/java/org/apache/causeway/commons/collections/CanTest.java
@@ -303,6 +303,91 @@ class CanTest {
                 subCans.getElseFail(2));
     }
 
+    // -- SUB-CAN 1 ARG
+
+    @Test
+    void subCan_singleArg_emptyCan() {
+        final Can<Integer> origin = Can.empty();
+        assertEquals(origin, origin.subCan(-1));
+        assertEquals(origin, origin.subCan(0));
+        assertEquals(origin, origin.subCan(1));
+    }
+
+    @Test
+    void subCan_singleArg_singleCan() {
+        final Can<Integer> origin = Can.of(9);
+        assertEquals(origin, origin.subCan(-1));
+        assertEquals(origin, origin.subCan(0));
+        assertEquals(Can.empty(), origin.subCan(1));
+        assertEquals(Can.empty(), origin.subCan(2));
+    }
+
+    @Test
+    void subCan_singleArg_multiCan() {
+        final Can<Integer> origin = Can.of(1, 2, 3);
+        assertEquals(origin, origin.subCan(-1));
+        assertEquals(origin, origin.subCan(0));
+        assertEquals(Can.of(2, 3), origin.subCan(1));
+        assertEquals(Can.of(3), origin.subCan(2));
+        assertEquals(Can.empty(), origin.subCan(3));
+        assertEquals(Can.empty(), origin.subCan(4));
+    }
+
+    // -- SUB-CAN 2 ARGS
+
+    @Test
+    void subCan_biArg_emptyCan() {
+        final Can<Integer> origin = Can.empty();
+        // negative end index semantics
+        assertEquals(origin, origin.subCan(-1, -1));
+        assertEquals(origin, origin.subCan(0, -1));
+        assertEquals(origin, origin.subCan(1, -1));
+        // ignore upper index overflow - same expectations as single arg call
+        assertEquals(origin, origin.subCan(-1, 1));
+        assertEquals(origin, origin.subCan(0, 1));
+        assertEquals(origin, origin.subCan(1, 1));
+    }
+
+    @Test
+    void subCan_biArg_singleCan() {
+        final Can<Integer> origin = Can.of(9);
+        assertEquals(Can.empty(), origin.subCan(-1, 0));
+        assertEquals(Can.empty(), origin.subCan(0, 0));
+        assertEquals(Can.empty(), origin.subCan(1, 0));
+        // negative end index semantics
+        assertEquals(Can.empty(), origin.subCan(-1, -1));
+        assertEquals(Can.empty(), origin.subCan(0, -1));
+        assertEquals(Can.empty(), origin.subCan(1, -1));
+        // ignore upper index overflow - same expectations as single arg call
+        assertEquals(origin, origin.subCan(-1, 2));
+        assertEquals(origin, origin.subCan(0, 2));
+        assertEquals(Can.empty(), origin.subCan(1, 2));
+        assertEquals(Can.empty(), origin.subCan(2, 2));
+    }
+
+    @Test
+    void subCan_biArg_multiCan() {
+        final Can<Integer> origin = Can.of(1, 2, 3);
+        assertEquals(Can.of(1, 2), origin.subCan(-1, 2));
+        assertEquals(Can.of(1, 2), origin.subCan(0, 2));
+        assertEquals(Can.of(2), origin.subCan(1, 2));
+        assertEquals(Can.empty(), origin.subCan(2, 2));
+        assertEquals(Can.empty(), origin.subCan(3, 2));
+        // negative end index semantics
+        assertEquals(Can.of(1, 2), origin.subCan(-1, -1));
+        assertEquals(Can.of(1, 2), origin.subCan(0, -1));
+        assertEquals(Can.of(2), origin.subCan(1, -1));
+        assertEquals(Can.empty(), origin.subCan(2, -1));
+        assertEquals(Can.empty(), origin.subCan(3, -1));
+        // ignore upper index overflow - same expectations as single arg call
+        assertEquals(origin, origin.subCan(-1, 4));
+        assertEquals(origin, origin.subCan(0, 4));
+        assertEquals(Can.of(2, 3), origin.subCan(1, 4));
+        assertEquals(Can.of(3), origin.subCan(2, 4));
+        assertEquals(Can.empty(), origin.subCan(3, 4));
+        assertEquals(Can.empty(), origin.subCan(4, 4));
+    }
+
 
     // -- HEPER
 

Reply via email to