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 02b02b2  Add a base class for `DataStore` having a "*.prj" auxiliary 
file.
02b02b2 is described below

commit 02b02b247dfc13abf6e12f5a8f9694a25d2588dd
Author: Martin Desruisseaux <martin.desruisse...@geomatys.com>
AuthorDate: Thu Mar 31 19:01:10 2022 +0200

    Add a base class for `DataStore` having a "*.prj" auxiliary file.
---
 .../main/java/org/apache/sis/setup/OptionKey.java  |  18 +-
 .../apache/sis/storage/netcdf/MetadataReader.java  |   2 +-
 .../apache/sis/internal/storage/PRJDataStore.java  | 306 +++++++++++++++++++++
 .../sis/internal/storage/ResourceOnFileSystem.java |   6 +-
 .../org/apache/sis/internal/storage/Resources.java |   5 +
 .../sis/internal/storage/Resources.properties      |   1 +
 .../sis/internal/storage/Resources_fr.properties   |   1 +
 .../apache/sis/internal/storage/URIDataStore.java  |  30 +-
 .../sis/internal/storage/io/IOUtilities.java       |  36 +++
 .../org/apache/sis/internal/storage/wkt/Store.java |  30 +-
 .../sis/internal/storage/wkt/StoreFormat.java      |  14 +-
 .../sis/internal/storage/wkt/package-info.java     |   2 +-
 .../java/org/apache/sis/storage/DataOptionKey.java |   5 +-
 .../sis/internal/storage/io/IOUtilitiesTest.java   |  14 +-
 14 files changed, 428 insertions(+), 42 deletions(-)

diff --git a/core/sis-utility/src/main/java/org/apache/sis/setup/OptionKey.java 
b/core/sis-utility/src/main/java/org/apache/sis/setup/OptionKey.java
index 2d9de2c..788645e 100644
--- a/core/sis-utility/src/main/java/org/apache/sis/setup/OptionKey.java
+++ b/core/sis-utility/src/main/java/org/apache/sis/setup/OptionKey.java
@@ -29,14 +29,15 @@ import java.util.TimeZone;
 import org.apache.sis.util.ArgumentChecks;
 import org.apache.sis.util.logging.Logging;
 import org.apache.sis.internal.system.Modules;
