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/12/03 22:44:07 UTC

[sis] branch geoapi-4.0 updated (e2a4f94058 -> 138a82a9e8)

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 e2a4f94058 Anticipate some of class name changes expected with upgrade to ISO 19157:2022. Not all impacted types are renamed, only the ones that are new in SIS 1.3 release. Other impacted classes and methods will be renamed later.
     new b7d5da1cc1 Minor documentation fixes.
     new 138a82a9e8 GPX data store should implement `WritableFeatureSet`.

The 2 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:
 .../org/apache/sis/internal/doclet/Preformat.java  |   2 +-
 .../org/apache/sis/internal/maven/Filenames.java   |   2 +-
 .../coverage/j2d/MultiBandsIndexColorModel.java    |   2 +-
 .../org/apache/sis/internal/feature/Resources.java |   2 +-
 .../org/apache/sis/metadata/PropertyAccessor.java  |   4 +-
 .../org/apache/sis/metadata/TreeNodeChildren.java  |   2 +-
 .../apache/sis/util/iso/DefaultNameFactory.java    |   2 +-
 .../org/apache/sis/util/iso/DefaultNameSpace.java  |   2 +-
 .../java/org/apache/sis/xml/ValueConverter.java    |   2 +-
 .../apache/sis/metadata/iso/MarshallingTest.java   |   2 +-
 .../java/org/apache/sis/test/MetadataAssert.java   |   4 +-
 .../apache/sis/test/xml/DocumentComparator.java    |   7 +-
 .../java/org/apache/sis/test/xml/package-info.java |   2 +-
 .../test/java/org/apache/sis/xml/XLinkTest.java    |   2 +-
 .../apache/sis/internal/gazetteer/Resources.java   |   4 +-
 .../org/apache/sis/geometry/AbstractEnvelope.java  |   2 +-
 .../java/org/apache/sis/geometry/Envelope2D.java   |   2 +-
 .../apache/sis/internal/referencing/Resources.java |   4 +-
 .../operation/CoordinateOperationFinder.java       |   2 +-
 .../operation/matrix/AffineTransforms2D.java       |   4 +-
 .../operation/transform/AbstractMathTransform.java |   2 +-
 .../operation/transform/ConcatenatedTransform.java |   2 +-
 .../transform/ConcatenatedTransform2D.java         |   2 +-
 .../transform/ConcatenatedTransformDirect2D.java   |   2 +-
 .../operation/transform/LinearTransform1D.java     |   2 +-
 .../apache/sis/referencing/crs/HardCodedCRS.java   |   6 +-
 .../transform/ConcatenatedTransformTest.java       |   4 +-
 .../operation/transform/MathTransformWrapper.java  |   2 +-
 .../java/org/apache/sis/internal/jdk9/JDK9.java    |  13 +
 .../apache/sis/util/logging/MonolineFormatter.java |   2 +-
 .../java/org/apache/sis/util/resources/Errors.java |   2 +-
 .../sis/util/resources/IndexedResourceBundle.java  |   2 +-
 .../org/apache/sis/util/resources/Messages.java    |   2 +-
 .../org/apache/sis/util/resources/Vocabulary.java  |   2 +-
 .../apache/sis/storage/netcdf/AttributeNames.java  |   4 +-
 .../shapefile/jdbc/sql/ClauseResolver.java         |   2 +-
 .../sis/internal/storage/image/FormatFinder.java   |   2 +-
 .../sis/internal/storage/io/IOUtilities.java       |   2 +-
 .../sis/storage/IllegalFeatureTypeException.java   |   2 +-
 .../org/apache/sis/storage/StorageConnector.java   |   2 +-
 .../apache/sis/internal/storage/gpx/Reader.java    |   2 +-
 .../org/apache/sis/internal/storage/gpx/Store.java | 103 +++++++-
 .../apache/sis/internal/storage/gpx/Updater.java   |  89 +++++++
 .../apache/sis/internal/storage/gpx/Writer.java    |  12 +-
 .../storage/xml/stream/RewriteOnUpdate.java        | 283 +++++++++++++++++++++
 .../internal/storage/xml/stream/StaxDataStore.java |  83 +++---
 .../storage/xml/stream/StaxStreamWriter.java       |  12 +-
 .../internal/storage/xml/stream/package-info.java  |   2 +-
 .../sis/internal/storage/gpx/UpdaterTest.java      | 179 +++++++++++++
 .../org/apache/sis/test/suite/GPXTestSuite.java    |   3 +-
 50 files changed, 780 insertions(+), 102 deletions(-)
 create mode 100644 storage/sis-xmlstore/src/main/java/org/apache/sis/internal/storage/gpx/Updater.java
 create mode 100644 storage/sis-xmlstore/src/main/java/org/apache/sis/internal/storage/xml/stream/RewriteOnUpdate.java
 create mode 100644 storage/sis-xmlstore/src/test/java/org/apache/sis/internal/storage/gpx/UpdaterTest.java


[sis] 02/02: GPX data store should implement `WritableFeatureSet`.

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 138a82a9e84a52c093d5b0314773427f68edea7c
Author: Martin Desruisseaux <ma...@geomatys.com>
AuthorDate: Sat Dec 3 23:43:15 2022 +0100

    GPX data store should implement `WritableFeatureSet`.
    
    https://issues.apache.org/jira/browse/SIS-411
---
 .../java/org/apache/sis/test/MetadataAssert.java   |   4 +-
 .../apache/sis/test/xml/DocumentComparator.java    |   7 +-
 .../java/org/apache/sis/test/xml/package-info.java |   2 +-
 .../java/org/apache/sis/internal/jdk9/JDK9.java    |  13 +
 .../sis/storage/IllegalFeatureTypeException.java   |   2 +-
 .../apache/sis/internal/storage/gpx/Reader.java    |   2 +-
 .../org/apache/sis/internal/storage/gpx/Store.java | 103 +++++++-
 .../apache/sis/internal/storage/gpx/Updater.java   |  89 +++++++
 .../apache/sis/internal/storage/gpx/Writer.java    |  12 +-
 .../storage/xml/stream/RewriteOnUpdate.java        | 283 +++++++++++++++++++++
 .../internal/storage/xml/stream/StaxDataStore.java |  83 +++---
 .../storage/xml/stream/StaxStreamWriter.java       |  12 +-
 .../internal/storage/xml/stream/package-info.java  |   2 +-
 .../sis/internal/storage/gpx/UpdaterTest.java      | 179 +++++++++++++
 .../org/apache/sis/test/suite/GPXTestSuite.java    |   3 +-
 15 files changed, 737 insertions(+), 59 deletions(-)

