Copied: 
sis/branches/JDK8/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/folder/WritableStore.java
 (from r1824938, 
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/WritableStore.java?p2=sis/branches/JDK8/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/folder/WritableStore.java&p1=sis/branches/JDK8/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/folder/Store.java&r1=1824938&r2=1825102&rev=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/WritableStore.java
 [UTF-8] Thu Feb 22 22:18:05 2018
@@ -16,554 +16,223 @@
  */
 package org.apache.sis.internal.storage.folder;
 
-import java.util.Map;
-import java.util.List;
-import java.util.ArrayList;
-import java.util.Collection;
-import java.util.Locale;
-import java.util.TimeZone;
-import java.nio.charset.Charset;
 import java.nio.file.Path;
 import java.nio.file.Files;
-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.StandardOpenOption;
 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 java.io.IOException;
+import java.util.Iterator;
 import org.apache.sis.setup.OptionKey;
 import org.apache.sis.storage.Resource;
-import org.apache.sis.storage.Aggregate;
 import org.apache.sis.storage.DataStore;
-import org.apache.sis.storage.DataStores;
 import org.apache.sis.storage.DataStoreProvider;
 import org.apache.sis.storage.StorageConnector;
 import org.apache.sis.storage.DataStoreException;
-import org.apache.sis.storage.UnsupportedStorageException;
-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.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;
+import org.apache.sis.internal.storage.StoreUtilities;
+import org.apache.sis.internal.storage.FileSystemProvider;
+import org.apache.sis.internal.storage.FileSystemResource;
+import org.apache.sis.internal.storage.Resources;
+import org.apache.sis.util.ArgumentChecks;
 
 
 /**
- * A folder store acts as an aggregate of multiple files in a single store.
- * Only visible files are considered; all hidden files are excluded.
- * Each visible file will be tested and eventually opened by another store.
- * This approach allows to discover the content of a folder or archive without
- * testing each file one by one.
+ * Writable version of the store which rely on given datastore provider to 
create new types.
  *
- * <p><b>Limitations:</b></p>
- * <ul>
- *   <li>Current version is read-only.</li>
- *   <li>Current version does not watch for external modifications in 
directory content.</li>
- *   <li>Current version open all files in the directory and keep those files 
open.
- *       If the directory is large, it will be a problem.</li>
- *   <li>We could open data stores concurrently. This is not yet done.</li>
- * </ul>
+ * Note 1: this implementation is experimental.
+ * Note 2: it has not been tested since we do not have writable feature sets 
yet.
  *
  * @author  Johann Sorel (Geomatys)
- * @author  Martin Desruisseaux (Geomatys)
  * @version 1.0
- * @since   0.8
+ * @since   1.0
  * @module
  */
-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;
-
-    /**
-     * Formating conventions of dates and numbers, or {@code null} if 
unspecified.
-     */
-    protected final Locale locale;
-
-    /**
-     * Timezone of dates in the data store, or {@code null} if unspecified.
-     */
-    protected final TimeZone timezone;
-
-    /**
-     * Character encoding used by the data store, or {@code null} if 
unspecified.
-     */
-    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;
-
-    /**
-     * Information about the data store as a whole, created when first needed.
-     *
-     * @see #getMetadata()
-     */
-    private transient Metadata metadata;
-
-    /**
-     * Resources in the folder given at construction time, created when first 
needed.
-     *
-     * @see #components()
-     */
-    protected 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.
-     */
-    private transient boolean sharedRepositoryReported;
-
-    /**
-     * Cached search and create provider to use.
-     */
-    private transient DataStoreProvider searchProvider;
-
+final class WritableStore extends Store implements WritableAggregate {
     /**
      * Creates a new folder store from the given file, path or URI.
-     *
-     * @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.
+     * Contrarily to the {@link Store} parent class, the {@code format} is 
mandatory for writable stores.
      */
-    @SuppressWarnings("ThisEscapedInObjectConstruction")    // Okay because 
'folders' does not escape.
-    Store(final DataStoreProvider provider, final StorageConnector connector, 
final String format)
+    WritableStore(final DataStoreProvider provider, final StorageConnector 
connector, final String format)
             throws DataStoreException, IOException
     {
-        super(provider, connector);
-        location = connector.getStorageAs(Path.class);
-        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);
-    }
-
-    /**
-     * Creates a new sub-folder store as a child of the given folder store.
-     *
-     * @param  parent     the parent folder store.
-     * @param  connector  information about the storage (URL, stream, 
<i>etc</i>).
-     * @throws DataStoreException if an error occurred while opening the 
stream.
-     */
-    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;
-    }
-
-    /**
-     * Returns the parameters used to open this data store.
-     */
-    @Override
-    public ParameterValueGroup getOpenParameters() {
-        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);
-        return pg;
-    }
-
-    /**
-     * Invoked during iteration for omitting hidden files.
-     */
-    @Override
-    public boolean accept(final Path entry) throws IOException {
-        return !Files.isHidden(entry);
+        super(provider, connector, format);
     }
 
     /**
-     * Returns information about the data store as a whole.
-     * Those metadata contains the directory name in the resource title.
-     *
-     * @return information about resources in the data store.
+     * Create a new file for the given resource.
+     * This implementation uses the provider specified by the format name 
given at creation time.
      */
     @Override