+import org.opengis.referencing.crs.CoordinateReferenceSystem;
 
 
 /**
  * Keys in a map of options for configuring various services
  * ({@link org.apache.sis.storage.DataStore}, <i>etc</i>).
  * {@code OptionKey}s are used for aspects that usually do not need to be 
configured, except in a few specialized cases.
- * For example most data file formats read by SIS do not require the user to 
specify the character encoding, since the
- * encoding it is often given in the file header or in the format 
specification. However if SIS needs to read plain
+ * For example most data file formats read by SIS do not require the user to 
specify the character encoding, because
+ * the encoding is often given in the file header or in the format 
specification. However if SIS needs to read plain
  * text files <em>and</em> the default platform encoding is not suitable, then 
the user can specify the desired encoding
  * explicitly using the {@link #ENCODING} option.
  *
@@ -61,7 +62,7 @@ import org.apache.sis.internal.system.Modules;
  * }
  *
  * @author  Martin Desruisseaux (Geomatys)
- * @version 0.8
+ * @version 1.2
  *
  * @param <T>  the type of option values.
  *
@@ -192,6 +193,17 @@ public class OptionKey<T> implements Serializable {
     public static final OptionKey<GeometryLibrary> GEOMETRY_LIBRARY = new 
OptionKey<>("GEOMETRY_LIBRARY", GeometryLibrary.class);
 
     /**
+     * The coordinate reference system (CRS) of data.
+     * This option can be used when the file to read does not describe itself 
the data CRS.
+     * For example this option can be used when reading ASCII Grid without CRS 
information,
+     * but is ignored if the ASCII Grid file is accompanied by a {@code *.prj} 
file giving the CRS.
+     *
+     * @since 1.2
+     */
+    public static final OptionKey<CoordinateReferenceSystem> 
COORDINATE_REFERENCE_SYSTEM =
+            new OptionKey<>("COORDINATE_REFERENCE_SYSTEM", 
CoordinateReferenceSystem.class);
+
+    /**
      * The number of spaces to use for indentation when formatting text files 
in WKT or XML formats.
      * A value of {@value org.apache.sis.io.wkt.WKTFormat#SINGLE_LINE} means 
to format the whole WKT
      * or XML document on a single line without line feeds or indentation.
diff --git 
a/storage/sis-netcdf/src/main/java/org/apache/sis/storage/netcdf/MetadataReader.java
 
b/storage/sis-netcdf/src/main/java/org/apache/sis/storage/netcdf/MetadataReader.java
index 4d9ebe0..92fe35e 100644
--- 
a/storage/sis-netcdf/src/main/java/org/apache/sis/storage/netcdf/MetadataReader.java
+++ 
b/storage/sis-netcdf/src/main/java/org/apache/sis/storage/netcdf/MetadataReader.java
@@ -660,7 +660,7 @@ split:  while ((start = 
CharSequences.skipLeadingWhitespaces(value, start, lengt
          */
         final String wkt = stringValue(GEOSPATIAL_BOUNDS);
         if (wkt != null) {
-            addBoundingPolygon(new StoreFormat(decoder.geomlib, 
decoder.listeners).parseGeometry(wkt,
+            addBoundingPolygon(new StoreFormat(null, null, decoder.geomlib, 
decoder.listeners).parseGeometry(wkt,
                     stringValue(GEOSPATIAL_BOUNDS + "_crs"), 
stringValue(GEOSPATIAL_BOUNDS + "_vertical_crs")));
         }
         final String[] format = decoder.getFormatDescription();
diff --git 
a/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/PRJDataStore.java
 
b/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/PRJDataStore.java
new file mode 100644
index 0000000..3889e26
--- /dev/null
+++ 
b/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/PRJDataStore.java
@@ -0,0 +1,306 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.sis.internal.storage;
+
+import java.net.URL;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.io.FileNotFoundException;
+import java.nio.charset.Charset;
+import java.nio.file.FileSystemNotFoundException;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.text.ParseException;
+import java.util.Arrays;
+import java.util.Locale;
+import java.util.Optional;
+import java.util.TimeZone;
+import org.opengis.parameter.ParameterDescriptor;
+import org.opengis.parameter.ParameterDescriptorGroup;
+import org.opengis.parameter.ParameterValueGroup;
+import org.opengis.referencing.crs.CoordinateReferenceSystem;
+import org.apache.sis.storage.DataStore;
+import org.apache.sis.storage.DataStoreException;
+import org.apache.sis.storage.DataStoreProvider;
+import org.apache.sis.storage.StorageConnector;
+import org.apache.sis.internal.storage.io.IOUtilities;
+import org.apache.sis.internal.storage.wkt.StoreFormat;
+import org.apache.sis.io.wkt.Convention;
+import org.apache.sis.parameter.ParameterBuilder;
+import org.apache.sis.parameter.Parameters;
+import org.apache.sis.setup.OptionKey;
+import org.apache.sis.util.resources.Vocabulary;
+import org.apache.sis.util.ArgumentChecks;
+import org.apache.sis.util.ArraysExt;
+
+
+/**
+ * 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#COORDINATE_REFERENCE_SYSTEM} 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).
+ *
+ * <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>
+ *
+ * @author  Martin Desruisseaux (Geomatys)
+ * @version 1.2
+ * @since   1.2
+ * @module
+ */
+public abstract class PRJDataStore extends URIDataStore {
+    /**
+     * The filename extension of {@code "*.prj"} files.
+     *
+     * @see #getComponentFiles()
+     */
+    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#COORDINATE_REFERENCE_SYSTEM} at construction time, 
and
+     * is modified later if a {@code "*.prj"} file is found.
+     */
+    protected CoordinateReferenceSystem crs;
+
+    /**
+     * Creates a new data store. The following options are recognized:
+     *
+     * <ul>
+     *   <li>{@link OptionKey#COORDINATE_REFERENCE_SYSTEM}: default CRS if no 
auxiliary {@code "*.prj"} file is found.</li>
+     *   <li>{@link OptionKey#ENCODING}: encoding of the {@code "*.prj"} file. 
Default is the JVM default.</li>
+     *   <li>{@link OptionKey#TIMEZONE}: timezone of dates in the {@code 
"*.prj"} file. Default is UTC.</li>
+     *   <li>{@link OptionKey#LOCALE}: locale for texts in the {@code "*.prj"} 
file. Default is English.</li>
+     * </ul>
+     *
+     * @param  provider   the factory that created this {@code PRJDataStore} 
instance, or {@code null} if unspecified.
+     * @param  connector  information about the storage (URL, stream, reader 
instance, <i>etc</i>).
+     * @throws DataStoreException if an error occurred while creating the data 
store for the given storage.
+     */
+    protected PRJDataStore(final DataStoreProvider provider, final 
StorageConnector connector) throws DataStoreException {
+        super(provider, connector);
+        crs      = connector.getOption(OptionKey.COORDINATE_REFERENCE_SYSTEM);
+        encoding = connector.getOption(OptionKey.ENCODING);
+        locale   = connector.getOption(OptionKey.LOCALE);       // For 
`InternationalString`, not for numbers.
+        timezone = connector.getOption(OptionKey.TIMEZONE);
+    }
+
+    /**
+     * Reads the {@code "*.prj"} auxiliary file. If the file is not found, 
then this method does nothing
+     * and {@link #crs} keeps its current value (usually the default value 
found at construction time).
+     *
+     * <p>This method does not verify if it has been invoked multiple time.
+     * Caller should track whether the data store has been initialized.</p>
+     *
+     * @throws DataStoreException if an error occurred while reading the file.
+     */
+    protected final void readPRJ() throws DataStoreException {
+        try {
+            final String wkt = readAuxiliaryFile(PRJ, encoding);
+            if (wkt != null) {
+                final StoreFormat format = new StoreFormat(locale, timezone, 
null, listeners);
+                format.setConvention(Convention.WKT1_COMMON_UNITS);
+                crs = (CoordinateReferenceSystem) format.parseObject(wkt);
+                format.validate(crs);
+            }
+        } catch (FileNotFoundException e) {
+            
listeners.warning(Resources.format(Resources.Keys.CanNotReadAuxiliaryFile_1, 
PRJ), e);
+        } catch (IOException | ParseException | ClassCastException e) {
+            throw new 
DataStoreException(Resources.format(Resources.Keys.CanNotReadAuxiliaryFile_1, 
PRJ), e);
+        }
+    }
+
+    /**
+     * Reads the content of the auxiliary file with the specified extension.
+     * This method uses the same URI than {@link #location},
+     * except for the extension which is replaced by the given value.
+     * This method is suitable for reasonably small files.
+     *
+     * @param  extension  the filename extension of the auxiliary file to open.
+     * @param  encoding   the encoding to use for reading the file content, or 
{@code null} for default.
+     * @return a stream opened on the specified file, or {@code null} if the 
file is not found.
+     * @throws FileNotFoundException if the auxiliary file has not been found.
+     * @throws IOException if another error occurred while opening the stream.
+     */
+    protected final String readAuxiliaryFile(final String extension, Charset 
encoding) throws IOException {
+        final URL url = IOUtilities.toAuxiliaryURL(location, extension);
+        if (url == null) {
+            return null;
+        }
+        if (encoding == null) {
+            encoding = Charset.defaultCharset();
+        }
+        try (InputStreamReader reader = new 
InputStreamReader(url.openStream(), encoding)) {
+            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) {
+                    buffer = Arrays.copyOf(buffer, offset*2);
+                }
+            }
+            return new String(buffer, 0, offset);
+        }
+    }
+
+    /**
+     * 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
+     * file with {@code ".prj"} extension if it exists.
+     *
+     * @return the URI as a path, or an empty array if the URI is null.
+     * @throws DataStoreException if the URI can not be converted to a {@link 
Path}.
+     */
+    @Override
+    public Path[] getComponentFiles() throws DataStoreException {
+        return listComponentFiles(PRJ);
+    }
+
+    /**
+     * Returns the {@linkplain #location} as a {@code Path} component together 
with auxiliary files.
+     * This method computes the path to the main file as {@link 
URIDataStore#getComponentFiles()},
+     * then add the sibling files with all extensions specified in the {@code 
auxiliaries} argument.
+     * Each auxiliary file is tested for existence. Paths that are not regular 
files are omitted.
+     * This is a helper method for {@link #getComponentFiles()} implementation.
+     *
+     * @param  auxiliaries  filename extension (without leading dot) of all 
auxiliary files.
+     * @return the URI as a path, followed by all auxiliary files that exist.
+     * @throws DataStoreException if the URI can not be converted to a {@link 
Path}.
+     */
+    protected final Path[] listComponentFiles(final String... auxiliaries) 
throws DataStoreException {
+        final Path path;
+        if (location == null) {
+            return new Path[0];
+        } else try {
+            path = Paths.get(location);
+        } catch (IllegalArgumentException | FileSystemNotFoundException e) {
+            throw new DataStoreException(e);
+        }
+        String base = path.getFileName().toString();
+        final int s = base.lastIndexOf('.');
+        if (s >= 0) {
+            base = base.substring(0, s+1);
+        }
+        final Path[] paths = new Path[auxiliaries.length + 1];
+        paths[0] = path;
+        int count = 1;
+        for (final String extension : auxiliaries) {
+            final Path p = path.resolveSibling(base.concat(extension));
+            if (Files.isRegularFile(p)) {
+                paths[count++] = p;
+            }
+        }
+        return ArraysExt.resize(paths, count);
+    }
+
+    /**
+     * Returns the parameters used to open this data store.
+     *
+     * @return parameters used for opening this {@code DataStore}.
+     */
+    @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();
+    }
+
+    /**
+     * Provider for {@link PRJDataStore} instances.
+     *
+     * @author  Martin Desruisseaux (Geomatys)
+     * @version 1.2
+     * @since   1.2
+     * @module
+     */
+    public abstract static class Provider extends URIDataStore.Provider {
+        /**
+         * Name of the {@link #COORDINATE_REFERENCE_SYSTEM} parameter.
+         */
+        static final String CRS_NAME = "crs";
+
+        /**
+         * Description of the optional parameter for the default coordinate 
reference system.
+         */
+        public static final ParameterDescriptor<CoordinateReferenceSystem> 
COORDINATE_REFERENCE_SYSTEM;
+        static {
+            final ParameterBuilder builder = new ParameterBuilder();
+            COORDINATE_REFERENCE_SYSTEM = 
builder.addName(CRS_NAME).setDescription(
+                    
Vocabulary.formatInternational(Vocabulary.Keys.CoordinateRefSys))
+                    .create(CoordinateReferenceSystem.class, null);
+        }
+
+        /**
+         * Creates a new provider.
+         */
+        protected Provider() {
+        }
+
+        /**
+         * 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 #COORDINATE_REFERENCE_SYSTEM}.
+         * 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.
+         */
+        @Override
+        protected ParameterDescriptorGroup build(final ParameterBuilder 
builder) {
+            return builder.createGroup(LOCATION_PARAM, 
COORDINATE_REFERENCE_SYSTEM);
+        }
+
+        /**
+         * Returns a data store implementation from the given parameters.
+         *
+         * @return a data store implementation associated with this provider 
for the given parameters.
+         * @throws DataStoreException if an error occurred while creating the 
data store instance.
+         */
+        @Override
+        public DataStore open(final ParameterValueGroup parameters) throws 
DataStoreException {
+            ArgumentChecks.ensureNonNull("parameter", parameters);
+            final StorageConnector connector = connector(this, parameters);
+            final Parameters pg = Parameters.castOrWrap(parameters);
+            connector.setOption(OptionKey.COORDINATE_REFERENCE_SYSTEM, 
pg.getValue(COORDINATE_REFERENCE_SYSTEM));
+            return open(connector);
+        }
+    }
+}
diff --git 
a/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/ResourceOnFileSystem.java
 
