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 099e84507a Add an `--operation` parameter which can be used instead of
`--sourceCRS` and `--targetCRS` in the command-line interface. This work
required a refactoring of the way that auxiliary files are read in data stores,
for reading both WKT and GML. As a side effect of this work, the PRJ files of
World-File rasters can be in GML in addition of WKT.
099e84507a is described below
commit 099e84507a5cd6753d542f0cd83893520e5b72e4
Author: Martin Desruisseaux <[email protected]>
AuthorDate: Wed Jan 24 17:36:00 2024 +0100
Add an `--operation` parameter which can be used instead of `--sourceCRS`
and `--targetCRS` in the command-line interface.
This work required a refactoring of the way that auxiliary files are read
in data stores, for reading both WKT and GML.
As a side effect of this work, the PRJ files of World-File rasters can be
in GML in addition of WKT.
---
.../org/apache/sis/console/OperationParser.java | 66 +++
.../main/org/apache/sis/console/Option.java | 6 +
.../main/org/apache/sis/console/Options.properties | 1 +
.../org/apache/sis/console/Options_fr.properties | 1 +
.../main/org/apache/sis/console/SIS.java | 11 +-
.../org/apache/sis/console/TransformCommand.java | 99 +++-
.../main/org/apache/sis/xml/XML.java | 9 +-
.../main/org/apache/sis/xml/util/URISource.java | 5 +-
.../factory/sql/InstallationScriptProvider.java | 2 +
.../sis/storage/landsat/LandsatStoreProvider.java | 4 +-
.../apache/sis/storage/geotiff/GeoTiffStore.java | 3 +-
.../sis/storage/geotiff/GeoTiffStoreProvider.java | 6 +-
.../sis/storage/netcdf/NetcdfStoreProvider.java | 4 +-
.../sis/storage/xml/stream/StaxDataStore.java | 2 +-
.../main/org/apache/sis/io/stream/IOUtilities.java | 4 +-
.../org/apache/sis/storage/DataStoreProvider.java | 4 +-
.../apache/sis/storage/base/AuxiliaryContent.java | 196 +++++++
.../sis/storage/base/DocumentedStoreProvider.java | 2 +-
.../apache/sis/storage/base/MetadataBuilder.java | 3 +
.../org/apache/sis/storage/base/PRJDataStore.java | 323 ++---------
.../org/apache/sis/storage/base/URIDataStore.java | 595 +++++++++++----------
.../sis/storage/base/URIDataStoreProvider.java | 236 ++++++++
.../main/org/apache/sis/storage/csv/Store.java | 2 +-
.../org/apache/sis/storage/csv/StoreProvider.java | 4 +-
.../org/apache/sis/storage/esri/RasterStore.java | 6 +-
.../apache/sis/storage/esri/RawRasterStore.java | 5 +-
.../apache/sis/storage/folder/StoreProvider.java | 12 +-
.../apache/sis/storage/image/WorldFileStore.java | 14 +-
.../org/apache/sis/storage/internal/Resources.java | 2 +-
.../sis/storage/internal/Resources.properties | 2 +-
.../main/org/apache/sis/storage/wkt/Store.java | 2 +-
.../org/apache/sis/storage/wkt/StoreProvider.java | 8 +-
.../apache/sis/storage/xml/AbstractProvider.java | 20 +
.../coveragejson/CoverageJsonStoreProvider.java | 4 +-
.../org/apache/sis/gui/dataset/PathAction.java | 4 +-
.../org/apache/sis/gui/dataset/ResourceCell.java | 4 +-
36 files changed, 1051 insertions(+), 620 deletions(-)
diff --git
a/endorsed/src/org.apache.sis.console/main/org/apache/sis/console/OperationParser.java
b/endorsed/src/org.apache.sis.console/main/org/apache/sis/console/OperationParser.java
new file mode 100644
index 0000000000..d31192845c
--- /dev/null
+++
b/endorsed/src/org.apache.sis.console/main/org/apache/sis/console/OperationParser.java
@@ -0,0 +1,66 @@
+/*
+ * 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.console;
+
+import java.util.Optional;
+import org.opengis.metadata.Metadata;
+import org.apache.sis.storage.DataStoreException;
+import org.apache.sis.storage.StorageConnector;
+import org.apache.sis.storage.base.PRJDataStore;
+import org.opengis.referencing.operation.CoordinateOperation;
+
+
+/**
+ * Reads a coordinate operation in GML or WKT format.
+ *
+ * @author Martin Desruisseaux (Geomatys)
+ */
+final class OperationParser extends PRJDataStore {
+ /**
+ * Creates a new parser for the given path or URL.
+ *
+ * @param storage path or URL (should not be a character string).
+ */
+ OperationParser(final Object storage) throws DataStoreException {
+ super(null, new StorageConnector(storage));
+ }
+
+ /**
+ * Access to the protected method from {@code PRJDataStore}.
+ *
+ * @return the coordinate operation, or empty if the file does not exist.
+ * @throws DataStoreException if an error occurred while reading the file.
+ */
+ final Optional<CoordinateOperation> read() throws DataStoreException {
+ return readWKT(CoordinateOperation.class, null);
+ }
+
+ /**
+ * Not used.
+ */
+ @Override
+ public Metadata getMetadata() {
+ return null;
+ }
+
+ /**
+ * Nothing to close.
+ */
+ @Override
+ public void close() {
+ }
+}
diff --git
a/endorsed/src/org.apache.sis.console/main/org/apache/sis/console/Option.java
b/endorsed/src/org.apache.sis.console/main/org/apache/sis/console/Option.java
index 8fdead8c96..3d42dd5a7c 100644
---
a/endorsed/src/org.apache.sis.console/main/org/apache/sis/console/Option.java
+++
b/endorsed/src/org.apache.sis.console/main/org/apache/sis/console/Option.java
@@ -37,6 +37,12 @@ enum Option {
*/
TARGET_CRS(true),
+ /**
+ * The Coordinate Operation to apply on data.
+ * This option can be used as an alternative to the {@link #SOURCE_CRS}
and {@link #TARGET_CRS} pair.
+ */
+ OPERATION(true),
+
/**
* Relative path to an auxiliary metadata file.
*/
diff --git
a/endorsed/src/org.apache.sis.console/main/org/apache/sis/console/Options.properties
b/endorsed/src/org.apache.sis.console/main/org/apache/sis/console/Options.properties
index ae275090d8..b0a7d90e26 100644
---
a/endorsed/src/org.apache.sis.console/main/org/apache/sis/console/Options.properties
+++
b/endorsed/src/org.apache.sis.console/main/org/apache/sis/console/Options.properties
@@ -2,6 +2,7 @@
# and to You under the Apache License, Version 2.0.
sourceCRS=The Coordinate Reference System of input data.
targetCRS=The Coordinate Reference System of output data.
+operation=The Coordinate Operation to apply on data (alternative to source and
target CRS).
metadata=Relative path to an auxiliary metadata file.
output=The output file.
format=The output format. Examples: xml, wkt, wkt1 or text.
diff --git
a/endorsed/src/org.apache.sis.console/main/org/apache/sis/console/Options_fr.properties
b/endorsed/src/org.apache.sis.console/main/org/apache/sis/console/Options_fr.properties
index 6e7bf7227f..efffd7a511 100644
---
a/endorsed/src/org.apache.sis.console/main/org/apache/sis/console/Options_fr.properties
+++
b/endorsed/src/org.apache.sis.console/main/org/apache/sis/console/Options_fr.properties
@@ -2,6 +2,7 @@
# and to You under the Apache License, Version 2.0.
sourceCRS=Le syst\u00e8me de r\u00e9f\u00e9rence des coordonn\u00e9es source.
targetCRS=Le syst\u00e8me de r\u00e9f\u00e9rence des coordonn\u00e9es
destination.
+operation=L\u2019op\u00e9ration \u00e0 appliquer sur les coordonn\u00e9es
(alternative aux CRS source et destination).
metadata=Chemin relatif vers un fichier auxiliaire de m\u00e9ta-donn\u00e9es.
output=Le fichier de sortie.
format=Le format de sortie. Exemples: xml, wkt, wkt1 ou text.
diff --git
a/endorsed/src/org.apache.sis.console/main/org/apache/sis/console/SIS.java
b/endorsed/src/org.apache.sis.console/main/org/apache/sis/console/SIS.java
index 03e2c82c41..3b6a099470 100644
--- a/endorsed/src/org.apache.sis.console/main/org/apache/sis/console/SIS.java
+++ b/endorsed/src/org.apache.sis.console/main/org/apache/sis/console/SIS.java
@@ -596,11 +596,18 @@ public final class SIS extends Static {
* <ul>
* <li>{@code --sourceCRS}: the coordinate reference system of input
points.</li>
* <li>{@code --targetCRS}: the coordinate reference system of output
points.</li>
+ * <li>{@code --operation}: the coordinate operation from source CRS to
target CRS.</li>
* </ul>
*
- * Arguments other than options are files, usually as character strings
but can also be
+ * The {@code --operation} parameter is optional.
+ * If provided, then the {@code --sourceCRS} and {@code --targetCRS}
parameters become optional.
+ * If the operation is specified together with the source and/or target
CRS, then the operation
+ * is used in the middle and conversions from/to the specified CRS are
concatenated before/after
+ * the specified operation.
+ *
+ * <p>Arguments other than options are files, usually as character
strings, but can also be
* {@link java.io.File}, {@link java.nio.file.Path} or {@link
java.net.URL} for example.
- * Usage example:
+ * Usage example:</p>
*
* {@snippet lang="java" :
*
SIS.TRANSFORM.sourceCRS("EPSG:3395").targetCRS("EPSG:4326").run("data.txt");
diff --git
a/endorsed/src/org.apache.sis.console/main/org/apache/sis/console/TransformCommand.java
b/endorsed/src/org.apache.sis.console/main/org/apache/sis/console/TransformCommand.java
index 8b4b6aff11..39975bbea8 100644
---
a/endorsed/src/org.apache.sis.console/main/org/apache/sis/console/TransformCommand.java
+++
b/endorsed/src/org.apache.sis.console/main/org/apache/sis/console/TransformCommand.java
@@ -45,6 +45,7 @@ import org.opengis.referencing.cs.CoordinateSystem;
import org.opengis.referencing.cs.CoordinateSystemAxis;
import org.opengis.referencing.operation.SingleOperation;
import org.opengis.referencing.operation.CoordinateOperation;
+import org.opengis.referencing.operation.CoordinateOperationAuthorityFactory;
import org.opengis.referencing.operation.ConcatenatedOperation;
import org.opengis.referencing.operation.PassThroughOperation;
import org.opengis.referencing.operation.MathTransform;
@@ -57,7 +58,6 @@ import org.apache.sis.referencing.util.DirectPositionView;
import org.apache.sis.referencing.util.ReferencingUtilities;
import org.apache.sis.storage.DataStore;
import org.apache.sis.storage.DataStores;
-import org.apache.sis.storage.DataStoreException;
import org.apache.sis.storage.StorageConnector;
import org.apache.sis.storage.base.CodeType;
import org.apache.sis.system.Modules;
@@ -74,6 +74,7 @@ import org.apache.sis.math.DecimalFunctions;
import org.apache.sis.math.MathFunctions;
import org.apache.sis.measure.Units;
import org.apache.sis.metadata.iso.extent.DefaultGeographicBoundingBox;
+import org.apache.sis.referencing.operation.DefaultCoordinateOperationFactory;
import org.apache.sis.util.resources.Vocabulary;
import org.apache.sis.util.resources.Errors;
import org.apache.sis.util.logging.Logging;
@@ -91,8 +92,15 @@ import org.opengis.referencing.ObjectDomain;
* <ul>
* <li>{@code --sourceCRS}: the coordinate reference system of input
points.</li>
* <li>{@code --targetCRS}: the coordinate reference system of output
points.</li>
+ * <li>{@code --operation}: the coordinate operation from source CRS to
target CRS.</li>
* </ul>
*
+ * The {@code --operation} parameter is optional.
+ * If provided, then the {@code --sourceCRS} and {@code --targetCRS}
parameters become optional.
+ * If the operation is specified together with the source and/or target CRS,
then the operation
+ * is used in the middle and conversions from/to the specified CRS are
concatenated before/after
+ * the specified operation.
+ *
* @author Martin Desruisseaux (Geomatys)
*/
final class TransformCommand extends FormattedOutputCommand {
@@ -150,7 +158,7 @@ final class TransformCommand extends FormattedOutputCommand
{
* Returns valid options for the {@code "transform"} commands.
*/
private static EnumSet<Option> options() {
- return EnumSet.of(Option.SOURCE_CRS, Option.TARGET_CRS, Option.VERBOSE,
+ return EnumSet.of(Option.SOURCE_CRS, Option.TARGET_CRS,
Option.OPERATION, Option.VERBOSE,
Option.LOCALE, Option.TIMEZONE, Option.ENCODING,
Option.COLORS, Option.HELP, Option.DEBUG);
}
@@ -162,23 +170,53 @@ final class TransformCommand extends
FormattedOutputCommand {
resources = Vocabulary.getResources(locale);
}
+ /**
+ * Creates the coordinate operation from the given authority code or file
path.
+ * The result is assigned to the {@link #operation} field.
+ *
+ * @param identifier the authority code or file path.
+ * @throws InvalidOptionException if the coordinate operation cannot be
read from the given identifier.
+ * @throws FactoryException if the operation failed for another reason.
+ */
+ private void fetchOperation(Object identifier) throws Exception {
+ if (identifier instanceof CharSequence) {
+ final String c = identifier.toString();
+ if (CodeType.guess(c).isCRS) try {
+ var factory = (CoordinateOperationAuthorityFactory)
CRS.getAuthorityFactory(null);
+ operation = factory.createCoordinateOperation(c);
+ return;
+ } catch (NoSuchAuthorityCodeException e) {
+ throw illegalOptionValue(Option.OPERATION, identifier, e);
+ }
+ identifier = IOUtilities.toFileOrURL(c, "UTF-8");
+ }
+ final Object path = identifier; // Because lambda function require
effectively final variable.
+ if (path != null) {
+ operation = new OperationParser(path).read()
+ .orElseThrow(() -> illegalOptionValue(Option.OPERATION,
path, null));
+ }
+ }
+
/**
* Fetches the source or target coordinate reference system from the value
given to the specified option.
*
- * @param option either {@link Option#SOURCE_CRS} or {@link
Option#TARGET_CRS}.
+ * @param option either {@link Option#SOURCE_CRS} or {@link
Option#TARGET_CRS}.
+ * @param mandatory whether the option is mandatory.
* @return the coordinate reference system for the given option.
* @throws InvalidOptionException if the given option is missing or have
an invalid value.
* @throws FactoryException if the operation failed for another reason.
*/
- private CoordinateReferenceSystem fetchCRS(final Option option) throws
InvalidOptionException, FactoryException, DataStoreException {
- final Object identifier = getMandatoryOption(option);
+ private CoordinateReferenceSystem fetchCRS(final Option option, final
boolean mandatory) throws Exception {
+ final Object identifier = mandatory ? getMandatoryOption(option) :
options.get(option);
+ if (identifier == null) {
+ return null;
+ }
if (identifier instanceof CharSequence) {
final String c = identifier.toString();
if (CodeType.guess(c).isCRS) try {
return CRS.forCode(c);
} catch (NoSuchAuthorityCodeException e) {
- final String name = option.label();
- throw new
InvalidOptionException(Errors.format(Errors.Keys.IllegalOptionValue_2, name,
identifier), e, name);
+ throw illegalOptionValue(option, identifier, e);
}
}
final Metadata metadata;
@@ -195,6 +233,14 @@ final class TransformCommand extends
FormattedOutputCommand {
throw new
InvalidOptionException(Errors.format(Errors.Keys.UnspecifiedCRS),
option.label());
}
+ /**
+ * Creates the exception to throw for an illegal option value.
+ */
+ private static InvalidOptionException illegalOptionValue(Option option,
Object identifier, Exception cause) {
+ final String name = option.label();
+ return new
InvalidOptionException(Errors.format(Errors.Keys.IllegalOptionValue_2, name,
identifier), cause, name);
+ }
+
/**
* Transforms coordinates from the files given in argument or from the
standard input stream.
*
@@ -202,8 +248,11 @@ final class TransformCommand extends
FormattedOutputCommand {
*/
@Override
public int run() throws Exception {
- final CoordinateReferenceSystem sourceCRS =
fetchCRS(Option.SOURCE_CRS);
- final CoordinateReferenceSystem targetCRS =
fetchCRS(Option.TARGET_CRS);
+ fetchOperation(options.get(Option.OPERATION));
+ CoordinateReferenceSystem sourceCRS = fetchCRS(Option.SOURCE_CRS,
operation == null);
+ CoordinateReferenceSystem targetCRS = fetchCRS(Option.TARGET_CRS,
operation == null);
+ if (sourceCRS == null) sourceCRS = operation.getSourceCRS(); // May
still be null.
+ if (targetCRS == null) targetCRS = operation.getTargetCRS();
/*
* Read all coordinates, so we can compute the area of interest.
* This will be used when searching for a coordinate operation.
@@ -234,7 +283,35 @@ final class TransformCommand extends
FormattedOutputCommand {
warning(e);
}
}
- operation = CRS.findOperation(sourceCRS, targetCRS, areaOfInterest);
+ /*
+ * Now find or complete the coordinate operation. Note that the source
and target CRS may be null
+ * if and only if a coordinate operation was explicitly specified. In
that case, CRS are optional.
+ * If both a coordinate operation and CRS are present, this code
ensures that the operation inputs
+ * and outputs match the specified CRS.
+ */
+ if (operation == null) {
+ operation = CRS.findOperation(sourceCRS, targetCRS,
areaOfInterest);
+ } else {
+ final var steps = new ArrayList<CoordinateOperation>(3);
+ if (sourceCRS != null) {
+ CoordinateReferenceSystem step = operation.getSourceCRS();
+ if (step != null && step != sourceCRS) {
+ steps.add(CRS.findOperation(sourceCRS, step,
areaOfInterest));
+ }
+ }
+ steps.add(operation);
+ if (targetCRS != null) {
+ CoordinateReferenceSystem step = operation.getTargetCRS();
+ if (step != null && step != targetCRS) {
+ steps.add(CRS.findOperation(step, targetCRS,
areaOfInterest));
+ }
+ }
+ if (steps.size() > 1) {
+ var factory = DefaultCoordinateOperationFactory.provider();
+ var properties = IdentifiedObjects.getProperties(operation,
CoordinateOperation.IDENTIFIERS_KEY);
+ operation = factory.createConcatenatedOperation(properties,
steps.toArray(CoordinateOperation[]::new));
+ }
+ }
/*
* Prints the header: source CRS, target CRS, operation steps and
positional accuracy.
*/
@@ -256,7 +333,7 @@ final class TransformCommand extends FormattedOutputCommand
{
* compute the number of digits to format and perform the actual
coordinate operations.
*/
if (!points.isEmpty()) {
- coordinateWidth = 15; //
Must be set before computeNumFractionDigits(…).
+ coordinateWidth = 15; // Must be set before
computeNumFractionDigits(…).
coordinateFormat = NumberFormat.getInstance(Locale.US);
coordinateFormat.setGroupingUsed(false);
computeNumFractionDigits(operation.getTargetCRS().getCoordinateSystem());
diff --git
a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/xml/XML.java
b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/xml/XML.java
index f92dad0a07..eb707e2da7 100644
--- a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/xml/XML.java
+++ b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/xml/XML.java
@@ -28,6 +28,7 @@ import java.io.InputStream;
import java.io.OutputStream;
import java.io.StringReader;
import java.io.StringWriter;
+import java.io.BufferedInputStream;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.StandardOpenOption;
@@ -47,6 +48,7 @@ import org.apache.sis.util.Workaround;
import org.apache.sis.util.resources.Errors;
import org.apache.sis.system.Modules;
import org.apache.sis.system.SystemListener;
+import org.apache.sis.xml.util.URISource;
import org.apache.sis.xml.bind.TypeRegistration;
import static org.apache.sis.util.ArgumentChecks.ensureNonNull;
@@ -615,11 +617,8 @@ public final class XML extends Static {
public static Object unmarshal(final Path input) throws JAXBException {
ensureNonNull("input", input);
final Object object;
- try (InputStream in = Files.newInputStream(input,
StandardOpenOption.READ)) {
- final MarshallerPool pool = getPool();
- final Unmarshaller unmarshaller = pool.acquireUnmarshaller();
- object = unmarshaller.unmarshal(in);
- pool.recycle(unmarshaller);
+ try (InputStream in = new
BufferedInputStream(Files.newInputStream(input, StandardOpenOption.READ))) {
+ object = unmarshal(URISource.create(in, input.toUri()), null);
} catch (IOException e) {
throw new JAXBException(Errors.format(Errors.Keys.CanNotRead_1,
input), e);
}
diff --git
a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/xml/util/URISource.java
b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/xml/util/URISource.java
index cf8747830c..ec049dcfaf 100644
---
a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/xml/util/URISource.java
+++
b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/xml/util/URISource.java
@@ -67,6 +67,7 @@ public final class URISource extends StreamSource {
*/
private URISource(final InputStream input, final URI source) {
super(input);
+ // SystemId will be computed only if requested.
document = source.normalize();
fragment = null;
}
@@ -78,7 +79,7 @@ public final class URISource extends StreamSource {
* @param source URL of the XML document, or {@code null} if none.
* @return the given input stream as a source.
*/
- static StreamSource create(final InputStream input, final URI source) {
+ public static StreamSource create(final InputStream input, final URI
source) {
if (source != null) {
return new URISource(input, source);
} else {
@@ -87,7 +88,7 @@ public final class URISource extends StreamSource {
}
/**
- * If this source if defined only by URI (no input stream), returns that
URI.
+ * If this source is defined only by URI (no input stream), returns that
URI.
* Otherwise returns {@code null}.
*
* @return the URI, or {@code null} if not applicable for reading the
document.
diff --git
a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/factory/sql/InstallationScriptProvider.java
b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/factory/sql/InstallationScriptProvider.java
index df9e33c62e..8c48379a51 100644
---
a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/factory/sql/InstallationScriptProvider.java
+++
b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/factory/sql/InstallationScriptProvider.java
@@ -228,6 +228,7 @@ public abstract class InstallationScriptProvider extends
InstallationResources {
* Opens the input stream for the SQL script of the given name.
* This method is invoked by the default implementation of {@link
#openScript(String, int)}
* for all scripts except {@link #PREPARE} and {@link #FINISH}.
+ * The returned input stream does not need to be buffered.
*
* <h4>Example 1</h4>
* if this {@code InstallationScriptProvider} instance gets the SQL
scripts from files in a well-known directory
@@ -365,6 +366,7 @@ public abstract class InstallationScriptProvider extends
InstallationResources {
/**
* Opens the input stream for the SQL script of the given name.
+ * The returned input stream does not need to be buffered.
*
* @param name name of the script file to open.
* @return an input stream opened of the given script file, or {@code
null} if the resource was not found.
diff --git
a/endorsed/src/org.apache.sis.storage.earthobservation/main/org/apache/sis/storage/landsat/LandsatStoreProvider.java
b/endorsed/src/org.apache.sis.storage.earthobservation/main/org/apache/sis/storage/landsat/LandsatStoreProvider.java
index 58f2bd40e4..9ca9da35b9 100644
---
a/endorsed/src/org.apache.sis.storage.earthobservation/main/org/apache/sis/storage/landsat/LandsatStoreProvider.java
+++
b/endorsed/src/org.apache.sis.storage.earthobservation/main/org/apache/sis/storage/landsat/LandsatStoreProvider.java
@@ -29,7 +29,7 @@ import org.apache.sis.storage.StorageConnector;
import org.apache.sis.storage.ProbeResult;
import org.apache.sis.storage.base.Capability;
import org.apache.sis.storage.base.StoreMetadata;
-import org.apache.sis.storage.base.URIDataStore;
+import org.apache.sis.storage.base.URIDataStoreProvider;
import org.apache.sis.storage.wkt.FirstKeywordPeek;
@@ -64,7 +64,7 @@ public class LandsatStoreProvider extends DataStoreProvider {
/**
* The parameter descriptor to be returned by {@link #getOpenParameters()}.
*/
- private static final ParameterDescriptorGroup OPEN_DESCRIPTOR =
URIDataStore.Provider.descriptor(NAME);
+ private static final ParameterDescriptorGroup OPEN_DESCRIPTOR =
URIDataStoreProvider.descriptor(NAME);
/**
* The object to use for verifying if the first keyword is the expected
one.
diff --git
a/endorsed/src/org.apache.sis.storage.geotiff/main/org/apache/sis/storage/geotiff/GeoTiffStore.java
b/endorsed/src/org.apache.sis.storage.geotiff/main/org/apache/sis/storage/geotiff/GeoTiffStore.java
index fc44ad6432..d5519f1420 100644
---
a/endorsed/src/org.apache.sis.storage.geotiff/main/org/apache/sis/storage/geotiff/GeoTiffStore.java
+++
b/endorsed/src/org.apache.sis.storage.geotiff/main/org/apache/sis/storage/geotiff/GeoTiffStore.java
@@ -50,6 +50,7 @@ import org.apache.sis.storage.IllegalNameException;
import org.apache.sis.storage.base.MetadataBuilder;
import org.apache.sis.storage.base.StoreUtilities;
import org.apache.sis.storage.base.URIDataStore;
+import org.apache.sis.storage.base.URIDataStoreProvider;
import org.apache.sis.storage.base.GridResourceWrapper;
import org.apache.sis.storage.event.StoreEvent;
import org.apache.sis.storage.event.StoreListener;
@@ -254,7 +255,7 @@ public class GeoTiffStore extends DataStore implements
Aggregate {
location = connector.getStorageAs(URI.class);
path = connector.getStorageAs(Path.class);
try {
- if (URIDataStore.Provider.isWritable(connector, true)) {
+ if (URIDataStoreProvider.isWritable(connector, true)) {
ChannelDataOutput output =
connector.commit(ChannelDataOutput.class, Constants.GEOTIFF);
writer = new Writer(this, output,
connector.getOption(FormatModifier.OPTION_KEY));
} else {
diff --git
a/endorsed/src/org.apache.sis.storage.geotiff/main/org/apache/sis/storage/geotiff/GeoTiffStoreProvider.java
b/endorsed/src/org.apache.sis.storage.geotiff/main/org/apache/sis/storage/geotiff/GeoTiffStoreProvider.java
index 573b40de9f..29ae8a2f19 100644
---
a/endorsed/src/org.apache.sis.storage.geotiff/main/org/apache/sis/storage/geotiff/GeoTiffStoreProvider.java
+++
b/endorsed/src/org.apache.sis.storage.geotiff/main/org/apache/sis/storage/geotiff/GeoTiffStoreProvider.java
@@ -30,7 +30,7 @@ import org.apache.sis.storage.ProbeResult;
import org.apache.sis.storage.StorageConnector;
import org.apache.sis.storage.base.StoreMetadata;
import org.apache.sis.storage.base.Capability;
-import org.apache.sis.storage.base.URIDataStore;
+import org.apache.sis.storage.base.URIDataStoreProvider;
import org.apache.sis.util.internal.Constants;
import org.apache.sis.util.resources.Vocabulary;
import org.apache.sis.parameter.ParameterBuilder;
@@ -91,7 +91,7 @@ public class GeoTiffStoreProvider extends DataStoreProvider {
final var builder = new ParameterBuilder();
final var modifiers =
builder.addName(MODIFIERS).setDescription(Vocabulary.formatInternational(Vocabulary.Keys.Options)).create(FormatModifier[].class,
null);
final var compression =
builder.addName(COMPRESSION).setDescription(Vocabulary.formatInternational(Vocabulary.Keys.Compression)).create(Compression.class,
null);
- OPEN_DESCRIPTOR =
builder.addName(Constants.GEOTIFF).createGroup(URIDataStore.Provider.LOCATION_PARAM,
modifiers, compression);
+ OPEN_DESCRIPTOR =
builder.addName(Constants.GEOTIFF).createGroup(URIDataStoreProvider.LOCATION_PARAM,
modifiers, compression);
}
/**
@@ -162,7 +162,7 @@ public class GeoTiffStoreProvider extends DataStoreProvider
{
*/
@Override
public DataStore open(final StorageConnector connector) throws
DataStoreException {
- if (URIDataStore.Provider.isWritable(connector, false)) {
+ if (URIDataStoreProvider.isWritable(connector, false)) {
return new WritableStore(this, connector);
}
return new GeoTiffStore(this, connector);
diff --git
a/endorsed/src/org.apache.sis.storage.netcdf/main/org/apache/sis/storage/netcdf/NetcdfStoreProvider.java
b/endorsed/src/org.apache.sis.storage.netcdf/main/org/apache/sis/storage/netcdf/NetcdfStoreProvider.java
index 1a40c27919..a3bdcff03c 100644
---
a/endorsed/src/org.apache.sis.storage.netcdf/main/org/apache/sis/storage/netcdf/NetcdfStoreProvider.java
+++
b/endorsed/src/org.apache.sis.storage.netcdf/main/org/apache/sis/storage/netcdf/NetcdfStoreProvider.java
@@ -44,7 +44,7 @@ import org.apache.sis.io.stream.ChannelDataInput;
import org.apache.sis.io.stream.IOUtilities;
import org.apache.sis.storage.base.StoreMetadata;
import org.apache.sis.storage.base.Capability;
-import org.apache.sis.storage.base.URIDataStore;
+import org.apache.sis.storage.base.URIDataStoreProvider;
import org.apache.sis.system.SystemListener;
import org.apache.sis.system.Modules;
import org.apache.sis.setup.GeometryLibrary;
@@ -98,7 +98,7 @@ public class NetcdfStoreProvider extends DataStoreProvider {
/**
* The parameter descriptor to be returned by {@link #getOpenParameters()}.
*/
- private static final ParameterDescriptorGroup OPEN_DESCRIPTOR =
URIDataStore.Provider.descriptor(NAME);
+ private static final ParameterDescriptorGroup OPEN_DESCRIPTOR =
URIDataStoreProvider.descriptor(NAME);
/**
* The name of the {@link ucar.nc2.NetcdfFile} class, which is {@value}.
diff --git
a/endorsed/src/org.apache.sis.storage.xml/main/org/apache/sis/storage/xml/stream/StaxDataStore.java
b/endorsed/src/org.apache.sis.storage.xml/main/org/apache/sis/storage/xml/stream/StaxDataStore.java
index d3d8fa93e1..538792945a 100644
---
a/endorsed/src/org.apache.sis.storage.xml/main/org/apache/sis/storage/xml/stream/StaxDataStore.java
+++
b/endorsed/src/org.apache.sis.storage.xml/main/org/apache/sis/storage/xml/stream/StaxDataStore.java
@@ -338,7 +338,7 @@ public abstract class StaxDataStore extends URIDataStore {
*/
@Override
public boolean isLoggable(final LogRecord warning) {
- warning.setLoggerName(null); // For allowing `listeners` to
select a logger name.
+ warning.setLoggerName(null); // For allowing `listeners` to
use the provider's logger name.
listeners.warning(warning);
return false;
}
diff --git
a/endorsed/src/org.apache.sis.storage/main/org/apache/sis/io/stream/IOUtilities.java
b/endorsed/src/org.apache.sis.storage/main/org/apache/sis/io/stream/IOUtilities.java
index c6eefebd10..f3d786aca5 100644
---
a/endorsed/src/org.apache.sis.storage/main/org/apache/sis/io/stream/IOUtilities.java
+++
b/endorsed/src/org.apache.sis.storage/main/org/apache/sis/io/stream/IOUtilities.java
@@ -206,8 +206,8 @@ public final class IOUtilities extends Static {
*/
public static String toString(final Object path) {
/*
- * For the following types, the string that we want can be obtained
only by toString(),
- * or the class is final so we know that the toString(à behavior
cannot be changed.
+ * For the following types, the string that we want can be obtained
only by `toString()`,
+ * or the class is final so we know that the `toString()` behavior
cannot be changed.
*/
if (path instanceof CharSequence || path instanceof Path || path
instanceof URL || path instanceof URI) {
return path.toString();
diff --git
a/endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/DataStoreProvider.java
b/endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/DataStoreProvider.java
index 0603c87f65..c6e338d1b6 100644
---
a/endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/DataStoreProvider.java
+++
b/endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/DataStoreProvider.java
@@ -28,7 +28,7 @@ import org.apache.sis.metadata.simple.SimpleFormat;
import org.apache.sis.metadata.iso.citation.DefaultCitation;
import org.apache.sis.metadata.iso.distribution.DefaultFormat;
import org.apache.sis.io.stream.Markable;
-import org.apache.sis.storage.base.URIDataStore;
+import org.apache.sis.storage.base.URIDataStoreProvider;
import org.apache.sis.storage.internal.RewindableLineReader;
import org.apache.sis.measure.Range;
import org.apache.sis.util.Version;
@@ -611,7 +611,7 @@ public abstract class DataStoreProvider {
*/
public DataStore open(final ParameterValueGroup parameters) throws
DataStoreException {
ArgumentChecks.ensureNonNull("parameter", parameters);
- return open(URIDataStore.Provider.connector(this, parameters));
+ return open(URIDataStoreProvider.connector(this, parameters));
}
/**
diff --git
a/endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/base/AuxiliaryContent.java
b/endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/base/AuxiliaryContent.java
new file mode 100644
index 0000000000..e64c09006f
--- /dev/null
+++
b/endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/base/AuxiliaryContent.java
@@ -0,0 +1,196 @@
+/*
+ * 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.storage.base;
+
+import java.util.Arrays;
+import java.net.URL;
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.nio.file.Path;
+import java.nio.charset.Charset;
+import javax.xml.transform.Source;
+import org.apache.sis.util.ArraysExt;
+import org.apache.sis.io.stream.IOUtilities;
+
+
+/**
+ * Content of a data store auxiliary file.
+ * Instances of this class should be short lived, because they hold larger
arrays than necessary.
+ *
+ * @author Martin Desruisseaux (Geomatys)
+ *
+ * @see PRJDataStore#readAuxiliaryFile(String, boolean)
+ */
+public final class AuxiliaryContent implements CharSequence {
+ /**
+ * Buffer size and initial array capacity.
+ * We take a small buffer size because the files to read are typically
small.
+ */
+ static final int BUFFER_SIZE = 1024;
+
+ /**
+ * Maximal length (in bytes) of auxiliary files. This is an arbitrary
restriction, we could let
+ * the buffer growth indefinitely instead. But a large auxiliary file is
probably an error and
+ * we do not want an {@link OutOfMemoryError} because of that.
+ */
+ private static final int MAXIMAL_LENGTH = 64 * 1024;
+
+ /**
+ * The XML source, or null if the file has not been identified as an XML
file.
+ * This can be non-null only if the auxiliary file has been read with an
+ * {@code acceptXML} parameter set to {@code true}.
+ */
+ public final Source source;
+
+ /**
+ * {@link Path} or {@link URL} that have been read.
+ */
+ private final Object location;
+
+ /**
+ * The textual content of the auxiliary file.
+ */
+ private final char[] buffer;
+
+ /**
+ * Index of the first valid character in {@link #buffer}.
+ */
+ private final int offset;
+
+ /**
+ * Number of valid characters in {@link #buffer}.
+ */
+ private final int length;
+
+ /**
+ * Creates an auxiliary content for an XML file.
+ *
+ * @param location {@link Path} or {@link URL} to read.
+ * @param source the source to use for parsing the XML file.
+ */
+ AuxiliaryContent(final Object location, final Source source) {
+ this.source = source;
+ this.location = location;
+ this.buffer = ArraysExt.EMPTY_CHAR;
+ this.offset = 0;
+ this.length = 0;
+ }
+
+ /**
+ * Wraps (without copying) the given array as the content of an auxiliary
file.
+ *
+ * @param location {@link Path} or {@link URL} that have been read.
+ * @param buffer the textual content of the auxiliary file.
+ * @param offset index of the first valid character in {@code buffer}.
+ * @param length number of valid characters in {@code buffer}.
+ */
+ private AuxiliaryContent(final Object location, final char[] buffer, final
int offset, final int length) {
+ this.source = null;
+ this.location = location;
+ this.buffer = buffer;
+ this.offset = offset;
+ this.length = length;
+ }
+
+ /**
+ * Reads the content of the given input stream.
+ * This method closes the given stream.
+ *
+ * @param location {@link Path} or {@link URL} to read.
+ * @param stream input stream to read.
+ * @param encoding character encoding, or {@code null} for the default.
+ * @return the file content, or {@code null} if too large.
+ * @throws IOException if an error occurred while reading the stream.
+ */
+ static AuxiliaryContent read(final Object location, final InputStream
stream, final Charset encoding) throws IOException {
+ try (InputStreamReader reader = (encoding != null)
+ ? new InputStreamReader(stream, encoding)
+ : new InputStreamReader(stream))
+ {
+ char[] buffer = new char[BUFFER_SIZE];
+ int offset = 0, count;
+ while ((count = reader.read(buffer, offset, buffer.length -
offset)) >= 0) {
+ offset += count;
+ if (offset >= buffer.length) {
+ if (offset >= MAXIMAL_LENGTH) return null;
+ buffer = Arrays.copyOf(buffer, offset*2);
+ }
+ }
+ return new AuxiliaryContent(location, buffer, 0, offset);
+ }
+ }
+
+ /**
+ * Returns the filename (without path) of the auxiliary file.
+ * This information is mainly for producing error messages.
+ *
+ * @return name of the auxiliary file that have been read.
+ */
+ public String getFilename() {
+ return IOUtilities.filename(location);
+ }
+
+ /**
+ * Returns the source as an URI if possible.
+ *
+ * @return the source as an URI, or {@code null} if none.
+ * @throws URISyntaxException if the URI cannot be parsed.
+ */
+ public URI getURI() throws URISyntaxException {
+ return IOUtilities.toURI(location);
+ }
+
+ /**
+ * Returns the number of valid characters in this sequence.
+ */
+ @Override
+ public int length() {
+ return length;
+ }
+
+ /**
+ * Returns the character at the given index. For performance reasons this
method does not check index bounds.
+ * The behavior of this method is undefined if the given index is not
smaller than {@link #length()}.
+ * We skip bounds check because this class should be used for Apache SIS
internal purposes only.
+ */
+ @Override
+ public char charAt(final int index) {
+ return buffer[offset + index];
+ }
+
+ /**
+ * Returns a sub-sequence of this auxiliary file content. For performance
reasons this method does not
+ * perform bound checks. The behavior of this method is undefined if
arguments are out of bounds.
+ * We skip bounds check because this class should be used for Apache SIS
internal purposes only.
+ */
+ @Override
+ public CharSequence subSequence(final int start, final int end) {
+ return new AuxiliaryContent(location, buffer, offset + start, end -
start);
+ }
+
+ /**
+ * Copies this auxiliary file content in a {@link String}.
+ * This method does not cache the result; caller should invoke at most
once.
+ */
+ @Override
+ public String toString() {
+ return new String(buffer, offset, length);
+ }
+}
diff --git
a/endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/base/DocumentedStoreProvider.java
b/endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/base/DocumentedStoreProvider.java
index 75ef89addc..4fb34d1c62 100644
---
a/endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/base/DocumentedStoreProvider.java
+++
b/endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/base/DocumentedStoreProvider.java
@@ -32,7 +32,7 @@ import org.apache.sis.system.Modules;
*
* @author Martin Desruisseaux (Geomatys)
*/
-public abstract class DocumentedStoreProvider extends URIDataStore.Provider {
+public abstract class DocumentedStoreProvider extends URIDataStoreProvider {
/**
* The primary key to use for searching in the {@code MD_Format} table, or
{@code null} if none.
* This primary name is also the value returned by {@link #getShortName()}
default implementation.
diff --git
a/endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/base/MetadataBuilder.java
b/endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/base/MetadataBuilder.java
index c2c3b43030..83df0f4256 100644
---
a/endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/base/MetadataBuilder.java
+++
b/endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/base/MetadataBuilder.java
@@ -84,6 +84,7 @@ import org.apache.sis.metadata.iso.spatial.*;
import org.apache.sis.metadata.sql.MetadataStoreException;
import org.apache.sis.metadata.sql.MetadataSource;
import org.apache.sis.metadata.internal.Merger;
+import org.apache.sis.storage.Resource;
import org.apache.sis.storage.AbstractResource;
import org.apache.sis.storage.AbstractFeatureSet;
import org.apache.sis.storage.AbstractGridCoverageResource;
@@ -837,6 +838,8 @@ public class MetadataBuilder {
* @param resource the resource for which to add metadata.
* @param listeners the listeners to notify in case of warning, or
{@code null} if none.
* @throws DataStoreException if an error occurred while reading metadata
from the data store.
+ *
+ * @see #addTitleOrIdentifier(Resource)
*/
public final void addDefaultMetadata(final AbstractResource resource,
final StoreListeners listeners) throws DataStoreException {
// Note: title is mandatory in ISO metadata, contrarily to the
identifier.
diff --git
a/endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/base/PRJDataStore.java
b/endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/base/PRJDataStore.java
index 0d0d46c253..dbfeba3ab8 100644
---
a/endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/base/PRJDataStore.java
+++
b/endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/base/PRJDataStore.java
@@ -16,25 +16,18 @@
*/
package org.apache.sis.storage.base;
-import java.net.URL;
-import java.net.URI;
import java.net.URISyntaxException;
-import java.net.UnknownServiceException;
import java.io.IOException;
-import java.io.InputStream;
-import java.io.InputStreamReader;
import java.io.BufferedWriter;
import java.io.FileNotFoundException;
-import java.nio.charset.Charset;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.NoSuchFileException;
import java.text.ParseException;
import java.text.ParsePosition;
import java.util.Arrays;
-import java.util.Locale;
import java.util.Optional;
-import java.util.TimeZone;
+import jakarta.xml.bind.JAXBException;
import org.opengis.parameter.ParameterDescriptor;
import org.opengis.parameter.ParameterDescriptorGroup;
import org.opengis.parameter.ParameterValueGroup;
@@ -43,11 +36,9 @@ import org.apache.sis.setup.OptionKey;
import org.apache.sis.storage.DataStore;
import org.apache.sis.storage.DataStoreProvider;
import org.apache.sis.storage.DataStoreException;
-import org.apache.sis.storage.DataStoreContentException;
import org.apache.sis.storage.DataStoreReferencingException;
import org.apache.sis.storage.StorageConnector;
import org.apache.sis.storage.internal.Resources;
-import org.apache.sis.io.stream.IOUtilities;
import org.apache.sis.storage.wkt.StoreFormat;
import org.apache.sis.io.wkt.Convention;
import org.apache.sis.parameter.ParameterBuilder;
@@ -61,23 +52,17 @@ import org.apache.sis.util.resources.Vocabulary;
/**
* A data store for a file or URI accompanied by an auxiliary file of the same
name with {@code .prj} extension.
* If the auxiliary file is absent, {@link OptionKey#DEFAULT_CRS} is used as a
fallback.
- * The WKT 1 variant used for parsing the {@code "*.prj"} file is the variant
used by "World Files" and GDAL;
- * this is not the standard specified by OGC 01-009 (they differ in there
interpretation of units of measurement).
+ * The default WKT 1 variant used for parsing the {@code "*.prj"} file is the
variant used by "World Files" and GDAL.
+ * This is not the standard specified by OGC 01-009 (they differ in there
interpretation of units of measurement).
+ * This implementation accepts also WKT 2 (in which case the WKT 1 convention
is ignored) and GML.
*
- * <p>It is still possible to create a data store with a {@link
java.nio.channels.ReadableByteChannel},
- * {@link java.io.InputStream} or {@link java.io.Reader}, in which case the
{@linkplain #location} will
- * be null and the CRS defined by the {@code OptionKey} will be used.</p>
+ * <p>The URI can be null if the only available storage is a {@link
java.nio.channels.ReadableByteChannel},
+ * {@link java.io.InputStream} or {@link java.io.Reader}. In such case, the
CRS should be specified by the
+ * {@link OptionKey#DEFAULT_CRS}.</p>
*
* @author Martin Desruisseaux (Geomatys)
*/
public abstract class PRJDataStore extends URIDataStore {
- /**
- * Maximal length (in bytes) of auxiliary files. This is an arbitrary
restriction, we could let
- * the buffer growth indefinitely instead. But a large auxiliary file is
probably an error and
- * we do not want an {@link OutOfMemoryError} because of that.
- */
- private static final int MAXIMAL_LENGTH = 64 * 1024;
-
/**
* The filename extension of {@code "*.prj"} files.
*
@@ -85,25 +70,6 @@ public abstract class PRJDataStore extends URIDataStore {
*/
protected static final String PRJ = "prj";
- /**
- * Character encoding in {@code *.prj} or other auxiliary files,
- * or {@code null} for the JVM default (usually UTF-8).
- */
- protected final Charset encoding;
-
- /**
- * The locale for texts in {@code *.prj} or other auxiliary files,
- * or {@code null} for {@link Locale#ROOT} (usually English).
- * This locale is <strong>not</strong> used for parsing numbers or dates.
- */
- private final Locale locale;
-
- /**
- * Timezone for dates in {@code *.prj} or other auxiliary files,
- * or {@code null} for UTC.
- */
- private final TimeZone timezone;
-
/**
* The coordinate reference system. This is initialized on the value
provided by {@link OptionKey#DEFAULT_CRS}
* at construction time, and is modified later if a {@code "*.prj"} file
is found.
@@ -128,10 +94,18 @@ public abstract class PRJDataStore extends URIDataStore {
*/
protected PRJDataStore(final DataStoreProvider provider, final
StorageConnector connector) throws DataStoreException {
super(provider, connector);
- crs = connector.getOption(OptionKey.DEFAULT_CRS);
- encoding = connector.getOption(OptionKey.ENCODING);
- locale = connector.getOption(OptionKey.LOCALE); // For
`InternationalString`, not for numbers.
- timezone = connector.getOption(OptionKey.TIMEZONE);
+ crs = connector.getOption(OptionKey.DEFAULT_CRS);
+ }
+
+ /**
+ * {@return the convention to use for parsing the PRJ file if Well-Known
Text 1 is used}.
+ * Unfortunately, many formats use the ambiguous conventions from the very
first specification,
+ * and ignore the clarifications done by OGC 01-009. In such case, we have
to tell the WKT parser
+ * that the ambiguous conventions are used. This method can be overridden
if the subclass has a way
+ * to know which WKT 1 conventions are used.
+ */
+ protected Convention getConvention() {
+ return Convention.WKT1_COMMON_UNITS;
}
/**
@@ -144,190 +118,61 @@ public abstract class PRJDataStore extends URIDataStore {
* @throws DataStoreException if an error occurred while reading the file.
*/
protected final void readPRJ() throws DataStoreException {
+ readWKT(CoordinateReferenceSystem.class, PRJ).ifPresent((result) ->
crs = result);
+ }
+
+ /**
+ * Reads an auxiliary file in WKT or GML format. Standard PRJ files use
WKT only,
+ * but the GML format is also accepted by this method as an extension
specific to Apache SIS.
+ *
+ * @param type base class or interface of the object to read.
+ * @param extension extension of the file to read (usually {@link
#PRJ}), or {@code null} for the main file.
+ * @return the parsed object, or an empty value if the file does not exist.
+ * @throws DataStoreException if an error occurred while reading the file.
+ */
+ protected final <T> Optional<T> readWKT(final Class<T> type, final String
extension) throws DataStoreException {
Exception cause = null, suppressed = null;
try {
- final AuxiliaryContent content = readAuxiliaryFile(PRJ);
+ final AuxiliaryContent content = readAuxiliaryFile(extension,
true);
if (content == null) {
- listeners.warning(cannotReadAuxiliaryFile(PRJ));
- return;
+ listeners.warning(cannotReadAuxiliaryFile(extension));
+ return Optional.empty();
+ }
+ if (content.source != null) {
+ // ClassCastException handled by `catch` statement below.
+ return Optional.of(type.cast(readXML(content.source)));
}
final String wkt = content.toString();
- final StoreFormat format = new StoreFormat(locale, timezone, null,
listeners);
- format.setConvention(Convention.WKT1_COMMON_UNITS); //
Ignored if the format is WKT 2.
+ final StoreFormat format = new StoreFormat(dataLocale, timezone,
null, listeners);
+ format.setConvention(getConvention()); // Ignored if the
format is WKT 2.
try {
format.setSourceFile(content.getURI());
} catch (URISyntaxException e) {
suppressed = e;
}
final ParsePosition pos = new ParsePosition(0);
- crs = (CoordinateReferenceSystem) format.parse(wkt, pos);
- if (crs != null) {
+ // ClassCastException handled by `catch` statement below.
+ final T result = type.cast(format.parse(wkt, pos));
+ if (result != null) {
/*
* Some characters may exist after the WKT definition. For
example, we sometimes see the CRS
* defined twice: as a WKT on the first line, followed by
key-value pairs on next lines.
- * Current Apache SIS implementation ignores the characters
after WKT.
+ * Current Apache SIS implementation ignores all characters
after the WKT.
*/
- format.validate(crs);
- return;
+ format.validate(result);
+ return Optional.of(result);
}
} catch (NoSuchFileException | FileNotFoundException e) {
- listeners.warning(cannotReadAuxiliaryFile(PRJ), e);
- return;
- } catch (IOException | ParseException | ClassCastException e) {
+ listeners.warning(cannotReadAuxiliaryFile(extension), e);
+ return Optional.empty();
+ } catch (IOException | ParseException | JAXBException |
ClassCastException e) {
cause = e;
}
- final var e = new
DataStoreReferencingException(cannotReadAuxiliaryFile(PRJ), cause);
+ final var e = new
DataStoreReferencingException(cannotReadAuxiliaryFile(extension), cause);
if (suppressed != null) e.addSuppressed(suppressed);
throw e;
}
- /**
- * Reads the content of the auxiliary file with the specified extension.
- * This method uses the same URI as {@link #location},
- * except for the extension which is replaced by the given value.
- * This method is suitable for reasonably small files.
- * An arbitrary size limit is applied for safety.
- *
- * @param extension the filename extension of the auxiliary file to
open.
- * @return the file content together with the source, or {@code null} if
none. Should be short-lived.
- * @throws NoSuchFileException if the auxiliary file has not been found
(when opened from path).
- * @throws FileNotFoundException if the auxiliary file has not been found
(when opened from URL).
- * @throws IOException if another error occurred while opening the stream.
- * @throws DataStoreException if the auxiliary file content seems too
large.
- */
- protected final AuxiliaryContent readAuxiliaryFile(final String extension)
throws IOException, DataStoreException {
- /*
- * Try to open the stream using the storage type (Path or URL) closest
to the type
- * given at construction time. We do that because those two types
cannot open the
- * same streams. For example, Path does not open HTTP or FTP
connections by default,
- * and URL does not open S3 files in current implementation.
- */
- final InputStream stream;
- Path path = locationAsPath;
- final Object source; // In case an error message is
produced.
- if (path != null) {
- final String base = getBaseFilename(path);
- path = path.resolveSibling(base.concat(extension));
- stream = Files.newInputStream(path);
- source = path;
- } else try {
- final URI uri = IOUtilities.toAuxiliaryURI(location, extension,
true);
- if (uri == null) {
- return null;
- }
- final URL url = uri.toURL();
- stream = url.openStream();
- source = url;
- } catch (URISyntaxException e) {
- throw new DataStoreException(cannotReadAuxiliaryFile(extension),
e);
- }
- /*
- * Reads the auxiliary file fully, with an arbitrary size limit.
- */
- try (InputStreamReader reader = (encoding != null)
- ? new InputStreamReader(stream, encoding)
- : new InputStreamReader(stream))
- {
- char[] buffer = new char[1024];
- int offset = 0, count;
- while ((count = reader.read(buffer, offset, buffer.length -
offset)) >= 0) {
- offset += count;
- if (offset >= buffer.length) {
- if (offset >= MAXIMAL_LENGTH) {
- throw new
DataStoreContentException(Resources.forLocale(listeners.getLocale())
-
.getString(Resources.Keys.AuxiliaryFileTooLarge_1,
IOUtilities.filename(source)));
- }
- buffer = Arrays.copyOf(buffer, offset*2);
- }
- }
- return new AuxiliaryContent(source, buffer, 0, offset);
- }
- }
-
- /**
- * Content of a file read by {@link #readAuxiliaryFile(String)}.
- * This is used as a workaround for not being able to return multiple
values from a single method.
- * Instances of this class should be short lived, because they hold larger
arrays than necessary.
- */
- protected static final class AuxiliaryContent implements CharSequence {
- /** {@link Path} or {@link URL} that have been read. */
- private final Object source;
-
- /** The textual content of the auxiliary file. */
- private final char[] buffer;
-
- /** Index of the first valid character in {@link #buffer}. */
- private final int offset;
-
- /** Number of valid characters in {@link #buffer}. */
- private final int length;
-
- /** Wraps (without copying) the given array as the content of an
auxiliary file. */
- private AuxiliaryContent(final Object source, final char[] buffer,
final int offset, final int length) {
- this.source = source;
- this.buffer = buffer;
- this.offset = offset;
- this.length = length;
- }
-
- /**
- * Returns the filename (without path) of the auxiliary file.
- * This information is mainly for producing error messages.
- *
- * @return name of the auxiliary file that have been read.
- */
- public String getFilename() {
- return IOUtilities.filename(source);
- }
-
- /**
- * Returns the source as an URI if possible.
- *
- * @return the source as an URI, or {@code null} if none.
- * @throws URISyntaxException if the URI cannot be parsed.
- */
- public URI getURI() throws URISyntaxException {
- return IOUtilities.toURI(source);
- }
-
- /**
- * Returns the number of valid characters in this sequence.
- */
- @Override
- public int length() {
- return length;
- }
-
- /**
- * Returns the character at the given index. For performance reasons
this method does not check index bounds.
- * The behavior of this method is undefined if the given index is not
smaller than {@link #length()}.
- * We skip bounds check because this class should be used for Apache
SIS internal purposes only.
- */
- @Override
- public char charAt(final int index) {
- return buffer[offset + index];
- }
-
- /**
- * Returns a sub-sequence of this auxiliary file content. For
performance reasons this method does not
- * perform bound checks. The behavior of this method is undefined if
arguments are out of bounds.
- * We skip bounds check because this class should be used for Apache
SIS internal purposes only.
- */
- @Override
- public CharSequence subSequence(final int start, final int end) {
- return new AuxiliaryContent(source, buffer, offset + start, end -
start);
- }
-
- /**
- * Copies this auxiliary file content in a {@link String}.
- * This method does not cache the result; caller should invoke at most
once.
- */
- @Override
- public String toString() {
- return new String(buffer, offset, length);
- }
- }
-
/**
* Writes the {@code "*.prj"} auxiliary file if {@link #crs} is non-null.
* If {@link #crs} is null and the auxiliary file exists, it is deleted.
@@ -346,7 +191,7 @@ public abstract class PRJDataStore extends URIDataStore {
if (crs == null) {
deleteAuxiliaryFile(PRJ);
} else try (BufferedWriter out = writeAuxiliaryFile(PRJ)) {
- final StoreFormat format = new StoreFormat(locale, timezone,
null, listeners);
+ final StoreFormat format = new StoreFormat(dataLocale,
timezone, null, listeners);
// Keep the default "WKT 2" format (see method javadoc).
format.format(crs, out);
out.newLine();
@@ -358,48 +203,6 @@ public abstract class PRJDataStore extends URIDataStore {
}
}
- /**
- * Creates a writer for an auxiliary file with the specified extension.
- * This method uses the same path as {@link #location},
- * except for the extension which is replaced by the given value.
- *
- * @param extension the filename extension of the auxiliary file to
write.
- * @return a stream opened on the specified file.
- * @throws UnknownServiceException if no {@link Path} or {@link
java.net.URI} is available.
- * @throws DataStoreException if the auxiliary file cannot be created.
- * @throws IOException if another error occurred while opening the stream.
- */
- protected final BufferedWriter writeAuxiliaryFile(final String extension)
- throws IOException, DataStoreException
- {
- final Path[] paths = super.getComponentFiles();
- if (paths.length == 0) {
- throw new UnknownServiceException();
- }
- Path path = paths[0];
- final String base = getBaseFilename(path);
- path = path.resolveSibling(base.concat(extension));
- return (encoding != null)
- ? Files.newBufferedWriter(path, encoding)
- : Files.newBufferedWriter(path);
- }
-
- /**
- * Deletes the auxiliary file with the given extension if it exists.
- * If the auxiliary file does not exist, then this method does nothing.
- *
- * @param extension the filename extension of the auxiliary file to
delete.
- * @throws DataStoreException if the auxiliary file is not on a supported
file system.
- * @throws IOException if an error occurred while deleting the file.
- */
- protected final void deleteAuxiliaryFile(final String extension) throws
DataStoreException, IOException {
- for (Path path : super.getComponentFiles()) {
- final String base = getBaseFilename(path);
- path = path.resolveSibling(base.concat(extension));
- Files.deleteIfExists(path);
- }
- }
-
/**
* Returns the {@linkplain #location} as a {@code Path} component together
with auxiliary files.
* The default implementation does the same computation as the
super-class, then adds the sibling
@@ -447,17 +250,6 @@ public abstract class PRJDataStore extends URIDataStore {
return paths;
}
- /**
- * Returns the filename of the given path without the file suffix.
- * The returned string always ends in {@code '.'}, making it ready
- * for concatenation of a new suffix.
- */
- private static String getBaseFilename(final Path path) {
- final String base = path.getFileName().toString();
- final int s = base.lastIndexOf(IOUtilities.EXTENSION_SEPARATOR);
- return (s >= 0) ? base.substring(0, s+1) : base +
IOUtilities.EXTENSION_SEPARATOR;
- }
-
/**
* Returns the parameters used to open this data store.
*
@@ -465,12 +257,9 @@ public abstract class PRJDataStore extends URIDataStore {
*/
@Override
public Optional<ParameterValueGroup> getOpenParameters() {
- final ParameterValueGroup pg = parameters(provider, location);
- if (pg != null) {
- pg.parameter(Provider.CRS_NAME).setValue(crs);
- return Optional.of(pg);
- }
- return Optional.empty();
+ final Optional<ParameterValueGroup> op = super.getOpenParameters();
+ op.ifPresent((pg) -> pg.parameter(Provider.CRS_NAME).setValue(crs));
+ return op;
}
/**
@@ -478,7 +267,7 @@ public abstract class PRJDataStore extends URIDataStore {
*
* @author Martin Desruisseaux (Geomatys)
*/
- public abstract static class Provider extends URIDataStore.Provider {
+ public abstract static class Provider extends URIDataStoreProvider {
/**
* Name of the {@link #DEFAULT_CRS} parameter.
*/
diff --git
a/endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/base/URIDataStore.java
b/endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/base/URIDataStore.java
index 88a51b19a0..3427bb2b73 100644
---
a/endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/base/URIDataStore.java
+++
b/endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/base/URIDataStore.java
@@ -16,47 +16,49 @@
*/
package org.apache.sis.storage.base;
+import java.util.Map;
+import java.util.HashMap;
import java.util.Arrays;
import java.util.Optional;
-import java.io.DataInput;
-import java.io.DataOutput;
+import java.util.Locale;
+import java.util.TimeZone;
+import java.io.BufferedWriter;
+import java.io.BufferedInputStream;
import java.io.InputStream;
-import java.io.OutputStream;
import java.io.IOException;
+import java.net.URL;
import java.net.URI;
import java.net.URISyntaxException;
import java.nio.file.Path;
import java.nio.file.Files;
-import java.nio.file.OpenOption;
import java.nio.file.StandardOpenOption;
import java.nio.charset.Charset;
+import javax.xml.transform.Source;
import jakarta.xml.bind.JAXBException;
import org.opengis.util.GenericName;
import org.opengis.parameter.ParameterValueGroup;
-import org.opengis.parameter.ParameterDescriptor;
-import org.opengis.parameter.ParameterDescriptorGroup;
-import org.opengis.parameter.ParameterNotFoundException;
-import org.apache.sis.parameter.ParameterBuilder;
import org.apache.sis.storage.StorageConnector;
import org.apache.sis.storage.DataOptionKey;
-import org.apache.sis.storage.Resource;
import org.apache.sis.storage.DataStore;
import org.apache.sis.storage.DataStoreProvider;
import org.apache.sis.storage.DataStoreException;
-import org.apache.sis.storage.IllegalOpenParameterException;
+import org.apache.sis.storage.DataStoreContentException;
+import org.apache.sis.storage.ReadOnlyStorageException;
import org.apache.sis.storage.internal.Resources;
import org.apache.sis.io.stream.IOUtilities;
import org.apache.sis.setup.OptionKey;
import org.apache.sis.util.ArraysExt;
import org.apache.sis.util.iso.Names;
-import org.apache.sis.util.logging.Logging;
import org.apache.sis.xml.XML;
+import org.apache.sis.xml.util.URISource;
/**
* A data store for a storage that may be represented by a {@link URI}.
- * It is still possible to create a data store with a {@link
java.nio.channels.ReadableByteChannel},
- * {@link java.io.InputStream} or {@link java.io.Reader}, in which case the
{@linkplain #location} will be null.
+ * The URI is stored in {@link #location} field and is used for populating
some default metadata.
+ * It is also use for resolving the path to auxiliary files, for example the
CRS definition in PRJ file.
+ * The URI can be null if the only available storage is a {@link
java.nio.channels.ReadableByteChannel},
+ * {@link InputStream} or {@link java.io.Reader}.
*
* @author Johann Sorel (Geomatys)
* @author Martin Desruisseaux (Geomatys)
@@ -82,11 +84,30 @@ public abstract class URIDataStore extends DataStore
implements StoreResource, R
*/
private final Path metadataPath;
+ /**
+ * User-specified character encoding, or {@code null} for the JVM default
(usually UTF-8).
+ * Subclasses may replace this value by a value read from the data file.
+ */
+ protected Charset encoding;
+
+ /**
+ * User-specified locale for textual content, or {@code null} for {@link
Locale#ROOT} (usually English).
+ * This locale is usually <strong>not</strong> used for parsing numbers or
dates.
+ * Subclasses may replace this value by a value read from the data file.
+ */
+ protected Locale dataLocale;
+
+ /**
+ * User-specified timezone for dates, or {@code null} for UTC.
+ * Subclasses may replace this value by a value read from the data file.
+ */
+ protected TimeZone timezone;
+
/**
* Creates a new data store. This constructor does not open the file,
* so subclass constructors can decide whether to open in read-only or
read/write mode.
* It is caller's responsibility to ensure that the {@link
java.nio.file.OpenOption}
- * are compatible with whether this data store is read-only or read/write.
+ * are compatible with the capabilities (read-only or read/write) of this
data store.
*
* @param provider the factory that created this {@code URIDataStore}
instance, or {@code null} if unspecified.
* @param connector information about the storage (URL, stream, reader
instance, <i>etc</i>).
@@ -101,6 +122,9 @@ public abstract class URIDataStore extends DataStore
implements StoreResource, R
} else {
metadataPath = null;
}
+ encoding = connector.getOption(OptionKey.ENCODING);
+ dataLocale = connector.getOption(OptionKey.LOCALE);
+ timezone = connector.getOption(OptionKey.TIMEZONE);
}
/**
@@ -127,9 +151,19 @@ public abstract class URIDataStore extends DataStore
implements StoreResource, R
}
/**
- * Returns the filename without path and without file extension, or {@code
null} if none.
+ * {@return the filename without path and without file extension, or null
if none}.
+ * This method can be used for building metadata like below (note that
{@link #getIdentifier()}
+ * should not be invoked during metadata construction time, for avoiding
recursive method calls):
+ *
+ * {@snippet lang="java" :
+ * builder.addTitleOrIdentifier(getFilename(),
MetadataBuilder.Scope.ALL);
+ * }
+ *
+ * Above snippet should not be applied before this data store did its best
effort for providing a title.
+ * The use of identifier as a title is a fallback for making valid
metadata, because the title is mandatory
+ * in ISO 19111 metadata.
*/
- private String getFilename() {
+ public final String getFilename() {
if (location == null) {
return null;
}
@@ -138,46 +172,64 @@ public abstract class URIDataStore extends DataStore
implements StoreResource, R
}
/**
- * {@return the path to the auxiliary metadata file, or {@code null} if
none}.
- * This is a path built from the {@link DataOptionKey#METADATA_PATH} value
if present.
- * Note that the metadata may be unavailable as a {@link Path} but
available as an {@link URI}.
+ * Returns the main and metadata locations as {@code Path} components, or
an empty array if none.
+ * The default implementation returns the storage specified at
construction time converted to a
+ * {@link Path} if such conversion was possible, or an empty array
otherwise. The array may also
+ * contains the path to the {@linkplain DataOptionKey#METADATA_PATH
auxiliary metadata file}.
+ *
+ * @return the URI to component files as paths, or an empty array if
unknown.
+ * @throws DataStoreException if an error occurred while getting the paths.
*/
- private Path getMetadataPath() throws IOException {
- Path path = replaceWildcard(metadataPath);
- if (path != null) {
- Path parent = locationAsPath;
- if (parent != null) {
- parent = parent.getParent();
- if (parent != null) {
- path = parent.resolve(path);
- }
- }
- if (Files.isSameFile(path, locationAsPath)) {
- return null;
- }
+ @Override
+ public Path[] getComponentFiles() throws DataStoreException {
+ try {
+ final var paths = new Path[] {locationAsPath, getMetadataPath()};
+ return ArraysExt.resize(paths, ArraysExt.removeDuplicated(paths,
ArraysExt.removeNulls(paths)));
+ } catch (IOException e) {
+ throw new DataStoreException(e);
}
- return path;
}
/**
- * {@return the URI to the auxiliary metadata file, or {@code null} if
none}.
- * This is a path built from the {@link DataOptionKey#METADATA_PATH} value
if present.
- * Note that the metadata may be unavailable as an {@link URI} but
available as a {@link Path}.
+ * Returns the parameters used to open this data store.
+ *
+ * @return parameters used for opening this {@code DataStore}.
*/
- private URI getMetadataURI() throws URISyntaxException {
- URI uri = location;
- if (uri != null) {
- final Path path = replaceWildcard(metadataPath);
- if (path != null) {
- uri = IOUtilities.toAuxiliaryURI(uri, path.toString(), false);
- if (!uri.equals(location)) {
- return uri;
- }
- }
- }
- return null;
+ @Override
+ public Optional<ParameterValueGroup> getOpenParameters() {
+ return Optional.ofNullable(parameters(provider, location));
}
+ /**
+ * Creates parameter value group for the current location, if non-null.
+ * This convenience method is used for {@link
DataStore#getOpenParameters()} implementations in public
+ * {@code DataStore} that cannot extend {@code URIDataStore} directly,
because this class is internal.
+ *
+ * @param provider the provider of the data store for which to get open
parameters.
+ * @param location file opened by the data store.
+ * @return parameters to be returned by {@link
DataStore#getOpenParameters()}, or {@code null} if unknown.
+ */
+ public static ParameterValueGroup parameters(final DataStoreProvider
provider, final URI location) {
+ if (location == null || provider == null) return null;
+ final ParameterValueGroup pg =
provider.getOpenParameters().createValue();
+ pg.parameter(DataStoreProvider.LOCATION).setValue(location);
+ return pg;
+ }
+
+
+
+
+ /*
+
┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
+ ┃
┃
+ ┃ AUXILIARY FILES
┃
+ ┃
┃
+ ┃ The following are helper methods for data stores made of many
files ┃
+ ┃ at some location relative to the main file (e.g. in the same
folder). ┃
+ ┃
┃
+
┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛
+ */
+
/**
* Returns the given path with the wildcard character replaced by the name
of the main file.
*
@@ -217,297 +269,262 @@ public abstract class URIDataStore extends DataStore
implements StoreResource, R
}
/**
- * Returns the main and metadata locations as {@code Path} components, or
an empty array if none.
- * The default implementation returns the storage specified at
construction time converted to a {@link Path}
- * if such conversion was possible, or {@code null} otherwise.
- *
- * @return the URI as a path, or an empty array if unknown.
- * @throws DataStoreException if an error occurred while getting the paths.
+ * {@return the path to the auxiliary metadata file, or null if none}.
+ * This is a path built from the {@link DataOptionKey#METADATA_PATH} value
if present.
+ * Note that the metadata may be unavailable as a {@link Path} but
available as an {@link URI}.
*/
- @Override
- public Path[] getComponentFiles() throws DataStoreException {
- try {
- final var paths = new Path[] {locationAsPath, getMetadataPath()};
- return ArraysExt.resize(paths, ArraysExt.removeDuplicated(paths,
ArraysExt.removeNulls(paths)));
- } catch (IOException e) {
- throw new DataStoreException(e);
+ private Path getMetadataPath() throws IOException {
+ Path path = replaceWildcard(metadataPath);
+ if (path != null) {
+ Path parent = locationAsPath;
+ if (parent != null) {
+ parent = parent.getParent();
+ if (parent != null) {
+ path = parent.resolve(path);
+ }
+ }
+ if (Files.isSameFile(path, locationAsPath)) {
+ return null;
+ }
}
+ return path;
}
/**
- * Returns the parameters used to open this data store.
- *
- * @return parameters used for opening this {@code DataStore}.
+ * {@return the URI to the auxiliary metadata file, or null if none}.
+ * This is a path built from the {@link DataOptionKey#METADATA_PATH} value
if present.
+ * Note that the metadata may be unavailable as an {@link URI} but
available as a {@link Path}.
*/
- @Override
- public Optional<ParameterValueGroup> getOpenParameters() {
- return Optional.ofNullable(parameters(provider, location));
+ private URI getMetadataURI() throws URISyntaxException {
+ URI uri = location;
+ if (uri != null) {
+ final Path path = replaceWildcard(metadataPath);
+ if (path != null) {
+ uri = IOUtilities.toAuxiliaryURI(uri, path.toString(), false);
+ if (!uri.equals(location)) {
+ return uri;
+ }
+ }
+ }
+ return null;
}
/**
- * Creates parameter value group for the current location, if non-null.
- * This convenience method is used for {@link
DataStore#getOpenParameters()} implementations in public
- * {@code DataStore} that cannot extend {@code URIDataStore} directly,
because this class is internal.
- *
- * @param provider the provider of the data store for which to get open
parameters.
- * @param location file opened by the data store.
- * @return parameters to be returned by {@link
DataStore#getOpenParameters()}.
+ * Opens a buffered input stream for the given path.
*
- * @todo Verify if non-exported classes in JDK9 are hidden from Javadoc,
like package-private classes.
- * If true, we could remove this hack and extend {@code
URIDataStore} even in public classes.
+ * @param path the path to the file to open.
+ * @return a new buffered input stream.
+ * @throws IOException in an error occurred while opening the file.
*/
- public static ParameterValueGroup parameters(final DataStoreProvider
provider, final URI location) {
- if (location == null || provider == null) return null;
- final ParameterValueGroup pg =
provider.getOpenParameters().createValue();
- pg.parameter(DataStoreProvider.LOCATION).setValue(location);
- return pg;
+ private static InputStream open(final Path path) throws IOException {
+ return new BufferedInputStream(Files.newInputStream(path,
StandardOpenOption.READ), AuxiliaryContent.BUFFER_SIZE);
}
/**
- * Provider for {@link URIDataStore} instances.
+ * If an auxiliary metadata file has been specified, merges that file to
the given metadata.
+ * This step should be done only after the data store added its own
metadata.
+ * Failure to load auxiliary metadata are only a warning.
*
- * @author Johann Sorel (Geomatys)
- * @author Martin Desruisseaux (Geomatys)
+ * @param builder where to merge the metadata.
*/
- public abstract static class Provider extends DataStoreProvider {
- /**
- * Description of the {@value #LOCATION} parameter.
- */
- public static final ParameterDescriptor<URI> LOCATION_PARAM;
-
- /**
- * Description of the "metadata" parameter.
- */
- public static final ParameterDescriptor<Path> METADATA_PARAM;
-
- /**
- * Description of the optional {@value #CREATE} parameter, which may
be present in writable data store.
- * This parameter is not included in the descriptor created by {@link
#build(ParameterBuilder)} default
- * implementation. It is subclass responsibility to add it if desired,
only if supported.
- */
- public static final ParameterDescriptor<Boolean> CREATE_PARAM;
-
- /**
- * Description of the optional parameter for character encoding used
by the data store.
- * This parameter is not included in the descriptor created by {@link
#build(ParameterBuilder)}
- * default implementation. It is subclass responsibility to add it if
desired.
- */
- public static final ParameterDescriptor<Charset> ENCODING;
- static {
- final ParameterBuilder builder = new ParameterBuilder();
- ENCODING =
builder.addName("encoding").setDescription(Resources.formatInternational(Resources.Keys.DataStoreEncoding)).create(Charset.class,
null);
- CREATE_PARAM = builder.addName( CREATE
).setDescription(Resources.formatInternational(Resources.Keys.DataStoreCreate
)).create(Boolean.class, null);
- METADATA_PARAM =
builder.addName("metadata").setDescription(Resources.formatInternational(Resources.Keys.MetadataLocation
)).create(Path.class, null);
- LOCATION_PARAM = builder.addName( LOCATION
).setDescription(Resources.formatInternational(Resources.Keys.DataStoreLocation)).setRequired(true).create(URI.class,
null);
- }
-
- /**
- * The parameter descriptor to be returned by {@link
#getOpenParameters()}.
- * Created when first needed.
- */
- private volatile ParameterDescriptorGroup openDescriptor;
-
- /**
- * Creates a new provider.
- */
- protected Provider() {
- }
-
- /**
- * Returns a description of all parameters accepted by this provider
for opening a data store.
- * This method creates the descriptor only when first needed.
Subclasses can override the
- * {@link #build(ParameterBuilder)} method if they need to modify the
descriptor to create.
- *
- * @return description of the parameters required or accepted for
opening a {@link DataStore}.
- */
- @Override
- public final ParameterDescriptorGroup getOpenParameters() {
- ParameterDescriptorGroup desc = openDescriptor;
- if (desc == null) {
- openDescriptor = desc = build(new
ParameterBuilder().addName(getShortName()));
+ protected final void mergeAuxiliaryMetadata(final MetadataBuilder builder)
{
+ Object metadata = null;
+ Exception error = null;
+ try {
+ final URI source;
+ final InputStream input;
+ final Path path = getMetadataPath();
+ if (path != null) {
+ source = path.toUri();
+ input = open(path);
+ } else {
+ source = getMetadataURI();
+ if (source == null) return;
+ input = source.toURL().openStream();
}
- return desc;
- }
-
- /**
- * Invoked by {@link #getOpenParameters()} the first time that a
parameter descriptor needs to be created.
- * When invoked, the parameter group name is set to a name derived
from the {@link #getShortName()} value.
- * The default implementation creates a group containing {@link
#LOCATION_PARAM} and {@link #METADATA_PARAM}.
- * Subclasses can override if they need to create a group with more
parameters.
- *
- * @param builder the builder to use for creating parameter
descriptor. The group name is already set.
- * @return the parameters descriptor created from the given builder.
- */
- protected ParameterDescriptorGroup build(final ParameterBuilder
builder) {
- return builder.createGroup(LOCATION_PARAM, METADATA_PARAM);
+ metadata = readXML(input, source);
+ } catch (URISyntaxException | IOException e) {
+ error = e;
+ } catch (JAXBException e) {
+ final Throwable cause = e.getCause();
+ error = (cause instanceof IOException) ? (Exception) cause : e;
}
-
- /**
- * Convenience method creating a parameter descriptor containing only
{@link #LOCATION_PARAM}.
- * This convenience method is used for public providers that cannot
extend this {@code Provider}
- * class because it is internal.
- *
- * @param name short name of the data store format.
- * @return the descriptor for open parameters.
- *
- * @todo Verify if non-exported classes in JDK9 are hidden from
Javadoc, like package-private classes.
- * If true, we could remove this hack and extend {@code
URIDataStore} even in public classes.
- */
- public static ParameterDescriptorGroup descriptor(final String name) {
- return new
ParameterBuilder().addName(name).createGroup(LOCATION_PARAM);
+ if (metadata != null) {
+ builder.mergeMetadata(metadata, getLocale());
+ } else if (error != null) {
+ listeners.warning(cannotReadAuxiliaryFile("xml"), error);
}
+ }
- /**
- * Creates a storage connector initialized to the location declared in
given parameters.
- * This convenience method does not set any other parameters.
- * In particular, reading (or ignoring) the {@value #CREATE} parameter
is left to callers,
- * because not all implementations may create data stores with {@link
java.nio.file.StandardOpenOption}.
- *
- * @param provider the provider for which to create a storage
connector (for error messages).
- * @param parameters the parameters to use for creating a storage
connector.
- * @return the storage connector initialized to the location specified
in the parameters.
- * @throws IllegalOpenParameterException if no {@value #LOCATION}
parameter has been found.
- */
- public static StorageConnector connector(final DataStoreProvider
provider, final ParameterValueGroup parameters)
- throws IllegalOpenParameterException
- {
- ParameterNotFoundException cause = null;
- if (parameters != null) try {
- final Object location =
parameters.parameter(LOCATION).getValue();
- if (location != null) {
- return new StorageConnector(location);
- }
- } catch (ParameterNotFoundException e) {
- cause = e;
- }
- throw new
IllegalOpenParameterException(Resources.format(Resources.Keys.UndefinedParameter_2,
- provider.getShortName(), LOCATION), cause);
+ /**
+ * Reads a XML document from an input stream using JAXB. The {@link
#dataLocale} and {@link #timezone}
+ * are specified to the unmarshaller. Warnings are redirected to the
listeners.
+ *
+ * @param input the input stream of the XML document. Will be closed by
this method.
+ * @param source the source of the XML document to read.
+ * @return the unmarshalled object.
+ * @throws JAXBException if an error occurred while parsing the XML
document.
+ * @throws IOException if an error occurred while closing the input stream.
+ */
+ protected final Object readXML(final InputStream input, final URI source)
throws IOException, JAXBException {
+ try (input) {
+ return readXML(URISource.create(input, source));
}
+ }
- /**
- * Returns {@code true} if the open options contains {@link
StandardOpenOption#WRITE}
- * or if the storage type is some kind of output stream. An ambiguity
may exist between
- * the case when a new file would be created and when an existing file
would be updated.
- * This ambiguity is resolved by the {@code ifNew} argument:
- * if {@code false}, then the two cases are not distinguished.
- * If {@code true}, then this method returns {@code true} only if a
new file would be created.
- *
- * @param connector the connector to use for opening a file.
- * @param ifNew whether to return {@code true} only if a new file
would be created.
- * @return whether the specified connector should open a writable data
store.
- * @throws DataStoreException if the storage object has already been
used and cannot be reused.
- */
- public static boolean isWritable(final StorageConnector connector,
final boolean ifNew) throws DataStoreException {
- final Object storage = connector.getStorage();
- if (storage instanceof OutputStream || storage instanceof
DataOutput) return true; // Must be tested first.
- if (storage instanceof InputStream || storage instanceof
DataInput) return false; // Ignore options.
- final OpenOption[] options =
connector.getOption(OptionKey.OPEN_OPTIONS);
- if (ArraysExt.contains(options, StandardOpenOption.WRITE)) {
- if (!ifNew || ArraysExt.contains(options,
StandardOpenOption.TRUNCATE_EXISTING)) {
- return true;
- }
- if (ArraysExt.contains(options,
StandardOpenOption.CREATE_NEW)) {
- return IOUtilities.isKindOfPath(storage);
- }
- if (ArraysExt.contains(options, StandardOpenOption.CREATE)) {
- final Path path = connector.getStorageAs(Path.class);
- return (path != null) && Files.notExists(path);
- }
- }
- return false;
- }
+ /**
+ * Reads a XML document from a source using JAXB. The {@link #dataLocale}
and {@link #timezone}
+ * are specified to the unmarshaller. Warnings are redirected to the
listeners.
+ *
+ * @param source the source of the XML document to read.
+ * @return the unmarshalled object.
+ * @throws JAXBException if an error occurred while parsing the XML
document.
+ */
+ protected final Object readXML(final Source source) throws JAXBException {
+ java.util.logging.Filter handler = (record) -> {
+ record.setLoggerName(null); // For allowing `listeners` to
use the provider's logger name.
+ listeners.warning(record);
+ return true;
+ };
+ // Cannot use Map.of(…) because it does not accept null values.
+ Map<String,Object> properties = new HashMap<>(8);
+ properties.put(XML.LOCALE, dataLocale);
+ properties.put(XML.TIMEZONE, timezone);
+ properties.put(XML.WARNING_FILTER, handler);
+ return XML.unmarshal(source, properties);
}
/**
- * Returns the location (path, URL, URI, <i>etc.</i>) of the given
resource.
- * The type of the returned object can be any of the types documented in
{@link DataStoreProvider#LOCATION}.
- * The main ones are {@link java.net.URI}, {@link java.nio.file.Path} and
JDBC {@linkplain javax.sql.DataSource}.
+ * Reads the content of the auxiliary file with the specified extension.
+ * This method uses the {@link #location} URI with the extension replaced
by the given value.
+ * The file content is read and stored as a character sequence decoded
according the store
+ * {@linkplain #encoding}, unless {@code acceptXML} is {@code true} and
the file has been
+ * identified as an XML file. In the latter case, the character sequence
is empty and the
+ * source must be read with {@link AuxiliaryContent#source}.
*
- * @param resource the resource for which to get the location, or {@code
null}.
- * @return location of the given resource, or {@code null} if none.
- * @throws DataStoreException if an error on the file system prevent the
creation of the path.
+ * <h4>Limitations</h4>
+ * This method is suitable for reasonably small files. An arbitrary size
limit is applied for safety,
+ * unless {@code acceptXML} is {@code true} and the file has been detected
as an XML file.
+ *
+ * @param extension the filename extension (without leading dot) of the
auxiliary file to open,
+ * or {@code null} for using the main file without
changing its extension.
+ * @param acceptXML whether to check if the source is a XML file.
+ * @return the file content together with the source, or {@code null} if
none. Should be short-lived.
+ * @throws NoSuchFileException if the auxiliary file has not been found
(when opened from path).
+ * @throws FileNotFoundException if the auxiliary file has not been found
(when opened from URL).
+ * @throws IOException if another error occurred while opening the stream.
+ * @throws DataStoreException if the auxiliary file content seems too
large.
*/
- public static Object location(final Resource resource) throws
DataStoreException {
- if (resource instanceof DataStore) {
- final Optional<ParameterValueGroup> p = ((DataStore)
resource).getOpenParameters();
- if (p.isPresent()) try {
- return
p.get().parameter(DataStoreProvider.LOCATION).getValue();
- } catch (ParameterNotFoundException e) {
- /*
- * This exception should not happen often since the "location"
parameter is recommended.
- * Note that it does not mean the same thing as "parameter
provided but value is null".
- * In that later case we want to return the null value as
specified in the parameters.
- */
- Logging.recoverableException(StoreUtilities.LOGGER,
URIDataStore.class, "location", e);
+ protected final AuxiliaryContent readAuxiliaryFile(final String extension,
final boolean acceptXML)
+ throws IOException, DataStoreException
+ {
+ /*
+ * Try to open the stream using the storage type (Path or URL) closest
to the type
+ * given at construction time. We do that because those two types
cannot open the
+ * same streams. For example, Path does not open HTTP or FTP
connections by default,
+ * and URL does not open S3 files in current implementation.
+ */
+ final InputStream stream;
+ Path path = locationAsPath;
+ final Object source; // In case an error message is
produced.
+ final URI sourceURI; // The source as an URI, or
null.
+ if (path != null) {
+ if (extension != null) {
+ path =
path.resolveSibling(getBaseFilename(path).concat(extension));
+ }
+ stream = open(path);
+ source = path;
+ sourceURI = null;
+ } else try {
+ sourceURI = (extension != null) ?
IOUtilities.toAuxiliaryURI(location, extension, true) : location;
+ if (sourceURI == null) {
+ return null;
}
+ final URL url = sourceURI.toURL();
+ stream = url.openStream();
+ source = url;
+ } catch (URISyntaxException e) {
+ throw new DataStoreException(cannotReadAuxiliaryFile(extension),
e);
}
/*
- * This fallback should not happen with `URIDataStore` implementation
because the "location" parameter
- * is always present even if null. This fallback is for resources
implementated by different classes.
+ * If enabled, tests if the file is an XML file. If this is the case,
we need to use `URISource`
+ * for giving a chance of `org.apache.sis.xml.XML.unmarshal(Source)`
to resolve relative links.
*/
- if (resource instanceof ResourceOnFileSystem) {
- final Path[] paths = ((ResourceOnFileSystem)
resource).getComponentFiles();
- if (paths != null && paths.length != 0) {
- return paths[0]; // First
path is presumed the main file.
- }
+ if (acceptXML && stream.markSupported() &&
org.apache.sis.storage.xml.AbstractProvider.isXML(stream)) {
+ return new AuxiliaryContent(source, URISource.create(stream, (path
!= null) ? path.toUri() : sourceURI));
}
- return null;
+ /*
+ * If the auxiliary file is not an XML file, reads it fully as a text
file with an arbitrary size limit.
+ */
+ var content = AuxiliaryContent.read(source, stream, encoding);
+ if (content != null) {
+ return content;
+ }
+ throw new DataStoreContentException(Resources.forLocale(getLocale())
+ .getString(Resources.Keys.AuxiliaryFileTooLarge_1,
IOUtilities.filename(source)));
}
/**
- * Adds the filename (without extension) as the citation title if there is
no title, or as the identifier otherwise.
- * This method should be invoked last, after {@code DataStore}
implementation did its best effort for adding a title.
- * The intend is actually to provide an identifier, but since the title is
mandatory in ISO 19115 metadata,
- * providing only an identifier without title would be invalid.
+ * Creates a writer for an auxiliary file with the specified extension.
+ * This method uses the same path as {@link #location},
+ * except for the extension which is replaced by the given value.
*
- * @param builder where to add the title or identifier.
+ * @param extension the filename extension of the auxiliary file to
write.
+ * @return a stream opened on the specified file.
+ * @throws DataStoreException if the auxiliary file cannot be created.
+ * @throws IOException if another error occurred while opening the stream.
*/
- protected final void addTitleOrIdentifier(final MetadataBuilder builder) {
- final String filename = getFilename();
- if (filename != null) {
- builder.addTitleOrIdentifier(filename, MetadataBuilder.Scope.ALL);
+ protected final BufferedWriter writeAuxiliaryFile(final String extension)
throws IOException, DataStoreException {
+ Path path = locationAsPath;
+ if (path == null) {
+ throw new ReadOnlyStorageException(Resources.forLocale(getLocale())
+ .getString(Resources.Keys.CanNotWriteResource_1,
getDisplayName()));
}
+ path = path.resolveSibling(getBaseFilename(path).concat(extension));
+ return (encoding != null) ? Files.newBufferedWriter(path, encoding)
+ : Files.newBufferedWriter(path);
}
/**
- * If an auxiliary metadata file has been specified, merge that file to
the given metadata.
- * This step should be done only after the data store added its own
metadata.
- * Failure to load auxiliary metadata are only a warning.
+ * Deletes the auxiliary file with the given extension if it exists.
+ * If the auxiliary file does not exist, then this method does nothing.
*
- * @param builder where to merge the metadata.
+ * @param extension the filename extension of the auxiliary file to
delete.
+ * @throws DataStoreException if the auxiliary file is not on a supported
file system.
+ * @throws IOException if an error occurred while deleting the file.
*/
- protected final void mergeAuxiliaryMetadata(final MetadataBuilder builder)
{
- Object metadata = null;
- Exception error = null;
- try {
- final Path path = getMetadataPath();
- if (path != null) {
- metadata = XML.unmarshal(path);
- } else {
- final URI uri = getMetadataURI();
- if (uri != null) {
- metadata = XML.unmarshal(uri.toURL());
- }
+ protected final void deleteAuxiliaryFile(final String extension) throws
DataStoreException, IOException {
+ String previous = null;
+ for (Path path : getComponentFiles()) {
+ final String base = getBaseFilename(path);
+ if (!base.equals(previous)) {
+ previous = base;
+ path = path.resolveSibling(base.concat(extension));
+ Files.deleteIfExists(path);
}
- } catch (URISyntaxException | IOException e) {
- error = e;
- } catch (JAXBException e) {
- final Throwable cause = e.getCause();
- error = (cause instanceof IOException) ? (Exception) cause : e;
- }
- if (metadata != null) {
- builder.mergeMetadata(metadata, getLocale());
- } else if (error != null) {
- listeners.warning(cannotReadAuxiliaryFile("xml"), error);
}
}
/**
- * {@return the error message for saying than auxiliary file cannot be
read}.
+ * Returns the filename of the given path without the file suffix.
+ * The returned string always ends in {@code '.'}, making it ready
+ * for concatenation of a new suffix.
+ */
+ static String getBaseFilename(final Path path) {
+ final String base = path.getFileName().toString();
+ final int s = base.lastIndexOf(IOUtilities.EXTENSION_SEPARATOR);
+ return (s >= 0) ? base.substring(0, s+1) : base +
IOUtilities.EXTENSION_SEPARATOR;
+ }
+
+ /**
+ * {@return the error message for saying that an auxiliary file cannot be
read}.
*
- * @param extension file extension of the auxiliary file, without
leading dot.
+ * @param extension file extension (without leading dot) of the
auxiliary file, or null for the main file.
*/
- protected final String cannotReadAuxiliaryFile(final String extension) {
+ protected final String cannotReadAuxiliaryFile(String extension) {
+ if (extension == null) {
+ extension = IOUtilities.extension(location);
+ }
return
Resources.forLocale(getLocale()).getString(Resources.Keys.CanNotReadAuxiliaryFile_1,
extension);
}
}
diff --git
a/endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/base/URIDataStoreProvider.java
b/endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/base/URIDataStoreProvider.java
new file mode 100644
index 0000000000..f70df8e68e
--- /dev/null
+++
b/endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/base/URIDataStoreProvider.java
@@ -0,0 +1,236 @@
+/*
+ * 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.storage.base;
+
+import java.util.Optional;
+import java.io.DataInput;
+import java.io.DataOutput;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.net.URI;
+import java.nio.file.Path;
+import java.nio.file.Files;
+import java.nio.file.OpenOption;
+import java.nio.file.StandardOpenOption;
+import java.nio.charset.Charset;
+import org.opengis.parameter.ParameterValueGroup;
+import org.opengis.parameter.ParameterDescriptor;
+import org.opengis.parameter.ParameterDescriptorGroup;
+import org.opengis.parameter.ParameterNotFoundException;
+import org.apache.sis.parameter.ParameterBuilder;
+import org.apache.sis.storage.StorageConnector;
+import org.apache.sis.storage.DataStore;
+import org.apache.sis.storage.DataStoreProvider;
+import org.apache.sis.storage.DataStoreException;
+import org.apache.sis.storage.IllegalOpenParameterException;
+import org.apache.sis.storage.Resource;
+import org.apache.sis.storage.internal.Resources;
+import org.apache.sis.io.stream.IOUtilities;
+import org.apache.sis.setup.OptionKey;
+import org.apache.sis.util.ArraysExt;
+import org.apache.sis.util.logging.Logging;
+
+
+/**
+ * Provider for {@link URIDataStore} instances.
+ *
+ * @author Johann Sorel (Geomatys)
+ * @author Martin Desruisseaux (Geomatys)
+ */
+public abstract class URIDataStoreProvider extends DataStoreProvider {
+ /**
+ * Description of the {@value #LOCATION} parameter.
+ */
+ public static final ParameterDescriptor<URI> LOCATION_PARAM;
+
+ /**
+ * Description of the "metadata" parameter.
+ */
+ public static final ParameterDescriptor<Path> METADATA_PARAM;
+
+ /**
+ * Description of the optional {@value #CREATE} parameter, which may be
present in writable data store.
+ * This parameter is not included in the descriptor created by {@link
#build(ParameterBuilder)} default
+ * implementation. It is subclass responsibility to add it if desired,
only if supported.
+ */
+ public static final ParameterDescriptor<Boolean> CREATE_PARAM;
+
+ /**
+ * Description of the optional parameter for character encoding used by
the data store.
+ * This parameter is not included in the descriptor created by {@link
#build(ParameterBuilder)}
+ * default implementation. It is subclass responsibility to add it if
desired.
+ */
+ public static final ParameterDescriptor<Charset> ENCODING;
+ static {
+ final ParameterBuilder builder = new ParameterBuilder();
+ ENCODING =
builder.addName("encoding").setDescription(Resources.formatInternational(Resources.Keys.DataStoreEncoding)).create(Charset.class,
null);
+ CREATE_PARAM = builder.addName( CREATE
).setDescription(Resources.formatInternational(Resources.Keys.DataStoreCreate
)).create(Boolean.class, null);
+ METADATA_PARAM =
builder.addName("metadata").setDescription(Resources.formatInternational(Resources.Keys.MetadataLocation
)).create(Path.class, null);
+ LOCATION_PARAM = builder.addName( LOCATION
).setDescription(Resources.formatInternational(Resources.Keys.DataStoreLocation)).setRequired(true).create(URI.class,
null);
+ }
+
+ /**
+ * The parameter descriptor to be returned by {@link #getOpenParameters()}.
+ * Created when first needed.
+ */
+ private volatile ParameterDescriptorGroup openDescriptor;
+
+ /**
+ * Creates a new provider.
+ */
+ protected URIDataStoreProvider() {
+ }
+
+ /**
+ * Returns a description of all parameters accepted by this provider for
opening a data store.
+ * This method creates the descriptor only when first needed. Subclasses
can override the
+ * {@link #build(ParameterBuilder)} method if they need to modify the
descriptor to create.
+ *
+ * @return description of the parameters required or accepted for opening
a {@link DataStore}.
+ */
+ @Override
+ public final ParameterDescriptorGroup getOpenParameters() {
+ ParameterDescriptorGroup desc = openDescriptor;
+ if (desc == null) {
+ openDescriptor = desc = build(new
ParameterBuilder().addName(getShortName()));
+ }
+ return desc;
+ }
+
+ /**
+ * Invoked by {@link #getOpenParameters()} the first time that a parameter
descriptor needs to be created.
+ * When invoked, the parameter group name is set to a name derived from
the {@link #getShortName()} value.
+ * The default implementation creates a group containing {@link
#LOCATION_PARAM} and {@link #METADATA_PARAM}.
+ * Subclasses can override if they need to create a group with more
parameters.
+ *
+ * @param builder the builder to use for creating parameter descriptor.
The group name is already set.
+ * @return the parameters descriptor created from the given builder.
+ */
+ protected ParameterDescriptorGroup build(final ParameterBuilder builder) {
+ return builder.createGroup(LOCATION_PARAM, METADATA_PARAM);
+ }
+
+ /**
+ * Convenience method creating a parameter descriptor containing only
{@link #LOCATION_PARAM}.
+ * This convenience method is used for public providers that cannot extend
this
+ * {@code URIDataStoreProvider} class because it is internal.
+ *
+ * @param name short name of the data store format.
+ * @return the descriptor for open parameters.
+ *
+ * @todo Verify if non-exported classes in JDK9 are hidden from Javadoc,
like package-private classes.
+ * If true, we could remove this hack and extend {@code
URIDataStore} even in public classes.
+ */
+ public static ParameterDescriptorGroup descriptor(final String name) {
+ return new
ParameterBuilder().addName(name).createGroup(LOCATION_PARAM);
+ }
+
+ /**
+ * Returns the location (path, URL, URI, <i>etc.</i>) of the given
resource.
+ * The type of the returned object can be any of the types documented in
{@link DataStoreProvider#LOCATION}.
+ *
+ * @param resource the resource for which to get the location, or {@code
null}.
+ * @return location of the given resource, or {@code null} if none.
+ * @throws DataStoreException if an error on the file system prevent the
creation of the path.
+ */
+ public static Object location(final Resource resource) throws
DataStoreException {
+ if (resource instanceof DataStore) {
+ final Optional<ParameterValueGroup> p = ((DataStore)
resource).getOpenParameters();
+ if (p.isPresent()) try {
+ return
p.get().parameter(DataStoreProvider.LOCATION).getValue();
+ } catch (ParameterNotFoundException e) {
+ /*
+ * This exception should not happen often since the "location"
parameter is recommended.
+ * Note that it does not mean the same thing as "parameter
provided but value is null".
+ * In that later case we want to return the null value as
specified in the parameters.
+ */
+ Logging.recoverableException(StoreUtilities.LOGGER,
URIDataStore.class, "location", e);
+ }
+ }
+ /*
+ * This fallback should not happen with `URIDataStore` implementation
because the "location" parameter
+ * is always present even if null. This fallback is for resources
implementated by different classes.
+ */
+ if (resource instanceof ResourceOnFileSystem) {
+ final Path[] paths = ((ResourceOnFileSystem)
resource).getComponentFiles();
+ if (paths != null && paths.length != 0) {
+ return paths[0]; // First
path is presumed the main file.
+ }
+ }
+ return null;
+ }
+
+ /**
+ * Creates a storage connector initialized to the location declared in
given parameters.
+ * This convenience method does not set any other parameters.
+ * In particular, reading (or ignoring) the {@value #CREATE} parameter is
left to callers,
+ * because not all implementations may create data stores with {@link
java.nio.file.StandardOpenOption}.
+ *
+ * @param provider the provider for which to create a storage
connector (for error messages).
+ * @param parameters the parameters to use for creating a storage
connector.
+ * @return the storage connector initialized to the location specified in
the parameters.
+ * @throws IllegalOpenParameterException if no {@value #LOCATION}
parameter has been found.
+ */
+ public static StorageConnector connector(final DataStoreProvider provider,
final ParameterValueGroup parameters)
+ throws IllegalOpenParameterException
+ {
+ ParameterNotFoundException cause = null;
+ if (parameters != null) try {
+ final Object location = parameters.parameter(LOCATION).getValue();
+ if (location != null) {
+ return new StorageConnector(location);
+ }
+ } catch (ParameterNotFoundException e) {
+ cause = e;
+ }
+ throw new
IllegalOpenParameterException(Resources.format(Resources.Keys.UndefinedParameter_2,
+ provider.getShortName(), LOCATION), cause);
+ }
+
+ /**
+ * Returns {@code true} if the open options contains {@link
StandardOpenOption#WRITE}
+ * or if the storage type is some kind of output stream. An ambiguity may
exist between
+ * the case when a new file would be created and when an existing file
would be updated.
+ * This ambiguity is resolved by the {@code ifNew} argument:
+ * if {@code false}, then the two cases are not distinguished.
+ * If {@code true}, then this method returns {@code true} only if a new
file would be created.
+ *
+ * @param connector the connector to use for opening a file.
+ * @param ifNew whether to return {@code true} only if a new file would
be created.
+ * @return whether the specified connector should open a writable data
store.
+ * @throws DataStoreException if the storage object has already been used
and cannot be reused.
+ */
+ public static boolean isWritable(final StorageConnector connector, final
boolean ifNew) throws DataStoreException {
+ final Object storage = connector.getStorage();
+ if (storage instanceof OutputStream || storage instanceof DataOutput)
return true; // Must be tested first.
+ if (storage instanceof InputStream || storage instanceof DataInput)
return false; // Ignore options.
+ final OpenOption[] options =
connector.getOption(OptionKey.OPEN_OPTIONS);
+ if (ArraysExt.contains(options, StandardOpenOption.WRITE)) {
+ if (!ifNew || ArraysExt.contains(options,
StandardOpenOption.TRUNCATE_EXISTING)) {
+ return true;
+ }
+ if (ArraysExt.contains(options, StandardOpenOption.CREATE_NEW)) {
+ return IOUtilities.isKindOfPath(storage);
+ }
+ if (ArraysExt.contains(options, StandardOpenOption.CREATE)) {
+ final Path path = connector.getStorageAs(Path.class);
+ return (path != null) && Files.notExists(path);
+ }
+ }
+ return false;
+ }
+}
diff --git
a/endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/csv/Store.java
b/endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/csv/Store.java
index 42f93fe314..f977152722 100644
---
a/endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/csv/Store.java
+++
b/endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/csv/Store.java
@@ -643,7 +643,7 @@ final class Store extends URIDataStore implements
FeatureSet {
builder.addExtent(envelope, listeners);
builder.addFeatureType(featureType, -1);
mergeAuxiliaryMetadata(builder);
- addTitleOrIdentifier(builder);
+ builder.addTitleOrIdentifier(getFilename(),
MetadataBuilder.Scope.ALL);
builder.setISOStandards(false);
metadata = builder.buildAndFreeze();
}
diff --git
a/endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/csv/StoreProvider.java
b/endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/csv/StoreProvider.java
index 56fcbbcf56..c66fc677ac 100644
---
a/endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/csv/StoreProvider.java
+++
b/endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/csv/StoreProvider.java
@@ -32,7 +32,7 @@ import org.apache.sis.feature.FoliationRepresentation;
import org.apache.sis.storage.internal.Resources;
import org.apache.sis.storage.base.Capability;
import org.apache.sis.storage.base.StoreMetadata;
-import org.apache.sis.storage.base.URIDataStore;
+import org.apache.sis.storage.base.URIDataStoreProvider;
import org.apache.sis.storage.wkt.FirstKeywordPeek;
import org.apache.sis.util.ArgumentChecks;
@@ -51,7 +51,7 @@ import org.apache.sis.util.ArgumentChecks;
fileSuffixes = "csv",
capabilities = Capability.READ,
resourceTypes = FeatureSet.class)
-public final class StoreProvider extends URIDataStore.Provider {
+public final class StoreProvider extends URIDataStoreProvider {
/**
* The format names for static features and moving features.
*/
diff --git
a/endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/esri/RasterStore.java
b/endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/esri/RasterStore.java
index 9743c7dad7..e8b41b97f5 100644
---
a/endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/esri/RasterStore.java
+++
b/endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/esri/RasterStore.java
@@ -172,7 +172,7 @@ abstract class RasterStore extends PRJDataStore implements
GridCoverageResource
}
}
mergeAuxiliaryMetadata(builder);
- addTitleOrIdentifier(builder);
+ builder.addTitleOrIdentifier(getFilename(), MetadataBuilder.Scope.ALL);
builder.setISOStandards(false);
metadata = builder.buildAndFreeze();
}
@@ -209,7 +209,7 @@ abstract class RasterStore extends PRJDataStore implements
GridCoverageResource
}
int count = 0;
long[] indexAndColors = ArraysExt.EMPTY_LONG; // Index in
highest 32 bits, ARGB in lowest 32 bits.
- for (final CharSequence line :
CharSequences.splitOnEOL(readAuxiliaryFile(CLR))) {
+ for (final CharSequence line :
CharSequences.splitOnEOL(readAuxiliaryFile(CLR, false))) {
final int end = CharSequences.skipTrailingWhitespaces(line, 0,
line.length());
final int start = CharSequences.skipLeadingWhitespaces(line, 0,
end);
if (start < end && Character.isDigit(Character.codePointAt(line,
start))) {
@@ -280,7 +280,7 @@ abstract class RasterStore extends PRJDataStore implements
GridCoverageResource
throws DataStoreException, IOException
{
final Statistics[] stats = new Statistics[sm.getNumBands()];
- for (final CharSequence line :
CharSequences.splitOnEOL(readAuxiliaryFile(STX))) {
+ for (final CharSequence line :
CharSequences.splitOnEOL(readAuxiliaryFile(STX, false))) {
final int end = CharSequences.skipTrailingWhitespaces(line, 0,
line.length());
final int start = CharSequences.skipLeadingWhitespaces(line, 0,
end);
if (start < end && Character.isDigit(Character.codePointAt(line,
start))) {
diff --git
a/endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/esri/RawRasterStore.java
b/endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/esri/RawRasterStore.java
index ddfa2321c0..8296557b59 100644
---
a/endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/esri/RawRasterStore.java
+++
b/endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/esri/RawRasterStore.java
@@ -41,6 +41,7 @@ import org.apache.sis.storage.DataStoreException;
import org.apache.sis.storage.DataStoreClosedException;
import org.apache.sis.storage.DataStoreContentException;
import org.apache.sis.storage.StorageConnector;
+import org.apache.sis.storage.base.AuxiliaryContent;
import org.apache.sis.storage.internal.Resources;
import org.apache.sis.io.stream.ChannelDataInput;
import org.apache.sis.referencing.util.j2d.AffineTransform2D;
@@ -241,6 +242,7 @@ final class RawRasterStore extends RasterStore {
*/
@Override
public synchronized List<SampleDimension> getSampleDimensions() throws
DataStoreException {
+ @SuppressWarnings("LocalVariableHidesMemberVariable")
final ChannelDataInput input = this.input;
List<SampleDimension> sampleDimensions = super.getSampleDimensions();
if (sampleDimensions == null) try {
@@ -335,6 +337,7 @@ final class RawRasterStore extends RasterStore {
*/
private void readHeader() throws IOException, DataStoreException {
assert Thread.holdsLock(this);
+ @SuppressWarnings("LocalVariableHidesMemberVariable")
final ChannelDataInput input = this.input;
if (input == null) {
throw new DataStoreClosedException(canNotRead());
@@ -355,7 +358,7 @@ final class RawRasterStore extends RasterStore {
int geomask = 0; // Mask telling whether ulxmap, ulymap,
xdim, ydim were specified (in that order).
RawRasterLayout layout = RawRasterLayout.BIL;
ByteOrder byteOrder = ByteOrder.nativeOrder();
- final AuxiliaryContent header =
readAuxiliaryFile(RawRasterStoreProvider.HDR);
+ final AuxiliaryContent header =
readAuxiliaryFile(RawRasterStoreProvider.HDR, false);
if (header == null) {
throw new
DataStoreException(cannotReadAuxiliaryFile(RawRasterStoreProvider.HDR));
}
diff --git
a/endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/folder/StoreProvider.java
b/endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/folder/StoreProvider.java
index 556a72567c..cc26a51f10 100644
---
a/endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/folder/StoreProvider.java
+++
b/endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/folder/StoreProvider.java
@@ -45,7 +45,7 @@ import org.apache.sis.storage.GridCoverageResource;
import org.apache.sis.util.ArgumentChecks;
import org.apache.sis.util.logging.Logging;
import org.apache.sis.storage.internal.Resources;
-import org.apache.sis.storage.base.URIDataStore;
+import org.apache.sis.storage.base.URIDataStoreProvider;
import org.apache.sis.storage.base.Capability;
import org.apache.sis.storage.base.StoreMetadata;
import org.apache.sis.storage.base.StoreUtilities;
@@ -106,12 +106,12 @@ public final class StoreProvider extends
DataStoreProvider {
final ParameterDescriptor<Path> location;
final ParameterBuilder builder = new ParameterBuilder();
final InternationalString remark =
Resources.formatInternational(Resources.Keys.UsedOnlyIfNotEncoded);
- ENCODING = annotate(builder, URIDataStore.Provider.ENCODING, remark);
+ ENCODING = annotate(builder, URIDataStoreProvider.ENCODING, remark);
LOCALE = builder.addName("locale"
).setDescription(Resources.formatInternational(Resources.Keys.DataStoreLocale
)).setRemarks(remark).create(Locale.class, null);
TIMEZONE =
builder.addName("timezone").setDescription(Resources.formatInternational(Resources.Keys.DataStoreTimeZone)).setRemarks(remark).create(TimeZone.class,
null);
FORMAT = builder.addName("format"
).setDescription(Resources.formatInternational(Resources.Keys.DirectoryContentFormatName)).create(String.class,
null);
- location = new
ParameterBuilder(URIDataStore.Provider.LOCATION_PARAM).create(Path.class, null);
- PARAMETERS = builder.addName(NAME).createGroup(location, LOCALE,
TIMEZONE, ENCODING, FORMAT, URIDataStore.Provider.CREATE_PARAM);
+ location = new
ParameterBuilder(URIDataStoreProvider.LOCATION_PARAM).create(Path.class, null);
+ PARAMETERS = builder.addName(NAME).createGroup(location, LOCALE,
TIMEZONE, ENCODING, FORMAT, URIDataStoreProvider.CREATE_PARAM);
}
/**
@@ -276,13 +276,13 @@ public final class StoreProvider extends
DataStoreProvider {
@Override
public DataStore open(final ParameterValueGroup parameters) throws
DataStoreException {
ArgumentChecks.ensureNonNull("parameter", parameters);
- final StorageConnector connector =
URIDataStore.Provider.connector(this, parameters);
+ final StorageConnector connector =
URIDataStoreProvider.connector(this, parameters);
final Parameters pg = Parameters.castOrWrap(parameters);
connector.setOption(OptionKey.LOCALE, pg.getValue(LOCALE));
connector.setOption(OptionKey.TIMEZONE, pg.getValue(TIMEZONE));
connector.setOption(OptionKey.ENCODING, pg.getValue(ENCODING));
final EnumSet<StandardOpenOption> options =
EnumSet.of(StandardOpenOption.WRITE);
- if
(Boolean.TRUE.equals(pg.getValue(URIDataStore.Provider.CREATE_PARAM))) {
+ if
(Boolean.TRUE.equals(pg.getValue(URIDataStoreProvider.CREATE_PARAM))) {
options.add(StandardOpenOption.CREATE);
}
return open(connector, pg.getValue(FORMAT), options);
diff --git
a/endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/image/WorldFileStore.java
b/endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/image/WorldFileStore.java
index 9daf3e10cf..bf06e72815 100644
---
a/endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/image/WorldFileStore.java
+++
b/endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/image/WorldFileStore.java
@@ -50,6 +50,7 @@ import org.apache.sis.storage.ReadOnlyStorageException;
import org.apache.sis.storage.UnsupportedStorageException;
import org.apache.sis.storage.base.PRJDataStore;
import org.apache.sis.storage.base.MetadataBuilder;
+import org.apache.sis.storage.base.AuxiliaryContent;
import org.apache.sis.referencing.util.j2d.AffineTransform2D;
import org.apache.sis.metadata.sql.MetadataStoreException;
import org.apache.sis.util.CharSequences;
@@ -282,6 +283,7 @@ public class WorldFileStore extends PRJDataStore {
* does not support the locale, the reader's default locale will be used.
*/
private void configureReader() {
+ @SuppressWarnings("LocalVariableHidesMemberVariable")
final ImageReader reader = this.reader;
try {
reader.setLocale(listeners.getLocale());
@@ -371,7 +373,7 @@ loop: for (int convention=0;; convention++) {
* @throws DataStoreException if the file content cannot be parsed.
*/
private AffineTransform2D readWorldFile(final String wld) throws
IOException, DataStoreException {
- final AuxiliaryContent content = readAuxiliaryFile(wld);
+ final AuxiliaryContent content = readAuxiliaryFile(wld, false);
if (content == null) {
listeners.warning(cannotReadAuxiliaryFile(wld));
return null;
@@ -422,11 +424,12 @@ loop: for (int convention=0;; convention++) {
* @return the requested names, or an empty array if none or unknown.
*/
public String[] getImageFormat(final boolean asMimeType) {
+ @SuppressWarnings("LocalVariableHidesMemberVariable")
final ImageReader reader = this.reader;
if (reader != null) {
- final ImageReaderSpi provider = reader.getOriginatingProvider();
- if (provider != null) {
- final String[] names = asMimeType ? provider.getMIMETypes() :
provider.getFormatNames();
+ final ImageReaderSpi p = reader.getOriginatingProvider();
+ if (p != null) {
+ final String[] names = asMimeType ? p.getMIMETypes() :
p.getFormatNames();
if (names != null) {
return names;
}
@@ -463,6 +466,7 @@ loop: for (int convention=0;; convention++) {
*/
final GridGeometry getGridGeometry(final int index) throws IOException,
DataStoreException {
assert Thread.holdsLock(this);
+ @SuppressWarnings("LocalVariableHidesMemberVariable")
final ImageReader reader = reader();
if (gridGeometry == null) {
final AffineTransform2D gridToCRS;
@@ -533,7 +537,7 @@ loop: for (int convention=0;; convention++) {
builder.addExtent(gridGeometry.getEnvelope(), listeners);
}
mergeAuxiliaryMetadata(builder);
- addTitleOrIdentifier(builder);
+ builder.addTitleOrIdentifier(getFilename(),
MetadataBuilder.Scope.ALL);
builder.setISOStandards(false);
metadata = builder.buildAndFreeze();
} catch (IOException e) {
diff --git
a/endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/internal/Resources.java
b/endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/internal/Resources.java
index 1e7d36088b..39f32775a0 100644
---
a/endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/internal/Resources.java
+++
b/endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/internal/Resources.java
@@ -85,7 +85,7 @@ public class Resources extends IndexedResourceBundle {
public static final short CanNotIntersectDataWithQuery_1 = 57;
/**
- * Cannot read “{0}” auxiliary file.
+ * Cannot read the “{0}” auxiliary file.
*/
public static final short CanNotReadAuxiliaryFile_1 = 66;
diff --git
a/endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/internal/Resources.properties
b/endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/internal/Resources.properties
index 6356fb4950..4e433e2b72 100644
---
a/endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/internal/Resources.properties
+++
b/endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/internal/Resources.properties
@@ -25,7 +25,7 @@ CanNotCreateFolderStore_1 = Cannot create resources
based on the content
CanNotDeriveTypeFromFeature_1 = Cannot infer the feature type resulting
from \u201c{0}\u201d filtering.
CanNotGetCommonMetadata_2 = Cannot get metadata common to
\u201c{0}\u201d files. The reason is: {1}
CanNotIntersectDataWithQuery_1 = Cannot intersect \u201c{0}\u201d data with
specified query.
-CanNotReadAuxiliaryFile_1 = Cannot read \u201c{0}\u201d auxiliary file.
+CanNotReadAuxiliaryFile_1 = Cannot read the \u201c{0}\u201d auxiliary
file.
CanNotReadCRS_WKT_1 = Cannot read the Coordinate Reference
System (CRS) Well Known Text (WKT) in \u201c{0}\u201d.
CanNotReadDirectory_1 = Cannot read \u201c{0}\u201d directory.
CanNotReadFile_2 = Cannot read \u201c{1}\u201d as a file in
the {0} format.
diff --git
a/endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/wkt/Store.java
b/endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/wkt/Store.java
index 1e4a7099a0..acb4923da4 100644
---
a/endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/wkt/Store.java
+++
b/endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/wkt/Store.java
@@ -184,7 +184,7 @@ final class Store extends URIDataStore {
mergeAuxiliaryMetadata(builder);
} else {
mergeAuxiliaryMetadata(builder);
- addTitleOrIdentifier(builder);
+ builder.addTitleOrIdentifier(getFilename(),
MetadataBuilder.Scope.ALL);
}
metadata = builder.buildAndFreeze();
}
diff --git
a/endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/wkt/StoreProvider.java
b/endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/wkt/StoreProvider.java
index 8371627650..d95493de90 100644
---
a/endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/wkt/StoreProvider.java
+++
b/endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/wkt/StoreProvider.java
@@ -26,7 +26,7 @@ import org.apache.sis.storage.StorageConnector;
import org.apache.sis.storage.ProbeResult;
import org.apache.sis.storage.base.Capability;
import org.apache.sis.storage.base.StoreMetadata;
-import org.apache.sis.storage.base.URIDataStore;
+import org.apache.sis.storage.base.URIDataStoreProvider;
import org.apache.sis.referencing.util.WKTKeywords;
import org.apache.sis.util.Version;
@@ -39,7 +39,7 @@ import org.apache.sis.util.Version;
@StoreMetadata(formatName = StoreProvider.NAME,
fileSuffixes = "prj",
capabilities = Capability.READ)
-public final class StoreProvider extends URIDataStore.Provider {
+public final class StoreProvider extends URIDataStoreProvider {
/**
* The format name.
*/
@@ -68,7 +68,9 @@ public final class StoreProvider extends
URIDataStore.Provider {
static final int MIN_LENGTH = 6;
/**
- * The set of WKT keywords.
+ * The set of WKT keywords for CRS definitions.
+ * This set does not include the WKT keywords for coordinate
operations,
+ * because the WKT store can only return metadata and metadata can
only store the CRS.
*/
private final Set<String> keywords;
diff --git
a/endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/xml/AbstractProvider.java
b/endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/xml/AbstractProvider.java
index 4fd9176d97..56e3e53326 100644
---
a/endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/xml/AbstractProvider.java
+++
b/endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/xml/AbstractProvider.java
@@ -19,6 +19,7 @@ package org.apache.sis.storage.xml;
import java.util.Map;
import java.io.Reader;
import java.io.IOException;
+import java.io.InputStream;
import java.nio.ByteBuffer;
import org.apache.sis.storage.DataStore;
import org.apache.sis.storage.DataStoreException;
@@ -91,6 +92,25 @@ public abstract class AbstractProvider extends
DocumentedStoreProvider {
this.mimeForRootElements = mimeForRootElements;
}
+ /**
+ * Returns {@code true} if the given input stream begins with the XML
header.
+ * This method should be invoked only if mark and reset are supported.
+ *
+ * @param in the input stream to test.
+ * @return whether the first bytes are {@code "<?xml "}.
+ * @throws IOException if an error occurred while reading the input stream.
+ */
+ public static boolean isXML(final InputStream in) throws IOException {
+ boolean isXML = true;
+ in.mark(HEADER.length);
+ for (int i=0; i<HEADER.length; i++) {
+ isXML = (in.read() == HEADER[i]);
+ if (!isXML) break;
+ }
+ in.reset();
+ return isXML;
+ }
+
/**
* Returns the MIME type if the given storage appears to be supported by
the data store.
* A {@linkplain ProbeResult#isSupported() supported} status does not
guarantee that reading
diff --git
a/incubator/src/org.apache.sis.storage.coveragejson/main/org/apache/sis/storage/coveragejson/CoverageJsonStoreProvider.java
b/incubator/src/org.apache.sis.storage.coveragejson/main/org/apache/sis/storage/coveragejson/CoverageJsonStoreProvider.java
index 7e84195424..723c9e6e8a 100644
---
a/incubator/src/org.apache.sis.storage.coveragejson/main/org/apache/sis/storage/coveragejson/CoverageJsonStoreProvider.java
+++
b/incubator/src/org.apache.sis.storage.coveragejson/main/org/apache/sis/storage/coveragejson/CoverageJsonStoreProvider.java
@@ -28,7 +28,7 @@ import org.apache.sis.storage.ProbeResult;
import org.apache.sis.storage.StorageConnector;
import org.apache.sis.storage.base.Capability;
import org.apache.sis.storage.base.StoreMetadata;
-import org.apache.sis.storage.base.URIDataStore;
+import org.apache.sis.storage.base.URIDataStoreProvider;
import org.apache.sis.util.Version;
@@ -65,7 +65,7 @@ public class CoverageJsonStoreProvider extends
DataStoreProvider {
/**
* The parameter descriptor to be returned by {@link #getOpenParameters()}.
*/
- private static final ParameterDescriptorGroup OPEN_DESCRIPTOR =
URIDataStore.Provider.descriptor(NAME);
+ private static final ParameterDescriptorGroup OPEN_DESCRIPTOR =
URIDataStoreProvider.descriptor(NAME);
public CoverageJsonStoreProvider() {
}
diff --git
a/optional/src/org.apache.sis.gui/main/org/apache/sis/gui/dataset/PathAction.java
b/optional/src/org.apache.sis.gui/main/org/apache/sis/gui/dataset/PathAction.java
index 81f00038ff..61a36be73e 100644
---
a/optional/src/org.apache.sis.gui/main/org/apache/sis/gui/dataset/PathAction.java
+++
b/optional/src/org.apache.sis.gui/main/org/apache/sis/gui/dataset/PathAction.java
@@ -33,7 +33,7 @@ import org.apache.sis.gui.internal.ExceptionReporter;
import org.apache.sis.storage.DataStoreException;
import org.apache.sis.storage.Resource;
import org.apache.sis.storage.base.ResourceOnFileSystem;
-import org.apache.sis.storage.base.URIDataStore;
+import org.apache.sis.storage.base.URIDataStoreProvider;
import org.apache.sis.io.stream.IOUtilities;
@@ -87,7 +87,7 @@ final class PathAction implements EventHandler<ActionEvent> {
final Resource resource = cell.getItem();
final Object path;
try {
- path = URIDataStore.location(resource);
+ path = URIDataStoreProvider.location(resource);
} catch (DataStoreException e) {
ExceptionReporter.show(cell, null, null, e);
return;
diff --git
a/optional/src/org.apache.sis.gui/main/org/apache/sis/gui/dataset/ResourceCell.java
b/optional/src/org.apache.sis.gui/main/org/apache/sis/gui/dataset/ResourceCell.java
index faee06a2f0..d7f1c7fe64 100644
---
a/optional/src/org.apache.sis.gui/main/org/apache/sis/gui/dataset/ResourceCell.java
+++
b/optional/src/org.apache.sis.gui/main/org/apache/sis/gui/dataset/ResourceCell.java
@@ -27,7 +27,7 @@ import javafx.scene.control.TreeItem;
import javafx.scene.paint.Color;
import org.apache.sis.storage.Resource;
import org.apache.sis.storage.DataStoreException;
-import org.apache.sis.storage.base.URIDataStore;
+import org.apache.sis.storage.base.URIDataStoreProvider;
import org.apache.sis.io.stream.IOUtilities;
import org.apache.sis.gui.internal.ExceptionReporter;
import org.apache.sis.gui.internal.DataStoreOpener;
@@ -139,7 +139,7 @@ final class ResourceCell extends TreeCell<Resource> {
*/
Object path;
try {
- path = URIDataStore.location(resource);
+ path = URIDataStoreProvider.location(resource);
} catch (DataStoreException e) {
path = null;
ResourceTree.unexpectedException("updateItem", e);