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

desruisseaux pushed a commit to branch geoapi-4.0
in repository https://gitbox.apache.org/repos/asf/sis.git

commit 028ac1be0847f1022c07aa0208be2373eaac4a32
Author: Martin Desruisseaux <[email protected]>
AuthorDate: Fri Mar 27 16:10:39 2026 +0100

    Allows `Containers.derivedList(…)` to return a list which supports addition 
of elements.
---
 .../org/apache/sis/util/collection/Containers.java |  30 ++++-
 .../apache/sis/util/collection/DerivedList.java    |  31 ++---
 .../sis/util/collection/WritableDerivedList.java   | 143 +++++++++++++++++++++
 .../{DerivedSetTest.java => DerivedListTest.java}  |  66 ++++------
 .../apache/sis/util/collection/DerivedMapTest.java |   1 +
 .../apache/sis/util/collection/DerivedSetTest.java |   1 +
 6 files changed, 210 insertions(+), 62 deletions(-)

diff --git 
a/endorsed/src/org.apache.sis.util/main/org/apache/sis/util/collection/Containers.java
 
b/endorsed/src/org.apache.sis.util/main/org/apache/sis/util/collection/Containers.java
index 05fc392fa1..2e4df801f8 100644
--- 
a/endorsed/src/org.apache.sis.util/main/org/apache/sis/util/collection/Containers.java
+++ 
b/endorsed/src/org.apache.sis.util/main/org/apache/sis/util/collection/Containers.java
@@ -569,18 +569,19 @@ public final class Containers {
     }
 
     /**
-     * Returns a list whose elements are derived <i>on-the-fly</i> from the 
given list.
+     * Returns a read/remove-only list whose elements are derived 
<i>on-the-fly</i> from the given list.
      * Conversions from the original elements to the derived elements are 
performed when needed
      * by invoking the {@link Function#apply(Object)} method on the given 
converter.
      * Those conversions are repeated every time that a {@code List} method 
needs to access values.
      * Consequently, any change in the original list is immediately visible in 
the derived list.
      *
+     * <p>The returned list support removal operations if the given {@code 
storage} list supports them.
+     * However, it does not support adding or modifying elements.</p>
+     *
      * <p>The returned list can be serialized if the given list and converter 
are serializable.
      * The returned list is not synchronized by itself, but is nevertheless 
thread-safe if the
      * given list (including its iterator) and converter are thread-safe.</p>
      *
-     * <p>The returned list does <em>not</em> implement {@link 
CheckedContainer}.</p>
-     *
      * @param  <S>        the type of elements in the storage (original) list.
      * @param  <E>        the type of elements in the derived list.
      * @param  storage    the storage list containing the original elements, 
or {@code null}.
@@ -598,6 +599,29 @@ public final class Containers {
         return new DerivedList<>(storage, converter);
     }
 
+    /**
+     * Returns a list whose elements are derived <i>on-the-fly</i> from the 
given list.
+     * This method does the same work as above {@link #derivedList(List, 
Function)},
+     * except that the returned list supports additions if the given {@code 
converter}
+     * is {@linkplain org.apache.sis.math.FunctionProperty#INVERTIBLE 
invertible}.
+     *
+     * @param  <S>        the type of elements in the storage (original) list.
+     * @param  <E>        the type of elements in the derived list.
+     * @param  storage    the storage list containing the original elements, 
or {@code null}.
+     * @param  converter  the converter from the elements in the storage list 
to the elements in the derived list.
+     * @return a view over the {@code storage} list containing all elements 
converted by the given converter,
+     *         or {@code null} if {@code storage} was null.
+     *
+     * @since 1.7
+     */
+    public static <S,E> List<E> derivedList(final List<S> storage, final 
ObjectConverter<S,E> converter) {
+        ArgumentChecks.ensureNonNull("converter", converter);
+        if (storage == null) {
+            return null;
+        }
+        return WritableDerivedList.create(storage, converter);
+    }
+
     /**
      * Returns a set whose elements are derived <i>on-the-fly</i> from the 
given set.
      * Conversions from the original elements to the derived elements are 
performed when needed
diff --git 
a/endorsed/src/org.apache.sis.util/main/org/apache/sis/util/collection/DerivedList.java
 
b/endorsed/src/org.apache.sis.util/main/org/apache/sis/util/collection/DerivedList.java
index 3639be2413..5907ac0f58 100644
--- 
a/endorsed/src/org.apache.sis.util/main/org/apache/sis/util/collection/DerivedList.java
+++ 
b/endorsed/src/org.apache.sis.util/main/org/apache/sis/util/collection/DerivedList.java
@@ -35,7 +35,7 @@ import java.util.function.Consumer;
  * @param  <S>  type of elements in the source list.
  * @param  <E>  type of elements in this list.
  */