b/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/ResourceOnFileSystem.java
index 85b74e8..11bb02e 100644
--- 
a/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/ResourceOnFileSystem.java
+++ 
b/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/ResourceOnFileSystem.java
@@ -24,8 +24,8 @@ import org.apache.sis.storage.Resource;
 /**
  * A resource which is loaded from one or many files on an arbitrary file 
system. This interface
  * allows a resource (typically a {@linkplain org.apache.sis.storage.DataStore 
data store}) to
- * list the files that it uses. This is for informative purpose only and 
should not be used for
- * copying or deleting resources.
+ * list the files that it uses. It may be used for copying or deleting 
resources if the caller
+ * is certain that those files are not in use.
  *
  * <h2>Alternatives</h2>
  * <p>For copying data from one location to another, consider using
@@ -61,6 +61,8 @@ public interface ResourceOnFileSystem extends Resource {
      * </div>
      *
      * This method should return paths to files only. It should not return 
paths to directories.
+     * The caller should verify that all paths are regular files;
+     * non-existent paths should be omitted.
      *
      * @return files used by this resource. Should never be {@code null}.
      * @throws DataStoreException if an error on the file system prevent the 
creation of the list.
diff --git 
a/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/Resources.java
 
b/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/Resources.java
index d222f51..5bc9bef 100644
--- 
a/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/Resources.java
+++ 
b/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/Resources.java
@@ -85,6 +85,11 @@ public final class Resources extends IndexedResourceBundle {
         public static final short CanNotIntersectDataWithQuery_1 = 57;
 
         /**
+         * Can not read “{0}” auxiliary file.
+         */
+        public static final short CanNotReadAuxiliaryFile_1 = 66;
+
+        /**
          * Can not read the Coordinate Reference System (CRS) Well Known Text 
(WKT) in “{0}”.
          */
         public static final short CanNotReadCRS_WKT_1 = 37;
