You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@sis.apache.org by de...@apache.org on 2022/09/20 14:21:10 UTC

[sis] branch geoapi-4.0 updated (b4cf213bf3 -> eba0a4bea2)

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

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


    from b4cf213bf3 Move `ValueUnderCursor.FromCoverage` as a top-level class for easier maintenance. Refactor for fetching and formatting values in a background thread instead of JavaFX thread.
     new 6400194662 If a slice does not intersect the requested extent, try other slice candidates. It may happen if the "best" slice was selected using only temporal creteria but the geographic area of that particular slice does not intersect.
     new 08331faee8 Workaround a misleading error message from ImageIO.
     new eba0a4bea2 Replace `c.addAll(Arrays.asList(…))` pattern by the simpler `Collections.addAll(c, …)` pattern when the number of elements is small.

The 3 revisions listed above as "new" are entirely new to this
repository and will be described in separate emails.  The revisions
listed as "add" were already present in the repository and have only
been added to this reference.


Summary of changes:
 .../sis/coverage/grid/DisjointExtentException.java |  6 +-
 .../org/apache/sis/feature/AbstractAttribute.java  |  3 +-
 .../sis/metadata/iso/MetadataScopeAdapter.java     |  6 +-
 .../sis/metadata/iso/citation/DefaultContact.java  |  4 +-
 .../sis/metadata/iso/DefaultMetadataTest.java      |  3 +-
 .../sis/parameter/DefaultParameterValueGroup.java  |  4 +-
 .../java/org/apache/sis/referencing/Builder.java   |  6 +-
 .../operation/builder/ProjectedTransformTry.java   |  4 +-
 .../parameter/DefaultParameterValueGroupTest.java  |  5 +-
 .../sis/internal/util/CheckedArrayListTest.java    |  3 +-
 .../test/java/org/apache/sis/test/TestRunner.java  |  3 +-
 .../util/collection/FrequencySortedSetTest.java    |  3 +-
 .../org/apache/sis/storage/landsat/BandGroup.java  |  4 +-
 .../sis/internal/netcdf/impl/ChannelDecoder.java   |  3 +-
 .../sis/internal/netcdf/impl/VariableInfo.java     |  2 +-
 .../sis/internal/storage/image/FormatFinder.java   | 34 ++++++++-
 .../org/apache/sis/storage/StorageConnector.java   | 36 +++++++++-
 .../aggregate/ConcatenatedGridCoverage.java        | 80 ++++++++++++++++------
 .../sis/storage/aggregate/GridSliceLocator.java    |  2 +-
 .../sis/storage/aggregate/MergeStrategy.java       | 14 ++--
 20 files changed, 164 insertions(+), 61 deletions(-)


[sis] 02/03: Workaround a misleading error message from ImageIO.

Posted by de...@apache.org.
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 08331faee8372ef19ce2e5787abe1390b8fdd01c
Author: Martin Desruisseaux <ma...@geomatys.com>
AuthorDate: Tue Sep 20 14:36:56 2022 +0200

    Workaround a misleading error message from ImageIO.
---
 .../sis/internal/storage/image/FormatFinder.java   | 34 ++++++++++++++++++--
 .../org/apache/sis/storage/StorageConnector.java   | 36 ++++++++++++++++++++--
 2 files changed, 65 insertions(+), 5 deletions(-)

diff --git a/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/image/FormatFinder.java b/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/image/FormatFinder.java
index d4f7f83837..72a63613bf 100644
--- a/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/image/FormatFinder.java
+++ b/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/image/FormatFinder.java
@@ -26,6 +26,7 @@ import java.nio.file.Files;
 import javax.imageio.ImageIO;
 import javax.imageio.ImageReader;
 import javax.imageio.ImageWriter;
+import javax.imageio.IIOException;
 import javax.imageio.spi.ImageReaderSpi;
 import javax.imageio.spi.ImageWriterSpi;
 import javax.imageio.stream.ImageInputStream;
@@ -34,6 +35,7 @@ import javax.imageio.stream.FileImageOutputStream;
 import org.apache.sis.storage.StorageConnector;
 import org.apache.sis.storage.DataStoreException;
 import org.apache.sis.internal.storage.io.IOUtilities;
+import org.apache.sis.util.Workaround;
 
 
 /**
@@ -42,7 +44,7 @@ import org.apache.sis.internal.storage.io.IOUtilities;
  * It also helps to choose which {@link WorldFileStore} subclass to instantiate.
  *
  * @author  Martin Desruisseaux (Geomatys)
- * @version 1.2
+ * @version 1.3
  * @since   1.2
  * @module
  */
