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

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

commit 2b6eec917a34e971fa73968d92a9a67c54da0075
Merge: ea85b9b19d 05a9bb3b01
Author: Martin Desruisseaux <martin.desruisse...@geomatys.com>
AuthorDate: Tue Dec 6 20:10:34 2022 +0100

    Merge branch 'geoapi-3.1'

 .../apache/sis/console/FormattedOutputCommand.java |   2 +-
 .../org/apache/sis/console/CRSCommandTest.java     |  14 +-
 .../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 +-
 .../internal/jaxb/metadata/DQM_Description.java    |  18 +-
 .../sis/internal/jaxb/metadata/DQM_Measure.java    |  18 +-
 .../DQ_StandaloneQualityReportInformation.java     |  18 +-
 .../jaxb/metadata/replace/QualityParameter.java    |   4 +-
 .../sis/internal/simple/CitationConstant.java      |  12 +-
 .../org/apache/sis/metadata/PropertyAccessor.java  |   4 +-
 .../org/apache/sis/metadata/TreeNodeChildren.java  |   2 +-
 .../sis/metadata/iso/quality/AbstractElement.java  |   6 +-
 .../metadata/iso/quality/DefaultBasicMeasure.java  |   6 +-
 .../metadata/iso/quality/DefaultDataQuality.java   |   6 +-
 ...ava => DefaultEvaluationReportInformation.java} |   6 +-
 ...ription.java => DefaultMeasureDescription.java} |   8 +-
 ...aultMeasure.java => DefaultQualityMeasure.java} |  24 +-
 .../sis/metadata/iso/quality/DefaultUsability.java |   4 +-
 .../iso/quality/DefaultUsabilityElement.java       | 108 --------
 .../apache/sis/util/iso/DefaultNameFactory.java    |   2 +-
 .../org/apache/sis/util/iso/DefaultNameSpace.java  |   2 +-
 .../java/org/apache/sis/xml/NilObjectHandler.java  |   7 +-
 .../java/org/apache/sis/xml/ValueConverter.java    |   2 +-
 .../metadata/replace/QualityParameterTest.java     |   4 +-
 .../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 +-
 .../sis/referencing/GeodeticObjectVerifier.java    |   6 +-
 .../apache/sis/referencing/crs/HardCodedCRS.java   |   6 +-
 .../factory/CommonAuthorityFactoryTest.java        |  27 +-
 .../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 +-
 pom.xml                                            |   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 +-
 .../org/apache/sis/storage/WritableFeatureSet.java |  10 +
 .../apache/sis/internal/storage/gpx/Reader.java    |   2 +-
 .../org/apache/sis/internal/storage/gpx/Store.java |  63 ++---
 .../sis/internal/storage/gpx/StoreProvider.java    |  10 +-
 .../apache/sis/internal/storage/gpx/Updater.java   |  91 +++++++
 .../sis/internal/storage/gpx/WritableStore.java    | 182 +++++++++++++
 .../apache/sis/internal/storage/gpx/Writer.java    |  14 +-
 .../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      | 182 +++++++++++++
 .../sis/internal/storage/gpx/WriterTest.java       |  14 +-
 .../org/apache/sis/test/suite/GPXTestSuite.java    |   3 +-
 73 files changed, 1014 insertions(+), 349 deletions(-)

diff --cc 
core/sis-metadata/src/main/java/org/apache/sis/internal/jaxb/metadata/DQM_Description.java
index e0d21d9efb,2334675a62..35b4851233
--- 
a/core/sis-metadata/src/main/java/org/apache/sis/internal/jaxb/metadata/DQM_Description.java
+++ 
b/core/sis-metadata/src/main/java/org/apache/sis/internal/jaxb/metadata/DQM_Description.java
@@@ -17,7 -17,8 +17,7 @@@
  package org.apache.sis.internal.jaxb.metadata;
  
  import javax.xml.bind.annotation.XmlElementRef;
- import org.apache.sis.metadata.iso.quality.DefaultDescription;
 -import org.opengis.metadata.quality.Description;
+ import org.apache.sis.metadata.iso.quality.DefaultMeasureDescription;
  import org.apache.sis.internal.jaxb.gco.PropertyType;
  
  
@@@ -30,7 -31,7 +30,7 @@@
   * @since   1.3
   * @module
   */