-    public synchronized Metadata getMetadata() {
-        if (metadata == null) {
-            final MetadataBuilder mb = new MetadataBuilder();
-            final String name = getDisplayName();
-            mb.addResourceScope(ScopeCode.COLLECTION, 
Resources.formatInternational(Resources.Keys.DirectoryContent_1, name));
-            mb.addLanguage(locale,   MetadataBuilder.Scope.RESOURCE);
-            mb.addEncoding(encoding, MetadataBuilder.Scope.RESOURCE);
-            mb.addTitleOrIdentifier(name, MetadataBuilder.Scope.ALL);
-            metadata = mb.build(true);
+    public synchronized Resource add(final Resource resource) throws 
DataStoreException {
+        ArgumentChecks.ensureNonNull("resource", resource);
+        if (!(resource instanceof FeatureSet)) {
+            throw new 
DataStoreException(message(Resources.Keys.CanNotStoreResourceType_2, new 
Object[] {
+                FolderStoreProvider.NAME, 
StoreUtilities.getInterface(resource.getClass())
+            }));
+        }
+        /*
+         * Infer a filename from the resource identifier, if one can be found.
+         * A suffix is added to the filename if available (some formats may 
have no suffix at all).
+         */
+        String filename = StoreUtilities.getIdentifier(resource.getMetadata());
+        if (filename == null) {
+            throw new 
DataStoreException(message(Resources.Keys.MissingResourceIdentifier_1, 
StoreUtilities.getLabel(resource)));
+        }
+        if (componentProvider instanceof FileSystemProvider) {
+            final Iterator<String> suffixes = ((FileSystemProvider) 
componentProvider).getSuffix().iterator();
+            if (suffixes.hasNext()) {
+                filename += '.' + suffixes.next();
+            }
         }
-        return metadata;
-    }
-
-    /**
-     * Returns all resources found in the folder given at construction time.
-     * Only the resources recognized by a {@link DataStore} will be included.
-     * This includes sub-folders. Resources are in no particular order.
-     */
-    @Override
-    @SuppressWarnings("ReturnOfCollectionOrArrayField")
-    public synchronized Collection<Resource> components() throws 
DataStoreException {
-        if (components == null) {
-            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.
-                     * In such case, use the existing data store.   A use case 
is a directory containing
-                     * hundred of GeoTIFF files all accompanied by ".prj" 
files having identical content.
-                     * (Note: those ".prj" files should be invisible since 
they should be identified as
-                     * GeoTIFF auxiliary files, but current Store 
implementation does not know that).
-                     */
-                    final Path real = candidate.toRealPath();
-                    DataStore next = children.get(real);
-                    if (next instanceof Store) {
-                        ((Store) next).sharedRepository(real);          // 
Warn about directories only.
-                    }
-                    if (next == null) {
-                        /*
-                         * The candidate file has never been read before. Try 
to read it now.
-                         * If the file format is unknown 
(UnsupportedStorageException), we will
-                         * check if we can open it as a child folder store 
before to skip it.
-                         */
-                        final StorageConnector connector = new 
StorageConnector(candidate);
-                        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();
-                                }
-                            } else {
-                                next = DataStores.open(connector);
-                            }
-
-                        } catch (UnsupportedStorageException ex) {
-                            if (!Files.isDirectory(candidate)) {
-                                connector.closeAllExcept(null);
-                                listeners.warning(Level.FINE, null, ex);
-                                continue;
-                            }
-                            next = new Store(this, connector);
-                        } catch (DataStoreException ex) {
-                            try {
-                                connector.closeAllExcept(null);
-                            } catch (DataStoreException s) {
-                                ex.addSuppressed(s);
-                            }
-                            throw ex;
-                        }
-                        /*
-                         * At this point we got the data store. It could 
happen that a store for
-                         * the same file has been added concurrently, so we 
need to check again.
-                         */
-                        final DataStore existing = children.putIfAbsent(real, 
next);
-                        if (existing != null) {
-                            next.close();
-                            next = existing;
-                            if (next instanceof Store) {
-                                ((Store) next).sharedRepository(real);      // 
Warn about directories only.
-                            }
+        /*
+         * Create new store/resource for write access, provided that no store 
already exist for the path.
+         * We use the CREATE_NEW option in order to intentionally fail if the 
resource already exists.
+         */
+        final Path path = location.resolve(filename);
+        if (!children.containsKey(path)) {
+            final StorageConnector connector = new StorageConnector(path);
+            connector.setOption(OptionKey.LOCALE,   locale);
+            connector.setOption(OptionKey.TIMEZONE, timezone);
+            connector.setOption(OptionKey.ENCODING, encoding);
+            connector.setOption(OptionKey.OPEN_OPTIONS, new 
StandardOpenOption[] {
+                StandardOpenOption.CREATE_NEW, StandardOpenOption.WRITE
+            });
+            final DataStore store = componentProvider.open(connector);
+            if (children.putIfAbsent(path, store) == null) {
+                /*
+                 * Check if we can write data. It should be the case since we 
specified StandardOpenOption.WRITE.
+                 * Note that if 'componentProvider' can create only read-only 
store, then open(connector) should
+                 * have failed with NoSuchFileException before we reach this 
point.
+                 */
+                // TODO: handle transactional case.
+                if (store instanceof WritableFeatureSet) {
+                    StoreUtilities.copy((FeatureSet) resource, 
(WritableFeatureSet) store);
+                    components = null;      // Clear cache. TODO: we should do 
something more efficient.
+                    return store;
+                }
+                /*
+                 * If the data store is not a WritableFeatureSet, current 
implementation can not use it.
+                 * Delete the file that the store may have created.
+                 *
+                 * TODO: we should set a flag for blocking next attempts to 
add a resources, since they are likely
+                 *       to fail as well. Maybe we should not delete any files 
since we are not sure to delete the
+                 *       right ones. For example store.getComponentPaths() may 
return a path outside the directory
+                 *       managed by this folder store.
+                 */
+                final DataStoreException ex = new 
DataStoreException(Resources.format(
+                        Resources.Keys.NotAWritableFeatureSet_1, 
store.getDisplayName()));
+                store.close();
+                try {
+                    if (store instanceof Store) {
+                        deleteRecursively(((Store) store).location, true);
+                    } else if (store instanceof FileSystemResource) {
+                        for (Path c : ((FileSystemResource) 
store).getResourcePaths()) {
+                            Files.delete(c);
                         }
                     }
-                    resources.add(next);
+                } catch (IOException e) {
+                    ex.addSuppressed(e);
                 }
-                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());
-            } catch (IOException ex) {
-                throw new DataStoreException(canNotRead(), ex);
-            } catch (BackingStoreException ex) {
-                throw ex.unwrapOrRethrow(DataStoreException.class);
+                children.remove(path, store);
+                throw ex;
             }
+            store.close();
         }
-        return components;              // Safe because unmodifiable list.
+        throw new 
DataStoreException(Resources.format(Resources.Keys.ResourceAlreadyExists_1, 
path));
     }
 
     /**
+     * Removes a {@code Resource} from this store. The resource must be a part 
of this {@code Aggregate}.
+     * For a folder store, this means that the resource must be a direct 
children of the directory managed
+     * by this store.
      *
-     * @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() {
-        return message(Resources.Keys.CanNotReadDirectory_1, getDisplayName());
-    }
-
-    /**
-     * Logs a warning about a file that could be read, but happen to be a 
directory that we have read previously.
-     * We could add the existing {@link Aggregate} instance in the parent 
{@code Aggregate} that we are building,
-     * but doing so may create a cycle. Current version logs a warning instead 
because users may not be prepared
-     * to handle cycles. Not that we have no guarantee that a cycle really 
exists at this stage, only that it may
-     * exist.
-     */
-    private void sharedRepository(final Path candidate) {
-        if (!sharedRepositoryReported) {
-            sharedRepositoryReported = true;
-            listeners.warning(message(Resources.Keys.SharedDirectory_1, 
candidate), null);
-        }
-    }
-
-    /**
-     * Returns a localized string for the given key and value.
-     *
-     * @param  key  one of the {@link Resources.Keys} constants ending with 
{@code _1} suffix.
-     */
-    private String message(final short key, final Object value) {
-        return Resources.forLocale(getLocale()).getString(key, value);
-    }
-
-    /**
-     * Closes all children resources.
+     * This operation is destructive: the {@link Resource} and it's related 
files will be deleted.
      */
     @Override
-    public synchronized void close() throws DataStoreException {
-        final Collection<Resource> resources = components;
-        if (resources != null) {
-            components = null;                                      // Clear 
first in case of failure.
-            DataStoreException failure = null;
-            for (final Resource r : resources) {
-                if (r instanceof DataStore) try {
-                    ((DataStore) r).close();
-                } catch (DataStoreException ex) {
-                    if (failure == null) {
-                        failure = ex;
-                    } else {
-                        failure.addSuppressed(ex);
+    public synchronized void remove(final Resource resource) throws 
DataStoreException {
+        if (resource instanceof DataStore) try {
+            if (resource instanceof Store) {
+                final Path path = ((Store) resource).location;
+                if (Files.isSameFile(path.getParent(), location)) {
+                    ((Store) resource).close();
+                    deleteRecursively(path, true);
+                    children.remove(path);
+                    return;
+                }
+            } else if (resource instanceof FileSystemResource) {
+                final Path[] componentPaths = ((FileSystemResource) 
resource).getResourcePaths().clone();
+                for (final Path root : componentPaths) {
+                    if (Files.isSameFile(root.getParent(), location)) {
+                        for (final Path path : componentPaths) {
+                            if (path.startsWith(root)) {
+                                Files.delete(path);
+                            }
+                        }
+                        children.values().removeIf((e) -> e == resource);
+                        components = null;      // Clear cache. TODO: we 
should do something more efficient.
+                        return;
                     }
                 }
             }
-            if (failure != null) {
-                throw failure;
-            }
+        } catch (IOException e) {
+            throw new 
DataStoreException(Resources.format(Resources.Keys.CanNotRemoveResource_2,
+                        getDisplayName(), ((DataStore) 
resource).getDisplayName()), e);
         }
+        throw new 
DataStoreException(Resources.format(Resources.Keys.NoSuchResourceInAggregate_2,
+                    getDisplayName(), StoreUtilities.getLabel(resource)));
     }
 
     /**
-     * Writable version of the store which rely on given datastore provider to 
create new types.
+     * Deletes all files and sub-directories in the specified directory.
+     * This method does nothing if the given {@code root} is a file rather 
than a directory.
+     * The root directory is left in place (after being emptied) if {@code 
deleteRoot} is {@code false}.
      *
-     * Note 1 : this implementation is experimental.
-     * Note 2 : it has not been tested since we do not have writable feature 
sets yet.
+     * @param  root        the directory to delete with all sub-directories.
+     * @param  deleteRoot  {@code true} for deleting also {@code root}, or 
{@code false} for leaving it empty.
      */
-    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);
-        }
+    static void deleteRecursively(final Path root, final boolean deleteRoot) 
throws IOException {
+        Files.walkFileTree(root, new SimpleFileVisitor<Path>() {
+            /**
+             * Count of the number of time we entered in a directory.
+             * We use this count for detecting when a file is the {@code root} 
argument.
+             */
+            private int depth;
 
-        /**
-         * 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.");
+            /**
+             * Invoked for a directory before entries in the directory are 
visited.
+             */
+            @Override
+            public FileVisitResult preVisitDirectory(Path dir, 
BasicFileAttributes attrs) throws IOException {
+                depth++;
+                return FileVisitResult.CONTINUE;
             }
 
-            //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");
+            /**
+             * Invoked for a file in a directory. Deletes the file provided 
that it is not the {@code root} argument.
+             * The later can happen if the path given to {@code 
deleteRecursively(…)} is a file rather than a directory.
+             */
+            @Override
+            public FileVisitResult visitFile(Path file, BasicFileAttributes 
attrs) throws IOException {
+                if (depth > 0) Files.delete(file);
+                return FileVisitResult.CONTINUE;
             }
 
-            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);
-                        }
-                    }
+            /**
+             * Invoked for a directory after entries in the directory have 
been visited.
+             * This method delete the directory unless it is the root and 
{@code deleteRoot} is {@code false}.
+             */
+            @Override
+            public FileVisitResult postVisitDirectory(Path dir, IOException 
ex) throws IOException {
+                if (ex != null) throw ex;
+                if (--depth > 0 || deleteRoot) {
+                    Files.delete(dir);
                 }
+                return FileVisitResult.CONTINUE;
             }
-        }
+        });
     }
 }