diff --git 
a/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/Resources.properties
 
b/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/Resources.properties
index b450bc4..da72a2a 100644
--- 
a/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/Resources.properties
+++ 
b/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/Resources.properties
@@ -24,6 +24,7 @@ CanNotCreateFolderStore_1         = Can not create resources 
based on the conten
 CanNotDeriveTypeFromFeature_1     = Can not infer the feature type resulting 
from \u201c{0}\u201d filtering.
 CanNotGetCommonMetadata_2         = Can not get metadata common to 
\u201c{0}\u201d files. The reason is: {1}
 CanNotIntersectDataWithQuery_1    = Can not intersect \u201c{0}\u201d data 
with specified query.
+CanNotReadAuxiliaryFile_1         = Can not read \u201c{0}\u201d auxiliary 
file.
 CanNotReadCRS_WKT_1               = Can not read the Coordinate Reference 
System (CRS) Well Known Text (WKT) in \u201c{0}\u201d.
 CanNotReadDirectory_1             = Can not read \u201c{0}\u201d directory.
 CanNotReadFile_2                  = Can not read \u201c{1}\u201d as a file in 
the {0} format.
diff --git 
a/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/Resources_fr.properties
 
b/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/Resources_fr.properties
index 9c37915..4d3b918 100644
--- 
a/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/Resources_fr.properties
+++ 
b/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/Resources_fr.properties
@@ -29,6 +29,7 @@ CanNotCreateFolderStore_1         = Ne peut pas cr\u00e9er 
des ressources bas\u0
 CanNotDeriveTypeFromFeature_1     = Ne peut pas d\u00e9terminer le type de 