-final class DerivedList<S,E> extends AbstractList<E> implements Serializable {
+class DerivedList<S,E> extends AbstractList<E> implements Serializable {
     /**
      * Serial number for inter-operability with different versions.
      */
@@ -45,47 +45,48 @@ final class DerivedList<S,E> extends AbstractList<E> 
implements Serializable {
      * The list of source elements.
      */
     @SuppressWarnings("serial")         // Not statically typed as 
Serializable.
-    private final List<S> source;
+    protected final List<S> storage;
 
     /**
      * The function for deriving an element in this list from an element in 
the source list.
      */
     @SuppressWarnings("serial")
-    private final Function<S,E> adapter;
+    protected final Function<S,E> adapter;
 
     /**
      * Creates a new derived list wrapping the given list.
      *
-     * @param  source   the list of source elements.
+     * @param  storage  the list of source elements.
      * @param  adapter  the function for deriving an element in this list from 
an element in the source list.
      */
-    DerivedList(final List<S> source, final Function<S,E> adapter) {
-        this.source  = source;
+    protected DerivedList(final List<S> storage, final Function<S,E> adapter) {
+        this.storage = storage;
         this.adapter = adapter;
     }
 
     /**
      * Delegates to the wrapped list.
      */
-    @Override public boolean   isEmpty()        {return source.isEmpty();}
-    @Override public int       size()           {return source.size();}
-    @Override public E         get(int i)       {return 
adapter.apply(source.get(i));}
-    @Override public E         remove(int i)    {return 
adapter.apply(source.remove(i));}
-    @Override public Stream<E> stream()         {return 
source.stream().map(adapter);};
-    @Override public Stream<E> parallelStream() {return 
source.parallelStream().map(adapter);};
+    @Override public void      clear()          {storage.clear();}
+    @Override public boolean   isEmpty()        {return storage.isEmpty();}
+    @Override public int       size()           {return storage.size();}
+    @Override public E         get(int i)       {return 
adapter.apply(storage.get(i));}
+    @Override public E         remove(int i)    {return 
adapter.apply(storage.remove(i));}
+    @Override public Stream<E> stream()         {return 
storage.stream().map(adapter);};
+    @Override public Stream<E> parallelStream() {return 
storage.parallelStream().map(adapter);};
 
     /**
      * Returns a view of the portion of this list.
      */
     @Override public List<E> subList(int fromIndex, int toIndex) {
-        return new DerivedList<>(source.subList(fromIndex, toIndex), adapter);
+        return new DerivedList<>(storage.subList(fromIndex, toIndex), adapter);
     }
 
     /**
      * Applies the given action on all elements in the list.
      */
     @Override public void forEach(final Consumer<? super E> action) {
-        source.forEach(adapt(adapter, action));
+        storage.forEach(adapt(adapter, action));
     }
 
     /**
@@ -103,7 +104,7 @@ final class DerivedList<S,E> extends AbstractList<E> 
implements Serializable {
      */
     @Override
     public Iterator<E> iterator() {
-        return new Iter<>(source.iterator(), adapter);
+        return new Iter<>(storage.iterator(), adapter);
     }
 
     /**
diff --git 
a/endorsed/src/org.apache.sis.util/main/org/apache/sis/util/collection/WritableDerivedList.java
 
b/endorsed/src/org.apache.sis.util/main/org/apache/sis/util/collection/WritableDerivedList.java
new file mode 100644
index 0000000000..cada58a2ae
--- /dev/null
+++ 
b/endorsed/src/org.apache.sis.util/main/org/apache/sis/util/collection/WritableDerivedList.java
@@ -0,0 +1,143 @@
+/*
+ * 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.apache.sis.util.collection;
+
+import java.util.List;
+import java.util.function.Function;
+import org.apache.sis.math.FunctionProperty;
+import org.apache.sis.util.ObjectConverter;
+
+
+/**
+ * A list in which values are derived from another list using a given function.
+ * The conversion is done on-the-fly every times that an element is read or 
stored.
+ *
+ * @author  Martin Desruisseaux (Geomatys)
+ *
+ * @param  <S>  type of elements in the source list.
+ * @param  <E>  type of elements in this list.
+ */
+final class WritableDerivedList<S,E> extends DerivedList<S,E> implements 
CheckedContainer<E> {
+    /**
+     * Serial number for inter-operability with different versions.
+     */
+    private static final long serialVersionUID = -2285713232906970418L;
+
+    /**
+     * The function for deriving an element to store in the source list.
+     */
+    @SuppressWarnings("serial")
+    private final Function<E,S> inverse;
+
+    /**
+     * Type of elements in this list.
+     */
+    private final Class<E> elementType;
+
+    /**
+     * Creates a new derived list from the specified storage list.
+     *
+     * @param  storage    the list which actually stores the elements.
+     * @param  converter  the converter from the type in the storage list to 
the type in the derived list.
+     */
+    static <S,E> List<E> create(final List<S> storage, final 
ObjectConverter<S,E> converter) {
+        if (converter.properties().contains(FunctionProperty.INVERTIBLE)) {
+            return new WritableDerivedList<>(storage, converter);
+        }
+        return new DerivedList<>(storage, converter);
+    }
+
+    /**
+     * Creates a new derived list wrapping the given list.
+     *
+     * @param  storage  the list of source elements.
+     * @param  adapter  the function for deriving an element in this list from 
an element in the source list.
+     */
+    private WritableDerivedList(final List<S> storage, final 
ObjectConverter<S,E> adapter) {
+        super(storage, adapter);
+        this.inverse = adapter.inverse();
+        elementType = adapter.getTargetClass();
+    }
+
+    /**
+     * Creates a sub-list of the given parent list.
+     */
+    private WritableDerivedList(final WritableDerivedList<S,E> parent, final 
List<S> storage) {
+        super(storage, parent.adapter);
+        inverse = parent.inverse;
+        elementType = parent.elementType;
+    }
+
+    /**
+     * Returns the base type of all elements in this list.
+     */
+    @Override
+    public Class<? extends E> getElementType() {
+        return elementType;
+    }
+
+    /**
+     * Returns whether this container is modifiable, unmodifiable or immutable.
+     */
+    @Override
+    public Mutability getMutability() {
+        return Mutability.MODIFIABLE;
+    }
+
+    /**
+     * Adds the given element in the list.
+     *
+     * @param  element  element to add to this list.
+     * @return whether the element has been added.
+     */
+    @Override
+    public boolean add(final E element) {
+        return storage.add(inverse.apply(element));
+    }
+
+    /**
+     * Adds the given element at the given index in the list.
+     *
+     * @param  index    index where to add the element.
+     * @param  element  element to add to this list.
+     * @return whether the element has been added.
+     */
+    @Override
+    public void add(final int index, final E element) {
+        storage.add(index, inverse.apply(element));
+    }
+
+    /**
+     * Modifies an element in this list.
+     *
+     * @param  index    index of the element to modify.
+     * @param  element  the new element at the given index.
+     * @return the old element at the given index.
+     */
+    @Override
+    public E set(final int index, final E element) {
+        return adapter.apply(storage.set(index, inverse.apply(element)));
+    }
+
+    /**
+     * Returns a view of the portion of this list.
+     */
+    @Override
+    public List<E> subList(int fromIndex, int toIndex) {
+        return new WritableDerivedList<>(this, storage.subList(fromIndex, 
toIndex));
+    }
+}
diff --git 
a/endorsed/src/org.apache.sis.util/test/org/apache/sis/util/collection/DerivedSetTest.java
 
b/endorsed/src/org.apache.sis.util/test/org/apache/sis/util/collection/DerivedListTest.java
similarity index 57%
copy from 
endorsed/src/org.apache.sis.util/test/org/apache/sis/util/collection/DerivedSetTest.java
copy to 
endorsed/src/org.apache.sis.util/test/org/apache/sis/util/collection/DerivedListTest.java
index 2296a549d1..32691265c6 100644
--- 
a/endorsed/src/org.apache.sis.util/test/org/apache/sis/util/collection/DerivedSetTest.java
+++ 
b/endorsed/src/org.apache.sis.util/test/org/apache/sis/util/collection/DerivedListTest.java
@@ -16,10 +16,9 @@
  */
 package org.apache.sis.util.collection;
 
-import java.util.List;
 import java.util.Set;
-import java.util.HashSet;
-import java.util.EnumSet;
+import java.util.List;
+import java.util.ArrayList;
 import org.apache.sis.math.FunctionProperty;
 import org.apache.sis.util.ObjectConverter;
 
@@ -30,42 +29,36 @@ import org.apache.sis.test.TestCase;
 
 
 /**
- * Tests the {@link DerivedSet}. For the purpose of this test, this class 
implements an
- * {@link ObjectConverter} for which input values are multiplied by 10, except 
value
- * {@value #EXCLUDED} which is converted to {@code null} (meaning: excluded 
from the
- * converted set).
+ * Tests the {@link DerivedList}. For the purpose of this test, this class 
implements
+ * an {@link ObjectConverter} for which input values are multiplied by 10.
  *
  * @author  Martin Desruisseaux (Geomatys)
  */
-public final class DerivedSetTest extends TestCase implements 
ObjectConverter<Integer,Integer> {
-    /**
-     * The value to replace by {@code null}.
-     */
-    protected static final int EXCLUDED = 19;                   // non-private 
for javadoc purpose.
-
+@SuppressWarnings("exports")
+public final class DerivedListTest extends TestCase implements 
ObjectConverter<Integer,Integer> {
     /**
      * Creates a new test case.
      */
-    public DerivedSetTest() {
+    public DerivedListTest() {
     }
 
     /**
-     * Tests {@link DerivedSet} without excluded value.
+     * Tests {@link DerivedList} with read and write operations.
      */
     @Test
-    public void testNoExclusion() {
-        final Set<Integer> source = new HashSet<>(List.of(2,  7,  12,  17,  20 
));
-        final Set<Integer> target = new HashSet<>(List.of(20, 70, 120, 170, 
200));
-        final Set<Integer> tested = DerivedSet.create(source, this);
+    public void testReadWrite() {
+        final List<Integer> source = new ArrayList<>(List.of(2,  7,  12,  17,  
20 ));
+        final List<Integer> target = new ArrayList<>(List.of(20, 70, 120, 170, 
200));
+        final List<Integer> tested = WritableDerivedList.create(source, this);
         assertEquals(target.size(), tested.size());
         assertEquals(target, tested);
 
         assertFalse(tested.contains(2 ));           // Original value.
         assertTrue (tested.contains(20));           // Derived value.
         assertTrue (source.contains(7 ));           // Test before change.
-        assertTrue (tested.remove  (70));
-        assertFalse(source.contains(7 ));           // Test after change.
-        assertTrue (target.remove  (70));           // For comparison purpose.
+        assertTrue (tested.remove(Integer.valueOf(70)));
+        assertFalse(source.contains(7 ));
+        assertTrue (target.remove(Integer.valueOf(70)));
         assertEquals(target, tested);
 
         assertFalse(source.contains(3 ));
@@ -76,39 +69,24 @@ public final class DerivedSetTest extends TestCase 
implements ObjectConverter<In
     }
 
     /**
-     * Tests {@link DerivedSet} with an excluded value.
-     */
-    @Test
-    public void testWithExclusion() {
-        final Set<Integer> source = new HashSet<>(List.of(2,  7,  12,  
EXCLUDED, 20));
-        final Set<Integer> target = new HashSet<>(List.of(20, 70, 120, 200));
-        final Set<Integer> tested = DerivedSet.create(source, this);
-        assertEquals(target.size(), tested.size());
-        assertEquals(target, tested);
-        assertFalse(tested.contains(EXCLUDED * 10));
-    }
-
-    /**
-     * Returns the converter properties, which is injective and preserve order.
+     * Returns the converter properties.
      */
     @Override
     public Set<FunctionProperty> properties() {
-        return EnumSet.of(FunctionProperty.INJECTIVE, 
FunctionProperty.ORDER_PRESERVING);
+        return Set.of(FunctionProperty.INVERTIBLE);
     }
+
     @Override public Class<Integer> getSourceClass() {return Integer.class;}
     @Override public Class<Integer> getTargetClass() {return Integer.class;}
 
     /**
-     * Multiply the given value by 10, except value {@value #EXCLUDED}.
+     * Multiply the given value by 10.
      *
      * @param  value  the value to multiply.
-     * @return the multiplied value, or {@code null}.
+     * @return the multiplied value.
      */
     @Override
     public Integer apply(final Integer value) {
-        if (value == EXCLUDED) {
-            return null;
-        }
         return value * 10;
     }
 
@@ -118,12 +96,12 @@ public final class DerivedSetTest extends TestCase 
implements ObjectConverter<In
     @Override
     public ObjectConverter<Integer,Integer> inverse() {
         return new ObjectConverter<Integer,Integer>() {
-            @Override public ObjectConverter<Integer,Integer> inverse() 
{return DerivedSetTest.this;}
+            @Override public ObjectConverter<Integer,Integer> inverse() 
{return DerivedListTest.this;}
             @Override public Class<Integer> getSourceClass()            
{return Integer.class;}
             @Override public Class<Integer> getTargetClass()            
{return Integer.class;}
             @Override public Integer        apply(Integer value)        
{return value / 10;}
             @Override public Set<FunctionProperty> properties() {
-                return EnumSet.of(FunctionProperty.SURJECTIVE, 
FunctionProperty.ORDER_PRESERVING);
+                return Set.of(FunctionProperty.INVERTIBLE);
             }
         };
     }
diff --git 
a/endorsed/src/org.apache.sis.util/test/org/apache/sis/util/collection/DerivedMapTest.java
 
b/endorsed/src/org.apache.sis.util/test/org/apache/sis/util/collection/DerivedMapTest.java
index 83e2e5791d..2727a45038 100644
--- 
a/endorsed/src/org.apache.sis.util/test/org/apache/sis/util/collection/DerivedMapTest.java
+++ 
b/endorsed/src/org.apache.sis.util/test/org/apache/sis/util/collection/DerivedMapTest.java
@@ -37,6 +37,7 @@ import org.apache.sis.test.TestCase;
  *
  * @author  Martin Desruisseaux (Geomatys)
  */
+@SuppressWarnings("exports")
 public final class DerivedMapTest extends TestCase implements 
ObjectConverter<Integer,Integer> {
     /**
      * The value to replace by {@code null}.
diff --git 
a/endorsed/src/org.apache.sis.util/test/org/apache/sis/util/collection/DerivedSetTest.java
 
b/endorsed/src/org.apache.sis.util/test/org/apache/sis/util/collection/DerivedSetTest.java
index 2296a549d1..68a472a779 100644
--- 
a/endorsed/src/org.apache.sis.util/test/org/apache/sis/util/collection/DerivedSetTest.java
+++ 
b/endorsed/src/org.apache.sis.util/test/org/apache/sis/util/collection/DerivedSetTest.java
@@ -37,6 +37,7 @@ import org.apache.sis.test.TestCase;
  *
  * @author  Martin Desruisseaux (Geomatys)
  */
+@SuppressWarnings("exports")
 public final class DerivedSetTest extends TestCase implements 
ObjectConverter<Integer,Integer> {
     /**
      * The value to replace by {@code null}.

Reply via email to