This is an automated email from the ASF dual-hosted git repository. desruisseaux pushed a commit to branch geoapi-4.0 in repository https://gitbox.apache.org/repos/asf/sis.git
The following commit(s) were added to refs/heads/geoapi-4.0 by this push: new aa41c4d0ec GPX store should implement `WritableFeatureSet` only if the stream allows write operations. aa41c4d0ec is described below commit aa41c4d0eca216d23940ca7bf5f5f13c72f3d78e Author: Martin Desruisseaux <martin.desruisse...@geomatys.com> AuthorDate: Sun Dec 4 16:23:35 2022 +0100 GPX store should implement `WritableFeatureSet` only if the stream allows write operations. --- .../apache/sis/console/FormattedOutputCommand.java | 2 +- .../org/apache/sis/internal/storage/gpx/Store.java | 146 ++--------------- .../sis/internal/storage/gpx/StoreProvider.java | 10 +- .../apache/sis/internal/storage/gpx/Updater.java | 4 +- .../sis/internal/storage/gpx/WritableStore.java | 182 +++++++++++++++++++++ .../apache/sis/internal/storage/gpx/Writer.java | 4 +- .../sis/internal/storage/gpx/UpdaterTest.java | 13 +- .../sis/internal/storage/gpx/WriterTest.java | 14 +- 8 files changed, 220 insertions(+), 155 deletions(-) diff --git a/application/sis-console/src/main/java/org/apache/sis/console/FormattedOutputCommand.java b/application/sis-console/src/main/java/org/apache/sis/console/FormattedOutputCommand.java index 5a2c95a52c..9070cfdf30 100644 --- a/application/sis-console/src/main/java/org/apache/sis/console/FormattedOutputCommand.java +++ b/application/sis-console/src/main/java/org/apache/sis/console/FormattedOutputCommand.java @@ -269,7 +269,7 @@ abstract class FormattedOutputCommand extends CommandRunner { * Note: after such generalization is done, revert the xml-store dependency * scope in pom.xml from "compile" to "runtime". */ - final org.apache.sis.internal.storage.gpx.Store fs = (org.apache.sis.internal.storage.gpx.Store) store; + final org.apache.sis.internal.storage.gpx.WritableStore fs = (org.apache.sis.internal.storage.gpx.WritableStore) store; if (version != null) { fs.setVersion(version); } diff --git a/storage/sis-xmlstore/src/main/java/org/apache/sis/internal/storage/gpx/Store.java b/storage/sis-xmlstore/src/main/java/org/apache/sis/internal/storage/gpx/Store.java index 5d14b374ef..5d20ef12b2 100644 --- a/storage/sis-xmlstore/src/main/java/org/apache/sis/internal/storage/gpx/Store.java +++ b/storage/sis-xmlstore/src/main/java/org/apache/sis/internal/storage/gpx/Store.java @@ -17,30 +17,22 @@ package org.apache.sis.internal.storage.gpx; import java.util.Optional; -import java.util.Iterator; -import java.util.function.Predicate; -import java.util.function.UnaryOperator; import java.util.stream.Stream; import java.util.stream.StreamSupport; -import java.io.IOException; -import java.io.UncheckedIOException; import java.net.URISyntaxException; import org.opengis.util.NameFactory; import org.opengis.util.FactoryException; import org.opengis.geometry.Envelope; import org.opengis.metadata.Metadata; import org.opengis.metadata.distribution.Format; -import org.apache.sis.storage.WritableFeatureSet; +import org.apache.sis.storage.FeatureSet; import org.apache.sis.storage.StorageConnector; import org.apache.sis.storage.DataStoreException; import org.apache.sis.storage.DataStoreContentException; -import org.apache.sis.storage.ConcurrentReadException; import org.apache.sis.storage.IllegalNameException; -import org.apache.sis.storage.IllegalFeatureTypeException; import org.apache.sis.internal.system.DefaultFactories; import org.apache.sis.internal.storage.StoreUtilities; import org.apache.sis.internal.storage.xml.stream.StaxDataStore; -import org.apache.sis.util.collection.BackingStoreException; import org.apache.sis.util.ArgumentChecks; import org.apache.sis.util.Version; import org.apache.sis.setup.OptionKey; @@ -66,7 +58,7 @@ import org.opengis.feature.FeatureType; * @since 0.8 * @module */ -public final class Store extends StaxDataStore implements WritableFeatureSet { +public class Store extends StaxDataStore implements FeatureSet { /** * Version of the GPX file, or {@code null} if unknown. */ @@ -216,21 +208,6 @@ public final class Store extends StaxDataStore implements WritableFeatureSet { return types.names.get(this, name); } - /** - * Verifies the type of feature instances in this feature set. - * This method does nothing if the specified type is equal to {@link #getType()}, - * or throws {@link IllegalFeatureTypeException} otherwise. - * - * @param newType new feature type definition (not {@code null}). - * @throws DataStoreException if the given type is not compatible with the types supported by the store. - */ - @Override - public void updateType(final FeatureType newType) throws DataStoreException { - if (!newType.equals(getType())) { - throw new IllegalFeatureTypeException(getLocale(), StoreProvider.NAME, newType.getName()); - } - } - /** * Returns the stream of features. * This store does not cache the features. Any new iteration over features will re-read from the file. @@ -260,113 +237,14 @@ public final class Store extends StaxDataStore implements WritableFeatureSet { } /** - * 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. + * Closes only the reader, without closing this store. + * This method may be invoked before write operation. */ - @Override - public synchronized void add(final Iterator<? extends Feature> features) throws DataStoreException { - try (Updater updater = updater()) { - updater.add(features); - updater.flush(); - } - } - - /** - * Removes all feature instances from this {@code FeatureSet} which matches the given predicate. - * - * @param filter a predicate which returns {@code true} for feature instances to be removed. - * @return {@code true} if any elements were removed. - * @throws DataStoreException if the feature stream cannot be obtained or updated. - */ - @Override - public synchronized boolean removeIf(final Predicate<? super Feature> filter) throws DataStoreException { - try (Updater updater = updater()) { - return updater.removeIf(filter); - } - } - - /** - * Updates all feature instances from this {@code FeatureSet} which match the given predicate. - * If the given operator returns {@code null}, then the filtered feature is removed. - * - * @param filter a predicate which returns {@code true} for feature instances to be updated. - * @param replacement operation called for each matching {@link Feature} instance. May return {@code null}. - * @throws DataStoreException if the feature stream cannot be obtained or updated. - */ - @Override - public synchronized void replaceIf(final Predicate<? super Feature> filter, final UnaryOperator<Feature> replacement) - throws DataStoreException - { - try (Updater updater = updater()) { - updater.replaceIf(filter, replacement); - updater.flush(); - } - } - - /** - * Returns the helper object to use for updating the GPX file. - * - * @todo In current version, we flush the updater after each write operation. - * In a future version, we should keep it in a private field and flush - * only after some delay, on close, or before a read operation. - */ - private Updater updater() throws DataStoreException { - try { - return new Updater(this, getSpecifiedPath()); - } catch (IOException e) { - throw new DataStoreException(e); - } - } - - /** - * Replaces the content of this GPX file by the given metadata and features. - * - * @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 { - 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. - */ - final Reader r = reader; - if (r != null) { - reader = null; - r.close(); - } - /* - * 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); + final void closeReader() throws Exception { + final Reader r = reader; + if (r != null) { + reader = null; + r.close(); } } @@ -378,10 +256,8 @@ public final class Store extends StaxDataStore implements WritableFeatureSet { @Override public synchronized void close() throws DataStoreException { listeners.close(); // Should never fail. - final Reader r = reader; - reader = null; - if (r != null) try { - r.close(); + try { + closeReader(); } catch (Exception e) { final DataStoreException ds = new DataStoreException(e); try { diff --git a/storage/sis-xmlstore/src/main/java/org/apache/sis/internal/storage/gpx/StoreProvider.java b/storage/sis-xmlstore/src/main/java/org/apache/sis/internal/storage/gpx/StoreProvider.java index 74e0a1faa3..6595a46bcb 100644 --- a/storage/sis-xmlstore/src/main/java/org/apache/sis/internal/storage/gpx/StoreProvider.java +++ b/storage/sis-xmlstore/src/main/java/org/apache/sis/internal/storage/gpx/StoreProvider.java @@ -35,7 +35,7 @@ import org.apache.sis.util.Version; * * @author Johann Sorel (Geomatys) * @author Martin Desruisseaux (Geomatys) - * @version 1.2 + * @version 1.3 * @since 0.8 * @module */ @@ -92,8 +92,12 @@ public final class StoreProvider extends StaxDataStoreProvider { * @throws DataStoreException if an error occurred while creating the data store instance. */ @Override - public DataStore open(StorageConnector connector) throws DataStoreException { - return new Store(this, connector); + public DataStore open(final StorageConnector connector) throws DataStoreException { + if (isWritable(connector)) { + return new WritableStore(this, connector); + } else { + return new Store(this, connector); + } } /** diff --git a/storage/sis-xmlstore/src/main/java/org/apache/sis/internal/storage/gpx/Updater.java b/storage/sis-xmlstore/src/main/java/org/apache/sis/internal/storage/gpx/Updater.java index 8a739175ff..d67c57bcaf 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 @@ -48,7 +48,7 @@ final class Updater extends RewriteOnUpdate { * @param location the main file, or {@code null} if unknown. * @throws IOException if an error occurred while determining whether the file is empty. */ - Updater(final Store source, final Path location) throws IOException { + Updater(final WritableStore source, final Path location) throws IOException { super(source, location); } @@ -84,6 +84,6 @@ final class Updater extends RewriteOnUpdate { */ @Override protected StaxStreamWriter createWriter(OutputStream temporary) throws Exception { - return new Writer((Store) source, metadata, temporary); + return new Writer((WritableStore) source, metadata, temporary); } } diff --git 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 new file mode 100644 index 0000000000..a4ced88d7e --- /dev/null +++ b/storage/sis-xmlstore/src/main/java/org/apache/sis/internal/storage/gpx/WritableStore.java @@ -0,0 +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; + + +/** + * 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 { + 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 { + try (Updater updater = updater()) { + updater.add(features); + updater.flush(); + } + } + + /** + * Removes all feature instances from this {@code FeatureSet} which matches the given predicate. + * + * @param filter a predicate which returns {@code true} for feature instances to be removed. + * @return {@code true} if any elements were removed. + * @throws DataStoreException if the feature stream cannot be obtained or updated. + */ + @Override + public synchronized boolean removeIf(final Predicate<? super Feature> filter) throws DataStoreException { + try (Updater updater = updater()) { + return updater.removeIf(filter); + } + } + + /** + * Updates all feature instances from this {@code FeatureSet} which match the given predicate. + * If the given operator returns {@code null}, then the filtered feature is removed. + * + * @param filter a predicate which returns {@code true} for feature instances to be updated. + * @param replacement operation called for each matching {@link Feature} instance. May return {@code null}. + * @throws DataStoreException if the feature stream cannot be obtained or updated. + */ + @Override + public synchronized void replaceIf(final Predicate<? super Feature> filter, final UnaryOperator<Feature> replacement) + throws DataStoreException + { + try (Updater updater = updater()) { + updater.replaceIf(filter, replacement); + updater.flush(); + } + } + + /** + * Returns the helper object to use for updating the GPX file. + * + * @todo In current version, we flush the updater after each write operation. + * In a future version, we should keep it in a private field and flush + * only after some delay, on close, or before a read operation. + */ + private Updater updater() throws DataStoreException { + try { + return new Updater(this, getSpecifiedPath()); + } catch (IOException e) { + throw new DataStoreException(e); + } + } + + /** + * Replaces the content of this GPX file by the given metadata and features. + * + * @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 { + 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 --git a/storage/sis-xmlstore/src/main/java/org/apache/sis/internal/storage/gpx/Writer.java b/storage/sis-xmlstore/src/main/java/org/apache/sis/internal/storage/gpx/Writer.java index 97d33b705a..8c5ac95d73 100644 --- a/storage/sis-xmlstore/src/main/java/org/apache/sis/internal/storage/gpx/Writer.java +++ b/storage/sis-xmlstore/src/main/java/org/apache/sis/internal/storage/gpx/Writer.java @@ -65,7 +65,7 @@ final class Writer extends StaxStreamWriter { * @throws XMLStreamException if an error occurred while opening the XML file. * @throws IOException if an error occurred while preparing the output stream. */ - Writer(final Store owner, final Metadata metadata, final OutputStream temporary) + Writer(final WritableStore owner, final Metadata metadata, final OutputStream temporary) throws DataStoreException, XMLStreamException, IOException { super(owner, temporary); @@ -146,7 +146,7 @@ final class Writer extends StaxStreamWriter { @Override public void write(final Feature 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(); if (types.wayPoint.isAssignableFrom(type)) { writeWayPoint(feature, Tags.WAY_POINT); diff --git a/storage/sis-xmlstore/src/test/java/org/apache/sis/internal/storage/gpx/UpdaterTest.java b/storage/sis-xmlstore/src/test/java/org/apache/sis/internal/storage/gpx/UpdaterTest.java index b951f5973e..8f2fc1b77e 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 @@ -107,12 +107,12 @@ public final strictfp class UpdaterTest extends TestCase { /** * Creates a new GPX data store which will read and write in a temporary file. */ - private Store create() throws DataStoreException, IOException { + 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 Store(provider, connector); + return new WritableStore(provider, connector); } /** @@ -123,7 +123,7 @@ public final strictfp class UpdaterTest extends TestCase { */ @Test public void testWriteEmpty() throws DataStoreException, IOException { - try (final Store store = create()) { + try (final WritableStore store = create()) { final Types types = store.types; final Feature point1 = types.wayPoint.newInstance(); final Feature point2 = types.wayPoint.newInstance(); @@ -160,7 +160,7 @@ public final strictfp class UpdaterTest extends TestCase { } assertTrue(containsLat20()); final boolean result; - try (final Store store = create()) { + try (final WritableStore store = create()) { result = store.removeIf((feature) -> { Object point = feature.getPropertyValue("sis:geometry"); return ((Point) point).getY() == 20; @@ -172,8 +172,11 @@ public final strictfp class UpdaterTest extends TestCase { /** * 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 { - return org.apache.sis.internal.jdk9.JDK9.readString(file).contains("lat=\"20"); // May have trailing ".0". + 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". } } diff --git a/storage/sis-xmlstore/src/test/java/org/apache/sis/internal/storage/gpx/WriterTest.java b/storage/sis-xmlstore/src/test/java/org/apache/sis/internal/storage/gpx/WriterTest.java index ae66a25895..71d9260874 100644 --- a/storage/sis-xmlstore/src/test/java/org/apache/sis/internal/storage/gpx/WriterTest.java +++ b/storage/sis-xmlstore/src/test/java/org/apache/sis/internal/storage/gpx/WriterTest.java @@ -46,7 +46,7 @@ import org.opengis.feature.Feature; /** * Tests (indirectly) the {@link Writer} class. - * This class creates a {@link Store} instance and uses it in write mode. + * This class creates a {@link WritableStore} instance and uses it in write mode. * The {@link Reader} is used for verifying the content. * * @author Johann Sorel (Geomatys) @@ -86,10 +86,10 @@ public final strictfp class WriterTest extends TestCase { /** * Creates a new GPX data store which will read and write in memory. */ - private Store create() throws DataStoreException { + private WritableStore create() throws DataStoreException { final StorageConnector connector = new StorageConnector(output); connector.setOption(OptionKey.GEOMETRY_LIBRARY, GeometryLibrary.ESRI); - return new Store(provider, connector); + return new WritableStore(provider, connector); } /** @@ -137,7 +137,7 @@ public final strictfp class WriterTest extends TestCase { */ private void testMetadata(final Version version, final String expected) throws Exception { final Metadata metadata = MetadataTest.create(); - try (Store store = create()) { + try (WritableStore store = create()) { store.setVersion(version); store.write(metadata, null); } @@ -233,7 +233,7 @@ public final strictfp class WriterTest extends TestCase { * @param expected name of a test file containing the expected XML result. */ private void testFeatures(final Version version, final Type type, final String expected) throws Exception { - try (Store store = create()) { + try (WritableStore store = create()) { store.version = version; testFeatures(store, type); } @@ -247,7 +247,7 @@ public final strictfp class WriterTest extends TestCase { * @param store the store where to write. * @param type the kind of feature to write: way point, route or track. */ - private void testFeatures(final Store store, final Type type) throws Exception { + private void testFeatures(final WritableStore store, final Type type) throws Exception { final Types types = store.types; /* * Way Points as defined in "waypoint.xml" test file. @@ -370,7 +370,7 @@ public final strictfp class WriterTest extends TestCase { final StorageConnector connector = new StorageConnector( TestUtilities.createTemporaryFile(ReaderTest.class, "1.1/metadata.xml")); connector.setOption(OptionKey.GEOMETRY_LIBRARY, GeometryLibrary.ESRI); - try (Store store = new Store(provider, connector)) { + try (WritableStore store = new WritableStore(provider, connector)) { /* * Read part of the file. We verify its content as a matter of principle, * but the main purpose of following code is to advance in the stream.