donn\u00e9es r\u00e9sultant du filtrage \u00ab\u202f{0}\u202f\u00bb.
 CanNotGetCommonMetadata_2         = Ne peut pas obtenir les 
m\u00e9ta-donn\u00e9es communes aux fichiers \u00ab\u202f{0}\u202f\u00bb. La 
raison est\u00a0: {1}
 CanNotIntersectDataWithQuery_1    = Ne peut pas intercepter les donn\u00e9es 
de \u00ab\u202f{0}\u202f\u00bb avec la requ\u00eate qui a \u00e9t\u00e9 
sp\u00e9cifi\u00e9e.
+CanNotReadAuxiliaryFile_1         = Ne peut pas lire le fichier auxiliaire de 
type \u00ab\u202f{0}\u202f\u00bb.
 CanNotReadCRS_WKT_1               = Ne peut pas lire le syst\u00e8me de 
r\u00e9f\u00e9rence spatial dans le \u00ab\u202fWell Known Text\u202f\u00bb 
(WKT) de \u00ab\u202f{0}\u202f\u00bb.
 CanNotReadDirectory_1             = Ne peut pas lire le r\u00e9pertoire 
\u00ab\u202f{0}\u202f\u00bb.
 CanNotReadFile_2                  = Ne peut pas lire 