- public final class DQM_Description extends PropertyType<DQM_Description, 
DefaultDescription> {
 -public final class DQM_Description extends PropertyType<DQM_Description, 
Description> {
++public final class DQM_Description extends PropertyType<DQM_Description, 
DefaultMeasureDescription> {
      /**
       * Empty constructor for JAXB only.
       */
@@@ -42,17 -43,17 +42,17 @@@
       * This method is indirectly invoked by the private constructor
       * below, so it shall not depend on the state of this object.
       *
-      * @return {@code DefaultDescription.class}
 -     * @return {@code Description.class}
++     * @return {@code DefaultMeasureDescription.class}
       */
      @Override
-     protected Class<DefaultDescription> getBoundType() {
-         return DefaultDescription.class;
 -    protected Class<Description> getBoundType() {
 -        return Description.class;
++    protected Class<DefaultMeasureDescription> getBoundType() {
++        return DefaultMeasureDescription.class;
      }
  
      /**
       * Constructor for the {@link #wrap} method only.
       */
-     private DQM_Description(final DefaultDescription metadata) {
 -    private DQM_Description(final Description metadata) {
++    private DQM_Description(final DefaultMeasureDescription metadata) {
          super(metadata);
      }
  
@@@ -64,7 -65,7 +64,7 @@@
       * @return a {@code PropertyType} wrapping the given the metadata element.
       */
      @Override
-     protected DQM_Description wrap(final DefaultDescription metadata) {
 -    protected DQM_Description wrap(final Description metadata) {
++    protected DQM_Description wrap(final DefaultMeasureDescription metadata) {
          return new DQM_Description(metadata);
      }
  
@@@ -76,8 -77,8 +76,8 @@@
       * @return the metadata to be marshalled.
       */
      @XmlElementRef
-     public DefaultDescription getElement() {
+     public DefaultMeasureDescription getElement() {
 -        return DefaultMeasureDescription.castOrCopy(metadata);
 +        return metadata;
      }
  
      /**
diff --cc 
core/sis-metadata/src/main/java/org/apache/sis/internal/jaxb/metadata/DQM_Measure.java
index 9849de5be8,26563d3502..08faf49d64
--- 
a/core/sis-metadata/src/main/java/org/apache/sis/internal/jaxb/metadata/DQM_Measure.java
+++ 
b/core/sis-metadata/src/main/java/org/apache/sis/internal/jaxb/metadata/DQM_Measure.java
@@@ -17,7 -17,8 +17,7 @@@
  package org.apache.sis.internal.jaxb.metadata;
  
  import javax.xml.bind.annotation.XmlElementRef;
- import org.apache.sis.metadata.iso.quality.DefaultMeasure;
 -import org.opengis.metadata.quality.Measure;
+ import org.apache.sis.metadata.iso.quality.DefaultQualityMeasure;
  import org.apache.sis.internal.jaxb.gco.PropertyType;
  
  
@@@ -30,7 -31,7 +30,7 @@@
   * @since   1.3
   * @module
   */
- public final class DQM_Measure extends PropertyType<DQM_Measure, 
DefaultMeasure> {
 -public final class DQM_Measure extends PropertyType<DQM_Measure, Measure> {
++public final class DQM_Measure extends PropertyType<DQM_Measure, 
DefaultQualityMeasure> {
      /**
       * Empty constructor for JAXB only.
       */
@@@ -42,17 -43,17 +42,17 @@@
       * This method is indirectly invoked by the private constructor
       * below, so it shall not depend on the state of this object.
       *
-      * @return {@code DefaultMeasure.class}
 -     * @return {@code Measure.class}
++     * @return {@code DefaultQualityMeasure.class}
       */
      @Override
-     protected Class<DefaultMeasure> getBoundType() {
-         return DefaultMeasure.class;
 -    protected Class<Measure> getBoundType() {
 -        return Measure.class;
++    protected Class<DefaultQualityMeasure> getBoundType() {
++        return DefaultQualityMeasure.class;
      }
  
      /**
       * Constructor for the {@link #wrap} method only.
       */
-     private DQM_Measure(final DefaultMeasure metadata) {
 -    private DQM_Measure(final Measure metadata) {
++    private DQM_Measure(final DefaultQualityMeasure metadata) {
          super(metadata);
      }
  
@@@ -64,7 -65,7 +64,7 @@@
       * @return a {@code PropertyType} wrapping the given the metadata element.
       */
      @Override
-     protected DQM_Measure wrap(final DefaultMeasure metadata) {
 -    protected DQM_Measure wrap(final Measure metadata) {
++    protected DQM_Measure wrap(final DefaultQualityMeasure metadata) {
          return new DQM_Measure(metadata);
      }
  
@@@ -76,8 -77,8 +76,8 @@@
       * @return the metadata to be marshalled.
       */
      @XmlElementRef
-     public DefaultMeasure getElement() {
+     public DefaultQualityMeasure getElement() {
 -        return DefaultQualityMeasure.castOrCopy(metadata);
 +        return metadata;
      }
  
      /**
diff --cc 
core/sis-metadata/src/main/java/org/apache/sis/internal/jaxb/metadata/DQ_StandaloneQualityReportInformation.java
index b219193903,cb94df4e17..53b25b04bc
--- 
a/core/sis-metadata/src/main/java/org/apache/sis/internal/jaxb/metadata/DQ_StandaloneQualityReportInformation.java
+++ 
b/core/sis-metadata/src/main/java/org/apache/sis/internal/jaxb/metadata/DQ_StandaloneQualityReportInformation.java
@@@ -17,7 -17,8 +17,7 @@@
  package org.apache.sis.internal.jaxb.metadata;
  
  import javax.xml.bind.annotation.XmlElementRef;
- import 
org.apache.sis.metadata.iso.quality.DefaultStandaloneQualityReportInformation;
 -import org.opengis.metadata.quality.StandaloneQualityReportInformation;
+ import org.apache.sis.metadata.iso.quality.DefaultEvaluationReportInformation;
  import org.apache.sis.internal.jaxb.gco.PropertyType;
  
  
@@@ -35,7 -36,7 +35,7 @@@
   * @module
   */
  public final class DQ_StandaloneQualityReportInformation extends
-         PropertyType<DQ_StandaloneQualityReportInformation, 
DefaultStandaloneQualityReportInformation>
 -        PropertyType<DQ_StandaloneQualityReportInformation, 
StandaloneQualityReportInformation>
++        PropertyType<DQ_StandaloneQualityReportInformation, 
DefaultEvaluationReportInformation>
  {
      /**
       * Empty constructor for JAXB only.
@@@ -48,17 -49,17 +48,17 @@@
       * This method is indirectly invoked by the private constructor
       * below, so it shall not depend on the state of this object.
       *
-      * @return {@code DefaultStandaloneQualityReportInformation.class}
 -     * @return {@code StandaloneQualityReportInformation.class}
++     * @return {@code DefaultEvaluationReportInformation.class}
       */
      @Override
-     protected Class<DefaultStandaloneQualityReportInformation> getBoundType() 
{
-         return DefaultStandaloneQualityReportInformation.class;
 -    protected Class<StandaloneQualityReportInformation> getBoundType() {
 -        return StandaloneQualityReportInformation.class;
++    protected Class<DefaultEvaluationReportInformation> getBoundType() {
++        return DefaultEvaluationReportInformation.class;
      }
  
      /**
       * Constructor for the {@link #wrap} method only.
       */
-     private DQ_StandaloneQualityReportInformation(final 
DefaultStandaloneQualityReportInformation metadata) {
 -    private DQ_StandaloneQualityReportInformation(final 
StandaloneQualityReportInformation metadata) {
++    private DQ_StandaloneQualityReportInformation(final 
DefaultEvaluationReportInformation metadata) {
          super(metadata);
      }
  
@@@ -71,7 -72,7 +71,7 @@@
       *         or {@code null} if marshalling a too old version of the 
standard.
       */
      @Override
-     protected DQ_StandaloneQualityReportInformation wrap(final 
DefaultStandaloneQualityReportInformation metadata) {
 -    protected DQ_StandaloneQualityReportInformation wrap(final 
StandaloneQualityReportInformation metadata) {
++    protected DQ_StandaloneQualityReportInformation wrap(final 
DefaultEvaluationReportInformation metadata) {
          return accept2014() ? new 
DQ_StandaloneQualityReportInformation(metadata) : null;
      }
  
@@@ -83,8 -84,8 +83,8 @@@
       * @return the metadata to be marshalled.
       */
      @XmlElementRef
-     public DefaultStandaloneQualityReportInformation getElement() {
+     public DefaultEvaluationReportInformation getElement() {
 -        return DefaultEvaluationReportInformation.castOrCopy(metadata);
 +        return metadata;
      }
  
      /**
diff --cc 
core/sis-metadata/src/main/java/org/apache/sis/internal/jaxb/metadata/replace/QualityParameter.java
index 5091d07037,3f04454a6e..0ea368d643
--- 
a/core/sis-metadata/src/main/java/org/apache/sis/internal/jaxb/metadata/replace/QualityParameter.java
+++ 
b/core/sis-metadata/src/main/java/org/apache/sis/internal/jaxb/metadata/replace/QualityParameter.java
@@@ -93,7 -96,7 +93,7 @@@ public final class QualityParameter ext
       * @see #getDescription()
       */
      @XmlElement
-     DefaultDescription description;
 -    Description description;
++    DefaultMeasureDescription description;
  
      /**
       * Value type of the data quality parameter (shall be one of the data 
types defined in ISO/TS 19103:2005).
diff --cc 
core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/quality/AbstractElement.java
index bc69208f3c,8524487ccb..0dccfd88c9
--- 
a/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/quality/AbstractElement.java
+++ 
b/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/quality/AbstractElement.java
@@@ -221,8 -218,11 +221,8 @@@ public class AbstractElement extends IS
              return AbstractCompleteness.castOrCopy((Completeness) object);
          }
          if (object instanceof Usability) {
-             return DefaultUsabilityElement.castOrCopy((Usability) object);
+             return DefaultUsability.castOrCopy((Usability) object);
          }
 -        if (object instanceof Metaquality) {
 -            return AbstractMetaquality.castOrCopy((Metaquality) object);
 -        }
          // Intentionally tested after the sub-interfaces.
          if (object == null || object instanceof AbstractElement) {
              return (AbstractElement) object;
diff --cc 
core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/quality/DefaultBasicMeasure.java
index 29e6e4fa80,c89a56ce63..a9e675f497
--- 
a/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/quality/DefaultBasicMeasure.java
+++ 
b/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/quality/DefaultBasicMeasure.java
@@@ -86,7 -81,7 +86,7 @@@ public class DefaultBasicMeasure extend
       * Illustration of the use of a data quality measure.
       */
      @SuppressWarnings("serial")
-     private DefaultDescription example;
 -    private Description example;
++    private DefaultMeasureDescription example;
  
      /**
       * Value type for the result of the basic measure.
@@@ -164,9 -186,9 +164,9 @@@
       *
       * @return usage example, or {@code null} if none.
       */
 -    @Override
      @XmlElement(name = "example")
 -    public Description getExample() {
 +    @UML(identifier="example", obligation=OPTIONAL, specification=UNSPECIFIED)
-     public DefaultDescription getExample() {
++    public DefaultMeasureDescription getExample() {
          return example;
      }
  
@@@ -175,7 -197,7 +175,7 @@@
       *
       * @param  newValues  the new basic measure example.
       */
-     public void setExample(final DefaultDescription newValues) {
 -    public void setExample(final Description newValues) {
++    public void setExample(final DefaultMeasureDescription newValues) {
          checkWritePermission(example);
          example = newValues;
      }
diff --cc 
core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/quality/DefaultDataQuality.java
index cb44ed7531,ec58188f17..506ee71b45
--- 
a/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/quality/DefaultDataQuality.java
+++ 
b/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/quality/DefaultDataQuality.java
@@@ -100,7 -96,7 +100,7 @@@ public class DefaultDataQuality extend
       * Can be used for providing more details than reported as standard 
metadata.
       */
      @SuppressWarnings("serial")
-     private DefaultStandaloneQualityReportInformation standaloneQualityReport;
 -    private StandaloneQualityReportInformation standaloneQualityReport;
++    private DefaultEvaluationReportInformation standaloneQualityReport;
  
      /**
       * Constructs an initially empty data quality.
@@@ -226,9 -220,9 +226,9 @@@
       *
       * @since 1.3
       */
 -    @Override
      @XmlElement(name = "standaloneQualityReport")
 -    public StandaloneQualityReportInformation getStandaloneQualityReport() {
 +    @UML(identifier="standaloneQualityReport", obligation=OPTIONAL, 
specification=UNSPECIFIED)
-     public DefaultStandaloneQualityReportInformation 
getStandaloneQualityReport() {
++    public DefaultEvaluationReportInformation getStandaloneQualityReport() {
          return standaloneQualityReport;
      }
  
@@@ -239,7 -233,7 +239,7 @@@
       *
       * @since 1.3
       */
-     public void setStandaloneQualityReport(final 
DefaultStandaloneQualityReportInformation newValue) {
 -    public void setStandaloneQualityReport(final 
StandaloneQualityReportInformation newValue) {
++    public void setStandaloneQualityReport(final 
DefaultEvaluationReportInformation newValue) {
          checkWritePermission(standaloneQualityReport);
          standaloneQualityReport = newValue;
      }
diff --cc 
core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/quality/DefaultEvaluationReportInformation.java
index 9fe15af3ab,63d88ad90e..e983b6961e
--- 
a/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/quality/DefaultEvaluationReportInformation.java
+++ 
b/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/quality/DefaultEvaluationReportInformation.java
@@@ -57,8 -53,7 +57,8 @@@ import static org.opengis.annotation.Sp
      "abstract"
  })
  @XmlRootElement(name = "DQ_StandaloneQualityReportInformation")
 -public class DefaultEvaluationReportInformation extends ISOMetadata 
implements StandaloneQualityReportInformation {
 +@UML(identifier="DQ_StandaloneQualityReportInformation", 
specification=UNSPECIFIED)
- public class DefaultStandaloneQualityReportInformation extends ISOMetadata {
++public class DefaultEvaluationReportInformation extends ISOMetadata {
      /**
       * Serial number for inter-operability with different versions.
       */
@@@ -88,8 -83,10 +88,8 @@@
       * given object are not recursively copied.
       *
       * @param object  the metadata to copy values from, or {@code null} if 
none.
 -     *
 -     * @see #castOrCopy(StandaloneQualityReportInformation)
       */
-     public DefaultStandaloneQualityReportInformation(final 
DefaultStandaloneQualityReportInformation object) {
 -    public DefaultEvaluationReportInformation(final 
StandaloneQualityReportInformation object) {
++    public DefaultEvaluationReportInformation(final 
DefaultEvaluationReportInformation object) {
          super(object);
          if (object != null) {
              reportReference  = object.getReportReference();
diff --cc 
core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/quality/DefaultMeasureDescription.java
index 8187447261,1f6a6c3c8a..cfb6188b1e
--- 
a/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/quality/DefaultMeasureDescription.java
+++ 
b/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/quality/DefaultMeasureDescription.java
@@@ -59,8 -53,7 +59,8 @@@ import static org.opengis.annotation.Sp
      "extendedDescription"
  })
  @XmlRootElement(name = "DQM_Description", namespace = Namespaces.DQM)
 -public class DefaultMeasureDescription extends ISOMetadata implements 
Description {
 +@UML(identifier="DQM_Description", specification=UNSPECIFIED)
- public class DefaultDescription extends ISOMetadata {
++public class DefaultMeasureDescription extends ISOMetadata {
      /**
       * Serial number for inter-operability with different versions.
       */
@@@ -99,8 -92,10 +99,8 @@@
       * given object are not recursively copied.
       *
       * @param object  the metadata to copy values from, or {@code null} if 
none.
 -     *
 -     * @see #castOrCopy(Description)
       */
-     public DefaultDescription(final DefaultDescription object) {
 -    public DefaultMeasureDescription(final Description object) {
++    public DefaultMeasureDescription(final DefaultMeasureDescription object) {
          super(object);
          if (object != null) {
              textDescription     = object.getTextDescription();
diff --cc 
core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/quality/DefaultQualityMeasure.java
index 2a8a9f6e13,bce5b0d225..a934aaa629
--- 
a/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/quality/DefaultQualityMeasure.java
+++ 
b/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/quality/DefaultQualityMeasure.java
@@@ -75,8 -74,7 +75,8 @@@ import static org.opengis.annotation.Sp
      "parameters"
  })
  @XmlRootElement(name = "DQM_Measure", namespace = Namespaces.DQM)
 -public class DefaultQualityMeasure extends ISOMetadata implements Measure {
 +@UML(identifier="DQM_Measure", specification=UNSPECIFIED)
- public class DefaultMeasure extends ISOMetadata {
++public class DefaultQualityMeasure extends ISOMetadata {
      /**
       * Serial number for inter-operability with different versions.
       */
@@@ -124,7 -122,7 +124,7 @@@
       * needed to establish the result of applying the measure.
       */
      @SuppressWarnings("serial")
-     private DefaultDescription description;
 -    private Description description;
++    private DefaultMeasureDescription description;
  
      /**
       * Reference to the source of an item that has been adopted from an 
external source.
@@@ -148,7 -151,7 +148,7 @@@
       * Illustration of the use of a data quality measure.
       */
      @SuppressWarnings("serial")
-     private Collection<DefaultDescription> examples;
 -    private Collection<Description> examples;
++    private Collection<DefaultMeasureDescription> examples;
  
      /**
       * Constructs an initially empty element.
@@@ -162,9 -165,11 +162,9 @@@
       * given object are not recursively copied.
       *
       * @param object  the metadata to copy values from, or {@code null} if 
none.
 -     *
 -     * @see #castOrCopy(Measure)
       */
      @SuppressWarnings({"unchecked", "rawtypes"})
-     public DefaultMeasure(final DefaultMeasure object) {
 -    public DefaultQualityMeasure(final Measure object) {
++    public DefaultQualityMeasure(final DefaultQualityMeasure object) {
          super(object);
          if (object != null) {
              measureIdentifier = object.getMeasureIdentifier();
@@@ -174,9 -179,10 +174,9 @@@
              definition        = object.getDefinition();
              description       = object.getDescription();
              valueType         = object.getValueType();
-             examples          = copyCollection(object.getExamples(), 
DefaultDescription.class);
 -            valueStructure    = object.getValueStructure();
 -            examples          = copyCollection(object.getExamples(), 
Description.class);
++            examples          = copyCollection(object.getExamples(), 
DefaultMeasureDescription.class);
              basicMeasure      = object.getBasicMeasure();
 -            sourceReferences  = copyCollection(object.getSourceReferences(), 
SourceReference.class);
 +            sourceReferences  = copyCollection(object.getSourceReferences(), 
DefaultSourceReference.class);
              parameters        = copyCollection(object.getParameters(), 
(Class) ParameterDescriptor.class);
          }
      }
@@@ -315,9 -346,9 +315,9 @@@
       *
       * @return description of data quality measure, or {@code null} if none.
       */
 -    @Override
      @XmlElement(name = "description")
 -    public Description getDescription() {
 +    @UML(identifier="description", obligation=CONDITIONAL, 
specification=UNSPECIFIED)
-     public DefaultDescription getDescription() {
++    public DefaultMeasureDescription getDescription() {
         return description;
      }
  
@@@ -326,7 -357,7 +326,7 @@@
       *
       * @param  newValue  the new measure description.
       */
-     public void setDescription(final DefaultDescription newValue)  {
 -    public void setDescription(final Description newValue)  {
++    public void setDescription(final DefaultMeasureDescription newValue)  {
          checkWritePermission(description);
          description = newValue;
      }
@@@ -404,10 -456,10 +404,10 @@@
       *
       * @return examples of applying the measure or the result obtained for 
the measure.
       */
 -    @Override
      @XmlElement(name = "example")
 -    public Collection<Description> getExamples() {
 -        return examples = nonNullCollection(examples, Description.class);
 +    @UML(identifier="example", obligation=OPTIONAL, specification=UNSPECIFIED)
-     public Collection<DefaultDescription> getExamples() {
-         return examples = nonNullCollection(examples, 
DefaultDescription.class);
++    public Collection<DefaultMeasureDescription> getExamples() {
++        return examples = nonNullCollection(examples, 
DefaultMeasureDescription.class);
      }
  
      /**
@@@ -415,7 -467,7 +415,7 @@@
       *
       * @param  newValues  the new examples.
       */
-     public void setExamples(final Collection<? extends DefaultDescription> 
newValues) {
-         examples = writeCollection(newValues, examples, 
DefaultDescription.class);
 -    public void setExamples(final Collection<? extends Description> 
newValues) {
 -        examples = writeCollection(newValues, examples, Description.class);
++    public void setExamples(final Collection<? extends 
DefaultMeasureDescription> newValues) {
++        examples = writeCollection(newValues, examples, 
DefaultMeasureDescription.class);
      }
  }
diff --cc 
core/sis-metadata/src/test/java/org/apache/sis/internal/jaxb/metadata/replace/QualityParameterTest.java
index 52ad321b87,8c53a55d1c..7783205172
--- 
a/core/sis-metadata/src/test/java/org/apache/sis/internal/jaxb/metadata/replace/QualityParameterTest.java
+++ 
b/core/sis-metadata/src/test/java/org/apache/sis/internal/jaxb/metadata/replace/QualityParameterTest.java
@@@ -17,10 -17,12 +17,10 @@@
  package org.apache.sis.internal.jaxb.metadata.replace;
  
  import javax.xml.bind.JAXBException;
 -import org.opengis.metadata.Identifier;
 -import org.opengis.referencing.operation.Matrix;
 -import org.opengis.metadata.quality.ValueStructure;
 +import org.opengis.referencing.ReferenceIdentifier;
  import org.apache.sis.util.iso.Names;
  import org.apache.sis.util.SimpleInternationalString;
- import org.apache.sis.metadata.iso.quality.DefaultDescription;
+ import org.apache.sis.metadata.iso.quality.DefaultMeasureDescription;
  import org.apache.sis.test.xml.TestCase;
  import org.apache.sis.xml.Namespaces;
  import org.junit.Test;
@@@ -44,10 -46,11 +44,10 @@@ public final strictfp class QualityPara
       */
      public static QualityParameter create() {
          final QualityParameter param = new QualityParameter();
 -        param.code           = "some parameter";
 -        param.definition     = new SimpleInternationalString("a definition");
 -        param.description    = new DefaultMeasureDescription("a description");
 -        param.valueStructure = ValueStructure.MATRIX;
 -        param.valueType      = Names.createTypeName(Integer.class);
 +        param.code        = "some parameter";
 +        param.definition  = new SimpleInternationalString("a definition");
-         param.description = new DefaultDescription("a description");
++        param.description = new DefaultMeasureDescription("a description");
 +        param.valueType   = Names.createTypeName(Integer.class);
          return param;
      }
  
diff --cc 
core/sis-referencing/src/test/java/org/apache/sis/referencing/factory/CommonAuthorityFactoryTest.java
index 3fd2c9823b,4217d64240..2724082835
--- 
a/core/sis-referencing/src/test/java/org/apache/sis/referencing/factory/CommonAuthorityFactoryTest.java
+++ 
b/core/sis-referencing/src/test/java/org/apache/sis/referencing/factory/CommonAuthorityFactoryTest.java
@@@ -388,10 -393,12 +393,12 @@@ public final strictfp class CommonAutho
                  "    AXIS[“Longitude (L)”, east, ORDER[1]],\n" +
                  "    AXIS[“Latitude (B)”, north, ORDER[2]],\n" +
                  "    ANGLEUNIT[“degree”, 0.017453292519943295],\n" +
-                 "  SCOPE[“Horizontal component of 3D system.\\E.*\\Q”],\n" +
+                 "\\E(?:  SCOPE\\[“.+”\\],\n)?\\Q" +                     // 
Ignore SCOPE[…] if present.
                  "  AREA[“World\\E.*\\Q”],\n" +
                  "  BBOX[-90.00, -180.00, 90.00, 180.00],\n" +
-                 "  ID[“CRS”, 84, CITATION[“WMS”], 
URI[“urn:ogc:def:crs:OGC:1.3:CRS84”]]]\\E", crs);
 -                "  ID[“CRS”, 84, CITATION[“OGC:WMS”], 
URI[“urn:ogc:def:crs:OGC:1.3:CRS84”]]" +
++                "  ID[“CRS”, 84, CITATION[“WMS”], 
URI[“urn:ogc:def:crs:OGC:1.3:CRS84”]]" +
+                 "\\E(?:,\n  REMARK\\[“.+”\\])?\\]",                     // 
Ignore trailing REMARK[…] if present.
+                 crs);
          /*
           * Note: the WKT specification defines the ID element as:
           *
diff --cc 
storage/sis-xmlstore/src/main/java/org/apache/sis/internal/storage/gpx/Updater.java
index 0000000000,d67c57bcaf..52c2e18f2b
mode 000000,100644..100644
--- 
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
@@@ -1,0 -1,89 +1,91 @@@
+ /*
+  * 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;
++
++// Branch-dependent imports
++import org.apache.sis.feature.AbstractFeature;
+ 
+ 
+ /**
+  * 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 WritableStore 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 {
++    protected Stream<? extends AbstractFeature> 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((WritableStore) source, metadata, temporary);
+     }
+ }
diff --cc 
storage/sis-xmlstore/src/main/java/org/apache/sis/internal/storage/gpx/WritableStore.java
index 0000000000,a4ced88d7e..44bb071d34
mode 000000,100644..100644
--- 
a/storage/sis-xmlstore/src/main/java/org/apache/sis/internal/storage/gpx/WritableStore.java
+++ 
b/storage/sis-xmlstore/src/main/java/org/apache/sis/internal/storage/gpx/WritableStore.java
@@@ -1,0 -1,182 +1,182 @@@
+ /*
+  * 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.Iterator;
+ import java.util.function.Predicate;
+ import java.util.function.UnaryOperator;
+ import java.util.stream.Stream;
+ import java.io.IOException;
+ import java.io.UncheckedIOException;
+ import org.opengis.metadata.Metadata;
+ import org.apache.sis.storage.WritableFeatureSet;
+ import org.apache.sis.storage.StorageConnector;
+ import org.apache.sis.storage.DataStoreException;
+ import org.apache.sis.storage.ConcurrentReadException;
+ import org.apache.sis.storage.IllegalFeatureTypeException;
+ import org.apache.sis.util.collection.BackingStoreException;
+ 
+ // Branch-dependent imports
 -import org.opengis.feature.Feature;
 -import org.opengis.feature.FeatureType;
++import org.apache.sis.feature.AbstractFeature;
++import org.apache.sis.feature.DefaultFeatureType;
+ 
+ 
+ /**
+  * A GPX store capable to write GPX file.
+  *
+  * @author  Johann Sorel (Geomatys)
+  * @author  Martin Desruisseaux (Geomatys)
+  * @version 1.3
+  * @since   1.3
+  * @module
+  */
+ public final class WritableStore extends Store implements WritableFeatureSet {
+     /**
+      * Creates a new GPX store from the given file, URL or stream object.
+      * This constructor invokes {@link 
StorageConnector#closeAllExcept(Object)},
+      * keeping open only the needed resource.
+      *
+      * @param  provider   the provider of this data store, or {@code null} if 
unspecified.
+      * @param  connector  information about the storage (URL, stream, 
<i>etc</i>).
+      * @throws DataStoreException if an error occurred while opening the GPX 
file.
+      */
+     public WritableStore(final StoreProvider provider, final StorageConnector 
connector) throws DataStoreException {
+         super(provider, connector);
+     }
+ 
+     /**
+      * 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 {
++    public void updateType(final DefaultFeatureType newType) throws 
DataStoreException {
+         if (!newType.equals(getType())) {
+             throw new IllegalFeatureTypeException(getLocale(), 
StoreProvider.NAME, newType.getName());
+         }
+     }
+ 
+     /**
+      * 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 {
++    public synchronized void add(final Iterator<? extends AbstractFeature> 
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 {
++    public synchronized boolean removeIf(final Predicate<? super 
AbstractFeature> 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}.
++     * @param  replacement  operation called for each matching {@code 
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)
++    public synchronized void replaceIf(final Predicate<? super 
AbstractFeature> filter, final UnaryOperator<AbstractFeature> 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.
+      *
+      * @param  metadata  the metadata to write, or {@code null} if none.
+      * @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 {
++    public synchronized void write(final Metadata metadata, final Stream<? 
extends AbstractFeature> 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.
+              */
+             closeReader();
+             /*
+              * 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), 
null)) {
+                 writer.writeStartDocument();
+                 if (features != null) {
+                     features.forEachOrdered(writer);
+                 }
+                 writer.writeEndDocument();
+             }
+         } 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);
+         }
+     }
+ }
diff --cc 
storage/sis-xmlstore/src/main/java/org/apache/sis/internal/storage/gpx/Writer.java
index 6e1d70ffc6,8c5ac95d73..33aec7c586
--- 
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
@@@ -142,10 -144,10 +144,10 @@@ final class Writer extends StaxStreamWr
       * @throws JAXBException if underlying JAXB marshaller encounter an error.
       */
      @Override
 -    public void write(final Feature feature) throws DataStoreException, 
XMLStreamException, JAXBException {
 +    public void write(final AbstractFeature feature) throws 
DataStoreException, XMLStreamException, JAXBException {
          if (feature != null) {
-             final Types types = ((Store) owner).types;
+             final Types types = ((WritableStore) owner).types;
 -            final FeatureType type = feature.getType();
 +            final DefaultFeatureType type = feature.getType();
              if (types.wayPoint.isAssignableFrom(type)) {
                  writeWayPoint(feature, Tags.WAY_POINT);
              } else {
diff --cc 
storage/sis-xmlstore/src/main/java/org/apache/sis/internal/storage/xml/stream/RewriteOnUpdate.java
index 0000000000,5377bda50b..7478d62bb6
mode 000000,100644..100644
--- 
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
@@@ -1,0 -1,283 +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;
++import org.apache.sis.feature.AbstractFeature;
+ 
+ 
+ /**
+  * 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;
++    private Stream<? extends AbstractFeature> 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 {
++    private Stream<? extends AbstractFeature> 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 {
++    protected Stream<? extends AbstractFeature> 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 {
++    public void add(final Iterator<? extends AbstractFeature> features) 
throws DataStoreException {
+         ArgumentChecks.ensureNonNull("features", features);
 -        final Stream<? extends Feature> toAdd = StreamSupport.stream(
++        final Stream<? extends AbstractFeature> 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 {
++    public boolean removeIf(final Predicate<? super AbstractFeature> 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}.
++     * @param  updater  operation called for each matching {@code 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 {
++    public void replaceIf(final Predicate<? super AbstractFeature> filter, 
final UnaryOperator<AbstractFeature> 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) {
++        try (Stream<? extends AbstractFeature> 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;
++        final Stream<? extends AbstractFeature> content = filtered;
+         if (content != null) {
+             filtered = null;
+             content.close();
+         }
+     }
+ }
diff --cc 
storage/sis-xmlstore/src/test/java/org/apache/sis/internal/storage/gpx/UpdaterTest.java
index 0000000000,8f2fc1b77e..20874e3cc9
mode 000000,100644..100644
--- 
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
@@@ -1,0 -1,182 +1,182 @@@
+ /*
+  * 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;
++import org.apache.sis.feature.AbstractFeature;
+ 
+ 
+ /**
+  * 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 WritableStore 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 WritableStore(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 WritableStore 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();
++            final AbstractFeature point1 = types.wayPoint.newInstance();
++            final AbstractFeature point2 = types.wayPoint.newInstance();
++            final AbstractFeature 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 WritableStore 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.
+      * Also checks some invariants such as the presence of metadata.
+      */
+     private boolean containsLat20() throws IOException {
+         final String xml = org.apache.sis.internal.jdk9.JDK9.readString(file);
+         assertTrue(xml.contains("<bounds "));       // Sentinel value for 
presence of metadata.
+         return xml.contains("lat=\"20");            // May have trailing ".0".
+     }
+ }

Reply via email to