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}.