\u00ab\u202f{1}\u202f\u00bb comme un fichier au format {0}.
diff --git 
a/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/URIDataStore.java
 
b/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/URIDataStore.java
index 4d04d9a..181f44b 100644
--- 
a/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/URIDataStore.java
+++ 
b/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/URIDataStore.java
@@ -33,21 +33,18 @@ 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.event.StoreEvent;
-import org.apache.sis.storage.event.StoreListener;
-import org.apache.sis.storage.event.WarningEvent;
 import org.apache.sis.internal.storage.io.IOUtilities;
 import org.apache.sis.util.logging.Logging;
 
 
 /**
  * A data store for a storage that may be represented by a {@link URI}.
- * It is still possible to create a data store with an {@link 
java.nio.channels.ReadableByteChannel},
+ * 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.
  *
  * @author  Johann Sorel (Geomatys)
  * @author  Martin Desruisseaux (Geomatys)
- * @version 1.1
+ * @version 1.2
  * @since   0.8
  * @module
  */
@@ -55,7 +52,7 @@ public abstract class URIDataStore extends DataStore 
implements StoreResource, R
     /**
      * The {@link DataStoreProvider#LOCATION} parameter value, or {@code null} 
if none.
      */
-    private final URI location;
+    protected final URI location;
 
     /**
      * Creates a new data store.
@@ -195,11 +192,10 @@ public abstract class URIDataStore extends DataStore 
implements StoreResource, R
         }
 
         /**
-         * 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 only
-         * {@link #LOCATION_PARAM}. Subclasses can override if they need to 
create a group with more
-         * parameters.
+         * 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 only {@link 
#LOCATION_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.
@@ -307,16 +303,4 @@ public abstract class URIDataStore extends DataStore 
implements StoreResource, R
             
builder.addTitleOrIdentifier(IOUtilities.filenameWithoutExtension(super.getDisplayName()),
 MetadataBuilder.Scope.ALL);
         }
     }
-
-    /**
-     * Registers only listeners for {@link WarningEvent}s on the assumption 
that most data stores
-     * (at least the read-only ones) produce no change events.
-     */
-    @Override
-    public <T extends StoreEvent> void addListener(Class<T> eventType, 
StoreListener<? super T> listener) {
-        // If an argument is null, we let the parent class throws (indirectly) 
NullArgumentException.
-        if (listener == null || eventType == null || 
eventType.isAssignableFrom(WarningEvent.class)) {
-            super.addListener(eventType, listener);
-        }
-    }
 }
diff --git 
a/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/io/IOUtilities.java
 
