sis-commits mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From desruisse...@apache.org
Subject [sis] 01/02: Reimplement GeoTiffStore.components() as a List instead than a Set.
Date Mon, 10 Dec 2018 01:29:07 GMT
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 53282903a63a4ac2bdd6684a25e7a685840ed9e0
Author: Martin Desruisseaux <martin.desruisseaux@geomatys.com>
AuthorDate: Sun Dec 9 14:09:48 2018 +0100

    Reimplement GeoTiffStore.components() as a List instead than a Set.
---
 .../main/java/org/apache/sis/coverage/ToNaN.java   |   8 +-
 .../sis/internal/util/ListOfUnknownSize.java       | 263 +++++++++++++++++++++
 .../apache/sis/internal/util/SetOfUnknownSize.java |  24 +-
 .../sis/internal/util/ListOfUnknownSizeTest.java   |  60 +++++
 .../apache/sis/test/suite/UtilityTestSuite.java    |   1 +
 .../apache/sis/storage/geotiff/GeoTiffStore.java   |  93 ++++----
 6 files changed, 399 insertions(+), 50 deletions(-)

diff --git a/core/sis-raster/src/main/java/org/apache/sis/coverage/ToNaN.java b/core/sis-raster/src/main/java/org/apache/sis/coverage/ToNaN.java
index 4a3fc3e..ab49274 100644
--- a/core/sis-raster/src/main/java/org/apache/sis/coverage/ToNaN.java
+++ b/core/sis-raster/src/main/java/org/apache/sis/coverage/ToNaN.java
@@ -59,8 +59,12 @@ final class ToNaN extends HashSet<Integer> implements DoubleToIntFunction
{
     }
 
     /**
-     * Returns a NaN ordinal value for the given sample value.
-     * The returned value can be given to {@link MathFunctions#toNanFloat(int)}.
+     * Mapping from sample values to ordinal values to be supplied to {@link MathFunctions#toNanFloat(int)}.
+     * That mapping shall ensure that there is no ordinal value collision between different
categories in
+     * the same {@link SampleDimension}.
+     *
+     * @param  value  a real number in the {@link Category#range} sample value range.
+     * @return a value between {@value MathFunctions#MIN_NAN_ORDINAL} and {@value MathFunctions#MAX_NAN_ORDINAL}
inclusive.
      */
     @Override
     public int applyAsInt(final double value) {
diff --git a/core/sis-utility/src/main/java/org/apache/sis/internal/util/ListOfUnknownSize.java
b/core/sis-utility/src/main/java/org/apache/sis/internal/util/ListOfUnknownSize.java
new file mode 100644
index 0000000..3849c6e
--- /dev/null
+++ b/core/sis-utility/src/main/java/org/apache/sis/internal/util/ListOfUnknownSize.java
@@ -0,0 +1,263 @@
+/*
+ * 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.internal.util;
+
+import java.util.List;
+import java.util.AbstractSequentialList;
+import java.util.Collection;
+import java.util.ListIterator;
+import java.util.NoSuchElementException;
+import java.util.Spliterator;
+import java.util.Spliterators;
+import java.util.Objects;
+
+
+/**
+ * An alternative to {@code AbstractList} for implementations having a costly {@link #size()}
method.
+ * This class overrides some methods in a way that avoid or reduce calls to {@link #size()}.
+ *
+ * <p>Despite extending {@link AbstractSequentialList}, this class expects implementations
to override
+ * the random access method {@link #get(int)} instead than {@link #listIterator(int)}.</p>
+ *
+ * @author  Martin Desruisseaux (Geomatys)
+ * @version 1.0
+ *
+ * @param <E>  the type of elements in the list.
+ *
+ * @since 1.0
+ * @module
+ */
+public abstract class ListOfUnknownSize<E> extends AbstractSequentialList<E>
{
+    /**
+     * For subclass constructors.
+     */
+    protected ListOfUnknownSize() {
+    }
+
+    /**
+     * Returns {@link #size()} if its value is already known, or -1 if the size is still
unknown.
+     * The size may become known for example if it has been cached by the subclass. In such
case,
+     * some {@code ListOfUnknownSize} methods will take a more efficient path.
+     *
+     * @return {@link #size()} if its value is already known, or -1 if it still costly to
compute.
+     */
+    protected int sizeIfKnown() {
+        return -1;
+    }
+
+    /**
+     * Returns the number of elements in this list. The default implementation counts the
number of elements
+     * for which {@link #exists(int)} returns {@code true}. Subclasses are encouraged to
cache this value if
+     * they know that the underlying storage is immutable.
+     *
+     * @return the number of elements in this list.
+     */
+    @Override
+    public int size() {
+        int size = sizeIfKnown();
+        if (size < 0) {
+            size = 0;
+            while (exists(size)) {
+                if (++size == Integer.MAX_VALUE) {
+                    break;
+                }
+            }
+        }
+        return size;
+    }
+
+    /**
+     * Returns {@code true} if this list is empty.
+     * This method avoids to invoke {@link #size()} unless it is cheap.
+     *
+     * @return {@code true} if this list is empty.
+     */
+    @Override
+    public boolean isEmpty() {
+        final int size = sizeIfKnown();
+        return (size == 0) || (size < 0 && !exists(0));
+    }
+
+    /**
+     * Returns {@code true} if an element exists at the given index. If an element at index
<var>i</var> exists,
+     * then all elements at index 0 to <var>i</var> - 1 also exist. Those elements
do not need to be computed
+     * immediately if their computation is deferred.
+     *
+     * @param  index  the index where to verify if an element exists.
+     * @return {@code true} if an element exists at the given index.
+     */
+    protected abstract boolean exists(int index);
+
+    /**
+     * Returns the element at the specified index.
+     * Invoking this method may trig computation of the element if their computation is deferred.
+     *
+     * @param  index  position of the element to get in this list.
+     * @return the element at the given index.
+     * @throws IndexOutOfBoundsException if the given index is out of bounds.
+     */
+    @Override
+    public abstract E get(int index);
+
+    /**
+     * Removes elements of the given collection from this list.
+     * This method avoids to invoke {@link #size()}.
+     *
+     * @param  c  the collection containing elements to remove.
+     * @return {@code true} if at least one element has been removed.
+     */
+    @Override
+    public boolean removeAll(final Collection<?> c) {
+        // See comment in SetOfUnknownSize.removeAll(…).
+        boolean modified = false;
+        for (final java.util.Iterator<?> it = c.iterator(); it.hasNext();) {
+            modified |= remove(it.next());
+        }
+        return modified;
+    }
+
+    /**
+     * Returns a list iterator over the elements in this list.
+     * The default implementation invokes {@link #exists(int)} and {@link #get(int)}.
+     * Write operations are not supported.
+     *
+     * @param  index  index of first element to be returned from the list.
+     * @return a list iterator over the elements in this list.
+     * @throws IndexOutOfBoundsException if the given index is out of bounds.
+     */
+    @Override
+    public ListIterator<E> listIterator(final int index) {
+        return new Iterator(index);
+    }
+
+    /**
+     * The iterator returned by {@link #listIterator()}.  Provided as a named class instead
+     * than anonymous class for more readable stack traces. This is especially useful since
+     * elements may be loaded or computed when first needed, and those operations may fail.
+     */
+    private final class Iterator implements ListIterator<E> {
+        /** Index of the next element to be returned. */
+        private int cursor;
+
+        /** Creates a new iterator starting at the given index. */
+        Iterator(final int index) {
+            cursor = index;
+        }
+
+        /** Index of element to be returned by {@link #next()}. */
+        @Override public int nextIndex() {
+            return cursor;
+        }
+
+        /** Whether {@link #next()} can succeed. */
+        @Override public boolean hasNext() {
+            final int size = sizeIfKnown();
+            return (size >= 0) ? cursor < size : exists(cursor);
+        }
+
+        /** Move forward by one element. */
+        @Override public E next() {
+            final E element;
+            try {
+                element = get(cursor);
+            } catch (IndexOutOfBoundsException e) {
+                throw (NoSuchElementException) new NoSuchElementException().initCause(e);
+            }
+            cursor++;           // Set only on success.
+            return element;
+        }
+
+        /** Index of element to be returned by {@link #previous()}. */
+        @Override public int previousIndex() {
+            return cursor - 1;
+        }
+
+        /** Whether {@link #previous()} can succeed. */
+        @Override public boolean hasPrevious() {
+            return cursor != 0;
+        }
+
+        /** Move backward by one element. */
+        @Override public E previous() {
+            if (cursor != 0) return get(--cursor);
+            throw new NoSuchElementException();
+        }
+
+        @Override public void set(E e) {throw new UnsupportedOperationException();}
+        @Override public void add(E e) {throw new UnsupportedOperationException();}
+        @Override public void remove() {throw new UnsupportedOperationException();}
+    }
+
+    /**
+     * Creates a {@code Spliterator} without knowledge of collection size.
+     *
+     * @return a {@code Spliterator} over the elements in this collection.
+     */
+    @Override
+    public Spliterator<E> spliterator() {
+        return sizeIfKnown() >= 0 ? super.spliterator() : Spliterators.spliteratorUnknownSize(iterator(),
Spliterator.ORDERED);
+    }
+
+    /**
+     * Returns the elements in an array.
+     *
+     * @return an array containing all list elements.
+     */
+    @Override
+    public Object[] toArray() {
+        return sizeIfKnown() >= 0 ? super.toArray() : SetOfUnknownSize.toArray(iterator(),
new Object[32], true);
+    }
+
+    /**
+     * Returns the elements in the given array, or in a new array of the same type
+     * if it was necessary to allocate more space.
+     *
+     * @param  <T>    the type array elements.
+     * @param  array  where to store the elements.
+     * @return an array containing all list elements.
+     */
+    @Override
+    @SuppressWarnings("SuspiciousToArrayCall")
+    public <T> T[] toArray(final T[] array) {
+        return sizeIfKnown() >= 0 ? super.toArray(array) : SetOfUnknownSize.toArray(iterator(),
array, false);
+    }
+
+    /**
+     * Returns {@code true} if the given object is also a list and the two lists have the
same content.
+     * This method avoids to invoke {@link #size()} on this instance.
+     *
+     * @param  object  the object to compare with this list.
+     * @return {@code true} if the two list have the same content.
+     */
+    @Override
+    public boolean equals(final Object object) {
+        if (object == this) {
+            return true;
+        }
+        if (!(object instanceof List<?>)) {
+            return false;
+        }
+        final java.util.Iterator<E> it = iterator();
+        final java.util.Iterator<?> ot = ((List<?>) object).iterator();
+        while (it.hasNext()) {
+            if (!ot.hasNext() || !Objects.equals(it.next(), ot.next())) {
+                return false;
+            }
+        }
+        return !ot.hasNext();
+    }
+}
diff --git a/core/sis-utility/src/main/java/org/apache/sis/internal/util/SetOfUnknownSize.java
b/core/sis-utility/src/main/java/org/apache/sis/internal/util/SetOfUnknownSize.java
index 0bf0f9e..be3fcda 100644
--- a/core/sis-utility/src/main/java/org/apache/sis/internal/util/SetOfUnknownSize.java
+++ b/core/sis-utility/src/main/java/org/apache/sis/internal/util/SetOfUnknownSize.java
@@ -21,6 +21,8 @@ import java.util.AbstractSet;
 import java.util.Collection;
 import java.util.Iterator;
 import java.util.Arrays;
+import java.util.Spliterator;
+import java.util.Spliterators;
 import org.apache.sis.util.ArraysExt;
 
 
@@ -29,7 +31,7 @@ import org.apache.sis.util.ArraysExt;
  * This class overrides some methods in a way that avoid or reduce calls to {@link #size()}.
  *
  * @author  Martin Desruisseaux (Geomatys)
- * @version 0.8
+ * @version 1.0
  *
  * @param <E>  the type of elements in the set.
  *
@@ -107,13 +109,23 @@ public abstract class SetOfUnknownSize<E> extends AbstractSet<E>
{
     }
 
     /**
+     * Creates a {@code Spliterator} without knowledge of collection size.
+     *
+     * @return a {@code Spliterator} over the elements in this collection.
+     */
+    @Override
+    public Spliterator<E> spliterator() {
+        return isSizeKnown() ? super.spliterator() : Spliterators.spliteratorUnknownSize(iterator(),
0);
+    }
+
+    /**
      * Returns the elements in an array.
      *
      * @return an array containing all set elements.
      */
     @Override
     public Object[] toArray() {
-        return isSizeKnown() ? super.toArray() : toArray(new Object[32], true);
+        return isSizeKnown() ? super.toArray() : toArray(iterator(), new Object[32], true);
     }
 
     /**
@@ -127,16 +139,16 @@ public abstract class SetOfUnknownSize<E> extends AbstractSet<E>
{
     @Override
     @SuppressWarnings("SuspiciousToArrayCall")
     public <T> T[] toArray(final T[] array) {
-        return isSizeKnown() ? super.toArray(array) : toArray(array, false);
+        return isSizeKnown() ? super.toArray(array) : toArray(iterator(), array, false);
     }
 
     /**
-     * Implementation of the public {@code toArray()} methods.
+     * Implementation of the public {@code toArray()} methods without call to {@link #size()}.
      */
     @SuppressWarnings("unchecked")
-    private <T> T[] toArray(T[] array, boolean trimToSize) {
+    static <T> T[] toArray(final Iterator<?> it, T[] array, boolean trimToSize)
{
         int i = 0;
-        for (final Iterator<E> it = iterator(); it.hasNext();) {
+        while (it.hasNext()) {
             if (i >= array.length) {
                 if (i >= Integer.MAX_VALUE >>> 1) {
                     throw new OutOfMemoryError("Required array size too large");
diff --git a/core/sis-utility/src/test/java/org/apache/sis/internal/util/ListOfUnknownSizeTest.java
b/core/sis-utility/src/test/java/org/apache/sis/internal/util/ListOfUnknownSizeTest.java
new file mode 100644
index 0000000..59a7cbc
--- /dev/null
+++ b/core/sis-utility/src/test/java/org/apache/sis/internal/util/ListOfUnknownSizeTest.java
@@ -0,0 +1,60 @@
+/*
+ * 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.internal.util;
+
+import java.util.ListIterator;
+import org.apache.sis.test.TestCase;
+import org.junit.Test;
+
+import static org.junit.Assert.*;
+
+
+/**
+ * Tests {@link ListOfUnknownSize}.
+ *
+ * @author  Martin Desruisseaux (Geomatys)
+ * @version 1.0
+ * @since   1.0
+ * @module
+ */
+public final strictfp class ListOfUnknownSizeTest extends TestCase {
+    /**
+     * Tests {@link ListOfUnknownSize#listIterator()}.
+     */
+    @Test
+    public void testListIterator() {
+        final Integer[] data = new Integer[] {4, 7, 1, 3, 0, -4, -3};
+        final ListIterator<Integer> it = new ListOfUnknownSize<Integer>() {
+            @Override
+            protected boolean exists(int index) {
+                return index >= 0 && index < data.length;
+            }
+
+            @Override
+            public Integer get(int index) {
+                return data[index];
+            }
+        }.listIterator();
+
+        for (int i=0; i<data.length; i++) {
+            assertTrue(it.hasNext());
+            assertEquals(i, it.nextIndex());
+            assertEquals(data[i], it.next());
+        }
+        assertFalse(it.hasNext());
+    }
+}
diff --git a/core/sis-utility/src/test/java/org/apache/sis/test/suite/UtilityTestSuite.java
b/core/sis-utility/src/test/java/org/apache/sis/test/suite/UtilityTestSuite.java
index eef2619..3e69b65 100644
--- a/core/sis-utility/src/test/java/org/apache/sis/test/suite/UtilityTestSuite.java
+++ b/core/sis-utility/src/test/java/org/apache/sis/test/suite/UtilityTestSuite.java
@@ -69,6 +69,7 @@ import org.junit.BeforeClass;
 
     // Collections.
     org.apache.sis.internal.util.CheckedArrayListTest.class,
+    org.apache.sis.internal.util.ListOfUnknownSizeTest.class,
     org.apache.sis.internal.system.ReferenceQueueConsumerTest.class,
     org.apache.sis.util.collection.FrequencySortedSetTest.class,
     org.apache.sis.util.collection.IntegerListTest.class,
diff --git a/storage/sis-geotiff/src/main/java/org/apache/sis/storage/geotiff/GeoTiffStore.java
b/storage/sis-geotiff/src/main/java/org/apache/sis/storage/geotiff/GeoTiffStore.java
index ea256a1..e59a393 100644
--- a/storage/sis-geotiff/src/main/java/org/apache/sis/storage/geotiff/GeoTiffStore.java
+++ b/storage/sis-geotiff/src/main/java/org/apache/sis/storage/geotiff/GeoTiffStore.java
@@ -17,9 +17,7 @@
 package org.apache.sis.storage.geotiff;
 
 import java.util.Locale;
-import java.util.Iterator;
-import java.util.Collection;
-import java.util.NoSuchElementException;
+import java.util.List;
 import java.util.logging.LogRecord;
 import java.net.URI;
 import java.io.IOException;
@@ -45,7 +43,6 @@ import org.apache.sis.storage.DataStoreClosedException;
 import org.apache.sis.storage.IllegalNameException;
 import org.apache.sis.storage.event.ChangeEvent;
 import org.apache.sis.storage.event.ChangeListener;
-import org.apache.sis.internal.referencing.LazySet;
 import org.apache.sis.internal.storage.io.ChannelDataInput;
 import org.apache.sis.internal.storage.io.IOUtilities;
 import org.apache.sis.internal.storage.MetadataBuilder;
@@ -53,6 +50,7 @@ import org.apache.sis.internal.storage.StoreUtilities;
 import org.apache.sis.internal.storage.URIDataStore;
 import org.apache.sis.internal.util.Constants;
 import org.apache.sis.internal.util.Numerics;
+import org.apache.sis.internal.util.ListOfUnknownSize;
 import org.apache.sis.metadata.sql.MetadataStoreException;
 import org.apache.sis.util.collection.BackingStoreException;
 import org.apache.sis.util.resources.Errors;
@@ -106,7 +104,7 @@ public class GeoTiffStore extends DataStore implements Aggregate {
      *
      * @see #components()
      */
-    private Collection<GridCoverageResource> components;
+    private List<GridCoverageResource> components;
 
     /**
      * Creates a new GeoTIFF store from the given file, URL or stream object.
@@ -253,48 +251,59 @@ public class GeoTiffStore extends DataStore implements Aggregate {
      */
     @Override
     @SuppressWarnings("ReturnOfCollectionOrArrayField")
-    public Collection<GridCoverageResource> components() throws DataStoreException
{
+    public List<GridCoverageResource> components() throws DataStoreException {
         if (components == null) {
-            components = new LazySet<GridCoverageResource>(new Iterator<GridCoverageResource>()
{
-                /** Index of the next image to fetch, or -1 if we fetched all of them. */
-                private int index;
+            components = new Components();
+        }
+        return components;
+    }
 
-                /** Value to be returned by {@link #next()}, or {@cod null} if not yet determined.
*/
-                private GridCoverageResource next;
+    /**
+     * The components returned by {@link #components}. Defined as a named class instead than
an anonymous
+     * class for more readable stack trace. This is especially useful since {@link BackingStoreException}
+     * may happen in any method.
+     */
+    private final class Components extends ListOfUnknownSize<GridCoverageResource>
{
+        /** The collection size, cached when first computed. */
+        private int size = -1;
 
-                /** Returns {@code true} if there is more resources. */
-                @Override public boolean hasNext() {
-                    if (next != null) {
-                        return true;
-                    }
-                    final int i = index;
-                    if (i >= 0) {
-                        index = -1;                     // Set now in case of failure.
-                        try {
-                            next = reader().getImageFileDirectory(i);
-                        } catch (IOException e) {
-                            throw new BackingStoreException(errorIO(e));
-                        } catch (DataStoreException e) {
-                            throw new BackingStoreException(e);
-                        }
-                        if (next != null) {
-                            index = i+1;
-                            return true;
-                        }
-                    }
-                    return false;
-                }
+        /** Returns the size or -1 if not yet known. */
+        @Override protected int sizeIfKnown() {
+            return size;
+        }
 
-                /** Returns the next element. */
-                @Override public GridCoverageResource next() {
-                    if (!hasNext()) throw new NoSuchElementException();
-                    final GridCoverageResource r = next;
-                    next = null;
-                    return r;
-                }
-            });
+        /** Returns the size, computing and caching it if needed. */
+        @Override public int size() {
+            if (size < 0) {
+                size = super.size();
+            }
+            return size;
+        }
+
+        /** Returns whether the given index is valid. */
+        @Override protected boolean exists(final int index) {
+            return (index >= 0) && getImageFileDirectory(index) != null;
+        }
+
+        /** Returns element at the given index or throw {@link IndexOutOfBoundsException}.
*/
+        @Override public GridCoverageResource get(final int index) {
+            if (index >= 0) {
+                GridCoverageResource image = getImageFileDirectory(index);
+                if (image != null) return image;
+            }
+            throw new IndexOutOfBoundsException(errors().getString(Errors.Keys.IndexOutOfBounds_1,
index));
+        }
+
+        /** Returns element at the given index or returns {@code null} if the index is invalid.
*/
+        private GridCoverageResource getImageFileDirectory(final int index) {
+            try {
+                return reader().getImageFileDirectory(index);
+            } catch (IOException e) {
+                throw new BackingStoreException(errorIO(e));
+            } catch (DataStoreException e) {
+                throw new BackingStoreException(e);
+            }
         }
-        return components;      // Safe to return because unmodifiable.
     }
 
     /**


Mime
View raw message