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