Author: desruisseaux
Date: Thu Feb 22 22:18:05 2018
New Revision: 1825102

URL: http://svn.apache.org/viewvc?rev=1825102&view=rev
Log:
Move the Store.Writable internal class as a separated class: WritableStore.
Be more conservative before to delete files, e.g. delete only if direct 
children of the directory managed by the store.
Avoid calls to components().contains(resources); check file existence instead.
Take StandardOpenOption in account when creating a folder store.
Replace strings by localized resources in exception messages.
Move some code in a StoreUtilities class so it can be shared.
Move some checks to the constructor.

Added:
    
sis/branches/JDK8/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/StoreUtilities.java
   (with props)
    
sis/branches/JDK8/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/folder/WritableStore.java
      - copied, changed from r1824938, 
sis/branches/JDK8/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/folder/Store.java
Modified:
    
sis/branches/JDK8/core/sis-utility/src/main/java/org/apache/sis/internal/util/Citations.java
    
sis/branches/JDK8/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/FileSystemResource.java
    
sis/branches/JDK8/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/Resources.java
    
sis/branches/JDK8/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/Resources.properties
    
sis/branches/JDK8/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/Resources_fr.properties
    
sis/branches/JDK8/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/URIDataStore.java
    
sis/branches/JDK8/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/folder/FolderStoreProvider.java
    
sis/branches/JDK8/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/folder/Store.java
    
sis/branches/JDK8/storage/sis-storage/src/main/java/org/apache/sis/storage/DataStoreProvider.java
    
sis/branches/JDK8/storage/sis-storage/src/test/java/org/apache/sis/internal/storage/folder/StoreTest.java

Modified: 
sis/branches/JDK8/core/sis-utility/src/main/java/org/apache/sis/internal/util/Citations.java
URL: 
http://svn.apache.org/viewvc/sis/branches/JDK8/core/sis-utility/src/main/java/org/apache/sis/internal/util/Citations.java?rev=1825102&r1=1825101&r2=1825102&view=diff
==============================================================================
--- 
sis/branches/JDK8/core/sis-utility/src/main/java/org/apache/sis/internal/util/Citations.java
 [UTF-8] (original)
+++ 
sis/branches/JDK8/core/sis-utility/src/main/java/org/apache/sis/internal/util/Citations.java
 [UTF-8] Thu Feb 22 22:18:05 2018
@@ -473,7 +473,16 @@ public final class Citations extends Sta
      *             after we moved the {@code sis-utility} code that use this 
