Fix for FileSystem blob store clearContainer with options
diff --git a/apis/filesystem/src/main/java/org/jclouds/filesystem/strategy/internal/FilesystemStorageStrategyImpl.java b/apis/filesystem/src/main/java/org/jclouds/filesystem/strategy/internal/FilesystemStorageStrategyImpl.java
index b557845..a9f0a9c 100644
--- a/apis/filesystem/src/main/java/org/jclouds/filesystem/strategy/internal/FilesystemStorageStrategyImpl.java
+++ b/apis/filesystem/src/main/java/org/jclouds/filesystem/strategy/internal/FilesystemStorageStrategyImpl.java
@@ -27,6 +27,7 @@
 import static java.nio.file.Files.probeContentType;
 import static java.nio.file.Files.readAttributes;
 import static java.nio.file.Files.setPosixFilePermissions;
+import static java.nio.file.Files.newDirectoryStream;
 import static org.jclouds.filesystem.util.Utils.delete;
 import static org.jclouds.filesystem.util.Utils.isPrivate;
 import static org.jclouds.filesystem.util.Utils.isWindows;
@@ -41,6 +42,7 @@
 import java.nio.ByteBuffer;
 import java.nio.charset.StandardCharsets;
 import java.nio.file.AccessDeniedException;
+import java.nio.file.DirectoryStream;
 import java.nio.file.NoSuchFileException;
 import java.nio.file.Path;
 import java.nio.file.attribute.BasicFileAttributes;
@@ -58,6 +60,7 @@
 import javax.inject.Named;
 import javax.inject.Provider;
 
+import com.google.common.base.Strings;
 import org.jclouds.blobstore.ContainerNotFoundException;
 import org.jclouds.blobstore.KeyNotFoundException;
 import org.jclouds.blobstore.LocalStorageStrategy;