Modified: 
sis/branches/JDK8/storage/sis-storage/src/main/java/org/apache/sis/storage/DataStoreProvider.java
URL: 
http://svn.apache.org/viewvc/sis/branches/JDK8/storage/sis-storage/src/main/java/org/apache/sis/storage/DataStoreProvider.java?rev=1825102&r1=1825101&r2=1825102&view=diff
==============================================================================
--- 
sis/branches/JDK8/storage/sis-storage/src/main/java/org/apache/sis/storage/DataStoreProvider.java
 [UTF-8] (original)
+++ 
sis/branches/JDK8/storage/sis-storage/src/main/java/org/apache/sis/storage/DataStoreProvider.java
 [UTF-8] Thu Feb 22 22:18:05 2018
@@ -57,7 +57,7 @@ import org.apache.sis.util.Version;
  *
  * @author  Martin Desruisseaux (Geomatys)
  * @author  Johann Sorel (Geomatys)
- * @version 0.8
+ * @version 1.0
  * @since   0.3
  * @module
  */
@@ -69,16 +69,35 @@ public abstract class DataStoreProvider
      *
      * <p>Implementors are encouraged to define a parameter with this name
      * to ensure a common and consistent definition among providers.
-     * The parameter should be defined as mandatory and declared with a 
well-known Java class such as
+     * The parameter should be defined as mandatory and typed with a 
well-known Java class such as
      * {@link java.net.URI}, {@link java.nio.file.Path}, JDBC {@linkplain 
javax.sql.DataSource}, <i>etc</i>.
      * The type should have a compact textual representation, for 