method.
      */
     public static String getUnicodeIdentifier(final Citation citation) {
-        final String identifier = getIdentifier(citation, true);
+        return removeIgnorableCharacters(getIdentifier(citation, true));
+    }
+
+    /**
+     * Removes characters that are ignorable according Unicode specification.
+     *
+     * @param  identifier  the character sequence from which to remove 
ignorable characters, or {@code null}.
+     * @return a character sequence with ignorable character removed. May be 
the same instance than the given argument.
+     */
+    public static String removeIgnorableCharacters(final String identifier) {
         if (identifier != null) {
             /*
              * First perform a quick check to see if there is any ignorable 
characters.

Modified: 
sis/branches/JDK8/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/FileSystemResource.java
URL: 
http://svn.apache.org/viewvc/sis/branches/JDK8/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/FileSystemResource.java?rev=1825102&r1=1825101&r2=1825102&view=diff
==============================================================================
--- 
sis/branches/JDK8/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/FileSystemResource.java
 [UTF-8] (original)
+++ 
sis/branches/JDK8/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/FileSystemResource.java
 [UTF-8] Thu Feb 22 22:18:05 2018
@@ -24,8 +24,17 @@ 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 information can be used for improving 
data management,
- * for example copy operations.
+ * list the files that it uses. This is for informative purpose only and 
should not be used for
+ * copying or deleting resources.
+ *
+ * <div class="section">Alternatives</div>
+ * <p>For copying data from one location to another, consider using
+ * {@link org.apache.sis.storage.WritableAggregate#add(Resource)} instead.
+ * The data store implementations may detect that some {@code add(…)} 
operations
+ * can be performed by verbatim copy of files.</p>
+ *
+ * <p>For deleting data, consider using
+ * {@link org.apache.sis.storage.WritableAggregate#remove(Resource)} 
instead.</p>
  *
  * @author  Johann Sorel (Geomatys)
  * @version 1.0
@@ -37,11 +46,31 @@ import org.apache.sis.storage.Resource;
  */
 public interface FileSystemResource extends Resource {
     /**
-     * Gets the paths to all files used by this resource. This is typically the
-     * files opened by a {@linkplain org.apache.sis.storage.DataStore data 
store}.
+     * Gets the paths to files potentially used by this resource.
+     * This is typically the files opened by a {@linkplain 
org.apache.sis.storage.DataStore data store}.
+     * There is no guarantee that all files are in the same directory or that 
each file is used exclusively
+     * by this data source (e.g. no guarantee that modifying or deleting a 
file will not impact other resources).
+     *
+     * <div class="note"><b>Example:</b>
+     * a resources created for a GRIB file may use the following component 
files:
+     * <ul>
+     *   <li>The main GRIB file.</li>
+     *   <li>If managed by the UCAR library, two auxiliary files next to the 
main GRIB file:
+     *       the index file ({@code ".gbx9"}) and the collection file ({@code 
".ncx"}).
+     *       Those two files are owned exclusively by the resource.</li>
+     *   <li>Eventually a GRIB table file. This table may be located in a path 
unrelated to
+     *       to the path of the main file and may be shared by many 
resources.</li>
+     * </ul>
+     * </div>
+     *
+     * This method should return paths to files only. It should not return 
paths to directories.
      *
      * @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.
+     *
+     * @todo Rename {@code getComponentFiles()}? The reason is that it is not 
returning the paths of
+     *       multiple resources, but paths to components of this single 
resources. The use of "Files"
+     *       is for emphasing that this method should not return path to 
directories.
      */
     Path[] getResourcePaths() throws DataStoreException;
 }

Modified: 
sis/branches/JDK8/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/Resources.java
URL: 
http://svn.apache.org/viewvc/sis/branches/JDK8/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/Resources.java?rev=1825102&r1=1825101&r2=1825102&view=diff
==============================================================================
--- 
sis/branches/JDK8/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/Resources.java
 [UTF-8] (original)
+++ 
sis/branches/JDK8/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/Resources.java
 [UTF-8] Thu Feb 22 22:18:05 2018
@@ -67,7 +67,12 @@ public final class Resources extends Ind
         public static final short AmbiguousName_4 = 15;
 
         /**
-         * Can not get metadata common to “{0}” files. Reason: {1}
+         * Can not create resources based on the content of “{0}” directory.
+         */
+        public static final short CanNotCreateFolderStore_1 = 43;
+
+        /**
+         * Can not get metadata common to “{0}” files. The reason is: {1}
          */
         public static final short CanNotGetCommonMetadata_2 = 39;
 
@@ -97,6 +102,16 @@ public final class Resources extends Ind
         public static final short CanNotReadFile_4 = 3;
 
         /**
+         * Can not remove resource “{1}” from aggregate “{0}”.
+         */
+        public static final short CanNotRemoveResource_2 = 49;
+
+        /**
+         * Can not save resources of type ‘{1}’ in a “{0}” store.
+         */
+        public static final short CanNotStoreResourceType_2 = 41;
+
+        /**
          * This {0} reader is closed.
          */
         public static final short ClosedReader_1 = 4;
@@ -137,6 +152,11 @@ public final class Resources extends Ind
         public static final short DataStoreTimeZone = 32;
 
         /**
+         * Name of the format to use for reading or writing the directory 
content.
+         */
+        public static final short DirectoryContentFormatName = 40;
+
+        /**
          * Content of “{0}” directory.
          */
         public static final short DirectoryContent_1 = 35;
@@ -158,9 +178,14 @@ public final class Resources extends Ind
         public static final short FeatureNotFound_2 = 17;
 
         /**
-         * Name of the format to use for reading or writing the directory 
content.
+         * A {1,choice,0#file|1#directory} already exists at “{0}”.
+         */
+        public static final short FileAlreadyExists_2 = 45;
+
+        /**
+         * The “{0}” file is not a directory of resources.
          */
-        public static final short FolderStoreProviderParameter = 40;
+        public static final short FileIsNotAResourceDirectory_1 = 44;
 
         /**
          * Whether to assemble trajectory fragments (lines in CSV file) in a 
single feature instance.
@@ -189,16 +214,41 @@ public final class Resources extends Ind
         public static final short InconsistentNameComponents_2 = 10;
 
         /**
+         * Resource “{0}” does not have an identifier.
+         */
+        public static final short MissingResourceIdentifier_1 = 42;
+
+        /**
          * Missing scheme in “{0}” URI.
          */
         public static final short MissingSchemeInURI_1 = 11;
 
         /**
+         * No directory of resources found at “{0}”.
+         */
+        public static final short NoSuchResourceDirectory_1 = 46;
+
+        /**
+         * Resource “{1}” is not part of aggregate “{0}”.
+         */
+        public static final short NoSuchResourceInAggregate_2 = 50;
+
+        /**
+         * Resource “{0}” is not a writable feature set.
+         */
+        public static final short NotAWritableFeatureSet_1 = 47;
+
+        /**
          * Processing executed on {0}.
          */
         public static final short ProcessingExecutedOn_1 = 12;
 
         /**
+         * A resource already exists at “{0}”.
+         */
+        public static final short ResourceAlreadyExists_1 = 48;
+
+        /**
          * More than one resource have the “{1}” identifier in the “{0}” data 
store.
          */
         public static final short ResourceIdentifierCollision_2 = 23;

Modified: 
sis/branches/JDK8/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/Resources.properties
URL: 
http://svn.apache.org/viewvc/sis/branches/JDK8/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/Resources.properties?rev=1825102&r1=1825101&r2=1825102&view=diff
==============================================================================
--- 
sis/branches/JDK8/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/Resources.properties
 [ISO-8859-1] (original)
+++ 
sis/branches/JDK8/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/Resources.properties
 [ISO-8859-1] Thu Feb 22 22:18:05 2018
@@ -20,12 +20,15 @@
 # For resources shared by all modules in the Apache SIS project, see 
"org.apache.sis.util.resources" package.
 #
 AmbiguousName_4                   = Name \u201c{3}\u201d is ambiguous because 
it can be understood as either \u201c{1}\u201d or \u201c{2}\u201d in the 
context of \u201c{0}\u201d data.
-CanNotGetCommonMetadata_2         = Can not get metadata common to 
\u201c{0}\u201d files. Reason: {1}
+CanNotCreateFolderStore_1         = Can not create resources based on the 
content of \u201c{0}\u201d directory.
+CanNotGetCommonMetadata_2         = Can not get metadata common to 
\u201c{0}\u201d files. The reason is: {1}
 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.
 CanNotReadFile_3                  = Can not read line {2} of \u201c{1}\u201d 
as part of a file in the {0} format.
 CanNotReadFile_4                  = Can not read line {2} (after column {3}) 
of \u201c{1}\u201d as part of a file in the {0} format.
+CanNotRemoveResource_2            = Can not remove resource \u201c{1}\u201d 
from aggregate \u201c{0}\u201d.
+CanNotStoreResourceType_2         = Can not save resources of type 
\u2018{1}\u2019 in a \u201c{0}\u201d store.
 ClosedReader_1                    = This {0} reader is closed.
 ClosedWriter_1                    = This {0} writer is closed.
 ConcurrentRead_1                  = One or more read operations are in 
progress in the \u201c{0}\u201d data store.
@@ -35,17 +38,24 @@ DataStoreLocale                   = Form
 DataStoreLocation                 = Data store location as a file or URL.
 DataStoreTimeZone                 = Timezone of dates in the data store.
 DirectoryContent_1                = Content of \u201c{0}\u201d directory.
+DirectoryContentFormatName        = Name of the format to use for reading or 
writing the directory content.
 FeatureAlreadyPresent_2           = A feature named \u201c{1}\u201d is already 
present in the \u201c{0}\u201d data store.
 FeatureNotFound_2                 = Feature \u201c{1}\u201d has not been found 
in the \u201c{0}\u201d data store.
-FolderStoreProviderParameter      = Name of the format to use for reading or 
writing the directory content.
+FileAlreadyExists_2               = A {1,choice,0#file|1#directory} already 
exists at \u201c{0}\u201d.
+FileIsNotAResourceDirectory_1     = The \u201c{0}\u201d file is not a 
directory of resources.
 FoliationRepresentation           = Whether to assemble trajectory fragments 
(lines in CSV file) in a single feature instance.
 ExcessiveStringSize_3             = Character string in the \u201c{0}\u201d 
file is too long. The string has {2} characters while the limit is {1}.
 IllegalFeatureType_2              = The {0} data store does not accept 
features of type \u201c{1}\u201d.
 IllegalInputTypeForReader_2       = The {0} reader does not accept inputs of 
type \u2018{1}\u2019.
 IllegalOutputTypeForWriter_2      = The {0} writer does not accept outputs of 
type \u2018{1}\u2019.
 InconsistentNameComponents_2      = Components of the \u201c{1}\u201d name are 
inconsistent with those of the name previously binded in \u201c{0}\u201d data 
store.
+MissingResourceIdentifier_1       = Resource \u201c{0}\u201d does not have an 
identifier.
 MissingSchemeInURI_1              = Missing scheme in \u201c{0}\u201d URI.
+NoSuchResourceDirectory_1         = No directory of resources found at 
\u201c{0}\u201d.
+NoSuchResourceInAggregate_2       = Resource \u201c{1}\u201d is not part of 
aggregate \u201c{0}\u201d.
+NotAWritableFeatureSet_1          = Resource \u201c{0}\u201d is not a writable 
feature set.
 ProcessingExecutedOn_1            = Processing executed on {0}.
+ResourceAlreadyExists_1           = A resource already exists at 
\u201c{0}\u201d.
 ResourceIdentifierCollision_2     = More than one resource have the 
\u201c{1}\u201d identifier in the \u201c{0}\u201d data store.
 ResourceNotFound_2                = No resource found for the \u201c{1}\u201d 
identifier in the \u201c{0}\u201d data store.
 ShallBeDeclaredBefore_2           = The \u201c{1}\u201d element must be 
declared before \u201c{0}\u201d.

Modified: 
sis/branches/JDK8/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/Resources_fr.properties
URL: 
http://svn.apache.org/viewvc/sis/branches/JDK8/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/Resources_fr.properties?rev=1825102&r1=1825101&r2=1825102&view=diff
==============================================================================
--- 
sis/branches/JDK8/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/Resources_fr.properties
 [ISO-8859-1] (original)
+++ 
sis/branches/JDK8/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/Resources_fr.properties
 [ISO-8859-1] Thu Feb 22 22:18:05 2018
@@ -25,12 +25,15 @@
 #   U+00A0 NO-BREAK SPACE         before  :
 #
 AmbiguousName_4                   = Le nom \u00ab\u202f{3}\u202f\u00bb est 
ambigu\u00eb car il peut \u00eatre interpr\u00e9t\u00e9 aussi bien comme 
\u00ab\u202f{1}\u202f\u00bb ou \u00ab\u202f{2}\u202f\u00bb dans le contexte des 
donn\u00e9es de \u00ab\u202f{0}\u202f\u00bb.
-CanNotGetCommonMetadata_2         = Ne peut pas obtenir les 
m\u00e9ta-donn\u00e9es communes aux fichiers \u00ab\u202f{0}\u202f\u00bb. 
Raison\u2008: {1}
+CanNotCreateFolderStore_1         = Ne peut pas cr\u00e9er des ressources 
bas\u00e9es sur le contenu du r\u00e9pertoire \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\u2008: {1}
 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}.
 CanNotReadFile_3                  = Ne peut pas lire la ligne {2} de 
\u00ab\u202f{1}\u202f\u00bb comme une partie d\u2019un fichier au format {0}.
 CanNotReadFile_4                  = Ne peut pas lire la ligne {2} (apr\u00e8s 
la colonne {3}) de \u00ab\u202f{1}\u202f\u00bb comme une partie d\u2019un 
fichier au format {0}.
+CanNotRemoveResource_2            = Ne peut pas supprimer la ressource 
\u00ab\u202f{1}\u202f\u00bb de l\u2019agr\u00e9gat \u00ab\u202f{0}\u202f\u00bb.
+CanNotStoreResourceType_2         = Ne peut pas enregistrer des ressources de 
type \u2018{1}\u2019 dans un entrep\u00f4t de donn\u00e9es 
\u00ab\u202f{0}\u202f\u00bb.
 ClosedReader_1                    = Ce lecteur {0} est ferm\u00e9.
 ClosedWriter_1                    = Cet encodeur {0} est ferm\u00e9.
 ConcurrentRead_1                  = Une ou plusieurs op\u00e9rations de 
lecture sont en cours sur les donn\u00e9es de \u00ab\u202f{0}\u202f\u00bb.
@@ -40,17 +43,24 @@ DataStoreLocale                   = Conv
 DataStoreLocation                 = Chemin (fichier ou URL) vers la source de 
donn\u00e9es.
 DataStoreTimeZone                 = Fuseau horaire des dates dans les 
donn\u00e9es.
 DirectoryContent_1                = Contenu du r\u00e9pertoire 
\u00ab\u202f{0}\u202f\u00bb.
+DirectoryContentFormatName        = Nom du format ou de la source de 
donn\u00e9es \u00e0 utiliser pour lire ou \u00e9crire le contenu du 
r\u00e9pertoire.
 ExcessiveStringSize_3             = La cha\u00eene de caract\u00e8res dans le 
fichier \u00ab\u202f{0}\u202f\u00bb est trop longue. La cha\u00eene fait {2} 
caract\u00e8res alors que la limite est {1}.
 FeatureAlreadyPresent_2           = Une entit\u00e9 nomm\u00e9e 
\u00ab\u202f{1}\u202f\u00bb est d\u00e9j\u00e0 pr\u00e9sente dans les 
donn\u00e9es de \u00ab\u202f{0}\u202f\u00bb.
 FeatureNotFound_2                 = L\u2019entit\u00e9 
\u00ab\u202f{1}\u202f\u00bb n\u2019a pas \u00e9t\u00e9 trouv\u00e9e dans les 
donn\u00e9es de \u00ab\u202f{0}\u202f\u00bb.
-FolderStoreProviderParameter      = Nom du format ou de la source de 
donn\u00e9es \u00e0 utiliser pour lire ou \u00e9crire le contenu du 
r\u00e9pertoire.
+FileAlreadyExists_2               = Un {1,choice,0#fichier|1#r\u00e9pertoire} 
existe d\u00e9j\u00e0 \u00e0 l\u2019emplacement \u00ab\u202f{0}\u202f\u00bb.
+FileIsNotAResourceDirectory_1     = Le fichier \u00ab\u202f{0}\u202f\u00bb 
n\u2019est pas un r\u00e9pertoire de ressources.
 FoliationRepresentation           = Indique s\u2019il faut assembler les 
fragments de trajectoires (lignes dans un fichier CSV) dans une entit\u00e9 
unique.
 IllegalFeatureType_2              = Le format {0} ne stocke pas de 
donn\u00e9es de type \u00ab\u202f{1}\u202f\u00bb.
 IllegalInputTypeForReader_2       = Le lecteur {0} n\u2019accepte pas des 
entr\u00e9s de type \u2018{1}\u2019.
 IllegalOutputTypeForWriter_2      = L\u2019encodeur {0} n\u2019accepte pas des 
sorties de type \u2018{1}\u2019.
 InconsistentNameComponents_2      = Les \u00e9l\u00e9ments qui composent le 
nom \u00ab\u202f{1}\u202f\u00bb ne sont pas coh\u00e9rents avec ceux du nom qui 
avait \u00e9t\u00e9 pr\u00e9c\u00e9demment li\u00e9 dans les donn\u00e9es de 
\u00ab\u202f{0}\u202f\u00bb.
+MissingResourceIdentifier_1       = La ressource \u00ab\u202f{0}\u202f\u00bb 
n\u2019a pas d\u2019identifiant.
 MissingSchemeInURI_1              = Il manque le sch\u00e9ma dans l\u2019URI 
\u00ab\u202f{0}\u202f\u00bb.
+NoSuchResourceDirectory_1         = Aucun r\u00e9pertoire de ressources 
n\u2019a \u00e9t\u00e9 trouv\u00e9 \u00e0 l\u2019emplacement 
\u00ab\u202f{0}\u202f\u00bb.
+NoSuchResourceInAggregate_2       = La ressource \u00ab\u202f{1}\u202f\u00bb 
n\u2019est pas une partie de l\u2019agr\u00e9gat \u00ab\u202f{0}\u202f\u00bb.
+NotAWritableFeatureSet_1          = La ressource \u00ab\u202f{0}\u202f\u00bb 
n\u2019est pas un ensemble d\u2019entit\u00e9s accessibles en \u00e9criture.
 ProcessingExecutedOn_1            = Traitement ex\u00e9cut\u00e9 sur {0}.
+ResourceAlreadyExists_1           = Une ressource existe d\u00e9j\u00e0 \u00e0 
l\u2019emplacement \u00ab\u202f{0}\u202f\u00bb.
 ResourceIdentifierCollision_2     = Plusieurs ressources utilisent 
l\u2019identifiant \u00ab\u202f{1}\u202f\u00bb dans les donn\u00e9es de 
\u00ab\u202f{0}\u202f\u00bb.
 ResourceNotFound_2                = Aucune ressource n\u2019a \u00e9t\u00e9 
trouv\u00e9e pour l\u2019identifiant \u00ab\u202f{1}\u202f\u00bb dans les 
donn\u00e9es de \u00ab\u202f{0}\u202f\u00bb.
 ShallBeDeclaredBefore_2           = L\u2019\u00e9l\u00e9ment 
\u00ab\u202f{1}\u202f\u00bb doit \u00eatre d\u00e9clar\u00e9 avant 
\u00ab\u202f{0}\u202f\u00bb.

Added: 
sis/branches/JDK8/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/StoreUtilities.java
URL: 
http://svn.apache.org/viewvc/sis/branches/JDK8/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/StoreUtilities.java?rev=1825102&view=auto
==============================================================================
--- 
sis/branches/JDK8/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/StoreUtilities.java
 (added)
+++ 
sis/branches/JDK8/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/StoreUtilities.java
 [UTF-8] Thu Feb 22 22:18:05 2018
@@ -0,0 +1,227 @@
+/*
+ * 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.util.EnumSet;
+import java.nio.file.OpenOption;
+import java.nio.file.StandardOpenOption;
+import java.util.stream.Stream;
+import org.opengis.metadata.Metadata;
+import org.opengis.metadata.identification.Identification;
+import org.opengis.metadata.identification.DataIdentification;
+import org.apache.sis.util.Static;
+import org.apache.sis.storage.FeatureSet;
+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.WritableFeatureSet;
+import org.apache.sis.internal.util.Citations;
+import org.apache.sis.util.Classes;
+
+// Branch-dependent imports
+import org.opengis.feature.Feature;
+
+
+/**
+ * Utility methods related to {@link DataStore}s, {@link DataStoreProvider}s 
and {@link Resource}s.
+ * This is not a commit API; any method in this class may change in any future 
Apache SIS version.
+ * Some methods may also move in public API if we feel confident enough.
+ *
+ * @author  Martin Desruisseaux (Geomatys)
+ * @version 1.0
+ * @since   1.0
+ * @module
+ */
+public final class StoreUtilities extends Static {
+    /**
+     * Do not allow instantiation of this class.
+     */
+    private StoreUtilities() {
+    }
+
+    /**
+     * Returns an identifier for the given data store provider, or {@code 
null} if none.
+     * The data store identifier should be the format name, but this is not 
guaranteed.
+     * It current version, it is not even guaranteed to be unique.
+     *
+     * <p>This method will need to be revisited since {@link 
DataStoreProvider#getShortName()} said that
+     * the short name is not to be used as an identifier. In the meantime, we 
use this method as a way
+     * to keep trace of the location in the code where an identifier is 
desired.</p>
+     *
+     * @param  provider  the provider for which to get an identifier, or 
{@code null}.
+     * @return an identifier for the given data store, or {@code null}.
+     */
+    public static String getIdentifier(final DataStoreProvider provider) {
+        return (provider != null) ? provider.getShortName() : null;
+    }
+
+    /**
+     * Returns an identifier for a resource having the given metadata, or 
{@code null} if none.
+     * This method checks the information returned by {@link 
Metadata#getIdentificationInfo()},
+     * with precedence to {@link DataIdentification} over other kinds of 
{@link Identification}.
+     *
+     * @param  metadata  the metadata from which to get a data identifier, or 
{@code null}.
+     * @return a data identifier, or {@code null} if none.
+     */
+    public static String getIdentifier(final Metadata metadata) {
+        return Citations.removeIgnorableCharacters(getIdentifier(metadata, 
true));
+    }
+
+    /**
+     * Implementation of {@link #getIdentifier(Metadata)} to be shared with 
{@link #getLabel(Resource)}.
+     *
+     * @param  metadata  the metadata from which to get a data identifier, or 
{@code null}.
+     * @param  unicode   whether to restrict to valid Unicode identifiers.
+     * @return a data identifier, or {@code null} if none.
+     */
+    private static String getIdentifier(final Metadata metadata, final boolean 
unicode) {
+        String fallback = null;
+        if (metadata != null) {
+            for (final Identification md : metadata.getIdentificationInfo()) {
+                String id = Citations.getIdentifier(md.getCitation(), unicode);
+                if (id != null) {
+                    if (md instanceof DataIdentification) {
+                        return id;
+                    } else if (fallback == null) {
+                        fallback = id;
+                    }
+                }
+            }
+        }
+        return fallback;
+    }
+
+    /**
+     * Returns a short label for the given resource. This method returns an 
identifier if possible,
+     * or the title otherwise. If neither an identifier or title can be found, 
then this method returns
+     * the kind of resource implemented by the given object.
+     *
+     * @param  resource  the resource for which to get a label.
+     * @return a human-readable label for the given resource (not to be used 
as an identifier).
+     * @throws DataStoreException if an error occurred while fetching metadata.
+     */
+    public static String getLabel(final Resource resource) throws 
DataStoreException {
+        String title = null;
+        if (resource instanceof DataStore) {
+            title = ((DataStore) resource).getDisplayName();
+        }
+        if (title == null) {
+            title = getIdentifier(resource.getMetadata(), false);
+            if (title == null) {
+                title = 
Classes.getShortName(getInterface(resource.getClass()));
+            }
+        }
+        return title;
+    }
+
+    /**
+     * Returns the most specific interface implemented by the given class.
+     * For indicative purpose only, as this method has arbitrary behavior if 
more than one leaf is found.
+     *
+     * @param  implementation  the implementation class.
+     * @return the most specific resource interface.
+     */
+    public static Class<? extends Resource> getInterface(final Class<? extends 
Resource> implementation) {
+        final Class<? extends Resource>[] types = 
Classes.getLeafInterfaces(implementation, Resource.class);
+        Class<? extends Resource> type = null;
+        for (int i=types.length; --i >= 0;) {
+            type = types[i];
+            if (FeatureSet.class.isAssignableFrom(type)) break;              
// Arbitrary precedence rule.
+        }
+        return type;                // Should never be null since the 'types' 
array should never be empty.
+    }
+
+    /**
+     * Converts the given sequence of options into a simplified set of 
standard options.
+     * The returned set can contain combinations of
+     * {@link StandardOpenOption#WRITE},
+     * {@link StandardOpenOption#CREATE CREATE},
+     * {@link StandardOpenOption#CREATE_NEW CREATE_NEW} and
+     * {@link StandardOpenOption#TRUNCATE_EXISTING TRUNCATE_EXISTING}.
+     * If the set is empty, then the data store should be read-only.
+     * If both {@code TRUNCATE_EXISTING} and {@code CREATE_NEW} are specified,
+     * then {@code CREATE_NEW} has precedence.
+     * More specifically:
+     *
+     * <p>{@link StandardOpenOption#WRITE}<br>
+     * means that the {@link DataStore} should be opened as writable 
resource.</p>
+     *
+     * <p>{@link StandardOpenOption#CREATE}<br>
+     * means that the {@link DataStore} is allowed to create new files.
+     * If this option is present, then {@code WRITE} is also present.
+     * If this option is absent, then writable data stores should not create 
any new file.
+     * This flag can be tested as below (this cover both the read-only case 
and the writable
+     * case where the files must exist):</p>
+     *
+     * {@preformat java
+     *     if (!options.contains(StandardOpenOption.CREATE)) {
+     *         // Throw an exception if the file does not exist.
+     *     }
+     * }
+     *
+     * <p>{@link StandardOpenOption#CREATE_NEW}<br>
+     * means that the {@link DataStore} should fail to open if the file 
already exists.
+     * This mode is used when creating new writable resources, for making sure 
that we
+     * do not modify existing resources.
+     * If this option is present, then {@code WRITE} and {@code CREATE} are 
also present.</p>
+     *
+     * <p>{@link StandardOpenOption#TRUNCATE_EXISTING}<br>
+     * means that the {@link DataStore} should overwrite the content of any 
pre-existing resources.
+     * If this option is present, then {@code WRITE} and {@code CREATE} are 
also present.</p>
+     *
+     * @param  options  the open options, or {@code null}.
+     * @return the open options as a bitmask.
+     */
+    @SuppressWarnings("fallthrough")
+    public static EnumSet<StandardOpenOption> toStandardOptions(final 
OpenOption[] options) {
+        final EnumSet<StandardOpenOption> set = 
EnumSet.noneOf(StandardOpenOption.class);
+        if (options != null) {
+            for (final OpenOption op : options) {
+                if (op instanceof StandardOpenOption) {
+                    switch ((StandardOpenOption) op) {  // Fallthrough in 
every cases.
+                        case CREATE_NEW:         
set.add(StandardOpenOption.CREATE_NEW);
+                        case TRUNCATE_EXISTING:  
set.add(StandardOpenOption.TRUNCATE_EXISTING);
+                        case CREATE:             
set.add(StandardOpenOption.CREATE);
+                        case APPEND: case WRITE: 
set.add(StandardOpenOption.WRITE);
+                    }
+                }
+            }
+            if (set.contains(StandardOpenOption.CREATE_NEW)) {
+                set.remove(StandardOpenOption.TRUNCATE_EXISTING);
+            }
+        }
+        return set;
+    }
+
+    /**
+     * Copies all feature from the given source to the given target.
+     * We use this method as central point where such copy occur, in case we 
want to implement
+     * a more efficient algorithm in some future Apache SIS version. For 
example we could copy
+     * the files using {@link java.nio.file.Files} if we determine that it is 
possible.
+     *
+     * @param  source  the source set of features.
+     * @param  target  where to copy the features.
+     * @throws DataStoreException if an error occurred during the copy 
operation.
+     */
+    public static void copy(final FeatureSet source, final WritableFeatureSet 
target) throws DataStoreException {
+        target.updateType(source.getType());
+        try (Stream<Feature> stream = source.features(false)) {
+            target.add(stream.iterator());
+        }
+    }
+}

Propchange: 
sis/branches/JDK8/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/StoreUtilities.java
------------------------------------------------------------------------------
    svn:eol-style = native

Propchange: 
sis/branches/JDK8/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/StoreUtilities.java
------------------------------------------------------------------------------
    svn:mime-type = text/plain;charset=UTF-8

Modified: 
sis/branches/JDK8/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/URIDataStore.java
URL: 
http://svn.apache.org/viewvc/sis/branches/JDK8/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/URIDataStore.java?rev=1825102&r1=1825101&r2=1825102&view=diff
==============================================================================
--- 
sis/branches/JDK8/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/URIDataStore.java
 [UTF-8] (original)
+++ 
sis/branches/JDK8/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/URIDataStore.java
 [UTF-8] Thu Feb 22 22:18:05 2018
@@ -165,7 +165,8 @@ public abstract class URIDataStore exten
          * strings, without the leading dot.</div>
          *
          * The suffixes are case-insensitive (no need to declare both 
lower-case and upper-case variants)
-         * and shall not contain the leading dot.
+         * and shall not contain the leading dot. The first element in the 
list is the preferred suffix
+         * to use for new files.
          *
          * @return the filename suffixes, case insensitive. Never null but can 
be empty.
          */

Modified: 
sis/branches/JDK8/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/folder/FolderStoreProvider.java
URL: 
http://svn.apache.org/viewvc/sis/branches/JDK8/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/folder/FolderStoreProvider.java?rev=1825102&r1=1825101&r2=1825102&view=diff
==============================================================================
--- 
sis/branches/JDK8/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/folder/FolderStoreProvider.java
 [UTF-8] (original)
+++ 
sis/branches/JDK8/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/folder/FolderStoreProvider.java
 [UTF-8] Thu Feb 22 22:18:05 2018
@@ -16,13 +16,17 @@
  */
 package org.apache.sis.internal.storage.folder;
 
-import java.io.IOException;
+import java.util.EnumSet;
 import java.util.Locale;
 import java.util.TimeZone;
+import java.io.IOException;
 import java.nio.charset.Charset;
-import java.nio.file.Files;
 import java.nio.file.Path;
+import java.nio.file.Files;
+import java.nio.file.FileAlreadyExistsException;
 import java.nio.file.FileSystemNotFoundException;
+import java.nio.file.NoSuchFileException;
+import java.nio.file.StandardOpenOption;
 import org.opengis.util.InternationalString;
 import org.opengis.parameter.ParameterValueGroup;
 import org.opengis.parameter.ParameterDescriptor;
@@ -57,7 +61,7 @@ public final class FolderStoreProvider e
     /**
      * A short name or abbreviation for the data format.
      */
-    private static final String NAME = "folder";
+    static final String NAME = "folder";
 
     /**
      * Description of the parameter for formating conventions of dates and 
numbers.
@@ -91,7 +95,7 @@ public final class FolderStoreProvider e
         ENCODING   = annotate(builder, URIDataStore.Provider.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("provider").setDescription(Resources.formatInternational(Resources.Keys.FolderStoreProviderParameter)).create(String.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);
     }
@@ -170,12 +174,73 @@ public final class FolderStoreProvider e
      */
     @Override
     public DataStore open(final StorageConnector connector) throws 
DataStoreException {
+        return open(connector, null, EnumSet.noneOf(StandardOpenOption.class));
+    }
+
+    /**
+     * Shared implementation of public {@code open(…)} methods.
+     *
+     * @param  connector  information about the storage (URL, path, 
<i>etc</i>).
+     * @param  format     format name, or {@code null} if unspecified.
+     * @param  options    whether to create a new directory, overwrite 
existing content, <i>etc</i>.
+     */
+    private DataStore open(final StorageConnector connector, final String 
format, final EnumSet<StandardOpenOption> options)
+            throws DataStoreException
+    {
+        Path path = null;
+        final Store store;
         try {
-            return new Store(this, connector, null);
+            /*
+             * If the user asked to create a new directory, we need to perform 
this task before
+             * to create the Store (otherwise constructor will fail with 
NoSuchFileException).
+             * In the particular case of CREATE_NEW, we unconditionally 
attempt to create the
+             * directory in order to rely on the atomic check performed by 
Files.createDirectory(…).
+             */
+            if (options.contains(StandardOpenOption.CREATE)) {
+                path = connector.getStorageAs(Path.class);
+                if (options.contains(StandardOpenOption.CREATE_NEW) || 
Files.notExists(path)) {
+                    Files.createDirectory(path);                        // 
IOException if the directory already exists.
+                }
+            }
+            // TODO: check also if @Capabilities.values().contains(WRITE).
+            if (options.contains(StandardOpenOption.WRITE)) {
+                store = new WritableStore(this, connector, format);     // May 
throw NoSuchFileException.
+            } else {
+                store = new Store(this, connector, format);             // May 
throw NoSuchFileException.
+            }
+            /*
+             * If there is a destructive operation to perform 
(TRUNCATE_EXISTING), do it last only
+             * after we have successfully created the data store. The check 
for directory existence
+             * is also done after creation to be sure to check the path used 
by the store.
+             */
+            path = store.location;
+            if (!Files.isDirectory(path)) {
+                throw new 
DataStoreException(Resources.format(Resources.Keys.FileIsNotAResourceDirectory_1,
 path));
+            }
+            if (options.contains(StandardOpenOption.TRUNCATE_EXISTING)) {
+                WritableStore.deleteRecursively(path, false);
+            }
         } catch (IOException e) {
-            throw new 
DataStoreException(Resources.format(Resources.Keys.CanNotReadDirectory_1,
-                    connector.getStorageName()), e);
+            /*
+             * In case of error, Java FileSystem implementation tries to throw 
a specific exception
+             * (NoSuchFileException or FileAlreadyExistsException), but this 
is not guaranteed.
+             */
+            int isDirectory = 0;
+            final short errorKey;
+            if (e instanceof FileAlreadyExistsException) {
+                if (path != null && Files.isDirectory(path)) {
+                    isDirectory = 1;
+                }
+                errorKey = Resources.Keys.FileAlreadyExists_2;
+            } else if (e instanceof NoSuchFileException) {
+                errorKey = Resources.Keys.NoSuchResourceDirectory_1;
+            } else {
+                errorKey = Resources.Keys.CanNotCreateFolderStore_1;
+            }
+            throw new DataStoreException(Resources.format(errorKey,
+                    (path != null) ? path : connector.getStorageName(), 
isDirectory), e);
         }
+        return store;
     }
 
     /**
@@ -193,16 +258,11 @@ public final class FolderStoreProvider e
         connector.setOption(OptionKey.TIMEZONE, pg.getValue(TIMEZONE));
         connector.setOption(OptionKey.ENCODING, pg.getValue(ENCODING));
         final String format = pg.getValue(FORMAT);
-        try {
-            if (format != null) {
-                return new Store.Writable(this, connector, format);
-            } else {
-                return new Store(this, connector, format);
-            }
-        } catch (IOException e) {
-            throw new 
DataStoreException(Resources.format(Resources.Keys.CanNotReadDirectory_1,
-                    connector.getStorageName()), e);
+        final EnumSet<StandardOpenOption> options = 
EnumSet.noneOf(StandardOpenOption.class);
+        if (format != null) {
+            options.add(StandardOpenOption.WRITE);
         }
+        return open(connector, format, options);
     }
 
     /**

Modified: 
sis/branches/JDK8/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/folder/Store.java
URL: 
http://svn.apache.org/viewvc/sis/branches/JDK8/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/folder/Store.java?rev=1825102&r1=1825101&r2=1825102&view=diff
==============================================================================
--- 
sis/branches/JDK8/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/folder/Store.java
 [UTF-8] (original)
+++ 
sis/branches/JDK8/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/folder/Store.java
 [UTF-8] Thu Feb 22 22:18:05 2018
@@ -22,6 +22,8 @@ import java.util.ArrayList;
 import java.util.Collection;
 import java.util.Locale;
 import java.util.TimeZone;
+import java.util.logging.Level;
+import java.util.concurrent.ConcurrentHashMap;
 import java.nio.charset.Charset;
 import java.nio.file.Path;
 import java.nio.file.Files;
@@ -29,17 +31,9 @@ import java.nio.file.DirectoryStream;
 import java.nio.file.DirectoryIteratorException;
 import java.io.IOException;
 import java.io.UncheckedIOException;
-import java.nio.file.FileVisitResult;
-import java.nio.file.SimpleFileVisitor;
-import java.nio.file.attribute.BasicFileAttributes;
-import java.util.Map.Entry;
-import java.util.logging.Level;
-import java.util.concurrent.ConcurrentHashMap;
-import java.util.stream.Stream;
 import org.opengis.metadata.Metadata;
 import org.opengis.metadata.maintenance.ScopeCode;
 import org.opengis.parameter.ParameterValueGroup;
-import org.opengis.metadata.identification.Identification;
 import org.apache.sis.setup.OptionKey;
 import org.apache.sis.storage.Resource;
 import org.apache.sis.storage.Aggregate;
@@ -52,20 +46,10 @@ import org.apache.sis.storage.Unsupporte
 import org.apache.sis.util.collection.BackingStoreException;
 import org.apache.sis.internal.util.UnmodifiableArrayList;
 import org.apache.sis.internal.storage.MetadataBuilder;
+import org.apache.sis.internal.storage.StoreUtilities;
 import org.apache.sis.internal.storage.Resources;
-import org.apache.sis.internal.storage.URIDataStore;
-import org.apache.sis.internal.storage.FileSystemResource;
-import org.apache.sis.metadata.iso.citation.Citations;
-import org.apache.sis.storage.FeatureSet;
-import org.apache.sis.storage.ProbeResult;
-import org.apache.sis.storage.ReadOnlyStorageException;
-import org.apache.sis.storage.WritableAggregate;
-import org.apache.sis.storage.WritableFeatureSet;
 import org.apache.sis.util.resources.Errors;
 
-// Branch-dependent imports
-import org.opengis.feature.Feature;
-
 
 /**
  * A folder store acts as an aggregate of multiple files in a single store.
@@ -91,23 +75,6 @@ import org.opengis.feature.Feature;
  */
 class Store extends DataStore implements Aggregate, 
DirectoryStream.Filter<Path> {
     /**
-     * File walker to delete file and folder recursively.
-     */
-    private static final SimpleFileVisitor<Path> FILE_DELETE = new 
SimpleFileVisitor<Path>() {
-        @Override
-        public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) 
throws IOException {
-            Files.delete(file);
-            return FileVisitResult.CONTINUE;
-        }
-
-        @Override
-        public FileVisitResult postVisitDirectory(Path dir, IOException exc) 
throws IOException {
-            Files.delete(dir);
-            return FileVisitResult.CONTINUE;
-        }
-    };
-
-    /**
      * The {@link FolderStoreProvider#LOCATION} parameter value, or {@code 
null} if none.
      */
     protected final Path location;
@@ -128,15 +95,10 @@ class Store extends DataStore implements
     protected final Charset encoding;
 
     /**
-     * Single provider to use in searches and creation operations, or {@code 
null} if unspecified.
-     */
-    protected final String providerName;
-
-    /**
      * All data stores (including sub-folders) found in the directory 
structure, including the root directory.
      * This is used for avoiding never-ending loop with symbolic links.
      */
-    protected final Map<Path,DataStore> children;
+    final Map<Path,DataStore> children;
 
     /**
      * Information about the data store as a whole, created when first needed.
@@ -150,28 +112,36 @@ class Store extends DataStore implements
      *
      * @see #components()
      */
-    protected transient Collection<Resource> components;
+    transient Collection<Resource> components;
 
     /**
-     * {@code true} if {@link #sharedRepository(Path)} has already been 
invoked for {@link #location} path.
-     * This is used for avoiding to report the same message many times.
+     * The provider to use for probing the directory content, opening files 
and creating new files.
+     * The provider is determined by the format name specified at construction 
time.
+     * This field is {@code null} if that format name is null.
      */
-    private transient boolean sharedRepositoryReported;
+    protected final DataStoreProvider componentProvider;
 
     /**
-     * Cached search and create provider to use.
+     * {@code true} if {@link #sharedRepository(Path)} has already been 
invoked for {@link #location} path.
+     * This is used for avoiding to report the same message many times.
      */
-    private transient DataStoreProvider searchProvider;
+    private transient boolean sharedRepositoryReported;
 
     /**
      * Creates a new folder store from the given file, path or URI.
+     * The folder store will attempt to open only the files of the given 
format, if non-null.
+     * If a null format name is specified, then the folder store will attempt 
to open any file
+     * found in the directory (this may produce confusing results).
      *
      * @param  provider   the factory that created this {@code DataStore} 
instance, or {@code null} if unspecified.
      * @param  connector  information about the storage (URL, stream, 
<i>etc</i>).
-     * @throws DataStoreException if an error occurred while opening the 
stream.
+     * @param  format     name of the format to use for reading or writing the 
directory content, or {@code null}.
+     * @throws UnsupportedStorageException if the given format name is unknown.
+     * @throws DataStoreException if an error occurred while fetching the 
directory {@link Path}.
+     * @throws IOException if an error occurred while using the directory 
{@code Path}.
      */
     @SuppressWarnings("ThisEscapedInObjectConstruction")    // Okay because 
'folders' does not escape.
-    Store(final DataStoreProvider provider, final StorageConnector connector, 
final String format)
+    Store(final DataStoreProvider provider, final StorageConnector connector, 
String format)
             throws DataStoreException, IOException
     {
         super(provider, connector);
@@ -179,9 +149,20 @@ class Store extends DataStore implements
         locale   = connector.getOption(OptionKey.LOCALE);
         timezone = connector.getOption(OptionKey.TIMEZONE);
         encoding = connector.getOption(OptionKey.ENCODING);
-        providerName = format;
         children = new ConcurrentHashMap<>();
         children.put(location.toRealPath(), this);
+        if (format == null) {
+            componentProvider = null;
+        } else {
+            format = format.trim();
+            for (DataStoreProvider cp : DataStores.providers()) {
+                if (format.equalsIgnoreCase(StoreUtilities.getIdentifier(cp))) 
{
+                    componentProvider = cp;
+                    return;
+                }
+            }
+            throw new 
UnsupportedStorageException(Errors.getResources(super.getLocale()).getString(Errors.Keys.UnsupportedFormat_1,
 format));
+        }
     }
 
     /**
@@ -193,13 +174,12 @@ class Store extends DataStore implements
      */
     private Store(final Store parent, final StorageConnector connector) throws 
DataStoreException {
         super(parent, connector);
-        location       = connector.getStorageAs(Path.class);
-        locale         = connector.getOption(OptionKey.LOCALE);
-        timezone       = connector.getOption(OptionKey.TIMEZONE);
-        encoding       = connector.getOption(OptionKey.ENCODING);
-        children       = parent.children;
-        providerName   = parent.providerName;
-        searchProvider = parent.searchProvider;
+        location          = connector.getStorageAs(Path.class);
+        locale            = connector.getOption(OptionKey.LOCALE);
+        timezone          = connector.getOption(OptionKey.TIMEZONE);
+        encoding          = connector.getOption(OptionKey.ENCODING);
+        children          = parent.children;
+        componentProvider = parent.componentProvider;
     }
 
     /**
@@ -207,12 +187,13 @@ class Store extends DataStore implements
      */
     @Override
     public ParameterValueGroup getOpenParameters() {
+        final String format = StoreUtilities.getIdentifier(componentProvider);
         final ParameterValueGroup pg = (provider != null ? 
provider.getOpenParameters() : FolderStoreProvider.PARAMETERS).createValue();
         pg.parameter(DataStoreProvider.LOCATION).setValue(location);
-        if (locale       != null) pg.parameter("locale"  ).setValue(locale  );
-        if (timezone     != null) pg.parameter("timezone").setValue(timezone);
-        if (encoding     != null) pg.parameter("encoding").setValue(encoding);
-        if (providerName != null) 
pg.parameter("provider").setValue(providerName);
+        if (locale   != null) pg.parameter("locale"  ).setValue(locale  );
+        if (timezone != null) pg.parameter("timezone").setValue(timezone);
+        if (encoding != null) pg.parameter("encoding").setValue(encoding);
+        if (format   != null) pg.parameter("format"  ).setValue(format);
         return pg;
     }
 
@@ -253,8 +234,8 @@ class Store extends DataStore implements
     @SuppressWarnings("ReturnOfCollectionOrArrayField")
     public synchronized Collection<Resource> components() throws 
DataStoreException {
         if (components == null) {
+            final List<DataStore> resources = new ArrayList<>();
             try (DirectoryStream<Path> stream = 
Files.newDirectoryStream(location, this)) {
-                final List<DataStore> resources = new ArrayList<>();
                 for (final Path candidate : stream) {
                     /*
                      * The candidate path may be a symbolic link to a file 
that we have previously read.
@@ -278,20 +259,17 @@ class Store extends DataStore implements
                         connector.setOption(OptionKey.LOCALE,   locale);
                         connector.setOption(OptionKey.TIMEZONE, timezone);
                         connector.setOption(OptionKey.ENCODING, encoding);
-
-                        final DataStoreProvider provider = 
getSearchAndCreateProvider();
                         try {
-                            if (provider != null) {
-                                final ProbeResult result = 
provider.probeContent(connector);
-                                if (result.isSupported()) {
-                                    next = provider.open(connector);
-                                } else {
-                                    throw new UnsupportedStorageException();
-                                }
+                            if (componentProvider == null) {
+                                next = DataStores.open(connector);          // 
May throw UnsupportedStorageException.
+                            } else if 
(componentProvider.probeContent(connector).isSupported()) {
+                                next = componentProvider.open(connector);   // 
Open a file of specified format.
+                            } else if (Files.isDirectory(candidate)) {
+                                next = new Store(this, connector);          // 
Open a sub-directory.
                             } else {
-                                next = DataStores.open(connector);
+                                connector.closeAllExcept(null);             // 
Not the format specified at construction time.
+                                continue;
                             }
-
                         } catch (UnsupportedStorageException ex) {
                             if (!Files.isDirectory(candidate)) {
                                 connector.closeAllExcept(null);
@@ -322,7 +300,6 @@ class Store extends DataStore implements
                     }
                     resources.add(next);
                 }
-                components = UnmodifiableArrayList.wrap(resources.toArray(new 
Resource[resources.size()]));
             } catch (DirectoryIteratorException | UncheckedIOException ex) {
                 // The cause is an IOException (no other type allowed).
                 throw new DataStoreException(canNotRead(), ex.getCause());
@@ -331,31 +308,12 @@ class Store extends DataStore implements
             } catch (BackingStoreException ex) {
                 throw ex.unwrapOrRethrow(DataStoreException.class);
             }
+            components = UnmodifiableArrayList.wrap(resources.toArray(new 
Resource[resources.size()]));
         }
         return components;              // Safe because unmodifiable list.
     }
 
     /**
-     *
-     * @return search and create provider, can be null
-     * @throws DataStoreException
-     */
-    protected DataStoreProvider getSearchAndCreateProvider() throws 
DataStoreException {
-        if (searchProvider == null && providerName != null) {
-            for (DataStoreProvider provider : DataStores.providers()) {
-                if (providerName.equals(provider.getShortName())) {
-                    searchProvider = provider;
-                    break;
-                }
-            }
-            if (searchProvider == null) {
-                throw new 
DataStoreException(Errors.getResources(getLocale()).getString(Errors.Keys.UnsupportedFormat_1,
 providerName));
-            }
-        }
-        return searchProvider;
-    }
-
-    /**
      * Builds an error message for an error occurring while reading files in 
the directory.
      */
     private String canNotRead() {
@@ -381,7 +339,7 @@ class Store extends DataStore implements
      *
      * @param  key  one of the {@link Resources.Keys} constants ending with 
{@code _1} suffix.
      */
-    private String message(final short key, final Object value) {
+    final String message(final short key, final Object value) {
         return Resources.forLocale(getLocale()).getString(key, value);
     }
 
@@ -410,160 +368,4 @@ class Store extends DataStore implements
             }
         }
     }
-
-    /**
-     * Writable version of the store which rely on given datastore provider to 
create new types.
-     *
-     * Note 1 : this implementation is experimental.
-     * Note 2 : it has not been tested since we do not have writable feature 
sets yet.
-     */
-    static class Writable extends Store implements WritableAggregate {
-
-        public Writable(final DataStoreProvider provider, final 
StorageConnector connector, final String format)
-            throws DataStoreException, IOException
-        {
-            super(provider, connector, format);
-        }
-
-        /**
-         * Create a new resource.
-         * This implementation uses the provider given in store creation 
parameters.
-         *
-         * @param resource
-         * @return
-         * @throws DataStoreException
-         */
-        @Override
-        public synchronized Resource add(Resource resource) throws 
DataStoreException {
-            if (!(resource instanceof FeatureSet)) {
-                throw new DataStoreException("Only FeatureSet resources can be 
imported in this store.");
-            }
-
-            if (components().contains(resource)) {
-                throw new DataStoreException("Resource is already in this 
aggregate.");
-            }
-
-            //we know it is not null in this instance
-            final DataStoreProvider provider = getSearchAndCreateProvider();
-            if (!(provider instanceof URIDataStore.Provider)) {
-                throw new DataStoreException("Resource creation is possible 
only with URIProviders");
-            }
-
-            final URIDataStore.Provider p = (URIDataStore.Provider) provider;
-
-            //build location
-            String fileName = null;
-            for (Identification id : 
resource.getMetadata().getIdentificationInfo()) {
-                fileName = Citations.getIdentifier(id.getCitation());
-                if (fileName!=null && !fileName.isEmpty()) break;
-            }
-            if (fileName == null || fileName.isEmpty()) {
-                throw new DataStoreException("Resource does not have an 
identifier.");
-            }
-
-            //some format may have no suffix at all
-            if (!p.getSuffix().isEmpty()) {
-                fileName += "."+ p.getSuffix().get(0);
-            }
-
-            //create new store/resource
-            final Path location = this.location.resolve(fileName);
-            final StorageConnector connector = new StorageConnector(location);
-            connector.setOption(OptionKey.LOCALE,   locale);
-            connector.setOption(OptionKey.TIMEZONE, timezone);
-            connector.setOption(OptionKey.ENCODING, encoding);
-            final DataStore store = p.open(connector);
-
-            //check we can write datas
-            if (!(store instanceof WritableFeatureSet)) {
-                try {
-                    //remove any created file
-                    if (resource instanceof FileSystemResource) {
-                        //delete resource files
-                        final Path[] resourcePaths = ((FileSystemResource) 
resource).getResourcePaths();
-                        for (Path path : resourcePaths) {
-                            Files.walkFileTree(path, FILE_DELETE);
-                        }
-                    }
-                    Files.deleteIfExists(location);
-                } catch (IOException ex) {
-                    //do nothing
-                } finally {
-                    store.close();
-                }
-                throw new DataStoreException("Created resource is not a 
WritableFeatureSet.");
-            }
-
-            //copy datas between resources
-            children.put(location, store);
-            final FeatureSet source = (FeatureSet) resource;
-            final WritableFeatureSet target = (WritableFeatureSet) store;
-            target.updateType(source.getType());
-            try (Stream<Feature> stream = source.features(false)) {
-                target.add(stream.iterator());
-            }
-
-
-            //clear cache
-            components = null;
-
-            return store;
-        }
-
-        /**
-         * Note : in this implementation we clear the cache after closing the 
stores and before deleting the files.
-         * This ensure in the worse case scenario a new store will be created 
on the possible remaining files.
-         *
-         * @param resource
-         * @throws ReadOnlyStorageException
-         * @throws DataStoreException
-         */
-        @Override
-        public synchronized void remove(Resource resource) throws 
ReadOnlyStorageException, DataStoreException {
-            if (!(components().contains(resource))) {
-                throw new DataStoreException("Unknown resource, verify it is 
part of this aggregate.");
-            }
-
-            //clear cache
-            components = null;
-
-            if (resource instanceof Store) {
-                final Store store = (Store) resource;
-                store.close();
-                //clear cache
-                children.remove(store.location);
-
-                try {
-                    Files.walkFileTree(store.location, FILE_DELETE);
-                } catch (IOException ex) {
-                    throw new DataStoreException(ex.getMessage(), ex);
-                }
-            } else {
-                //resource is a datastore, we are sure of it
-                final DataStore store = (DataStore) resource;
-                store.close();
-
-                //clear cache, we need to do this loop in case the resource is
-                //not a FileSystemResource or wrongly declares the used files
-                for (Entry<Path,DataStore> entry : children.entrySet()) {
-                    if (entry.getValue() == store) {
-                        children.remove(entry.getKey());
-                        break;
-                    }
-                }
-
-                if (resource instanceof FileSystemResource) {
-                    //delete resource files
-                    final Path[] resourcePaths = ((FileSystemResource) 
resource).getResourcePaths();
-                    for (Path path : resourcePaths) {
-                        try {
-                            Files.walkFileTree(path, FILE_DELETE);
-                        } catch (IOException ex) {
-                            throw new DataStoreException(ex.getMessage(), ex);
-                        }
-                    }
-                }
-            }
-        }
-    }
 }


Reply via email to