b/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/io/IOUtilities.java
index 7396df8..3439243 100644
--- 
a/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/io/IOUtilities.java
+++ 
b/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/io/IOUtilities.java
@@ -202,6 +202,42 @@ public final class IOUtilities extends Static {
     }
 
     /**
+     * Converts the given {@link URI} to a {@link URL} with the same path 
except for the file extension,
+     * which is replaced by the given extension. This method is used for 
opening auxiliary files such as
+     * {@code "*.prj"} and {@code "*.tfw"} files that come with e.g. TIFF 
files.
+     *
+     * @param  location   the URI to convert to a URL with a different 
extension, or {@code null}.
+     * @param  extension  the file extension (without {@code '.'}) of the 
auxiliary file.
+     * @return URL for the auxiliary file with the given extension, or {@code 
null} if none.
+     * @throws MalformedURLException if the URI uses an unknown protocol or a 
negative port number other than -1.
+     *
+     * @since 1.2
+     */
+    public static URL toAuxiliaryURL(final URI location, final String 
extension) throws MalformedURLException {
+        if (location == null || !location.isAbsolute() || location.isOpaque()) 
{
+            return null;
+        }
+        String path = location.getRawPath();    // Raw because URL constructor 
needs encoded strings.
+        int s = path.indexOf('?');              // Shall be before '#' in a 
valid URL.
+        if (s < 0) {
+            s = path.indexOf('#');              // A '?' after '#' would be 
part of the anchor.
+            if (s < 0) {
+                s = path.length();
+            }
+        }
+        s = path.lastIndexOf('.', s);
+        if (s >= 0) {
+            path = path.substring(0, s+1) + extension;
+        } else {
+            path = path + '.' + extension;
+        }
+        return new URL(location.getScheme(),            // http, https, file 
or jar.
+                       location.getRawAuthority(),      // Host name or 
literal IP address.
+                       location.getPort(),              // -1 if undefined.
+                       path);
+    }
+
+    /**
      * Returns the given path without the directories and without the 
extension.
      * For example if the given path is {@code "/Users/name/Map.png"}, then 
this
      * method returns {@code "Map"}.
diff --git 
a/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/wkt/Store.java
 
b/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/wkt/Store.java
index d6d21ad..dc15d87 100644
--- 
a/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/wkt/Store.java
+++ 
b/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/wkt/Store.java
@@ -19,6 +19,8 @@ package org.apache.sis.internal.storage.wkt;
 import java.util.List;
 import java.util.Arrays;
 import java.util.ArrayList;
+import java.util.Locale;
+import java.util.TimeZone;
 import java.io.Reader;
 import java.io.IOException;
 import java.text.ParsePosition;
@@ -40,8 +42,12 @@ import org.apache.sis.util.CharSequences;
 /**
  * A data store which creates data objects from a WKT definition.
  *
+ * <div class="note"><b>Note:</b>
+ * this class differs from {@link 
org.apache.sis.internal.storage.PRJDataStore} in that
+ * the file containing WKT definition is the main file, not an auxiliary 
file.</div>
+ *
  * @author  Martin Desruisseaux (Geomatys)
- * @version 1.0
+ * @version 1.2
  * @since   0.7
  * @module
  */