@@ -253,16 +256,45 @@
    @Override
    public void clearContainer(String container, ListContainerOptions options) {
       filesystemContainerNameValidator.validate(container);
-      // TODO: these require calling removeDirectoriesTreeOfBlobKey
-      checkArgument(options.getDir() == null && options.getPrefix() == null, "cannot specify directory or prefix");
+      checkArgument(options.getDir() == null || options.getPrefix() == null, "cannot specify both directory and prefix");
+      String optsPrefix = Strings.nullToEmpty(options.getDir() == null ? options.getPrefix() : options.getDir());
+      String normalizedOptsPath = normalize(optsPrefix);
+      String basePath = buildPathStartingFromBaseDir(container, normalizedOptsPath);
+      filesystemBlobKeyValidator.validate(basePath);
       try {
-         File containerFile = openFolder(container);
-         File[] children = containerFile.listFiles();
-         if (null != children) {
-            for (File child : children)
-               if (options.isRecursive() || child.isFile()) {
-                  Utils.deleteRecursively(child);
+         File object = new File(basePath);
+         if (object.isFile()) {
+            // To mimic the S3 type blobstores, a prefix for an object blob
+            // should also get deleted
+            delete(object);
+         }
+         else if (object.isDirectory() && (optsPrefix.endsWith(File.separator) || isNullOrEmpty(optsPrefix))) {
+            // S3 blobstores will only match prefixes that end with a trailing slash/file separator
+            // For instance, if we have a blob at /path/1/2/a, a prefix of /path/1/2 will not list /path/1/2/a
+            // but a prefix of /path/1/2/ will
+            File containerFile = openFolder(container + File.separator + normalizedOptsPath);
+            File[] children = containerFile.listFiles();
+            if (null != children) {
+               for (File child : children) {
+                  if (options.isRecursive()) {
+                     Utils.deleteRecursively(child);
+                  } else {
+                     if (child.isFile()) {
+                        Utils.delete(child);
+                     }
+                  }
                }
+            }
+
+            // Empty dirs in path if they don't have any objects
+            if (!optsPrefix.isEmpty()) {
+               if (options.isRecursive()) {
+                  //first, remove the empty dir. It should be totally empty if it was a
+                  // recursive delete
+                  deleteDirectory(container, optsPrefix);
+               }
+               removeDirectoriesTreeOfBlobKey(container, optsPrefix);
+            }
          }
       } catch (IOException e) {
          logger.error(e, "An error occurred while clearing container %s", container);
@@ -859,6 +891,18 @@
    }
 
    /**
+    * Checks if a directory is empty using a DirectoryStream iterator
+    *
+    * @param directoryPath
+    */
+   private boolean isDirEmpty(String directoryPath) throws IOException {
+      Path path = new File(directoryPath).toPath();
+      try (DirectoryStream<Path> dirStream = newDirectoryStream(path)) {
+         return !dirStream.iterator().hasNext();
+      }
+   }
+
+   /**
     * Removes recursively the directory structure of a complex blob key, only if the directory is
     * empty
     *
@@ -889,16 +933,21 @@
             logger.debug("Could not look for attributes from %s: %s", directory, e);
          }
 
-         String[] children = directory.list();
-         if (null == children || children.length == 0) {
-            try {
-               delete(directory);
-            } catch (IOException e) {
-               logger.debug("Could not delete %s: %s", directory, e);
-               return;
+         // Don't need to do a listing on the dir, which could be costly. The iterator should be more performant.
+         try {
+            if (isDirEmpty(directory.getPath())) {
+               try {
+                  delete(directory);
+               } catch (IOException e) {
+                  logger.debug("Could not delete %s: %s", directory, e);
+                  return;
+               }
+               // recursively call for removing other path
+               removeDirectoriesTreeOfBlobKey(container, parentPath);
             }
-            // recursively call for removing other path
-            removeDirectoriesTreeOfBlobKey(container, parentPath);
+         } catch (IOException e) {
+            logger.debug("Could not locate directory %s", directory, e);
+            return;
          }
       }
    }
diff --git a/apis/filesystem/src/test/java/org/jclouds/filesystem/integration/FilesystemContainerIntegrationTest.java b/apis/filesystem/src/test/java/org/jclouds/filesystem/integration/FilesystemContainerIntegrationTest.java
index c9fee73..45f88d0 100644
--- a/apis/filesystem/src/test/java/org/jclouds/filesystem/integration/FilesystemContainerIntegrationTest.java
+++ b/apis/filesystem/src/test/java/org/jclouds/filesystem/integration/FilesystemContainerIntegrationTest.java
@@ -194,8 +194,4 @@
       throw new SkipException("filesystem does not support anonymous access");
    }
 
-   @Override
-   public void testClearWithOptions() throws InterruptedException {
-      throw new SkipException("filesystem does not support clear with options");
-   }
 }
diff --git a/blobstore/src/test/java/org/jclouds/blobstore/integration/internal/BaseContainerIntegrationTest.java b/blobstore/src/test/java/org/jclouds/blobstore/integration/internal/BaseContainerIntegrationTest.java
index 6364c98..0c7b503 100644
--- a/blobstore/src/test/java/org/jclouds/blobstore/integration/internal/BaseContainerIntegrationTest.java
+++ b/blobstore/src/test/java/org/jclouds/blobstore/integration/internal/BaseContainerIntegrationTest.java
@@ -182,6 +182,8 @@
       try {
          ListContainerOptions options;
 
+         // Should wipe out all objects, as there are empty folders
+         // above
          add5NestedBlobsToContainer(containerName);
          options = new ListContainerOptions();
          options.prefix("path/1/");
@@ -195,7 +197,9 @@
          options.prefix("path/1/2/3");
          options.recursive();
          view.getBlobStore().clearContainer(containerName, options);
-         assertConsistencyAwareContainerSize(containerName, 2);
+         assertConsistencyAwareBlobExists(containerName, "path/1/a");
+         assertConsistencyAwareBlobExists(containerName, "path/1/2/b");
+         assertConsistencyAwareBlobDoesntExist(containerName, "path/1/2/3");
 
          view.getBlobStore().clearContainer(containerName);
          add5NestedBlobsToContainer(containerName);
@@ -203,7 +207,10 @@
          options.prefix("path/1/2/3/4/");
          options.recursive();
          view.getBlobStore().clearContainer(containerName, options);
-         assertConsistencyAwareContainerSize(containerName, 4);
+         assertConsistencyAwareBlobExists(containerName, "path/1/a");
+         assertConsistencyAwareBlobExists(containerName, "path/1/2/b");
+         assertConsistencyAwareBlobExists(containerName, "path/1/2/3/5/e");
+         assertConsistencyAwareBlobDoesntExist(containerName, "path/1/2/3/4");
 
          // non-recursive, should not clear anything, as prefix does not match
          view.getBlobStore().clearContainer(containerName);
@@ -211,7 +218,11 @@
          options = new ListContainerOptions();
          options.prefix("path/1/2/3");
          view.getBlobStore().clearContainer(containerName, options);
-         assertConsistencyAwareContainerSize(containerName, 5);
+         assertConsistencyAwareBlobExists(containerName, "path/1/a");
+         assertConsistencyAwareBlobExists(containerName, "path/1/2/b");
+         assertConsistencyAwareBlobExists(containerName, "path/1/2/3/c");
+         assertConsistencyAwareBlobExists(containerName, "path/1/2/3/5/e");
+
 
          // non-recursive, should only clear path/1/2/3/c
          view.getBlobStore().clearContainer(containerName);
@@ -219,7 +230,10 @@
          options = new ListContainerOptions();
          options.prefix("path/1/2/3/");
          view.getBlobStore().clearContainer(containerName, options);
-         assertConsistencyAwareContainerSize(containerName, 4);
+         assertConsistencyAwareBlobExists(containerName, "path/1/a");
+         assertConsistencyAwareBlobExists(containerName, "path/1/2/b");
+         assertConsistencyAwareBlobExists(containerName, "path/1/2/3/4/d");
+         assertConsistencyAwareBlobDoesntExist(containerName, "path/1/2/3/c");
 
          // non-recursive, should only clear path/1/2/3/c
          view.getBlobStore().clearContainer(containerName);
@@ -227,7 +241,10 @@
          options = new ListContainerOptions();
          options.prefix("path/1/2/3/c");
          view.getBlobStore().clearContainer(containerName, options);
-         assertConsistencyAwareContainerSize(containerName, 4);
+         assertConsistencyAwareBlobExists(containerName, "path/1/a");
+         assertConsistencyAwareBlobExists(containerName, "path/1/2/b");
+         assertConsistencyAwareBlobExists(containerName, "path/1/2/3/4/d");
+         assertConsistencyAwareBlobDoesntExist(containerName, "path/1/2/3/c");
       } finally {
          returnContainer(containerName);
       }