@@ -212,7 +214,7 @@ final class FormatFinder implements AutoCloseable {
                     ImageInputStream stream = null;
                     for (final Map.Entry<ImageReaderSpi,Boolean> entry : deferred.entrySet()) {
                         if (entry.getValue()) {
-                            if (stream == null) {
+                            if (stream == null) try {
                                 if (isWritable) {
                                     // ImageOutputStream is both read and write.
                                     stream = ImageIO.createImageOutputStream(storage);
@@ -221,6 +223,8 @@ final class FormatFinder implements AutoCloseable {
                                     stream = ImageIO.createImageInputStream(storage);
                                     if (stream == null) break;
                                 }
+                            } catch (IIOException e) {
+                                throw unwrap(e);
                             }
                             final ImageReaderSpi p = entry.getKey();
                             if (p.canDecodeInput(stream)) {
@@ -259,9 +263,11 @@ final class FormatFinder implements AutoCloseable {
                                 final File file = connector.getStorageAs(File.class);
                                 if (file != null) {
                                     stream = new FileImageOutputStream(file);
-                                } else {
+                                } else try {
                                     stream = ImageIO.createImageOutputStream(storage);
                                     if (stream == null) break;
+                                } catch (IIOException e) {
+                                    throw unwrap(e);
                                 }
                             }
                             final ImageWriterSpi p = entry.getKey();
@@ -277,6 +283,28 @@ final class FormatFinder implements AutoCloseable {
         return writer;
     }
 
+    /**
+     * Returns the cause of given exception if it exists, or the exception itself otherwise.
+     * This method is invoked in the {@code catch} block of a {@code try} block invoking
+     * {@link ImageIO#createImageInputStream(Object)} or
+     * {@link ImageIO#createImageOutputStream(Object)}.
+     *
+     * <h4>Rational</h4>
+     * As of Java 18, above-cited methods systematically catch all {@link IOException}s and wrap
+     * them in an {@link IIOException} with <cite>"Can't create cache file!"</cite> error message.
+     * This is conform to Image I/O specification but misleading if the stream provider throws an
+     * {@link IOException} for another reason. Even when the failure is really caused by a problem
+     * with cache file, we want to propagate the original exception to user because its message
+     * may tell that there is no space left on device or no write permission.
+     *
+     * @see org.apache.sis.storage.StorageConnector#unwrap(IIOException)
+     */
+    @Workaround(library = "JDK", version = "18")
+    private static IOException unwrap(final IIOException e) {
+        final Throwable cause = e.getCause();
+        return (cause instanceof IOException) ? (IOException) cause : e;
+    }
+
     /**
      * Closes all unused resources. Keep open only the find of objects needed by the image reader or writer.
      * This method must be invoked after by {@link WorldFileStore} construction.
diff --git a/storage/sis-storage/src/main/java/org/apache/sis/storage/StorageConnector.java b/storage/sis-storage/src/main/java/org/apache/sis/storage/StorageConnector.java
index 5560158acd..9ba807bbb7 100644
--- a/storage/sis-storage/src/main/java/org/apache/sis/storage/StorageConnector.java
+++ b/storage/sis-storage/src/main/java/org/apache/sis/storage/StorageConnector.java
@@ -38,12 +38,14 @@ import java.nio.channels.SeekableByteChannel;
 import java.nio.file.NoSuchFileException;
 import javax.imageio.stream.ImageInputStream;
 import javax.imageio.stream.ImageOutputStream;
+import javax.imageio.IIOException;
 import javax.imageio.ImageIO;
 import java.sql.Connection;
 import java.sql.SQLException;
 import javax.sql.DataSource;
 import org.apache.sis.util.Debug;
 import org.apache.sis.util.Classes;
+import org.apache.sis.util.Workaround;
 import org.apache.sis.util.ArgumentChecks;
 import org.apache.sis.util.ObjectConverters;
 import org.apache.sis.util.resources.Errors;
@@ -1037,7 +1039,11 @@ public class StorageConnector implements Serializable {
             views.put(DataInput.class, c);                          // Share the same Coupled instance.
         } else {
             reset();
-            asDataInput = ImageIO.createImageInputStream(storage);
+            try {
+                asDataInput = ImageIO.createImageInputStream(storage);
+            } catch (IIOException e) {
+                throw unwrap(e);
+            }
             addView(DataInput.class, asDataInput, null, (byte) (CASCADE_ON_RESET | CASCADE_ON_CLOSE));
             /*
              * Note: Java Image I/O wrappers for Input/OutputStream do NOT close the underlying streams.
@@ -1361,7 +1367,11 @@ public class StorageConnector implements Serializable {
             views.put(DataOutput.class, c);                         // Share the same Coupled instance.
         } else {
             reset();
-            asDataOutput = ImageIO.createImageOutputStream(storage);
+            try {
+                asDataOutput = ImageIO.createImageOutputStream(storage);
+            } catch (IIOException e) {
+                throw unwrap(e);
+            }
             addView(DataOutput.class, asDataOutput, null, (byte) (CASCADE_ON_RESET | CASCADE_ON_CLOSE));
             /*
              * Note: Java Image I/O wrappers for Input/OutputStream do NOT close the underlying streams.
@@ -1598,6 +1608,28 @@ public class StorageConnector implements Serializable {
         }
     }
 
+    /**
+     * Returns the cause of given exception if it exists, or the exception itself otherwise.
+     * This method is invoked in the {@code catch} block of a {@code try} block invoking
+     * {@link ImageIO#createImageInputStream(Object)} or
+     * {@link ImageIO#createImageOutputStream(Object)}.
+     *
+     * <h4>Rational</h4>
+     * As of Java 18, above-cited methods systematically catch all {@link IOException}s and wrap
+     * them in an {@link IIOException} with <cite>"Can't create cache file!"</cite> error message.
+     * This is conform to Image I/O specification but misleading if the stream provider throws an
+     * {@link IOException} for another reason. Even when the failure is really caused by a problem
+     * with cache file, we want to propagate the original exception to user because its message
+     * may tell that there is no space left on device or no write permission.
+     *
+     * @see org.apache.sis.internal.storage.image.FormatFinder#unwrap(IIOException)
+     */
+    @Workaround(library = "JDK", version = "18")
+    private static IOException unwrap(final IIOException e) {
+        final Throwable cause = e.getCause();
+        return (cause instanceof IOException) ? (IOException) cause : e;
+    }
+
     /**
      * Returns a string representation of this {@code StorageConnector} for debugging purpose.
      * This string representation is for diagnostic and may change in any future version.


[sis] 01/03: If a slice does not intersect the requested extent, try other slice candidates. It may happen if the "best" slice was selected using only temporal creteria but the geographic area of that particular slice does not intersect.

Posted by de...@apache.org.
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 640019466211ac9af699bff4486d95b6dd10b013
Author: Martin Desruisseaux <ma...@geomatys.com>
AuthorDate: Tue Sep 20 13:05:59 2022 +0200

    If a slice does not intersect the requested extent, try other slice candidates.
    It may happen if the "best" slice was selected using only temporal creteria
    but the geographic area of that particular slice does not intersect.
---
 .../sis/coverage/grid/DisjointExtentException.java |  6 +-
 .../aggregate/ConcatenatedGridCoverage.java        | 80 ++++++++++++++++------
 .../sis/storage/aggregate/GridSliceLocator.java    |  2 +-
 .../sis/storage/aggregate/MergeStrategy.java       | 14 ++--
 4 files changed, 73 insertions(+), 29 deletions(-)

diff --git a/core/sis-feature/src/main/java/org/apache/sis/coverage/grid/DisjointExtentException.java b/core/sis-feature/src/main/java/org/apache/sis/coverage/grid/DisjointExtentException.java
index 1aa54b6a0b..aebaa23bd9 100644
--- a/core/sis-feature/src/main/java/org/apache/sis/coverage/grid/DisjointExtentException.java
+++ b/core/sis-feature/src/main/java/org/apache/sis/coverage/grid/DisjointExtentException.java
@@ -25,7 +25,7 @@ import org.apache.sis.internal.feature.Resources;
  *
  * @author  Johann Sorel (Geomatys)
  * @author  Martin Desruisseaux (Geomatys)
- * @version 1.1
+ * @version 1.3
  * @since   1.0
  * @module
  */
@@ -81,8 +81,10 @@ public class DisjointExtentException extends IllegalGridGeometryException {
      * @param source   extent of the source.
      * @param request  extent of a slice requested by user.
      * @param dim      index of the dimension having an invalid value.
+     *
+     * @since 1.3
      */
-    DisjointExtentException(final GridExtent source, final GridExtent request, final int dim) {
+    public DisjointExtentException(final GridExtent source, final GridExtent request, final int dim) {
         this(source.getAxisIdentification(dim, dim),
                 source .getLow(dim), source .getHigh(dim),
                 request.getLow(dim), request.getHigh(dim));
diff --git a/storage/sis-storage/src/main/java/org/apache/sis/storage/aggregate/ConcatenatedGridCoverage.java b/storage/sis-storage/src/main/java/org/apache/sis/storage/aggregate/ConcatenatedGridCoverage.java
index db43078431..b3672cffa7 100644
--- a/storage/sis-storage/src/main/java/org/apache/sis/storage/aggregate/ConcatenatedGridCoverage.java
+++ b/storage/sis-storage/src/main/java/org/apache/sis/storage/aggregate/ConcatenatedGridCoverage.java
@@ -18,6 +18,7 @@ package org.apache.sis.storage.aggregate;
 
 import java.util.List;
 import java.util.ArrayList;
+import java.util.logging.Logger;
 import java.awt.image.RenderedImage;
 import org.apache.sis.coverage.SampleDimension;
 import org.apache.sis.coverage.grid.GridExtent;
@@ -28,8 +29,10 @@ import org.apache.sis.coverage.grid.DisjointExtentException;
 import org.apache.sis.storage.GridCoverageResource;
 import org.apache.sis.storage.DataStoreException;
 import org.apache.sis.internal.storage.Resources;
+import org.apache.sis.internal.system.Modules;
 import org.apache.sis.internal.util.Numerics;
 import org.apache.sis.util.collection.Cache;
+import org.apache.sis.util.logging.Logging;
 
 // Branch-dependent imports
 import org.opengis.coverage.CannotEvaluateException;
@@ -242,6 +245,8 @@ final class ConcatenatedGridCoverage extends GridCoverage {
 
     /**
      * Returns a two-dimensional slice of grid data as a rendered image.
+     * Invoking this method may cause the loading of data from {@link ConcatenatedGridResource}.
+     * Most recently used slices are cached for future invocations of this method.
      *
      * @param  extent  a subspace of this grid coverage extent where all dimensions except two have a size of 1 cell.
      * @return the grid slice as a rendered image. Image location is relative to {@code sliceExtent}.
@@ -255,11 +260,10 @@ final class ConcatenatedGridCoverage extends GridCoverage {
         } else {
             extent = gridGeometry.getExtent();
         }
+        final GridGeometry   request;           // The geographic area and temporal extent requested by user.
+        final GridGeometry[] candidates;        // Grid geometry of all slices that intersect the request.
         final int count = upper - lower;
-        if (count != 1) {
-            if (count == 0) {
-                throw new DisjointExtentException();
-            }
+        if (count > 1) {
             if (strategy == null) {
                 /*
                  * Can not infer a slice. If the user specified a single slice but that slice
@@ -278,35 +282,71 @@ final class ConcatenatedGridCoverage extends GridCoverage {
                 throw new SubspaceNotSpecifiedException(Resources.format(message, arguments));
             }
             /*
-             * Select a slice using the user-specified merge strategy.
-             * Current implementation does only a selection; a future version may allow real merges.
+             * Prepare a list of slice candidates. Later in this method, a single slice will be selected
+             * among those candidates using the user-specified merge strategy. Elements in `candidates`
+             * array will become null if that candidate did not worked and we want to look again among
+             * remaining candidates.
              */
-            final GridGeometry[] geometries = new GridGeometry[count];
             try {
+                request    = new GridGeometry(getGridGeometry(), extent, null);
+                candidates = new GridGeometry[count];
                 for (int i=0; i<count; i++) {
                     final int j = lower + i;
                     final Object slice = slices[j];
-                    geometries[i] = isDeferred(j) ? ((GridCoverageResource) slice).getGridGeometry()
+                    candidates[i] = isDeferred(j) ? ((GridCoverageResource) slice).getGridGeometry()
                                                   : ((GridCoverage)         slice).getGridGeometry();
                 }
-                lower += strategy.apply(new GridGeometry(getGridGeometry(), extent, null), geometries);
             } catch (DataStoreException | TransformException e) {
                 throw new CannotEvaluateException(Resources.format(Resources.Keys.CanNotSelectSlice), e);
             }
+        } else {
+            request    = null;
+            candidates = null;
         }
         /*
-         * Argument have been validated and slice has been located.
-         * If the coverage has not already been loaded, load it now.
+         * The following loop should be executed exactly once. However it may happen that the "best" slice
+         * actually does not intersect the requested extent, for example because the merge strategy looked
+         * only for temporal intersection and did not saw that the geographic extents do not intersect.
          */
-        final GridCoverage coverage;
-        final Object slice = slices[lower];
-        if (!isDeferred(lower)) {
-            coverage = (GridCoverage) slice;    // Should never fail.
-        } else try {
-            coverage = loader.getOrLoad(lower, (GridCoverageResource) slice).forConvertedValues(isConverted);
-        } catch (DataStoreException e) {
-            throw new CannotEvaluateException(Resources.format(Resources.Keys.CanNotReadSlice_1, lower + startAt), e);
+        DisjointExtentException failure = null;
+        if (count > 0) do {
+            int index = lower;
+            if (candidates != null) {
+                final Integer n = strategy.apply(request, candidates);
+                if (n == null) break;
+                candidates[n] = null;
+                index += n;
+            }
+            final Object slice = slices[index];
+            final GridCoverage coverage;
+            if (!isDeferred(index)) {
+                coverage = (GridCoverage) slice;        // This cast should never fail.
+            } else try {
+                coverage = loader.getOrLoad(index, (GridCoverageResource) slice).forConvertedValues(isConverted);
+            } catch (DataStoreException e) {
+                throw new CannotEvaluateException(Resources.format(Resources.Keys.CanNotReadSlice_1, index + startAt), e);
+            }
+            /*
+             * At this point, coverage of the "best" slice has been fetched from the cache or read from resource.
+             * Delegate the rendering to that coverage, after converting the extent from this grid coverage space
+             * to the slice coordinate space. If the coverage said that the converted extent does not intersect,
+             * try the "next best" slice until we succeed or until we exhausted the candidate list.
+             */
+            try {
+                final RenderedImage image = coverage.render(locator.toSliceExtent(extent, index));
+                if (failure != null) {
+                    Logging.ignorableException(Logger.getLogger(Modules.STORAGE),
+                            ConcatenatedGridCoverage.class, "render", failure);
+                }
+                return image;
+            } catch (DisjointExtentException e) {
+                if (failure == null) failure = e;
+                else failure.addSuppressed(e);
+            }
+        } while (candidates != null);
+        if (failure == null) {
+            failure = new DisjointExtentException(gridGeometry.getExtent(), extent, locator.searchDimension);
         }
-        return coverage.render(locator.toSliceExtent(extent, lower));
+        throw failure;
     }
 }
diff --git a/storage/sis-storage/src/main/java/org/apache/sis/storage/aggregate/GridSliceLocator.java b/storage/sis-storage/src/main/java/org/apache/sis/storage/aggregate/GridSliceLocator.java
index 786c940fd6..1aaa45d040 100644
--- a/storage/sis-storage/src/main/java/org/apache/sis/storage/aggregate/GridSliceLocator.java
+++ b/storage/sis-storage/src/main/java/org/apache/sis/storage/aggregate/GridSliceLocator.java
@@ -49,7 +49,7 @@ final class GridSliceLocator {
      *
      * @see GroupByTransform#findConcatenatedDimensions()
      */
-    private final int searchDimension;
+    final int searchDimension;
 
     /**
      * Lows grid coordinates of each slice (inclusive) in the search dimension.
diff --git a/storage/sis-storage/src/main/java/org/apache/sis/storage/aggregate/MergeStrategy.java b/storage/sis-storage/src/main/java/org/apache/sis/storage/aggregate/MergeStrategy.java
index 6c80c7d835..00fcc2b4fa 100644
--- a/storage/sis-storage/src/main/java/org/apache/sis/storage/aggregate/MergeStrategy.java
+++ b/storage/sis-storage/src/main/java/org/apache/sis/storage/aggregate/MergeStrategy.java
@@ -131,7 +131,7 @@ public final class MergeStrategy {
      * A future version may allow real merge operations.
      *
      * @param  request     the geographic area and temporal extent requested by user.
-     * @param  candidates  grid geometry of all slices that intersect the request.
+     * @param  candidates  grid geometry of all slices that intersect the request. Null elements are ignored.
      * @return index of best slice according the heuristic rules of this {@code MergeStrategy}.
      */
     final Integer apply(final GridGeometry request, final GridGeometry[] candidates) {
@@ -145,11 +145,13 @@ public final class MergeStrategy {
         }
         for (int i=0; i < candidates.length; i++) {
             final GridGeometry candidate = candidates[i];
-            final Instant[] t = candidate.getTemporalExtent();
-            final int n = t.length;
-            selector.evaluate(candidate.getGeographicExtent().orElse(null),
-                              (n == 0) ? null : t[0],
-                              (n == 0) ? null : t[n-1], i);
+            if (candidate != null) {
+                final Instant[] t = candidate.getTemporalExtent();
+                final int n = t.length;
+                selector.evaluate(candidate.getGeographicExtent().orElse(null),
+                                  (n == 0) ? null : t[0],
+                                  (n == 0) ? null : t[n-1], i);
+            }
         }
         return selector.best();
     }


[sis] 03/03: Replace `c.addAll(Arrays.asList(…))` pattern by the simpler `Collections.addAll(c, …)` pattern when the number of elements is small.

Posted by de...@apache.org.
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 eba0a4bea2b29072de400fa1742eb90a9535ea56
Author: Martin Desruisseaux <ma...@geomatys.com>
AuthorDate: Tue Sep 20 16:07:25 2022 +0200

    Replace `c.addAll(Arrays.asList(…))` pattern by the simpler `Collections.addAll(c, …)` pattern when the number of elements is small.
---
 .../src/main/java/org/apache/sis/feature/AbstractAttribute.java     | 3 +--
 .../main/java/org/apache/sis/metadata/iso/MetadataScopeAdapter.java | 6 +++---
 .../java/org/apache/sis/metadata/iso/citation/DefaultContact.java   | 4 ++--
 .../test/java/org/apache/sis/metadata/iso/DefaultMetadataTest.java  | 3 ++-
 .../java/org/apache/sis/parameter/DefaultParameterValueGroup.java   | 4 ++--
 .../src/main/java/org/apache/sis/referencing/Builder.java           | 6 +++---
 .../sis/referencing/operation/builder/ProjectedTransformTry.java    | 4 ++--
 .../org/apache/sis/parameter/DefaultParameterValueGroupTest.java    | 5 ++---
 .../java/org/apache/sis/internal/util/CheckedArrayListTest.java     | 3 ++-
 core/sis-utility/src/test/java/org/apache/sis/test/TestRunner.java  | 3 ++-
 .../java/org/apache/sis/util/collection/FrequencySortedSetTest.java | 3 +--
 .../src/main/java/org/apache/sis/storage/landsat/BandGroup.java     | 4 ++--
 .../java/org/apache/sis/internal/netcdf/impl/ChannelDecoder.java    | 3 +--
 .../main/java/org/apache/sis/internal/netcdf/impl/VariableInfo.java | 2 +-
 14 files changed, 26 insertions(+), 27 deletions(-)

diff --git a/core/sis-feature/src/main/java/org/apache/sis/feature/AbstractAttribute.java b/core/sis-feature/src/main/java/org/apache/sis/feature/AbstractAttribute.java
index 83f1007149..e897bb4829 100644
--- a/core/sis-feature/src/main/java/org/apache/sis/feature/AbstractAttribute.java
+++ b/core/sis-feature/src/main/java/org/apache/sis/feature/AbstractAttribute.java
@@ -17,7 +17,6 @@
 package org.apache.sis.feature;
 
 import java.util.Map;
-import java.util.Arrays;
 import java.util.Collection;
 import java.util.Collections;
 import java.io.Serializable;
@@ -178,7 +177,7 @@ public abstract class AbstractAttribute<V> extends Field<V> implements Attribute
             final Attribute<?>[] characterizedBy = (Attribute<?>[]) in.readObject();
             if (characterizedBy != null) {
                 characteristics = newCharacteristicsMap();
-                characteristics.values().addAll(Arrays.asList(characterizedBy));
+                Collections.addAll(characteristics.values(), characterizedBy);
             }
         } catch (RuntimeException e) {
             // At least ClassCastException, NullPointerException, IllegalArgumentException and IllegalStateException.
diff --git a/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/MetadataScopeAdapter.java b/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/MetadataScopeAdapter.java
index 4e498fa81d..a3decc1e6c 100644
--- a/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/MetadataScopeAdapter.java
+++ b/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/MetadataScopeAdapter.java
@@ -16,10 +16,10 @@
  */
 package org.apache.sis.metadata.iso;
 
-import java.util.Arrays;
 import java.util.List;
 import java.util.Iterator;
 import java.util.Collection;
+import java.util.Collections;
 import java.util.ConcurrentModificationException;
 import org.opengis.metadata.MetadataScope;
 import org.apache.sis.internal.metadata.legacy.LegacyPropertyAdapter;
@@ -83,10 +83,10 @@ abstract class MetadataScopeAdapter<L> extends LegacyPropertyAdapter<L,MetadataS
                         remaining[n++] = it.next();
                         it.remove();
                     }
-                    if (n != remaining.length) { // Paranoiac check.
+                    if (n != remaining.length) {                        // Paranoiac check.
                         throw new ConcurrentModificationException();
                     }
-                    elements.addAll(Arrays.asList(remaining));
+                    Collections.addAll(elements, remaining);
                 }
             }
             return update(scope, newValue);
diff --git a/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/citation/DefaultContact.java b/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/citation/DefaultContact.java
index 5e8b19e195..491b983296 100644
--- a/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/citation/DefaultContact.java
+++ b/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/citation/DefaultContact.java
@@ -16,9 +16,9 @@
  */
 package org.apache.sis.metadata.iso.citation;
 
-import java.util.Arrays;
 import java.util.ArrayList;
 import java.util.Collection;
+import java.util.Collections;
 import javax.xml.bind.annotation.XmlElement;
 import javax.xml.bind.annotation.XmlRootElement;
 import javax.xml.bind.annotation.XmlType;
@@ -217,7 +217,7 @@ public class DefaultContact extends ISOMetadata implements Contact {
             }
             if (modified) {
                 phones.clear();
-                phones.addAll(Arrays.asList(p));
+                Collections.addAll(phones, p);
             }
         }
     }
diff --git a/core/sis-metadata/src/test/java/org/apache/sis/metadata/iso/DefaultMetadataTest.java b/core/sis-metadata/src/test/java/org/apache/sis/metadata/iso/DefaultMetadataTest.java
index 214ef0ff2b..23ebde8350 100644
--- a/core/sis-metadata/src/test/java/org/apache/sis/metadata/iso/DefaultMetadataTest.java
+++ b/core/sis-metadata/src/test/java/org/apache/sis/metadata/iso/DefaultMetadataTest.java
@@ -21,6 +21,7 @@ import java.util.Locale;
 import java.util.Arrays;
 import java.util.Iterator;
 import java.util.Collection;
+import java.util.Collections;
 import java.net.URISyntaxException;
 import javax.xml.bind.JAXBException;
 import org.opengis.metadata.MetadataScope;
@@ -113,7 +114,7 @@ public final strictfp class DefaultMetadataTest extends TestCase {
          * Add other languages. They should appear as additional entries after the first one.
          * The "language" property shall be unmodified by changes in the "other locales" one.
          */
-        metadata.getLocales().addAll(Arrays.asList(Locale.FRENCH, Locale.ENGLISH));
+        Collections.addAll(metadata.getLocales(), Locale.FRENCH, Locale.ENGLISH);
         assertLanguagesEquals(metadata, Locale.JAPANESE, Locale.FRENCH, Locale.ENGLISH);
         /*
          * Ensure that the "locales" list is modifiable, since JAXB writes directly in it.
diff --git a/core/sis-referencing/src/main/java/org/apache/sis/parameter/DefaultParameterValueGroup.java b/core/sis-referencing/src/main/java/org/apache/sis/parameter/DefaultParameterValueGroup.java
index 96b5b103b9..fe091065c6 100644
--- a/core/sis-referencing/src/main/java/org/apache/sis/parameter/DefaultParameterValueGroup.java
+++ b/core/sis-referencing/src/main/java/org/apache/sis/parameter/DefaultParameterValueGroup.java
@@ -88,9 +88,9 @@ import org.apache.sis.util.Utilities;
  *
  * <div class="note"><b>Example:</b>
  * {@preformat java
- *     ParameterValue<?>[] parameter = ...; // Defined elsewhere.
+ *     ParameterValue<?>[] parameter = ...;        // Defined elsewhere.
  *     ParameterValueGroup mercator = Mercator.PARAMETERS.createValue();
- *     mercator.values().addAll(Arrays.asList(parameters));
+ *     Collections.addAll(mercator.values(), parameters);
  * }
  * </div>
  *
diff --git a/core/sis-referencing/src/main/java/org/apache/sis/referencing/Builder.java b/core/sis-referencing/src/main/java/org/apache/sis/referencing/Builder.java
index 50eb32159a..b64626ca33 100644
--- a/core/sis-referencing/src/main/java/org/apache/sis/referencing/Builder.java
+++ b/core/sis-referencing/src/main/java/org/apache/sis/referencing/Builder.java
@@ -20,7 +20,7 @@ import java.util.Map;
 import java.util.List;
 import java.util.HashMap;
 import java.util.ArrayList;
-import java.util.Arrays;
+import java.util.Collections;
 import java.lang.reflect.Type;
 import java.lang.reflect.ParameterizedType;
 import org.opengis.util.NameSpace;
@@ -273,8 +273,8 @@ public abstract class Builder<B extends Builder<B>> {
             properties.putAll(IdentifiedObjects.getProperties(object));
             final GenericName[] valueAlias = (GenericName[]) properties.remove(IdentifiedObject.ALIAS_KEY);
             final Identifier[]  valueIds   = (Identifier[])  properties.remove(IdentifiedObject.IDENTIFIERS_KEY);
-            if (valueAlias != null) aliases.addAll(Arrays.asList(valueAlias));
-            if (valueIds != null) identifiers.addAll(Arrays.asList(valueIds));
+            if (valueAlias != null) Collections.addAll(aliases, valueAlias);
+            if (valueIds   != null) Collections.addAll(identifiers, valueIds);
         }
     }
 
diff --git a/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/builder/ProjectedTransformTry.java b/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/builder/ProjectedTransformTry.java
index 547fb73672..cd867e6a5f 100644
--- a/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/builder/ProjectedTransformTry.java
+++ b/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/builder/ProjectedTransformTry.java
@@ -19,8 +19,8 @@ package org.apache.sis.referencing.operation.builder;
 import java.util.Map;
 import java.util.List;
 import java.util.Queue;
-import java.util.Arrays;
 import java.util.Locale;
+import java.util.Collections;
 import java.text.NumberFormat;
 import org.opengis.geometry.MismatchedDimensionException;
 import org.opengis.referencing.operation.MathTransform;
@@ -287,7 +287,7 @@ final class ProjectedTransformTry implements Comparable<ProjectedTransformTry>,
      */
     static void recycle(final double[][] arrays, final Queue<double[]> pool) {
         if (arrays != null) {
-            pool.addAll(Arrays.asList(arrays));
+            Collections.addAll(pool, arrays);
         }
     }
 
diff --git a/core/sis-referencing/src/test/java/org/apache/sis/parameter/DefaultParameterValueGroupTest.java b/core/sis-referencing/src/test/java/org/apache/sis/parameter/DefaultParameterValueGroupTest.java
index 02baa497e1..93a09d9916 100644
--- a/core/sis-referencing/src/test/java/org/apache/sis/parameter/DefaultParameterValueGroupTest.java
+++ b/core/sis-referencing/src/test/java/org/apache/sis/parameter/DefaultParameterValueGroupTest.java
@@ -17,7 +17,6 @@
 package org.apache.sis.parameter;
 
 import java.util.List;
-import java.util.Arrays;
 import java.util.ArrayList;
 import java.util.Collections;
 import org.opengis.parameter.GeneralParameterDescriptor;
@@ -89,7 +88,7 @@ public final strictfp class DefaultParameterValueGroupTest extends TestCase {
      */
     private DefaultParameterValueGroup createGroup(final int step) {
         final DefaultParameterValueGroup group = new DefaultParameterValueGroup(descriptor);
-        group.values().addAll(Arrays.asList(createValues(step)));
+        Collections.addAll(group.values(), createValues(step));
         return group;
     }
 
@@ -223,7 +222,7 @@ public final strictfp class DefaultParameterValueGroupTest extends TestCase {
         assertEquals("Initial size", 2, values.size());
 
         final DefaultParameterValue<?>[] parameters = createValues(10);
-        assertTrue(values.addAll(Arrays.asList(parameters)));
+        assertTrue(Collections.addAll(values, parameters));
         assertEquals("Final size", parameters.length, values.size());
         for (int i=0; i<parameters.length; i++) {
             assertSame(parameters[i], values.get(i));
diff --git a/core/sis-utility/src/test/java/org/apache/sis/internal/util/CheckedArrayListTest.java b/core/sis-utility/src/test/java/org/apache/sis/internal/util/CheckedArrayListTest.java
index 139b7d2e40..7bce1d5a98 100644
--- a/core/sis-utility/src/test/java/org/apache/sis/internal/util/CheckedArrayListTest.java
+++ b/core/sis-utility/src/test/java/org/apache/sis/internal/util/CheckedArrayListTest.java
@@ -19,6 +19,7 @@ package org.apache.sis.internal.util;
 import java.util.List;
 import java.util.Arrays;
 import java.util.Collection;
+import java.util.Collections;
 import org.apache.sis.util.NullArgumentException;
 import org.apache.sis.test.DependsOnMethod;
 import org.apache.sis.test.TestCase;
@@ -55,7 +56,7 @@ public final strictfp class CheckedArrayListTest extends TestCase {
     public void testAddAll() {
         final CheckedArrayList<String> list = new CheckedArrayList<>(String.class);
         assertTrue(list.add("One"));
-        assertTrue(list.addAll(Arrays.asList("Two", "Three")));
+        assertTrue(Collections.addAll(list, "Two", "Three"));
         assertEquals(Arrays.asList("One", "Two", "Three"), list);
     }
 
diff --git a/core/sis-utility/src/test/java/org/apache/sis/test/TestRunner.java b/core/sis-utility/src/test/java/org/apache/sis/test/TestRunner.java
index 452af950fa..515691284c 100644
--- a/core/sis-utility/src/test/java/org/apache/sis/test/TestRunner.java
+++ b/core/sis-utility/src/test/java/org/apache/sis/test/TestRunner.java
@@ -20,6 +20,7 @@ import java.util.Set;
 import java.util.List;
 import java.util.HashSet;
 import java.util.Arrays;
+import java.util.Collections;
 import java.io.PrintWriter;
 
 import org.junit.Test;
@@ -268,7 +269,7 @@ public final class TestRunner extends BlockJUnit4ClassRunner {
             final FrameworkMethod method = methods[i];
             final DependsOnMethod depend = method.getAnnotation(DependsOnMethod.class);
             if (depend != null) {
-                dependencies.addAll(Arrays.asList(depend.value()));
+                Collections.addAll(dependencies, depend.value());
                 for (int j=methods.length; --j>i;) {
                     if (dependencies.contains(methods[j].getName())) {
                         /*
diff --git a/core/sis-utility/src/test/java/org/apache/sis/util/collection/FrequencySortedSetTest.java b/core/sis-utility/src/test/java/org/apache/sis/util/collection/FrequencySortedSetTest.java
index 0c666c0e32..4eb9ee7694 100644
--- a/core/sis-utility/src/test/java/org/apache/sis/util/collection/FrequencySortedSetTest.java
+++ b/core/sis-utility/src/test/java/org/apache/sis/util/collection/FrequencySortedSetTest.java
@@ -16,7 +16,6 @@
  */
 package org.apache.sis.util.collection;
 
-import java.util.Arrays;
 import java.util.Collections;
 import org.apache.sis.test.DependsOnMethod;
 import org.apache.sis.test.TestCase;
@@ -75,7 +74,7 @@ public final strictfp class FrequencySortedSetTest extends TestCase {
     @DependsOnMethod("testTwoElements")
     public void testSubSet() {
         final FrequencySortedSet<Integer> set = new FrequencySortedSet<>();
-        set.addAll(Arrays.asList(2, 5, 3, 2, 4, 2, 3, 6, 2));
+        Collections.addAll(set, 2, 5, 3, 2, 4, 2, 3, 6, 2);
         assertArrayEquals(new Integer[] {5, 4, 6, 3, 2}, set.toArray());
         assertArrayEquals(new int[] {1, 1, 1, 2, 4}, set.frequencies());
 
diff --git a/storage/sis-earth-observation/src/main/java/org/apache/sis/storage/landsat/BandGroup.java b/storage/sis-earth-observation/src/main/java/org/apache/sis/storage/landsat/BandGroup.java
index d161908052..4b3fadada4 100644
--- a/storage/sis-earth-observation/src/main/java/org/apache/sis/storage/landsat/BandGroup.java
+++ b/storage/sis-earth-observation/src/main/java/org/apache/sis/storage/landsat/BandGroup.java
@@ -17,9 +17,9 @@
 package org.apache.sis.storage.landsat;
 
 import java.util.List;
-import java.util.Arrays;
 import java.util.ArrayList;
 import java.util.Collection;
+import java.util.Collections;
 import java.util.Optional;
 import org.opengis.util.GenericName;
 import org.opengis.metadata.Metadata;
@@ -143,7 +143,7 @@ final class BandGroup extends AbstractResource implements Aggregate, StoreResour
         final List<Band> bands = new ArrayList<>();
         if (components != null) {
             for (final BandGroup c : components) {
-                bands.addAll(Arrays.asList(c.components));
+                Collections.addAll(bands, c.components);
             }
         }
         return bands;
diff --git a/storage/sis-netcdf/src/main/java/org/apache/sis/internal/netcdf/impl/ChannelDecoder.java b/storage/sis-netcdf/src/main/java/org/apache/sis/internal/netcdf/impl/ChannelDecoder.java
index 6b82e8e78a..0d3d376cd3 100644
--- a/storage/sis-netcdf/src/main/java/org/apache/sis/internal/netcdf/impl/ChannelDecoder.java
+++ b/storage/sis-netcdf/src/main/java/org/apache/sis/internal/netcdf/impl/ChannelDecoder.java
@@ -18,7 +18,6 @@ package org.apache.sis.internal.netcdf.impl;
 
 import java.util.Set;
 import java.util.Map;
-import java.util.Arrays;
 import java.util.Collection;
 import java.util.Collections;
 import java.util.AbstractMap;
@@ -950,7 +949,7 @@ public final class ChannelDecoder extends Decoder {
                 break;
             }
             axes.add(axis);
-            dimensions.addAll(Arrays.asList(axis.dimensions));
+            Collections.addAll(dimensions, axis.dimensions);
         }
         return true;
     }
diff --git a/storage/sis-netcdf/src/main/java/org/apache/sis/internal/netcdf/impl/VariableInfo.java b/storage/sis-netcdf/src/main/java/org/apache/sis/internal/netcdf/impl/VariableInfo.java
index fcc5f72a15..3dc462680b 100644
--- a/storage/sis-netcdf/src/main/java/org/apache/sis/internal/netcdf/impl/VariableInfo.java
+++ b/storage/sis-netcdf/src/main/java/org/apache/sis/internal/netcdf/impl/VariableInfo.java
@@ -322,7 +322,7 @@ final class VariableInfo extends Variable implements Comparable<VariableInfo> {
         boolean isUnknown    = false;           // True if `total` is actually unknown.
         for (final VariableInfo variable : variables) {
             // Opportunistically store names of all axes listed in "coordinates" attributes of all variables.
-            referencedAsAxis.addAll(Arrays.asList(variable.getCoordinateVariables()));
+            Collections.addAll(referencedAsAxis, variable.getCoordinateVariables());
             if (variable.isUnlimited()) {
                 final long paddedSize = variable.paddedSize();
                 unlimited[count++] = variable;