serialization in XML or configuration files.
      * Consequently {@link java.io.InputStream} and {@link 
java.nio.channels.Channel} should be avoided.</p>
      *
+     * @see #CREATE
      * @see #getOpenParameters()
      */
     public static final String LOCATION = "location";
 
     /**
+     * Name of the parameter that specifies whether to allow creation of a new 
{@code DataStore} if none exist
+     * at the given location. A parameter named {@value} may be included in 
the group of parameters returned by
+     * {@link #getOpenParameters()} if the data store supports write 
operations. The parameter value is often a
+     * {@link Boolean}, but other types are allowed. The default value should 
be {@link Boolean#FALSE} or equivalent.
+     *
+     * <p>Implementors are encouraged to define a parameter with this name in 
complement to the {@value #LOCATION}
+     * parameter if write operations are supported. The parameter should be 
defined as optional and typed with a
+     * well-known Java class such as {@link Boolean} or {@link String}. If 
this parameter value is not set or is
+     * set to {@code false}, then the {@link #open(ParameterValueGroup)} 
method should fail if no file or database
+     * exists at the URL or path given by the {@value #LOCATION} parameter. 
Otherwise if this parameter is set to
+     * {@code true}, then the {@code open(…)} method may create files, a 
directory or a database at the given location.</p>
+     *
+     * @see #LOCATION
+     * @see #getOpenParameters()
+     */
+    public static final String CREATE = "create";
+
+    /**
      * Creates a new provider.
      */
     protected DataStoreProvider() {
@@ -156,7 +175,8 @@ public abstract class DataStoreProvider
      * from a path or URL, together with additional information like character 
encoding.
      *
      * <p>Implementors are responsible for declaring all parameters and 
whether they are mandatory or optional.
-     * It is recommended to define at least a parameter named {@value 
#LOCATION}.
+     * It is recommended to define at least a parameter named {@value 
#LOCATION}, completed by {@value #CREATE}
+     * if the data store supports write operations.
      * That parameter will be recognized by the default {@code 
DataStoreProvider} methods and used whenever a
      * {@link StorageConnector} is required.</p>
      *
@@ -174,6 +194,8 @@ public abstract class DataStoreProvider
      *
      * @return description of the parameters required or accepted for opening 
a {@link DataStore}.
      *
+     * @see #LOCATION
+     * @see #CREATE
      * @see #open(ParameterValueGroup)
      * @see DataStore#getOpenParameters()
      *
@@ -280,6 +302,8 @@ public abstract class DataStoreProvider
      * @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.
      *
+     * @see #LOCATION
+     * @see #CREATE
      * @see #getOpenParameters()
      *
      * @since 0.8

Modified: 
sis/branches/JDK8/storage/sis-storage/src/test/java/org/apache/sis/internal/storage/folder/StoreTest.java
URL: 
http://svn.apache.org/viewvc/sis/branches/JDK8/storage/sis-storage/src/test/java/org/apache/sis/internal/storage/folder/StoreTest.java?rev=1825102&r1=1825101&r2=1825102&view=diff
==============================================================================
--- 
sis/branches/JDK8/storage/sis-storage/src/test/java/org/apache/sis/internal/storage/folder/StoreTest.java
 [UTF-8] (original)
+++ 
sis/branches/JDK8/storage/sis-storage/src/test/java/org/apache/sis/internal/storage/folder/StoreTest.java
 [UTF-8] Thu Feb 22 22:18:05 2018
@@ -91,7 +91,7 @@ public final strictfp class StoreTest ex
         final Set<String> identifiers = new HashSet<>(Arrays.asList("Sample 
1", "Sample 2", "Sample 3", "data4"));
         final Parameters params = 
Parameters.castOrWrap(FolderStoreProvider.PARAMETERS.createValue());
         params.parameter("location").setValue(testDirectory());
-        params.parameter("provider").setValue("XML");
+        params.parameter("format").setValue("XML");
         try (Store store = (Store) FolderStoreProvider.INSTANCE.open(params)) {
             assertEquals("Expected three data stores.", 3, 
store.components().size());
             verifyContent(store, identifiers);


Reply via email to