diff --git a/core/sis-metadata/src/test/java/org/apache/sis/test/MetadataAssert.java b/core/sis-metadata/src/test/java/org/apache/sis/test/MetadataAssert.java
index 703fb6f0e8..0081be869e 100644
--- a/core/sis-metadata/src/test/java/org/apache/sis/test/MetadataAssert.java
+++ b/core/sis-metadata/src/test/java/org/apache/sis/test/MetadataAssert.java
@@ -134,8 +134,8 @@ public strictfp class MetadataAssert extends Assert {
      *
      * <ul>
      *   <li>{@link org.w3c.dom.Node}: used directly without further processing.</li>
-     *   <li>{@link java.io.File}, {@link java.net.URL} or {@link java.net.URI}: the
-     *       stream is opened and parsed as a XML document.</li>
+     *   <li>{@link java.nio.file.Path}, {@link java.io.File}, {@link java.net.URL} or {@link java.net.URI}:
+     *       the stream is opened and parsed as a XML document.</li>
      *   <li>{@link String}: The string content is parsed directly as a XML document.</li>
      * </ul>
      *
diff --git a/core/sis-metadata/src/test/java/org/apache/sis/test/xml/DocumentComparator.java b/core/sis-metadata/src/test/java/org/apache/sis/test/xml/DocumentComparator.java
index 908b307cc8..d8388b9870 100644
--- a/core/sis-metadata/src/test/java/org/apache/sis/test/xml/DocumentComparator.java
+++ b/core/sis-metadata/src/test/java/org/apache/sis/test/xml/DocumentComparator.java
@@ -22,6 +22,8 @@ import java.util.List;
 import java.util.HashMap;
 import java.util.HashSet;
 import java.util.ArrayList;
+import java.nio.file.Files;
+import java.nio.file.Path;
 import java.net.URI;
 import java.net.URL;
 import java.io.File;
@@ -71,7 +73,7 @@ import static org.apache.sis.util.CharSequences.trimWhitespaces;
  * @author  Johann Sorel (Geomatys)
  * @author  Martin Desruisseaux (Geomatys)
  * @author  Guilhem Legal (Geomatys)
- * @version 1.0
+ * @version 1.3
  *
  * @see TestCase
  * @see org.apache.sis.test.MetadataAssert#assertXmlEquals(Object, Object, String[])
@@ -190,7 +192,7 @@ public strictfp class DocumentComparator {
      *
      * <ul>
      *   <li>{@link Node}; used directly without further processing.</li>
-     *   <li>{@link File}, {@link URL} or {@link URI}: the stream is opened and parsed as a XML document.</li>
+     *   <li>{@link Path}, {@link File}, {@link URL} or {@link URI}: the stream is opened and parsed as a XML document.</li>
      *   <li>{@link String}: The string content is parsed directly as a XML document.</li>
      * </ul>
      *
@@ -248,6 +250,7 @@ public strictfp class DocumentComparator {
         if (input instanceof File)        return new FileInputStream((File) input);
         if (input instanceof URI)         return ((URI) input).toURL().openStream();
         if (input instanceof URL)         return ((URL) input).openStream();
+        if (input instanceof Path)        return Files.newInputStream((Path) input);
         if (input instanceof String)      return new ByteArrayInputStream(input.toString().getBytes("UTF-8"));
         throw new IOException("Cannot handle input type: " + (input != null ? input.getClass() : input));
     }
diff --git a/core/sis-metadata/src/test/java/org/apache/sis/test/xml/package-info.java b/core/sis-metadata/src/test/java/org/apache/sis/test/xml/package-info.java
index da318ef7df..e4405984f3 100644
--- a/core/sis-metadata/src/test/java/org/apache/sis/test/xml/package-info.java
+++ b/core/sis-metadata/src/test/java/org/apache/sis/test/xml/package-info.java
@@ -25,7 +25,7 @@
  * in any future version without notice.</p>
  *
  * @author  Martin Desruisseaux (Geomatys)
- * @version 1.1
+ * @version 1.3
  * @since   1.0
  * @module
  */
diff --git a/core/sis-utility/src/main/java/org/apache/sis/internal/jdk9/JDK9.java b/core/sis-utility/src/main/java/org/apache/sis/internal/jdk9/JDK9.java
index ec1de1d236..047b6f326b 100644
--- a/core/sis-utility/src/main/java/org/apache/sis/internal/jdk9/JDK9.java
+++ b/core/sis-utility/src/main/java/org/apache/sis/internal/jdk9/JDK9.java
@@ -16,6 +16,7 @@
  */
 package org.apache.sis.internal.jdk9;
 
+import java.io.IOException;
 import java.nio.Buffer;
 import java.nio.ByteBuffer;
 import java.nio.DoubleBuffer;
@@ -23,6 +24,8 @@ import java.nio.FloatBuffer;
 import java.nio.IntBuffer;
 import java.nio.LongBuffer;
 import java.nio.ShortBuffer;
+import java.nio.file.Files;
+import java.nio.file.Path;
 import java.util.Arrays;
 import java.util.Set;
 import java.util.List;
@@ -32,6 +35,7 @@ import java.util.HashMap;
 import java.util.LinkedHashSet;
 import java.util.Map;
 import java.util.Optional;
+import java.util.StringJoiner;
 import java.util.function.Consumer;
 import java.util.stream.Stream;
 import org.apache.sis.internal.util.CollectionsExt;
@@ -350,4 +354,13 @@ public final class JDK9 {
     public static <T> List<T> toList(final Stream<T> s) {
         return (List<T>) UnmodifiableArrayList.wrap(s.toArray());
     }
+
+    /**
+     * Placeholder for {@link Files#readString(Path)}.
+     */
+    public static String readString(final Path path) throws IOException {
+        final StringJoiner j = new StringJoiner("\n");
+        Files.readAllLines(path).forEach(j::add);
+        return j.toString();
+    }
 }
diff --git a/storage/sis-storage/src/main/java/org/apache/sis/storage/IllegalFeatureTypeException.java b/storage/sis-storage/src/main/java/org/apache/sis/storage/IllegalFeatureTypeException.java
index 809f6b2e79..4e9352ca54 100644
--- a/storage/sis-storage/src/main/java/org/apache/sis/storage/IllegalFeatureTypeException.java
+++ b/storage/sis-storage/src/main/java/org/apache/sis/storage/IllegalFeatureTypeException.java
@@ -69,7 +69,7 @@ public class IllegalFeatureTypeException extends DataStoreException {
     /**
      * Creates an exception with a default message in the given locale.
      *
-     * @param locale     the message locale.
+     * @param locale     the locale for the message, or {@code null} for the default locale.
      * @param format     short name of the format that do not accept the given feature type.
      * @param dataType   name of the feature type that cannot be accepted by the data store.
      */
diff --git a/storage/sis-xmlstore/src/main/java/org/apache/sis/internal/storage/gpx/Reader.java b/storage/sis-xmlstore/src/main/java/org/apache/sis/internal/storage/gpx/Reader.java
index ebf94fb328..09f7f574db 100644
--- a/storage/sis-xmlstore/src/main/java/org/apache/sis/internal/storage/gpx/Reader.java
+++ b/storage/sis-xmlstore/src/main/java/org/apache/sis/internal/storage/gpx/Reader.java
@@ -100,7 +100,7 @@ final class Reader extends StaxStreamReader {
      * @throws IOException if an error occurred while preparing the input stream.
      * @throws Exception if another kind of error occurred while closing a previous stream.
      */
-    public Reader(final Store owner) throws Exception {
+    Reader(final Store owner) throws Exception {
         super(owner);
     }
 
diff --git a/storage/sis-xmlstore/src/main/java/org/apache/sis/internal/storage/gpx/Store.java b/storage/sis-xmlstore/src/main/java/org/apache/sis/internal/storage/gpx/Store.java
index 2e353f687e..5d14b374ef 100644
--- a/storage/sis-xmlstore/src/main/java/org/apache/sis/internal/storage/gpx/Store.java
+++ b/storage/sis-xmlstore/src/main/java/org/apache/sis/internal/storage/gpx/Store.java
@@ -17,8 +17,12 @@
 package org.apache.sis.internal.storage.gpx;
 
 import java.util.Optional;
+import java.util.Iterator;
+import java.util.function.Predicate;
+import java.util.function.UnaryOperator;
 import java.util.stream.Stream;
 import java.util.stream.StreamSupport;
+import java.io.IOException;
 import java.io.UncheckedIOException;
 import java.net.URISyntaxException;
 import org.opengis.util.NameFactory;
@@ -26,12 +30,13 @@ import org.opengis.util.FactoryException;
 import org.opengis.geometry.Envelope;
 import org.opengis.metadata.Metadata;
 import org.opengis.metadata.distribution.Format;
-import org.apache.sis.storage.FeatureSet;
+import org.apache.sis.storage.WritableFeatureSet;
 import org.apache.sis.storage.StorageConnector;
 import org.apache.sis.storage.DataStoreException;
 import org.apache.sis.storage.DataStoreContentException;
 import org.apache.sis.storage.ConcurrentReadException;
 import org.apache.sis.storage.IllegalNameException;
+import org.apache.sis.storage.IllegalFeatureTypeException;
 import org.apache.sis.internal.system.DefaultFactories;
 import org.apache.sis.internal.storage.StoreUtilities;
 import org.apache.sis.internal.storage.xml.stream.StaxDataStore;
@@ -52,6 +57,8 @@ import org.opengis.feature.FeatureType;
 
 /**
  * A data store backed by GPX files.
+ * This store does not cache the feature instances.
+ * Any new {@linkplain #features(boolean) request for features} will re-read from the file.
  *
  * @author  Johann Sorel (Geomatys)
  * @author  Martin Desruisseaux (Geomatys)
@@ -59,7 +66,7 @@ import org.opengis.feature.FeatureType;
  * @since   0.8
  * @module
  */
-public final class Store extends StaxDataStore implements FeatureSet {
+public final class Store extends StaxDataStore implements WritableFeatureSet {
     /**
      * Version of the GPX file, or {@code null} if unknown.
      */
@@ -73,6 +80,7 @@ public final class Store extends StaxDataStore implements FeatureSet {
     /**
      * If a reader has been created for parsing the {@linkplain #metadata} and has not yet been used
      * for iterating over the features, that reader. Otherwise {@code null}.
+     * Used for continuing XML parsing after metadata header instead of closing and reopening the file.
      */
     private Reader reader;
 
@@ -208,8 +216,26 @@ public final class Store extends StaxDataStore implements FeatureSet {
         return types.names.get(this, name);
     }
 
+    /**
+     * Verifies the type of feature instances in this feature set.
+     * This method does nothing if the specified type is equal to {@link #getType()},
+     * or throws {@link IllegalFeatureTypeException} otherwise.
+     *
+     * @param  newType  new feature type definition (not {@code null}).
+     * @throws DataStoreException if the given type is not compatible with the types supported by the store.
+     */
+    @Override
+    public void updateType(final FeatureType newType) throws DataStoreException {
+        if (!newType.equals(getType())) {
+            throw new IllegalFeatureTypeException(getLocale(), StoreProvider.NAME, newType.getName());
+        }
+    }
+
     /**
      * Returns the stream of features.
+     * This store does not cache the features. Any new iteration over features will re-read from the file.
+     * The XML file is kept open until the feature stream is closed;
+     * callers should not modify the file while an iteration is in progress.
      *
      * @param  parallel  ignored in current implementation.
      * @return a stream over all features in the XML file.
@@ -233,6 +259,68 @@ public final class Store extends StaxDataStore implements FeatureSet {
         return features.onClose(r);
     }
 
+    /**
+     * Appends new feature instances in this {@code FeatureSet}.
+     * Any feature already present in this {@link FeatureSet} will remain unmodified.
+     *
+     * @param  features  feature instances to append in this {@code FeatureSet}.
+     * @throws DataStoreException if the feature stream cannot be obtained or updated.
+     */
+    @Override
+    public synchronized void add(final Iterator<? extends Feature> features) throws DataStoreException {
+        try (Updater updater = updater()) {
+            updater.add(features);
+            updater.flush();
+        }
+    }
+
+    /**
+     * Removes all feature instances from this {@code FeatureSet} which matches the given predicate.
+     *
+     * @param  filter  a predicate which returns {@code true} for feature instances to be removed.
+     * @return {@code true} if any elements were removed.
+     * @throws DataStoreException if the feature stream cannot be obtained or updated.
+     */
+    @Override
+    public synchronized boolean removeIf(final Predicate<? super Feature> filter) throws DataStoreException {
+        try (Updater updater = updater()) {
+            return updater.removeIf(filter);
+        }
+    }
+
+    /**
+     * Updates all feature instances from this {@code FeatureSet} which match the given predicate.
+     * If the given operator returns {@code null}, then the filtered feature is removed.
+     *
+     * @param  filter       a predicate which returns {@code true} for feature instances to be updated.
+     * @param  replacement  operation called for each matching {@link Feature} instance. May return {@code null}.
+     * @throws DataStoreException if the feature stream cannot be obtained or updated.
+     */
+    @Override
+    public synchronized void replaceIf(final Predicate<? super Feature> filter, final UnaryOperator<Feature> replacement)
+            throws DataStoreException
+    {
+        try (Updater updater = updater()) {
+            updater.replaceIf(filter, replacement);
+            updater.flush();
+        }
+    }
+
+    /**
+     * Returns the helper object to use for updating the GPX file.
+     *
+     * @todo In current version, we flush the updater after each write operation.
+     *       In a future version, we should keep it in a private field and flush
+     *       only after some delay, on close, or before a read operation.
+     */
+    private Updater updater() throws DataStoreException {
+        try {
+            return new Updater(this, getSpecifiedPath());
+        } catch (IOException e) {
+            throw new DataStoreException(e);
+        }
+    }
+
     /**
      * Replaces the content of this GPX file by the given metadata and features.
      *
@@ -240,13 +328,18 @@ public final class Store extends StaxDataStore implements FeatureSet {
      * @param  features  the features to write, or {@code null} if none.
      * @throws ConcurrentReadException if the {@code features} stream was provided by this data store.
      * @throws DataStoreException if an error occurred while writing the data.
+     *
+     * @deprecated To be replaced by {@link #add(Iterator)}, after we resolved how to specify metadata.
+     *
+     * @see <a href="https://issues.apache.org/jira/browse/SIS-411">SIS-411</a>
      */
+    @Deprecated
     public synchronized void write(final Metadata metadata, final Stream<? extends Feature> features) throws DataStoreException {
         try {
             /*
              * If we created a reader for reading metadata, we need to close that reader now otherwise the call
-             * to 'new Writer(…)' will fail.  Note that if that reader was in use by someone else, the 'reader'
-             * field would be null and the 'new Writer(…)' call should detect that a reader is in use somewhere.
+             * to `new Writer(…)` will fail.  Note that if that reader was in use by someone else, the `reader`
+             * field would be null and the `new Writer(…)` call should detect that a reader is in use somewhere.
              */
             final Reader r = reader;
             if (r != null) {
@@ -256,7 +349,7 @@ public final class Store extends StaxDataStore implements FeatureSet {
             /*
              * Get the writer if no read or other write operation is in progress, then write the data.
              */
-            try (Writer writer = new Writer(this, org.apache.sis.internal.storage.gpx.Metadata.castOrCopy(metadata, locale))) {
+            try (Writer writer = new Writer(this, org.apache.sis.internal.storage.gpx.Metadata.castOrCopy(metadata, locale), null)) {
                 writer.writeStartDocument();
                 if (features != null) {
                     features.forEachOrdered(writer);
diff --git a/storage/sis-xmlstore/src/main/java/org/apache/sis/internal/storage/gpx/Updater.java b/storage/sis-xmlstore/src/main/java/org/apache/sis/internal/storage/gpx/Updater.java
new file mode 100644
index 0000000000..8a739175ff
--- /dev/null
+++ b/storage/sis-xmlstore/src/main/java/org/apache/sis/internal/storage/gpx/Updater.java
@@ -0,0 +1,89 @@
+/*
+ * 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.storage.gpx;
+
+import java.io.IOException;
+import java.io.OutputStream;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.stream.Stream;
+import org.apache.sis.internal.storage.xml.stream.RewriteOnUpdate;
+import org.apache.sis.internal.storage.xml.stream.StaxStreamWriter;
+import org.apache.sis.storage.DataStoreException;
+import org.opengis.feature.Feature;
+
+
+/**
+ * Updates the content of a GPX file by rewriting it.
+ *
+ * @author  Martin Desruisseaux (Geomatys)
+ * @version 1.3
+ * @since   1.3
+ * @module
+ */
+final class Updater extends RewriteOnUpdate {
+    /**
+     * The metadata to write.
+     */
+    private Metadata metadata;
+
+    /**
+     * Creates an updater for the given source of features.
+     *
+     * @param  source    the set of features to update.
+     * @param  location  the main file, or {@code null} if unknown.
+     * @throws IOException if an error occurred while determining whether the file is empty.
+     */
+    Updater(final Store source, final Path location) throws IOException {
+        super(source, location);
+    }
+
+    /**
+     * Returns the stream of features to copy.
+     *
+     * @return all features contained in the dataset.
+     * @throws DataStoreException if an error occurred while fetching the features.
+     */
+    @Override
+    protected Stream<? extends Feature> features() throws DataStoreException {
+        metadata = Metadata.castOrCopy(source.getMetadata(), getLocale());
+        return super.features();
+    }
+
+    /**
+     * Creates an initially empty temporary file.
+     *
+     * @return the temporary file.
+     * @throws IOException if an error occurred while creating the temporary file.
+     */
+    @Override
+    protected Path createTemporaryFile() throws IOException {
+        return Files.createTempFile(StoreProvider.NAME, ".xml");
+    }
+
+    /**
+     * Creates a new GPX writer for an output in the specified file.
+     *
+     * @param  temporary  the temporary stream where to write, or {@code null} for writing directly in the store file.
+     * @return the writer where to copy updated features.
+     * @throws Exception if an error occurred while creating the writer.
+     */
+    @Override
+    protected StaxStreamWriter createWriter(OutputStream temporary) throws Exception {
+        return new Writer((Store) source, metadata, temporary);
+    }
+}
diff --git a/storage/sis-xmlstore/src/main/java/org/apache/sis/internal/storage/gpx/Writer.java b/storage/sis-xmlstore/src/main/java/org/apache/sis/internal/storage/gpx/Writer.java
index 840873ab8b..97d33b705a 100644
--- a/storage/sis-xmlstore/src/main/java/org/apache/sis/internal/storage/gpx/Writer.java
+++ b/storage/sis-xmlstore/src/main/java/org/apache/sis/internal/storage/gpx/Writer.java
@@ -17,6 +17,7 @@
 package org.apache.sis.internal.storage.gpx;
 
 import java.io.IOException;
+import java.io.OutputStream;
 import java.util.Collection;
 import javax.xml.stream.XMLStreamException;
 import javax.xml.bind.JAXBException;
@@ -39,7 +40,7 @@ import org.opengis.feature.FeatureType;
  *
  * @author  Johann Sorel (Geomatys)
  * @author  Martin Desruisseaux (Geomatys)
- * @version 0.8
+ * @version 1.3
  * @since   0.8
  * @module
  */
@@ -57,16 +58,17 @@ final class Writer extends StaxStreamWriter {
     /**
      * Creates a new GPX writer for the given data store.
      *
-     * @param  owner     the data store for which this writer is created.
-     * @param  metadata  the metadata to write, or {@code null} if none.
+     * @param  owner      the data store for which this writer is created.
+     * @param  metadata   the metadata to write, or {@code null} if none.
+     * @param  temporary  the temporary stream where to write, or {@code null} for the main storage.
      * @throws DataStoreException if the output type is not recognized or the data store is closed.
      * @throws XMLStreamException if an error occurred while opening the XML file.
      * @throws IOException if an error occurred while preparing the output stream.
      */
-    public Writer(final Store owner, final Metadata metadata)
+    Writer(final Store owner, final Metadata metadata, final OutputStream temporary)
             throws DataStoreException, XMLStreamException, IOException
     {
-        super(owner);
+        super(owner, temporary);
         this.metadata = metadata;
         final Version ver = owner.version;
         if (ver != null && ver.compareTo(StoreProvider.V1_0, 2) <= 0) {
diff --git a/storage/sis-xmlstore/src/main/java/org/apache/sis/internal/storage/xml/stream/RewriteOnUpdate.java b/storage/sis-xmlstore/src/main/java/org/apache/sis/internal/storage/xml/stream/RewriteOnUpdate.java
new file mode 100644
index 0000000000..5377bda50b
--- /dev/null
+++ b/storage/sis-xmlstore/src/main/java/org/apache/sis/internal/storage/xml/stream/RewriteOnUpdate.java
@@ -0,0 +1,283 @@
+/*
+ * 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.storage.xml.stream;
+
+import java.util.Locale;
+import java.util.Iterator;
+import java.util.Spliterator;
+import java.util.Spliterators;
+import java.util.stream.Stream;
+import java.util.function.Predicate;
+import java.util.function.UnaryOperator;
+import java.util.stream.StreamSupport;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.io.UncheckedIOException;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.StandardCopyOption;
+import org.apache.sis.storage.FeatureSet;
+import org.apache.sis.storage.DataStoreException;
+import org.apache.sis.storage.ReadOnlyStorageException;
+import org.apache.sis.util.collection.BackingStoreException;
+import org.apache.sis.util.ArgumentChecks;
+
+// Branch-dependent imports
+import org.opengis.feature.Feature;
+
+
+/**
+ * Helper class for updating an existing XML file, with no feature type change permitted.
+ * The implementation strategy is to rewrite fully the updated features in a temporary file,
+ * then replaces the source file by the temporary file when ready.
+ *
+ * <p>The {@link #flush()} method should always been invoked before a {@code RewriteOnUpdate}
+ * reference is lost, otherwise data may be lost.</p>
+ *
+ * <h2>Multi-threading</h2>
+ * This class is not synchronized for multi-threading. Synchronization is caller's responsibility,
+ * because the caller usually needs to take in account other data store operations such as reads.
+ *
+ * @author  Martin Desruisseaux (Geomatys)
+ * @version 1.3
+ * @since   1.3
+ * @module
+ */
+public abstract class RewriteOnUpdate implements AutoCloseable {
+    /**
+     * The set of features to update. This is the set specified at construction time.
+     */
+    protected final FeatureSet source;
+
+    /**
+     * The main file, or {@code null} if unknown.
+     */
+    private final Path location;
+
+    /**
+     * Whether the store is initially empty.
+     * It may be the underlying file does not exist or has a length of zero.
+     */
+    private boolean isSourceEmpty;
+
+    /**
+     * The features to write, fetched when first needed.
+     *
+     * @see #filtered()
+     */
+    private Stream<? extends Feature> filtered;
+
+    /**
+     * Creates an updater for the given source of features.
+     *
+     * @param  source    the set of features to update.
+     * @param  location  the main file, or {@code null} if unknown.
+     * @throws IOException if an error occurred while determining whether the file is empty.
+     */
+    public RewriteOnUpdate(final FeatureSet source, final Path location) throws IOException {
+        this.source   = source;
+        this.location = location;
+        isSourceEmpty = (location == null) || Files.notExists(location) || Files.size(location) == 0;
+    }
+
+    /**
+     * Returns the locale to use for locale-sensitive data, or {@code null} if unspecified.
+     * This is <strong>not</strong> for logging or warning messages.
+     *
+     * @return the data locale, or {@code null}.
+     */
+    protected final Locale getLocale() {
+        return (source instanceof StaxDataStore) ? ((StaxDataStore) source).locale : null;
+    }
+
+    /**
+     * Returns {@code true} if there is currently no data.
+     */
+    private boolean isEmpty() throws ReadOnlyStorageException {
+        if (isSourceEmpty) {
+            return filtered == null;
+        } else if (location != null) {
+            return false;
+        } else {
+            throw new ReadOnlyStorageException();
+        }
+    }
+
+    /**
+     * Returns the features to write.
+     *
+     * @throws DataStoreException if the feature stream cannot be obtained.
+     */
+    private Stream<? extends Feature> filtered() throws DataStoreException {
+        if (filtered == null) {
+            filtered = features();
+        }
+        return filtered;
+    }
+
+    /**
+     * Returns the stream of features to copy.
+     * The default implementation delegates to {@link FeatureSet#features(boolean)}.
+     *
+     * @return all features contained in the dataset.
+     * @throws DataStoreException if an error occurred while fetching the features.
+     */
+    protected Stream<? extends Feature> features() throws DataStoreException {
+        return source.features(false);
+    }
+
+    /**
+     * Appends new feature instances in the {@code FeatureSet}.
+     * Any feature already present in the {@link FeatureSet} will remain unmodified.
+     *
+     * @param  features  feature instances to append in the {@code FeatureSet}.
+     * @throws DataStoreException if the feature stream cannot be obtained or updated.
+     */
+    public void add(final Iterator<? extends Feature> features) throws DataStoreException {
+        ArgumentChecks.ensureNonNull("features", features);
+        final Stream<? extends Feature> toAdd = StreamSupport.stream(
+                Spliterators.spliteratorUnknownSize(features, Spliterator.ORDERED), false);
+        if (isEmpty()) {
+            filtered = toAdd;
+        } else {
+            filtered = Stream.concat(filtered(), toAdd);
+        }
+    }
+
+    /**
+     * Removes all feature instances from the {@code FeatureSet} which matches the given predicate.
+     *
+     * @param  filter  a predicate which returns {@code true} for feature instances to be removed.
+     * @return {@code true} if any elements were removed.
+     * @throws DataStoreException if the feature stream cannot be obtained or updated.
+     */
+    public boolean removeIf(final Predicate<? super Feature> filter) throws DataStoreException {
+        ArgumentChecks.ensureNonNull("filter", filter);
+        if (isEmpty()) {
+            return false;
+        }
+        filtered = filtered().filter((feature) -> {
+            boolean r = filter.test(feature);
+            if (r) modified = true;
+            return !r;
+        });
+        modified = false;
+        flush();            // Need immediate execution for getting the boolean value.
+        return modified;
+    }
+
+    /**
+     * A flag telling whether {@link #removeIf(Predicate)} removed at least one feature.
+     */
+    private boolean modified;
+
+    /**
+     * Updates all feature instances from the {@code FeatureSet} which match the given predicate.
+     * If the given operator returns {@code null}, then the filtered feature is removed.
+     *
+     * @param  filter   a predicate which returns {@code true} for feature instances to be updated.
+     * @param  updater  operation called for each matching {@link Feature} instance. May return {@code null}.
+     * @throws DataStoreException if the feature stream cannot be obtained or updated.
+     */
+    public void replaceIf(final Predicate<? super Feature> filter, final UnaryOperator<Feature> updater) throws DataStoreException {
+        ArgumentChecks.ensureNonNull("filter",  filter);
+        ArgumentChecks.ensureNonNull("updater", updater);
+        if (!isEmpty()) {
+            filtered = filtered().map((feature) -> (feature != null) && filter.test(feature) ? updater.apply(feature) : feature);
+        }
+    }
+
+    /**
+     * Creates an initially empty temporary file.
+     *
+     * @return the temporary file.
+     * @throws IOException if an error occurred while creating the temporary file.
+     */
+    protected abstract Path createTemporaryFile() throws IOException;
+
+    /**
+     * Creates a new XML document writer for an output in the specified temporary file.
+     * Caller is responsible for closing the writer.
+     *
+     * @param  temporary  the temporary stream where to write, or {@code null} for writing directly in the store file.
+     * @return the writer where to copy updated features.
+     * @throws Exception if an error occurred while creating the writer.
+     *         May be {@link DataStoreException}, {@link IOException}, {@link RuntimeException}, <i>etc.</i>
+     */
+    protected abstract StaxStreamWriter createWriter(OutputStream temporary) throws Exception;
+
+    /**
+     * Writes immediately all feature instances.
+     * This method does nothing if there is no data to write.
+     *
+     * @throws DataStoreException if an error occurred.
+     */
+    public void flush() throws DataStoreException {
+        try (Stream<? extends Feature> content = filtered) {
+            if (content != null) {
+                filtered = null;
+                OutputStream temporary = null;
+                Path target = isSourceEmpty ? null : createTemporaryFile();
+                try {
+                    if (target != null) {
+                        temporary = Files.newOutputStream(target);
+                    }
+                    try (StaxStreamWriter writer = createWriter(temporary)) {
+                        temporary = null;       // Stream will be closed by writer.
+                        isSourceEmpty = false;
+                        writer.writeStartDocument();
+                        content.sequential().forEachOrdered(writer);
+                        writer.writeEndDocument();
+                    }
+                    if (target != null) {
+                        Files.move(target, location, StandardCopyOption.REPLACE_EXISTING);
+                        target = null;
+                    }
+                } finally {
+                    if (temporary != null) temporary.close();
+                    if (target != null) Files.delete(target);       // Delete the temporary file if an error occurred.
+                }
+            }
+        } catch (DataStoreException e) {
+            throw e;
+        } catch (BackingStoreException e) {
+            final Throwable cause = e.getCause();
+            if (cause instanceof DataStoreException) {
+                throw (DataStoreException) cause;
+            }
+            throw new DataStoreException(e.getLocalizedMessage(), cause);
+        } catch (Exception e) {
+            if (e instanceof UncheckedIOException) {
+                e = ((UncheckedIOException) e).getCause();
+            }
+            throw new DataStoreException(e);
+        }
+    }
+
+    /**
+     * Releases resources used by this updater. If {@link #flush()} has not been invoked, data may be lost.
+     * This method is useful in try-with-resource in case something fails before {@link #flush()} invocation.
+     */
+    @Override
+    public void close() {
+        final Stream<? extends Feature> content = filtered;
+        if (content != null) {
+            filtered = null;
+            content.close();
+        }
+    }
+}
diff --git a/storage/sis-xmlstore/src/main/java/org/apache/sis/internal/storage/xml/stream/StaxDataStore.java b/storage/sis-xmlstore/src/main/java/org/apache/sis/internal/storage/xml/stream/StaxDataStore.java
index 55cd4d4b32..3901ea351a 100644
--- a/storage/sis-xmlstore/src/main/java/org/apache/sis/internal/storage/xml/stream/StaxDataStore.java
+++ b/storage/sis-xmlstore/src/main/java/org/apache/sis/internal/storage/xml/stream/StaxDataStore.java
@@ -24,6 +24,7 @@ import java.util.logging.Filter;
 import java.io.Closeable;
 import java.io.IOException;
 import java.io.InputStream;
+import java.io.OutputStream;
 import java.nio.file.Path;
 import java.nio.charset.Charset;
 import java.nio.file.StandardOpenOption;
@@ -60,7 +61,7 @@ import org.apache.sis.storage.UnsupportedStorageException;
  *
  * @author  Johann Sorel (Geomatys)
  * @author  Martin Desruisseaux (Geomatys)
- * @version 0.8
+ * @version 1.3
  * @since   0.8
  * @module
  */
@@ -395,7 +396,7 @@ public abstract class StaxDataStore extends URIDataStore {
     /**
      * Returns the factory for StAX writers. The same instance is returned for all {@code StaxDataStore} lifetime.
      *
-     * <p>This method is indirectly invoked by {@link #createWriter(StaxStreamWriter)},
+     * <p>This method is indirectly invoked by {@link #createWriter(StaxStreamWriter, Object)},
      * through a call to {@link OutputType#create(StaxDataStore, Object)}.</p>
      */
     final XMLOutputFactory outputFactory() {
@@ -515,55 +516,65 @@ public abstract class StaxDataStore extends URIDataStore {
      * whether this method will succeed in creating a new writer depends on the storage type
      * (e.g. file or output stream).
      *
-     * @param  target  the writer which will store the {@code XMLStreamWriter} reference.
+     * @param  target     the writer which will store the {@code XMLStreamWriter} reference.
+     * @param  temporary  the temporary stream where to write, or {@code null} for the main storage.
      * @return a new writer for writing the XML data.
      * @throws DataStoreException if the output type is not recognized or the data store is closed.
      * @throws XMLStreamException if an error occurred while opening the XML file.
      * @throws IOException if an error occurred while preparing the output stream.
      */
-    final synchronized XMLStreamWriter createWriter(final StaxStreamWriter target)
+    final synchronized XMLStreamWriter createWriter(final StaxStreamWriter target, final OutputStream temporary)
             throws DataStoreException, XMLStreamException, IOException
     {
-        Object outputOrFile = storage;
-        if (outputOrFile == null) {
-            throw new DataStoreClosedException(getLocale(), getFormatName(), StandardOpenOption.WRITE);
-        }
-        switch (state) {
-            default:       throw new AssertionError(state);
-            case READING:  throw new ConcurrentReadException (getLocale(), getDisplayName());
-            case WRITING:  throw new ConcurrentWriteException(getLocale(), getDisplayName());
-            case START:    break;         // Stream already at the data start; nothing to do.
-            case FINISHED: {
-                if (reset()) break;
-                throw new ForwardOnlyStorageException(getLocale(), getDisplayName(), StandardOpenOption.WRITE);
+        AutoCloseable output;
+        Object outputOrFile;
+        OutputType outputType;
+        if (temporary == null) {
+            output       = stream;
+            outputOrFile = storage;
+            outputType   = storageToWriter;
+            if (outputOrFile == null) {
+                throw new DataStoreClosedException(getLocale(), getFormatName(), StandardOpenOption.WRITE);
             }
-        }
-        /*
-         * If the storage given by the user was not one of OutputStream, Writer or other type recognized
-         * by OutputType, then maybe that storage was a Path, File or URL, in which case the constructor
-         * should have opened an InputStream (not an OutputStream) for it. In some cases (e.g. reading a
-         * channel opened on a file), the input stream can be converted to an output stream.
-         */
-        AutoCloseable output = stream;
-        OutputType type = storageToWriter;
-        if (type == null) {
-            type   = OutputType.STREAM;
-            output = IOUtilities.toOutputStream(output);
-            if (output == null) {
-                throw new UnsupportedStorageException(getLocale(), getFormatName(), storage, StandardOpenOption.WRITE);
+            switch (state) {
+                default:       throw new AssertionError(state);
+                case READING:  throw new ConcurrentReadException (getLocale(), getDisplayName());
+                case WRITING:  throw new ConcurrentWriteException(getLocale(), getDisplayName());
+                case START:    break;         // Stream already at the data start; nothing to do.
+                case FINISHED: {
+                    if (reset()) break;
+                    throw new ForwardOnlyStorageException(getLocale(), getDisplayName(), StandardOpenOption.WRITE);
+                }
             }
-            outputOrFile = output;
-            if (output != stream) {
-                stream = output;
-                mark();
+            /*
+             * If the storage given by the user was not one of OutputStream, Writer or other type recognized
+             * by OutputType, then maybe that storage was a Path, File or URL, in which case the constructor
+             * should have opened an InputStream (not an OutputStream) for it. In some cases (e.g. reading a
+             * channel opened on a file), the input stream can be converted to an output stream.
+             */
+            if (outputType == null) {
+                outputType = OutputType.STREAM;
+                outputOrFile = output = IOUtilities.toOutputStream(output);
+                if (output == null) {
+                    throw new UnsupportedStorageException(getLocale(), getFormatName(), outputOrFile, StandardOpenOption.WRITE);
+                }
+                if (output != stream) {
+                    stream = output;
+                    mark();
+                }
             }
+        } else {
+            outputType = OutputType.STREAM;
+            outputOrFile = output = temporary;
         }
-        XMLStreamWriter writer = type.create(this, outputOrFile);
+        XMLStreamWriter writer = outputType.create(this, outputOrFile);
         if (indentation >= 0) {
             writer = new FormattedWriter(writer, indentation);
         }
         target.stream = output;
-        state = WRITING;
+        if (temporary == null) {
+            state = WRITING;
+        }
         return writer;
     }
 
diff --git a/storage/sis-xmlstore/src/main/java/org/apache/sis/internal/storage/xml/stream/StaxStreamWriter.java b/storage/sis-xmlstore/src/main/java/org/apache/sis/internal/storage/xml/stream/StaxStreamWriter.java
index 701812d20b..ece5ea44a5 100644
--- a/storage/sis-xmlstore/src/main/java/org/apache/sis/internal/storage/xml/stream/StaxStreamWriter.java
+++ b/storage/sis-xmlstore/src/main/java/org/apache/sis/internal/storage/xml/stream/StaxStreamWriter.java
@@ -20,6 +20,7 @@ import java.util.Map;
 import java.util.Date;
 import java.util.function.Consumer;
 import java.io.IOException;
+import java.io.OutputStream;
 import java.io.UncheckedIOException;
 import java.nio.charset.Charset;
 import javax.xml.namespace.QName;
@@ -85,7 +86,7 @@ import org.opengis.feature.Feature;
  *
  * @author  Johann Sorel (Geomatys)
  * @author  Martin Desruisseaux (Geomatys)
- * @version 0.8
+ * @version 1.3
  * @since   0.8
  * @module
  */
@@ -106,15 +107,18 @@ public abstract class StaxStreamWriter extends StaxStreamIO implements Consumer<
     /**
      * Creates a new XML writer for the given data store.
      *
-     * @param  owner  the data store for which this writer is created.
+     * @param  owner      the data store for which this writer is created.
+     * @param  temporary  the temporary stream where to write, or {@code null} for the main storage.
      * @throws DataStoreException if the output type is not recognized or the data store is closed.
      * @throws XMLStreamException if an error occurred while opening the XML file.
      * @throws IOException if an error occurred while preparing the output stream.
      */
     @SuppressWarnings("ThisEscapedInObjectConstruction")
-    protected StaxStreamWriter(final StaxDataStore owner) throws DataStoreException, XMLStreamException, IOException {
+    protected StaxStreamWriter(final StaxDataStore owner, final OutputStream temporary)
+            throws DataStoreException, XMLStreamException, IOException
+    {
         super(owner);
-        writer = owner.createWriter(this);      // Okay because will not store the 'this' reference.
+        writer = owner.createWriter(this, temporary);      // Okay because will not store the `this` reference.
     }
 
     /**
diff --git a/storage/sis-xmlstore/src/main/java/org/apache/sis/internal/storage/xml/stream/package-info.java b/storage/sis-xmlstore/src/main/java/org/apache/sis/internal/storage/xml/stream/package-info.java
index ac07cc8613..20f295c0f6 100644
--- a/storage/sis-xmlstore/src/main/java/org/apache/sis/internal/storage/xml/stream/package-info.java
+++ b/storage/sis-xmlstore/src/main/java/org/apache/sis/internal/storage/xml/stream/package-info.java
@@ -24,7 +24,7 @@
  *
  * @author  Johann Sorel (Geomatys)
  * @author  Martin Desruisseaux (Geomatys)
- * @version 1.0
+ * @version 1.3
  * @since   0.8
  * @module
  */
diff --git a/storage/sis-xmlstore/src/test/java/org/apache/sis/internal/storage/gpx/UpdaterTest.java b/storage/sis-xmlstore/src/test/java/org/apache/sis/internal/storage/gpx/UpdaterTest.java
new file mode 100644
index 0000000000..b951f5973e
--- /dev/null
+++ b/storage/sis-xmlstore/src/test/java/org/apache/sis/internal/storage/gpx/UpdaterTest.java
@@ -0,0 +1,179 @@
+/*
+ * 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.storage.gpx;
+
+import java.util.Arrays;
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.StandardOpenOption;
+import java.time.Instant;
+import com.esri.core.geometry.Point;
+import java.io.InputStream;
+import java.nio.file.StandardCopyOption;
+import org.apache.sis.setup.GeometryLibrary;
+import org.apache.sis.setup.OptionKey;
+import org.apache.sis.storage.DataStoreException;
+import org.apache.sis.storage.StorageConnector;
+import org.apache.sis.test.DependsOn;
+import org.apache.sis.test.TestCase;
+import org.junit.BeforeClass;
+import org.junit.AfterClass;
+import org.junit.Before;
+import org.junit.After;
+import org.junit.Test;
+
+import static org.apache.sis.test.MetadataAssert.*;
+
+// Branch-dependent imports
+import org.opengis.feature.Feature;
+
+
+/**
+ * Tests (indirectly) the {@link Updater} class.
+ *
+ * @author  Martin Desruisseaux (Geomatys)
+ * @version 1.3
+ *
+ * @see <a href="https://issues.apache.org/jira/browse/SIS-411">SIS-411</a>
+ *
+ * @since 1.3
+ * @module
+ */
+@DependsOn(WriterTest.class)
+public final strictfp class UpdaterTest extends TestCase {
+    /**
+     * The provider shared by all data stores created in this test class.
+     */
+    private static StoreProvider provider;
+
+    /**
+     * Creates the provider to be shared by all data stores created in this test class.
+     */
+    @BeforeClass
+    public static void createProvider() {
+        provider = new StoreProvider();
+    }
+
+    /**
+     * Disposes the data store provider after all tests have been completed.
+     */
+    @AfterClass
+    public static void disposeProvider() {
+        provider = null;
+    }
+
+    /**
+     * Temporary file where to write the GPX file.
+     */
+    private Path file;
+
+    /**
+     * Creates the temporary file before test execution.
+     *
+     * @throws IOException if the temporary file cannot be created.
+     */
+    @Before
+    public void createTemporaryFile() throws IOException {
+        file = Files.createTempFile("GPX", ".xml");
+    }
+
+    /**
+     * Deletes temporary file after test execution.
+     *
+     * @throws IOException if the temporary file cannot be deleted.
+     */
+    @After
+    public void deleteTemporaryFile() throws IOException {
+        if (file != null) {
+            Files.delete(file);
+        }
+    }
+
+    /**
+     * Creates a new GPX data store which will read and write in a temporary file.
+     */
+    private Store create() throws DataStoreException, IOException {
+        final StorageConnector connector = new StorageConnector(file);
+        connector.setOption(OptionKey.GEOMETRY_LIBRARY, GeometryLibrary.ESRI);
+        connector.setOption(OptionKey.OPEN_OPTIONS, new StandardOpenOption[] {
+                            StandardOpenOption.READ, StandardOpenOption.WRITE});
+        return new Store(provider, connector);
+    }
+
+    /**
+     * Tests writing in an initially empty file.
+     *
+     * @throws IOException if an error occurred while creating the temporary file.
+     * @throws DataStoreException if an error occurred while using the GPX store.
+     */
+    @Test
+    public void testWriteEmpty() throws DataStoreException, IOException {
+        try (final Store store = create()) {
+            final Types types = store.types;
+            final Feature point1 = types.wayPoint.newInstance();
+            final Feature point2 = types.wayPoint.newInstance();
+            final Feature point3 = types.wayPoint.newInstance();
+            point1.setPropertyValue("sis:geometry", new Point(15, 10));
+            point2.setPropertyValue("sis:geometry", new Point(25, 20));
+            point3.setPropertyValue("sis:geometry", new Point(35, 30));
+            point1.setPropertyValue("time", Instant.parse("2010-01-10T00:00:00Z"));
+            point3.setPropertyValue("time", Instant.parse("2010-01-30T00:00:00Z"));
+            store.add(Arrays.asList(point1, point2, point3).iterator());
+        }
+        assertXmlEquals(
+                "<gpx xmlns=\"" + Tags.NAMESPACE + "1/1\" version=\"1.1\">\n" +
+                "  <wpt lat=\"10.0\" lon=\"15.0\">\n" +
+                "    <time>2010-01-10T00:00:00Z</time>\n" +
+                "  </wpt>\n" +
+                "  <wpt lat=\"20.0\" lon=\"25.0\"/>\n" +
+                "  <wpt lat=\"30.0\" lon=\"35.0\">\n" +
+                "    <time>2010-01-30T00:00:00Z</time>\n" +
+                "  </wpt>\n" +
+                "</gpx>", file, "xmlns:*");
+    }
+
+    /**
+     * Tests an update which requires rewriting the XML file.
+     *
+     * @throws IOException if an error occurred while creating the temporary file.
+     * @throws DataStoreException if an error occurred while using the GPX store.
+     */
+    @Test
+    public void testRewrite() throws DataStoreException, IOException {
+        try (InputStream in = UpdaterTest.class.getResourceAsStream("1.1/waypoint.xml")) {
+            Files.copy(in, file, StandardCopyOption.REPLACE_EXISTING);
+        }
+        assertTrue(containsLat20());
+        final boolean result;
+        try (final Store store = create()) {
+            result = store.removeIf((feature) -> {
+                Object point = feature.getPropertyValue("sis:geometry");
+                return ((Point) point).getY() == 20;
+            });
+        }
+        assertTrue(result);
+        assertFalse(containsLat20());
+    }
+
+    /**
+     * Returns whether the temporary file contains the {@code lat="20"} string.
+     */
+    private boolean containsLat20() throws IOException {
+        return org.apache.sis.internal.jdk9.JDK9.readString(file).contains("lat=\"20");     // May have trailing ".0".
+    }
+}
diff --git a/storage/sis-xmlstore/src/test/java/org/apache/sis/test/suite/GPXTestSuite.java b/storage/sis-xmlstore/src/test/java/org/apache/sis/test/suite/GPXTestSuite.java
index e3ce9d97c7..f72d8132e3 100644
--- a/storage/sis-xmlstore/src/test/java/org/apache/sis/test/suite/GPXTestSuite.java
+++ b/storage/sis-xmlstore/src/test/java/org/apache/sis/test/suite/GPXTestSuite.java
@@ -28,7 +28,8 @@ import org.junit.BeforeClass;
     org.apache.sis.internal.storage.gpx.TypesTest.class,
     org.apache.sis.internal.storage.gpx.MetadataTest.class,
     org.apache.sis.internal.storage.gpx.ReaderTest.class,
-    org.apache.sis.internal.storage.gpx.WriterTest.class
+    org.apache.sis.internal.storage.gpx.WriterTest.class,
+    org.apache.sis.internal.storage.gpx.UpdaterTest.class
 })
 public final strictfp class GPXTestSuite extends TestSuite {
     /**


[sis] 01/02: Minor documentation fixes.

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 b7d5da1cc1740dffd6ae40048a5118e183229d14
Author: Martin Desruisseaux <ma...@geomatys.com>
AuthorDate: Sat Dec 3 11:04:10 2022 +0100

    Minor documentation fixes.
---
 .../src/main/java/org/apache/sis/internal/doclet/Preformat.java     | 2 +-
 .../src/main/java/org/apache/sis/internal/maven/Filenames.java      | 2 +-
 .../apache/sis/internal/coverage/j2d/MultiBandsIndexColorModel.java | 2 +-
 .../src/main/java/org/apache/sis/internal/feature/Resources.java    | 2 +-
 .../src/main/java/org/apache/sis/metadata/PropertyAccessor.java     | 4 ++--
 .../src/main/java/org/apache/sis/metadata/TreeNodeChildren.java     | 2 +-
 .../src/main/java/org/apache/sis/util/iso/DefaultNameFactory.java   | 2 +-
 .../src/main/java/org/apache/sis/util/iso/DefaultNameSpace.java     | 2 +-
 .../src/main/java/org/apache/sis/xml/ValueConverter.java            | 2 +-
 .../src/test/java/org/apache/sis/metadata/iso/MarshallingTest.java  | 2 +-
 core/sis-metadata/src/test/java/org/apache/sis/xml/XLinkTest.java   | 2 +-
 .../src/main/java/org/apache/sis/internal/gazetteer/Resources.java  | 4 ++--
 .../src/main/java/org/apache/sis/geometry/AbstractEnvelope.java     | 2 +-
 .../src/main/java/org/apache/sis/geometry/Envelope2D.java           | 2 +-
 .../main/java/org/apache/sis/internal/referencing/Resources.java    | 4 ++--
 .../apache/sis/referencing/operation/CoordinateOperationFinder.java | 2 +-
 .../apache/sis/referencing/operation/matrix/AffineTransforms2D.java | 4 ++--
 .../sis/referencing/operation/transform/AbstractMathTransform.java  | 2 +-
 .../sis/referencing/operation/transform/ConcatenatedTransform.java  | 2 +-
 .../referencing/operation/transform/ConcatenatedTransform2D.java    | 2 +-
 .../operation/transform/ConcatenatedTransformDirect2D.java          | 2 +-
 .../sis/referencing/operation/transform/LinearTransform1D.java      | 2 +-
 .../src/test/java/org/apache/sis/referencing/crs/HardCodedCRS.java  | 6 +++---
 .../referencing/operation/transform/ConcatenatedTransformTest.java  | 4 ++--
 .../sis/referencing/operation/transform/MathTransformWrapper.java   | 2 +-
 .../main/java/org/apache/sis/util/logging/MonolineFormatter.java    | 2 +-
 .../src/main/java/org/apache/sis/util/resources/Errors.java         | 2 +-
 .../java/org/apache/sis/util/resources/IndexedResourceBundle.java   | 2 +-
 .../src/main/java/org/apache/sis/util/resources/Messages.java       | 2 +-
 .../src/main/java/org/apache/sis/util/resources/Vocabulary.java     | 2 +-
 .../src/main/java/org/apache/sis/storage/netcdf/AttributeNames.java | 4 ++--
 .../org/apache/sis/internal/shapefile/jdbc/sql/ClauseResolver.java  | 2 +-
 .../java/org/apache/sis/internal/storage/image/FormatFinder.java    | 2 +-
 .../main/java/org/apache/sis/internal/storage/io/IOUtilities.java   | 2 +-
 .../src/main/java/org/apache/sis/storage/StorageConnector.java      | 2 +-
 35 files changed, 43 insertions(+), 43 deletions(-)

diff --git a/core/sis-build-helper/src/main/java/org/apache/sis/internal/doclet/Preformat.java b/core/sis-build-helper/src/main/java/org/apache/sis/internal/doclet/Preformat.java
index 560ebc1fac..0ca8c07a00 100644
--- a/core/sis-build-helper/src/main/java/org/apache/sis/internal/doclet/Preformat.java
+++ b/core/sis-build-helper/src/main/java/org/apache/sis/internal/doclet/Preformat.java
@@ -50,7 +50,7 @@ public final class Preformat extends Taglet {
      * Special characters to replace by HTML entities.
      */
     private static final String[] SPECIAL_CHARS = new String[] {
-        "&#64;", "@", // Because we can't use @ directly in {@preformat}.
+        "&#64;", "@", // Because we cannot use @ directly in {@preformat}.
         "&",     "&amp;",
         "<",     "&lt;",
         ">",     "&gt;"
diff --git a/core/sis-build-helper/src/main/java/org/apache/sis/internal/maven/Filenames.java b/core/sis-build-helper/src/main/java/org/apache/sis/internal/maven/Filenames.java
index 27a144d430..317f0d10e5 100644
--- a/core/sis-build-helper/src/main/java/org/apache/sis/internal/maven/Filenames.java
+++ b/core/sis-build-helper/src/main/java/org/apache/sis/internal/maven/Filenames.java
@@ -104,7 +104,7 @@ final class Filenames {
         final File outDirectory = new File(new File(rootDirectory, TARGET_DIRECTORY), DISTRIBUTION_DIRECTORY);
         if (!outDirectory.isDirectory()) {
             if (!outDirectory.mkdir()) {
-                throw new MojoExecutionException("Can't create the \"" + DISTRIBUTION_DIRECTORY + "\" directory.");
+                throw new MojoExecutionException("Cannot create the \"" + DISTRIBUTION_DIRECTORY + "\" directory.");
             }
         }
         return new File(outDirectory, filename);
diff --git a/core/sis-feature/src/main/java/org/apache/sis/internal/coverage/j2d/MultiBandsIndexColorModel.java b/core/sis-feature/src/main/java/org/apache/sis/internal/coverage/j2d/MultiBandsIndexColorModel.java
index 11885f3243..43eb231687 100644
--- a/core/sis-feature/src/main/java/org/apache/sis/internal/coverage/j2d/MultiBandsIndexColorModel.java
+++ b/core/sis-feature/src/main/java/org/apache/sis/internal/coverage/j2d/MultiBandsIndexColorModel.java
@@ -35,7 +35,7 @@ import java.awt.image.SinglePixelPackedSampleModel;
  *
  * <p><b>Reminder:</b> {@link #getNumComponents()} will return 3 or 4 no matter
  * how many bands were specified to the constructor. This is not specific to this class;
- * {@code IndexColorModel} behave that way. So we can't rely on this method for checking
+ * {@code IndexColorModel} behave that way. So we cannot rely on this method for checking
  * the number of bands.</p>
  *
  * @author  Martin Desruisseaux (IRD, Geomatys)
diff --git a/core/sis-feature/src/main/java/org/apache/sis/internal/feature/Resources.java b/core/sis-feature/src/main/java/org/apache/sis/internal/feature/Resources.java
index 63115fde43..af18fe363f 100644
--- a/core/sis-feature/src/main/java/org/apache/sis/internal/feature/Resources.java
+++ b/core/sis-feature/src/main/java/org/apache/sis/internal/feature/Resources.java
@@ -504,7 +504,7 @@ public final class Resources extends IndexedResourceBundle {
      *
      * @param  properties  the map of properties, or {@code null} if none.
      * @return resources in the given locale.
-     * @throws MissingResourceException if resources can't be found.
+     * @throws MissingResourceException if resources cannot be found.
      *
      * @since 0.4
      */
diff --git a/core/sis-metadata/src/main/java/org/apache/sis/metadata/PropertyAccessor.java b/core/sis-metadata/src/main/java/org/apache/sis/metadata/PropertyAccessor.java
index 66d591fd63..cd2825b9cf 100644
--- a/core/sis-metadata/src/main/java/org/apache/sis/metadata/PropertyAccessor.java
+++ b/core/sis-metadata/src/main/java/org/apache/sis/metadata/PropertyAccessor.java
@@ -969,7 +969,7 @@ class PropertyAccessor {
         Object newValue = newValues[0];
         Class<?> targetType = getter.getReturnType();
         if (newValue == null) {
-            // Can't test elementType, because it has been converted to the wrapper class.
+            // Cannot test elementType, because it has been converted to the wrapper class.
             if (targetType.isPrimitive()) {
                 newValues[0] = Numbers.valueOfNil(targetType);
             }
@@ -1073,7 +1073,7 @@ class PropertyAccessor {
      *
      * @param  elements    the array which contains element to convert.
      * @param  targetType  the base type of target elements.
-     * @throws ClassCastException if an element can't be converted.
+     * @throws ClassCastException if an element cannot be converted.
      */
     @SuppressWarnings({"unchecked","rawtypes"})
     private void convert(final Object[] elements, final Class<?> targetType) throws ClassCastException {
diff --git a/core/sis-metadata/src/main/java/org/apache/sis/metadata/TreeNodeChildren.java b/core/sis-metadata/src/main/java/org/apache/sis/metadata/TreeNodeChildren.java
index 1199670c68..8ccfed083b 100644
--- a/core/sis-metadata/src/main/java/org/apache/sis/metadata/TreeNodeChildren.java
+++ b/core/sis-metadata/src/main/java/org/apache/sis/metadata/TreeNodeChildren.java
@@ -123,7 +123,7 @@ final class TreeNodeChildren extends AbstractCollection<TreeTable.Node> {
 
     /**
      * Modification count, incremented when the content of this collection is modified. This check
-     * is done on a <cite>best effort basis</cite> only, since we can't not track the changes which
+     * is done on a <cite>best effort basis</cite> only, since we cannot not track the changes which
      * are done independently in the {@linkplain #metadata} object.
      */
     int modCount;
diff --git a/core/sis-metadata/src/main/java/org/apache/sis/util/iso/DefaultNameFactory.java b/core/sis-metadata/src/main/java/org/apache/sis/util/iso/DefaultNameFactory.java
index e0e690f796..f7a20de0e8 100644
--- a/core/sis-metadata/src/main/java/org/apache/sis/util/iso/DefaultNameFactory.java
+++ b/core/sis-metadata/src/main/java/org/apache/sis/util/iso/DefaultNameFactory.java
@@ -365,7 +365,7 @@ public class DefaultNameFactory extends AbstractFactory implements NameFactory {
      * @param  value  the object to cast into an array of generic names, or {@code null}.
      * @return the generic names, or {@code null} if the given {@code value} was null.
      *         Note that it may be the {@code value} reference itself casted to {@code GenericName[]}.
-     * @throws ClassCastException if {@code value} can't be casted.
+     * @throws ClassCastException if {@code value} cannot be casted.
      *
      * @since 0.5
      */
diff --git a/core/sis-metadata/src/main/java/org/apache/sis/util/iso/DefaultNameSpace.java b/core/sis-metadata/src/main/java/org/apache/sis/util/iso/DefaultNameSpace.java
index a8d2eade67..99ef3143be 100644
--- a/core/sis-metadata/src/main/java/org/apache/sis/util/iso/DefaultNameSpace.java
+++ b/core/sis-metadata/src/main/java/org/apache/sis/util/iso/DefaultNameSpace.java
@@ -443,7 +443,7 @@ public class DefaultNameSpace implements NameSpace, Serializable {
                 {
                     child = new DefaultNameSpace(this, name, headSeparator, separator);
                     /*
-                     * Do not cache that instance. Actually we can't guess if that instance
+                     * Do not cache that instance. Actually we cannot guess if that instance
                      * would be more appropriate for caching purpose than the old one. We
                      * just assume that keeping the oldest one is more conservative.
                      */
diff --git a/core/sis-metadata/src/main/java/org/apache/sis/xml/ValueConverter.java b/core/sis-metadata/src/main/java/org/apache/sis/xml/ValueConverter.java
index f5b8d3974b..420e05cbc5 100644
--- a/core/sis-metadata/src/main/java/org/apache/sis/xml/ValueConverter.java
+++ b/core/sis-metadata/src/main/java/org/apache/sis/xml/ValueConverter.java
@@ -98,7 +98,7 @@ public class ValueConverter {
      *
      * @param  <T>         the compile-time type of the {@code sourceType} argument.
      * @param  context     context (GML version, locale, <i>etc.</i>) of the (un)marshalling process.
-     * @param  value       the value that can't be converted.
+     * @param  value       the value that cannot be converted.
      * @param  sourceType  the base type of the value to convert. This is determined by the argument type of the method
      *                     that caught the exception. For example, the source type is always {@code URI.class}
      *                     if the exception has been caught by the {@link #toURL(MarshalContext, URI)} method.
diff --git a/core/sis-metadata/src/test/java/org/apache/sis/metadata/iso/MarshallingTest.java b/core/sis-metadata/src/test/java/org/apache/sis/metadata/iso/MarshallingTest.java
index 2b110691d3..ddf453bd58 100644
--- a/core/sis-metadata/src/test/java/org/apache/sis/metadata/iso/MarshallingTest.java
+++ b/core/sis-metadata/src/test/java/org/apache/sis/metadata/iso/MarshallingTest.java
@@ -644,7 +644,7 @@ public final class MarshallingTest extends TestUsingFile implements Filter {
 
     /**
      * Invoked when a warning occurred while marshalling a test XML fragment. Expected warnings are
-     * "Can't find resource for bundle {@code java.util.PropertyResourceBundle}, key <cite>Foo</cite>".
+     * "Cannot find resource for bundle {@code java.util.PropertyResourceBundle}, key <cite>Foo</cite>".
      * When marshalling legacy XML only, additional warnings may occur.
      *
      * @param  warning  the warning.
diff --git a/core/sis-metadata/src/test/java/org/apache/sis/xml/XLinkTest.java b/core/sis-metadata/src/test/java/org/apache/sis/xml/XLinkTest.java
index fd701adc51..53ad676fc8 100644
--- a/core/sis-metadata/src/test/java/org/apache/sis/xml/XLinkTest.java
+++ b/core/sis-metadata/src/test/java/org/apache/sis/xml/XLinkTest.java
@@ -77,7 +77,7 @@ public final strictfp class XLinkTest extends TestCase {
         assertFalse("Hash code cannot be zero.", hashCode == 0);
 
         link.setShow(XLink.Show.NEW);
-        assertNull("Can't be Type.SIMPLE if a label is defined.", link.getType());
+        assertNull("Cannot be Type.SIMPLE if a label is defined.", link.getType());
         assertEquals("XLink[href=\"org:apache:sis:href\", role=\"org:apache:sis:role\", title=\"Some title\", show=\"new\", label=\"SomeLabel\"]", link.toString());
         assertFalse("Hash code should have changed.", hashCode == (hashCode = link.hashCode()));
         assertFalse("Hash code cannot be zero.", hashCode == 0);
diff --git a/core/sis-referencing-by-identifiers/src/main/java/org/apache/sis/internal/gazetteer/Resources.java b/core/sis-referencing-by-identifiers/src/main/java/org/apache/sis/internal/gazetteer/Resources.java
index 53b9bb2343..1e4efb5ade 100644
--- a/core/sis-referencing-by-identifiers/src/main/java/org/apache/sis/internal/gazetteer/Resources.java
+++ b/core/sis-referencing-by-identifiers/src/main/java/org/apache/sis/internal/gazetteer/Resources.java
@@ -166,7 +166,7 @@ public final class Resources extends IndexedResourceBundle {
      *
      * @param  locale  the locale, or {@code null} for the default locale.
      * @return resources in the given locale.
-     * @throws MissingResourceException if resources can't be found.
+     * @throws MissingResourceException if resources cannot be found.
      */
     public static Resources forLocale(final Locale locale) throws MissingResourceException {
         return getBundle(Resources.class, locale);
@@ -179,7 +179,7 @@ public final class Resources extends IndexedResourceBundle {
      *
      * @param  properties  the map of properties, or {@code null} if none.
      * @return resources in the given locale.
-     * @throws MissingResourceException if resources can't be found.
+     * @throws MissingResourceException if resources cannot be found.
      */
     public static Resources forProperties(final Map<?,?> properties) throws MissingResourceException {
         return forLocale(getLocale(properties));
diff --git a/core/sis-referencing/src/main/java/org/apache/sis/geometry/AbstractEnvelope.java b/core/sis-referencing/src/main/java/org/apache/sis/geometry/AbstractEnvelope.java
index 3a04dd63f7..db823c6fad 100644
--- a/core/sis-referencing/src/main/java/org/apache/sis/geometry/AbstractEnvelope.java
+++ b/core/sis-referencing/src/main/java/org/apache/sis/geometry/AbstractEnvelope.java
@@ -535,7 +535,7 @@ public abstract class AbstractEnvelope extends FormattableObject implements Enve
      * @param  unit  the unit for the return value.
      * @return the span in terms of the given unit.
      * @throws IndexOutOfBoundsException if the given index is out of bounds.
-     * @throws IncommensurableException if the length can't be converted to the specified units.
+     * @throws IncommensurableException if the length cannot be converted to the specified units.
      */
     public double getSpan(final int dimension, final Unit<?> unit)
             throws IndexOutOfBoundsException, IncommensurableException
diff --git a/core/sis-referencing/src/main/java/org/apache/sis/geometry/Envelope2D.java b/core/sis-referencing/src/main/java/org/apache/sis/geometry/Envelope2D.java
index ec9805fb8b..e9d8a34c6e 100644
--- a/core/sis-referencing/src/main/java/org/apache/sis/geometry/Envelope2D.java
+++ b/core/sis-referencing/src/main/java/org/apache/sis/geometry/Envelope2D.java
@@ -40,7 +40,7 @@ import static org.apache.sis.math.MathFunctions.isNegativeZero;
 import static org.apache.sis.util.ArgumentChecks.ensureDimensionMatches;
 import static org.apache.sis.internal.referencing.Formulas.isPoleToPole;
 
-// Following imports are needed because we can't extend AbstractEnvelope.
+// Following imports are needed because we cannot extend AbstractEnvelope.
 // We want to write this class as if it was an AbstractEnvelope subclass.
 import static org.apache.sis.geometry.AbstractEnvelope.getAxis;
 import static org.apache.sis.geometry.AbstractEnvelope.getCommonCRS;
diff --git a/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/Resources.java b/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/Resources.java
index f469d3d014..e971562b92 100644
--- a/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/Resources.java
+++ b/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/Resources.java
@@ -618,7 +618,7 @@ public final class Resources extends IndexedResourceBundle {
      *
      * @param  locale  the locale, or {@code null} for the default locale.
      * @return resources in the given locale.
-     * @throws MissingResourceException if resources can't be found.
+     * @throws MissingResourceException if resources cannot be found.
      */
     public static Resources forLocale(final Locale locale) throws MissingResourceException {
         return getBundle(Resources.class, locale);
@@ -631,7 +631,7 @@ public final class Resources extends IndexedResourceBundle {
      *
      * @param  properties  the map of properties, or {@code null} if none.
      * @return resources in the given locale.
-     * @throws MissingResourceException if resources can't be found.
+     * @throws MissingResourceException if resources cannot be found.
      */
     public static Resources forProperties(final Map<?,?> properties) throws MissingResourceException {
         return forLocale(getLocale(properties));
diff --git a/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/CoordinateOperationFinder.java b/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/CoordinateOperationFinder.java
index d535142f81..8ef1e80388 100644
--- a/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/CoordinateOperationFinder.java
+++ b/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/CoordinateOperationFinder.java
@@ -1091,7 +1091,7 @@ public class CoordinateOperationFinder extends CoordinateOperationRegistry {
      * @param  step1  the first  step, or {@code null} for the identity operation.
      * @param  step2  the second step, or {@code null} for the identity operation.
      * @return a concatenated operation, or {@code null} if all arguments were null.
-     * @throws FactoryException if the operation can't be constructed.
+     * @throws FactoryException if the operation cannot be constructed.
      */
     private CoordinateOperation concatenate(final CoordinateOperation step1,
                                             final CoordinateOperation step2)
diff --git a/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/matrix/AffineTransforms2D.java b/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/matrix/AffineTransforms2D.java
index 9a910a5209..768058e244 100644
--- a/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/matrix/AffineTransforms2D.java
+++ b/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/matrix/AffineTransforms2D.java
@@ -268,7 +268,7 @@ public final class AffineTransforms2D extends Static {
      *                    this rectangle will not be modified except if {@code dest} references the same object.
      * @param  dest       rectangle in which to place the result. If {@code null}, a new rectangle will be created.
      * @return the inverse transform of the {@code bounds} rectangle, or {@code null} if {@code bounds} was null.
-     * @throws NoninvertibleTransformException if the affine transform can't be inverted.
+     * @throws NoninvertibleTransformException if the affine transform cannot be inverted.
      */
     public static Rectangle2D inverseTransform(final AffineTransform transform,
             final Rectangle2D bounds, final Rectangle2D dest) throws NoninvertibleTransformException
@@ -307,7 +307,7 @@ public final class AffineTransforms2D extends Static {
      *                    this point will not be modified except if {@code dest} references the same object.
      * @param  dest       point in which to place the result. If {@code null}, a new point will be created.
      * @return the inverse transform of the {@code vector}, or {@code null} if {@code source} was null.
-     * @throws NoninvertibleTransformException if the affine transform can't be inverted.
+     * @throws NoninvertibleTransformException if the affine transform cannot be inverted.
      */
     public static Point2D inverseDeltaTransform(final AffineTransform transform,
             final Point2D vector, final Point2D dest) throws NoninvertibleTransformException
diff --git a/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/transform/AbstractMathTransform.java b/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/transform/AbstractMathTransform.java
index 8ad28d4391..917a028f12 100644
--- a/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/transform/AbstractMathTransform.java
+++ b/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/transform/AbstractMathTransform.java
@@ -527,7 +527,7 @@ public abstract class AbstractMathTransform extends FormattableObject
      *                 May be the same than {@code srcPts}.
      * @param  dstOff  the offset to the location of the first transformed point that is stored in the destination array.
      * @param  numPts  the number of point objects to be transformed.
-     * @throws TransformException if a point can't be transformed. Some implementations will stop at the first failure,
+     * @throws TransformException if a point cannot be transformed. Some implementations will stop at the first failure,
      *         wile some other implementations will fill the un-transformable points with {@link Float#NaN} values,
      *         continue and throw the exception only at end. Implementations that fall in the latter case should set
      *         the {@linkplain TransformException#getLastCompletedTransform last completed transform} to {@code this}.
diff --git a/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/transform/ConcatenatedTransform.java b/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/transform/ConcatenatedTransform.java
index 2d6084806d..db143e9bc2 100644
--- a/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/transform/ConcatenatedTransform.java
+++ b/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/transform/ConcatenatedTransform.java
@@ -848,7 +848,7 @@ class ConcatenatedTransform extends AbstractMathTransform implements Serializabl
      *
      * @param  point  the position where to evaluate the derivative.
      * @return the derivative at the specified point (never {@code null}).
-     * @throws TransformException if the derivative can't be evaluated at the specified point.
+     * @throws TransformException if the derivative cannot be evaluated at the specified point.
      */
     @Override
     public Matrix derivative(final DirectPosition point) throws TransformException {
diff --git a/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/transform/ConcatenatedTransform2D.java b/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/transform/ConcatenatedTransform2D.java
index 5d1532765f..ed225f27d0 100644
--- a/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/transform/ConcatenatedTransform2D.java
+++ b/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/transform/ConcatenatedTransform2D.java
@@ -95,7 +95,7 @@ final class ConcatenatedTransform2D extends ConcatenatedTransform implements Mat
      *
      * @param  point  the position where to evaluate the derivative.
      * @return the derivative at the specified point as a 2×2 matrix.
-     * @throws TransformException if the derivative can't be evaluated at the specified point.
+     * @throws TransformException if the derivative cannot be evaluated at the specified point.
      */
     @Override
     public Matrix derivative(final Point2D point) throws TransformException {
diff --git a/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/transform/ConcatenatedTransformDirect2D.java b/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/transform/ConcatenatedTransformDirect2D.java
index 9a8761e5a0..83c8aef745 100644
--- a/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/transform/ConcatenatedTransformDirect2D.java
+++ b/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/transform/ConcatenatedTransformDirect2D.java
@@ -87,7 +87,7 @@ final class ConcatenatedTransformDirect2D extends ConcatenatedTransformDirect im
      *
      * @param  point  the position where to evaluate the derivative.
      * @return the derivative at the specified point (never {@code null}).
-     * @throws TransformException if the derivative can't be evaluated at the specified point.
+     * @throws TransformException if the derivative cannot be evaluated at the specified point.
      */
     @Override
     public Matrix derivative(final Point2D point) throws TransformException {
diff --git a/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/transform/LinearTransform1D.java b/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/transform/LinearTransform1D.java
index 2e40cbb9b8..6b9c7577d0 100644
--- a/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/transform/LinearTransform1D.java
+++ b/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/transform/LinearTransform1D.java
@@ -372,7 +372,7 @@ class LinearTransform1D extends AbstractMathTransform1D implements LinearTransfo
              * NOTE: 'LinearTransform1D' and 'ConstantTransform1D' are heavily used by 'Category'
              * from 'org.apache.sis.coverage' package. It is essential for Cateory to differenciate
              * various NaN values. Because 'equals' is used by WeakHashSet.unique(Object) (which
-             * is used by 'DefaultMathTransformFactory'), test for equality can't use the non-raw
+             * is used by 'DefaultMathTransformFactory'), test for equality cannot use the non-raw
              * doubleToLongBits method because it collapse all NaN into a single canonical value.
              * The 'doubleToRawLongBits' method instead provides the needed functionality.
              */
diff --git a/core/sis-referencing/src/test/java/org/apache/sis/referencing/crs/HardCodedCRS.java b/core/sis-referencing/src/test/java/org/apache/sis/referencing/crs/HardCodedCRS.java
index af947fefcb..4717314852 100644
--- a/core/sis-referencing/src/test/java/org/apache/sis/referencing/crs/HardCodedCRS.java
+++ b/core/sis-referencing/src/test/java/org/apache/sis/referencing/crs/HardCodedCRS.java
@@ -234,7 +234,7 @@ public final strictfp class HardCodedCRS {
     /**
      * A two-dimensional Cartesian coordinate reference system with (x,y) axes in metres.
      * By default, this CRS has no transformation path to any other CRS (i.e. a map using
-     * this CS can't be reprojected to a geographic coordinate reference system for example).
+     * this CS cannot be reprojected to a geographic coordinate reference system for example).
      */
     public static final DefaultEngineeringCRS CARTESIAN_2D = new DefaultEngineeringCRS(
             getProperties(HardCodedCS.CARTESIAN_2D), HardCodedDatum.UNKNOWN, HardCodedCS.CARTESIAN_2D);
@@ -242,7 +242,7 @@ public final strictfp class HardCodedCRS {
     /**
      * A two-dimensional Cartesian coordinate reference system with (x,y,z) axes in metres.
      * By default, this CRS has no transformation path to any other CRS (i.e. a map using
-     * this CS can't be reprojected to a geographic coordinate reference system for example).
+     * this CS cannot be reprojected to a geographic coordinate reference system for example).
      */
     public static final DefaultEngineeringCRS CARTESIAN_3D = new DefaultEngineeringCRS(
             getProperties(HardCodedCS.CARTESIAN_3D), HardCodedDatum.UNKNOWN, HardCodedCS.CARTESIAN_3D);
@@ -339,7 +339,7 @@ public final strictfp class HardCodedCRS {
     /**
      * A two-dimensional Cartesian coordinate reference system with (column, row) axes.
      * By default, this CRS has no transformation path to any other CRS (i.e. a map using
-     * this CS can't be reprojected to a geographic coordinate reference system for example).
+     * this CS cannot be reprojected to a geographic coordinate reference system for example).
      *
      * <p>The {@code PixelInCell} attribute of the associated {@code ImageDatum}
      * is set to {@link PixelInCell#CELL_CENTER}.</p>
diff --git a/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/transform/ConcatenatedTransformTest.java b/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/transform/ConcatenatedTransformTest.java
index 07319b98fe..da29f2ed6c 100644
--- a/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/transform/ConcatenatedTransformTest.java
+++ b/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/transform/ConcatenatedTransformTest.java
@@ -74,11 +74,11 @@ public final strictfp class ConcatenatedTransformTest extends MathTransformTestC
         validate();
         verifyTransform(source, target);
 
-        // Direct for general case - can't be validated.
+        // Direct for general case - cannot be validated.
         transform = new ConcatenatedTransformDirect(first, second);
         verifyTransform(source, target);
 
-        // Most general case - can't be validated.
+        // Most general case - cannot be validated.
         transform = new ConcatenatedTransform(first, second);
         verifyTransform(source, target);
 
diff --git a/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/transform/MathTransformWrapper.java b/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/transform/MathTransformWrapper.java
index 17336573d6..e2564b1135 100644
--- a/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/transform/MathTransformWrapper.java
+++ b/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/transform/MathTransformWrapper.java
@@ -192,7 +192,7 @@ public strictfp class MathTransformWrapper extends FormattableObject implements
     /**
      * Returns a <cite>Well Known Text</cite> (WKT) for this transform.
      *
-     * @throws UnsupportedOperationException if this object can't be formatted as WKT.
+     * @throws UnsupportedOperationException if this object cannot be formatted as WKT.
      */
     @Override
     public final String toWKT() throws UnsupportedOperationException {
diff --git a/core/sis-utility/src/main/java/org/apache/sis/util/logging/MonolineFormatter.java b/core/sis-utility/src/main/java/org/apache/sis/util/logging/MonolineFormatter.java
index d35f3da7f6..97bd7adc04 100644
--- a/core/sis-utility/src/main/java/org/apache/sis/util/logging/MonolineFormatter.java
+++ b/core/sis-utility/src/main/java/org/apache/sis/util/logging/MonolineFormatter.java
@@ -151,7 +151,7 @@ public class MonolineFormatter extends Formatter {
      * A comparator for logging level. This comparator sorts finest levels first and severe levels last.
      */
     private static final Comparator<Level> COMPARATOR = (final Level l1, final Level l2) -> {
-        // We can't just return (i1 - i2) because some levels are
+        // We cannot just return (i1 - i2) because some levels are
         // Integer.MIN_VALUE or Integer.MAX_VALUE, which cause overflow.
         final int i1 = l1.intValue();
         final int i2 = l2.intValue();
diff --git a/core/sis-utility/src/main/java/org/apache/sis/util/resources/Errors.java b/core/sis-utility/src/main/java/org/apache/sis/util/resources/Errors.java
index 35e81a1c00..22316a1a01 100644
--- a/core/sis-utility/src/main/java/org/apache/sis/util/resources/Errors.java
+++ b/core/sis-utility/src/main/java/org/apache/sis/util/resources/Errors.java
@@ -1111,7 +1111,7 @@ public final class Errors extends IndexedResourceBundle {
      *
      * @param  properties  the map of properties, or {@code null} if none.
      * @return resources in the given locale.
-     * @throws MissingResourceException if resources can't be found.
+     * @throws MissingResourceException if resources cannot be found.
      *
      * @since 0.4
      */
diff --git a/core/sis-utility/src/main/java/org/apache/sis/util/resources/IndexedResourceBundle.java b/core/sis-utility/src/main/java/org/apache/sis/util/resources/IndexedResourceBundle.java
index 5cc87e623c..5beaaf851d 100644
--- a/core/sis-utility/src/main/java/org/apache/sis/util/resources/IndexedResourceBundle.java
+++ b/core/sis-utility/src/main/java/org/apache/sis/util/resources/IndexedResourceBundle.java
@@ -154,7 +154,7 @@ public class IndexedResourceBundle extends ResourceBundle implements Localized {
      * @param  base     the resource bundle class.
      * @param  locale   the locale, or {@code null} for the default locale.
      * @return resources in the given locale.
-     * @throws MissingResourceException if resources can't be found.
+     * @throws MissingResourceException if resources cannot be found.
      *
      * @see Vocabulary#getResources(Locale)
      * @see Errors#getResources(Locale)
diff --git a/core/sis-utility/src/main/java/org/apache/sis/util/resources/Messages.java b/core/sis-utility/src/main/java/org/apache/sis/util/resources/Messages.java
index e13cacf163..58fad5df53 100644
--- a/core/sis-utility/src/main/java/org/apache/sis/util/resources/Messages.java
+++ b/core/sis-utility/src/main/java/org/apache/sis/util/resources/Messages.java
@@ -258,7 +258,7 @@ public final class Messages extends IndexedResourceBundle {
      *
      * @param  locale  the locale, or {@code null} for the default locale.
      * @return resources in the given locale.
-     * @throws MissingResourceException if resources can't be found.
+     * @throws MissingResourceException if resources cannot be found.
      */
     public static Messages getResources(final Locale locale) throws MissingResourceException {
         return getBundle(Messages.class, locale);
diff --git a/core/sis-utility/src/main/java/org/apache/sis/util/resources/Vocabulary.java b/core/sis-utility/src/main/java/org/apache/sis/util/resources/Vocabulary.java
index 4a50e661f6..4f82def088 100644
--- a/core/sis-utility/src/main/java/org/apache/sis/util/resources/Vocabulary.java
+++ b/core/sis-utility/src/main/java/org/apache/sis/util/resources/Vocabulary.java
@@ -1435,7 +1435,7 @@ public final class Vocabulary extends IndexedResourceBundle {
      *
      * @param  locale  the locale, or {@code null} for the default locale.
      * @return resources in the given locale.
-     * @throws MissingResourceException if resources can't be found.
+     * @throws MissingResourceException if resources cannot be found.
      */
     public static Vocabulary getResources(final Locale locale) throws MissingResourceException {
         return getBundle(Vocabulary.class, locale);
diff --git a/storage/sis-netcdf/src/main/java/org/apache/sis/storage/netcdf/AttributeNames.java b/storage/sis-netcdf/src/main/java/org/apache/sis/storage/netcdf/AttributeNames.java
index 777156f777..4df0a85eab 100644
--- a/storage/sis-netcdf/src/main/java/org/apache/sis/storage/netcdf/AttributeNames.java
+++ b/storage/sis-netcdf/src/main/java/org/apache/sis/storage/netcdf/AttributeNames.java
@@ -58,7 +58,7 @@ import org.opengis.metadata.extent.GeographicDescription;
  * The attributes recognized by SIS are listed below:
  *
  * <blockquote><table class="compact">
- * <caption>List of all netCDF attributes</caption>
+ * <caption>List of all netCDF attributes mapped by Apache SIS to ISO 19115 metadata</caption>
  * <tr style="vertical-align:top"><td style="width: 25%">
  * {@value     #ACCESS_CONSTRAINT}<br>
  * {@value     #ACKNOWLEDGEMENT}<br>
@@ -135,7 +135,7 @@ import org.opengis.metadata.extent.GeographicDescription;
  *
  * <h2>References</h2>
  * <ul>
- *   <li><a href="http://wiki.esipfed.org/index.php/Category:Attribute_Conventions_Dataset_Discovery">NetCDF
+ *   <li><a href="https://wiki.esipfed.org/Category:Attribute_Conventions_Dataset_Discovery">NetCDF
  *       Attribute Convention for Dataset Discovery</a> wiki</li>
  * </ul>
  *
diff --git a/storage/sis-shapefile/src/main/java/org/apache/sis/internal/shapefile/jdbc/sql/ClauseResolver.java b/storage/sis-shapefile/src/main/java/org/apache/sis/internal/shapefile/jdbc/sql/ClauseResolver.java
index fa92bd2d20..f978ff3c36 100644
--- a/storage/sis-shapefile/src/main/java/org/apache/sis/internal/shapefile/jdbc/sql/ClauseResolver.java
+++ b/storage/sis-shapefile/src/main/java/org/apache/sis/internal/shapefile/jdbc/sql/ClauseResolver.java
@@ -228,7 +228,7 @@ public abstract class ClauseResolver extends AutoChecker {
             return compare(rs, value1, value2);
         else
         {
-            // if one value doesn't match to a type, we can't perform the comparison.
+            // if one value doesn't match to a type, we cannot perform the comparison.
             if ((w1 == false && b1 == false) || (w2 == false && b2 == false))
                return null;
             else {
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 72a63613bf..971f6a197b 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
@@ -291,7 +291,7 @@ final class FormatFinder implements AutoCloseable {
      *
      * <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.
+     * them in an {@link IIOException} with <cite>"Cannot 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
diff --git a/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/io/IOUtilities.java b/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/io/IOUtilities.java
index 7be19a3d6c..3df8039a40 100644
--- a/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/io/IOUtilities.java
+++ b/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/io/IOUtilities.java
@@ -475,7 +475,7 @@ public final class IOUtilities extends Static {
      *                   then {@code null}. This argument is ignored if the given path does not need
      *                   to be converted from URL to {@code File}.
      * @return the path as a {@link File} if possible, or a {@link URL} otherwise.
-     * @throws IOException if the given path is not a file and can't be parsed as a URL.
+     * @throws IOException if the given path is not a file and cannot be parsed as a URL.
      */
     public static Object toFileOrURL(final String path, final String encoding) throws IOException {
         if (path == null) {
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 a5e1d5851d..c5619f6e55 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
@@ -1616,7 +1616,7 @@ public class StorageConnector implements Serializable {
      *
      * <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.
+     * them in an {@link IIOException} with <cite>"Cannot 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