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