@@ -59,6 +65,18 @@ final class Store extends URIDataStore {
     private Reader source;
 
     /**
+     * The locale for {@link org.opengis.util.InternationalString} localization
+     * 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, or {@code null} for UTC.
+     */
+    private final TimeZone timezone;
+
+    /**
      * The geometry library, or {@code null} for the default.
      */
     private final GeometryLibrary library;
@@ -83,9 +101,11 @@ final class Store extends URIDataStore {
      */
     public Store(final StoreProvider provider, final StorageConnector 
connector) throws DataStoreException {
         super(provider, connector);
-        objects = new ArrayList<>();
-        source  = connector.commit(Reader.class, StoreProvider.NAME);
-        library = connector.getOption(OptionKey.GEOMETRY_LIBRARY);
+        objects  = new ArrayList<>();
+        locale   = connector.getOption(OptionKey.LOCALE);       // For 
`InternationalString`, not for numbers.
+        timezone = connector.getOption(OptionKey.TIMEZONE);
+        library  = connector.getOption(OptionKey.GEOMETRY_LIBRARY);
+        source   = connector.commit(Reader.class, StoreProvider.NAME);
         listeners.useWarningEventsOnly();
     }
 
@@ -123,7 +143,7 @@ final class Store extends URIDataStore {
              * definitions.
              */
             final ParsePosition pos = new ParsePosition(0);
-            final StoreFormat parser = new StoreFormat(library, listeners);
+            final StoreFormat parser = new StoreFormat(locale, timezone, 
library, listeners);
             do {
                 final Object obj = parser.parse(wkt, pos);
                 objects.add(obj);
diff --git 
a/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/wkt/StoreFormat.java
 
b/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/wkt/StoreFormat.java
index a74cc84..cf1ead8 100644
--- 
a/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/wkt/StoreFormat.java
+++ 
b/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/wkt/StoreFormat.java
@@ -17,6 +17,8 @@
 package org.apache.sis.internal.storage.wkt;
 
 import java.text.ParseException;
+import java.util.Locale;
+import java.util.TimeZone;
 import java.util.logging.Level;
 import java.util.logging.LogRecord;
 import org.opengis.geometry.Geometry;
@@ -41,7 +43,7 @@ import org.apache.sis.util.ArraysExt;
  * For example WKT may also appear in some global attributes of CF-netCDF 
files.
  *
  * @author  Martin Desruisseaux (Geomatys)
- * @version 0.8
+ * @version 1.2
  * @since   0.8
  * @module
  */
@@ -59,12 +61,18 @@ public final class StoreFormat extends WKTFormat {
 
     /**
      * Creates a new WKT parser and encoder.
+     * The given locale will be used for {@link InternationalString} 
localization;
+     * this is <strong>not</strong> the locale for number format.
      *
+     * @param  locale     the locale for the new {@code Format}, or {@code 
null} for {@code Locale.ROOT}.
+     * @param  timezone   the timezone, or {@code null} for UTC.
      * @param  library    the geometry library, or {@code null} for the 
default.
      * @param  listeners  where to send warnings.
      */
-    public StoreFormat(final GeometryLibrary library, final StoreListeners 
listeners) {
-        super(null, null);
+    public StoreFormat(final Locale locale, final TimeZone timezone,
+                       final GeometryLibrary library, final StoreListeners 
listeners)
+    {
+        super(locale, timezone);
         this.library   = library;
         this.listeners = listeners;
     }
diff --git 
a/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/wkt/package-info.java
 
b/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/wkt/package-info.java
index 51f03d9..21ef05a 100644
--- 
a/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/wkt/package-info.java
+++ 
b/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/wkt/package-info.java
@@ -19,7 +19,7 @@
  * {@link org.apache.sis.storage.DataStore} implementation for Well Known Text.
  *
  * @author  Martin Desruisseaux (Geomatys)
- * @version 1.1
+ * @version 1.2
  * @since   0.7
  * @module
  */
diff --git 
a/storage/sis-storage/src/main/java/org/apache/sis/storage/DataOptionKey.java 
b/storage/sis-storage/src/main/java/org/apache/sis/storage/DataOptionKey.java
index 17f0936..699dcf5 100644
--- 
a/storage/sis-storage/src/main/java/org/apache/sis/storage/DataOptionKey.java
+++ 
b/storage/sis-storage/src/main/java/org/apache/sis/storage/DataOptionKey.java
@@ -22,9 +22,8 @@ import org.apache.sis.feature.FoliationRepresentation;
 
 /**
  * Keys in a map of options for configuring the way data are read or written 
to a storage.
- * {@code DataOptionKey} extends {@link OptionKey} with options about 
features, coverages or other kinds of structure
- * in data files. Contrarily to {@code OptionKey}, the options defined in this 
{@code DataOptionKey} class are usually
- * not applicable to other kinds of file (e.g. configuration or program files).
+ * {@code DataOptionKey} extends {@link OptionKey} with options about 
features, coverages
+ * or other kinds of structure that are specific to some data formats.
  *
  * @author  Martin Desruisseaux (Geomatys)
  * @version 1.0
diff --git 
a/storage/sis-storage/src/test/java/org/apache/sis/internal/storage/io/IOUtilitiesTest.java
 
b/storage/sis-storage/src/test/java/org/apache/sis/internal/storage/io/IOUtilitiesTest.java
index dae3080..09b0d56 100644
--- 
a/storage/sis-storage/src/test/java/org/apache/sis/internal/storage/io/IOUtilitiesTest.java
+++ 
b/storage/sis-storage/src/test/java/org/apache/sis/internal/storage/io/IOUtilitiesTest.java
@@ -35,7 +35,7 @@ import static org.junit.Assert.*;
  *
  * @author  Martin Desruisseaux (Geomatys)
  * @author  Johann Sorel (Geomatys)
- * @version 0.8
+ * @version 1.2
  * @since   0.3
  * @module
  */
@@ -112,6 +112,18 @@ public final strictfp class IOUtilitiesTest extends 
TestCase {
     }
 
     /**
+     * Tests {@link IOUtilities#toAuxiliaryURL(URI, int)}.
+     *
+     * @throws URISyntaxException if a URI can not be parsed.
+     * @throws MalformedURLException if a URL can not be parsed.
+     */
+    @Test
+    public void testAuxiliaryURL() throws URISyntaxException, 
MalformedURLException {
+        assertEquals(new URL("http://localhost/directory/image.tfw";),
+                IOUtilities.toAuxiliaryURL(new 
URI("http://localhost/directory/image.tiff";), "tfw"));
+    }
+
+    /**
      * Tests {@link IOUtilities#filenameWithoutExtension(String)}.
      */
     @Test

Reply via email to