This is an automated email from the ASF dual-hosted git repository.

desruisseaux pushed a commit to branch geoapi-4.0
in repository https://gitbox.apache.org/repos/asf/sis.git

commit 520c658428ead2e79a5023c2031067366e259c35
Author: Martin Desruisseaux <martin.desruisse...@geomatys.com>
AuthorDate: Fri May 6 16:37:54 2022 +0200

    Make the name separator configurable when using a S3 storage service.
---
 .../apache/sis/cloud/aws/s3/ClientFileSystem.java  |  40 +++++--
 .../org/apache/sis/cloud/aws/s3/FileService.java   |  14 ++-
 .../java/org/apache/sis/cloud/aws/s3/KeyPath.java  | 115 +++++++++++----------
 .../apache/sis/cloud/aws/s3/KeyPathMatcher.java    |  22 +++-
 .../org/apache/sis/cloud/aws/s3/PathIterator.java  |  12 ++-
 .../sis/cloud/aws/s3/ClientFileSystemTest.java     |   4 +-
 .../sis/cloud/aws/s3/KeyPathMatcherTest.java       |   2 +-
 7 files changed, 130 insertions(+), 79 deletions(-)

diff --git 
a/cloud/sis-cloud-S3/src/main/java/org/apache/sis/cloud/aws/s3/ClientFileSystem.java
 
b/cloud/sis-cloud-S3/src/main/java/org/apache/sis/cloud/aws/s3/ClientFileSystem.java
index 5aff968e12..4de328d2ef 100644
--- 
a/cloud/sis-cloud-S3/src/main/java/org/apache/sis/cloud/aws/s3/ClientFileSystem.java
+++ 
b/cloud/sis-cloud-S3/src/main/java/org/apache/sis/cloud/aws/s3/ClientFileSystem.java
@@ -31,6 +31,7 @@ import java.nio.file.attribute.BasicFileAttributeView;
 import java.nio.file.attribute.UserPrincipalLookupService;
 import java.nio.file.spi.FileSystemProvider;
 import org.apache.sis.internal.util.Strings;
+import org.apache.sis.util.ArgumentChecks;
 import software.amazon.awssdk.regions.Region;
 import software.amazon.awssdk.services.s3.S3Client;
 import software.amazon.awssdk.services.s3.S3ClientBuilder;
@@ -49,6 +50,11 @@ import 
software.amazon.awssdk.auth.credentials.StaticCredentialsProvider;
  * @module
  */
 final class ClientFileSystem extends FileSystem {
+    /**
+     * The default separator.
+     */
+    static final String DEFAULT_SEPARATOR = "/";
+
     /**
      * The AWS S3 access key, or {@code null} if none.
      * Also used as key of this file system in the {@link 
FileService#fileSystems} map.
@@ -67,12 +73,25 @@ final class ClientFileSystem extends FileSystem {
     private volatile S3Client client;
 
     /**
-     * Creates a file system with default credential.
+     * The character used as a separator in path component.
+     * This is usually "/".
+     */
+    final String separator;
+
+    /**
+     * The {@link #separator} repeated twice. Used for detecting empty paths.
+     */
+    final String duplicatedSeparator;
+
+    /**
+     * Creates a file system with default credential and default separator.
      */
     ClientFileSystem(final FileService provider, final S3Client client) {
         this.provider  = provider;
         this.client    = client;
         this.accessKey = null;
+        this.separator = DEFAULT_SEPARATOR;
+        duplicatedSeparator = DEFAULT_SEPARATOR + DEFAULT_SEPARATOR;
     }
 
     /**
@@ -81,9 +100,16 @@ final class ClientFileSystem extends FileSystem {
      * @param provider    the provider creating this file system.
      * @param region      the AWS region, or {@code null} for default.
      * @param accessKey   the AWS S3 access key for this file system.
-     * @param properties  properties to configure the file system, or {@code 
null} if none.
+     * @param secret      the password.
+     * @param separator   the separator in paths, or {@code null} for the 
default value.
      */
-    ClientFileSystem(final FileService provider, final Region region, final 
String accessKey, final String secret) {
+    ClientFileSystem(final FileService provider, final Region region, final 
String accessKey, final String secret,
+                     String separator)
+    {
+        if (separator == null) {
+            separator = DEFAULT_SEPARATOR;
+        }
+        ArgumentChecks.ensureNonEmpty("separator", separator);
         this.provider  = provider;
         this.accessKey = accessKey;
         S3ClientBuilder builder = S3Client.builder().credentialsProvider(
@@ -92,6 +118,8 @@ final class ClientFileSystem extends FileSystem {
             builder = builder.region(region);
         }
         client = builder.build();
+        this.separator = separator;
+        duplicatedSeparator = separator.concat(separator);
     }
 
     /**
@@ -150,12 +178,10 @@ final class ClientFileSystem extends FileSystem {
 
     /**
      * Returns the name separator used to separate names in a path string.
-     *
-     * @see KeyPath#SEPARATOR
      */
     @Override
     public String getSeparator() {
-        return "/";
+        return separator;
     }
 
     /**
@@ -217,7 +243,7 @@ final class ClientFileSystem extends FileSystem {
      */
     @Override
     public PathMatcher getPathMatcher(final String syntaxAndPattern) {
-        return new KeyPathMatcher(syntaxAndPattern);
+        return new KeyPathMatcher(syntaxAndPattern, separator);
     }
 
     /**
diff --git 
a/cloud/sis-cloud-S3/src/main/java/org/apache/sis/cloud/aws/s3/FileService.java 
b/cloud/sis-cloud-S3/src/main/java/org/apache/sis/cloud/aws/s3/FileService.java
index fad0b0a1f8..2a0700fd84 100644
--- 
a/cloud/sis-cloud-S3/src/main/java/org/apache/sis/cloud/aws/s3/FileService.java
+++ 
b/cloud/sis-cloud-S3/src/main/java/org/apache/sis/cloud/aws/s3/FileService.java
@@ -121,6 +121,15 @@ public class FileService extends FileSystemProvider {
      */
     public static final String AWS_REGION = "aws.region";
 
+    /**
+     * The property for the name-separator characters.
+     * The default value is "/" for simulating Unix paths.
+     * The separator must contain at least one character.
+     * They usually have only one character, but longer separators are 
accepted.
+     * The separator can contain any characters which are valid in a S3 object 
name.
+     */
+    public static final String SEPARATOR = "separator";
+
     /**
      * All file systems created by this provider. Keys are AWS S3 access keys.
      */
@@ -199,6 +208,7 @@ public class FileService extends FileSystemProvider {
         if (accessKey == null || (secret = Containers.property(properties, 
AWS_SECRET_ACCESS_KEY, String.class)) == null) {
             throw new 
IllegalArgumentException(Resources.format(Resources.Keys.MissingAccessKey_2, 
(accessKey == null) ? 0 : 1, uri));
         }
+        final String separator = Containers.property(properties, SEPARATOR, 
String.class);
         final Region region = Containers.property(properties, AWS_REGION, 
Region.class);
         final class Creator implements Function<String, ClientFileSystem> {
             /** Identifies if a new file system is created. */ boolean created;
@@ -206,7 +216,7 @@ public class FileService extends FileSystemProvider {
             /** Invoked if the map does not already contains the file system. 
*/
             @Override public ClientFileSystem apply(final String key) {
                 created = true;
-                return new ClientFileSystem(FileService.this, region, key, 
secret);
+                return new ClientFileSystem(FileService.this, region, key, 
secret, separator);
             }
         }
         final Creator c = new Creator();
@@ -278,7 +288,7 @@ public class FileService extends FileSystemProvider {
             fs = getDefaultFileSystem();
         } else {
             // TODO: we may need a way to get password here.
-            fs = fileSystems.computeIfAbsent(accessKey, (key) -> new 
ClientFileSystem(FileService.this, null, key, null));
+            fs = fileSystems.computeIfAbsent(accessKey, (key) -> new 
ClientFileSystem(FileService.this, null, key, null, null));
         }
         return new KeyPath(fs, uri.getHost(), new String[] {uri.getPath()}, 
true);
     }
diff --git 
a/cloud/sis-cloud-S3/src/main/java/org/apache/sis/cloud/aws/s3/KeyPath.java 
b/cloud/sis-cloud-S3/src/main/java/org/apache/sis/cloud/aws/s3/KeyPath.java
index 6162fc3868..907dad7407 100644
--- a/cloud/sis-cloud-S3/src/main/java/org/apache/sis/cloud/aws/s3/KeyPath.java
+++ b/cloud/sis-cloud-S3/src/main/java/org/apache/sis/cloud/aws/s3/KeyPath.java
@@ -44,8 +44,8 @@ import 
software.amazon.awssdk.services.s3.model.ListObjectsV2Request;
  *
  * <p>AWS S3 has no concept of file directories.
  * Instead a bucket is more like a big {@link java.util.HashMap} with 
arbitrary {@link String} keys.
- * Those keys may contain the {@value #SEPARATOR} character, but AWS S3 gives 
no special meaning to it.
- * The interpretation of {@value #SEPARATOR} as a path separator is done by 
this class.</p>
+ * Those keys may contain the {@code "/"} character, but AWS S3 gives no 
special meaning to it.
+ * The interpretation of {@link ClientFileSystem#separator} as a path 
separator is done by this class.</p>
  *
  * @author  Martin Desruisseaux (Geomatys)
  * @version 1.2
@@ -68,13 +68,6 @@ final class KeyPath implements Path {
      */
     private static final int SCHEME_LENGTH = SCHEME.length() + 
SCHEME_SEPARATOR.length();
 
-    /**
-     * The character used as a separator in path component.
-     *
-     * @see ClientFileSystem#getSeparator()
-     */
-    static final char SEPARATOR = '/';
-
     /**
      * The file system that created this path. This file system gives access 
to the
      * {@link software.amazon.awssdk.services.s3.S3Client} instance from AWS 
SDK.
@@ -93,8 +86,8 @@ final class KeyPath implements Path {
 
     /**
      * The key for locating the S3 object (shall not be empty), or {@code 
null} if this path is the root.
-     * If the key contains {@value #SEPARATOR} character, it will be 
interpreted as a list of path components.
-     * However that character has no special meaning for S3; this is an 
interpretation added by this wrapper.
+     * If the key contains {@link ClientFileSystem#separator}, it will be 
interpreted as a list of path components.
+     * However the separator characters have no special meaning for S3; this 
is an interpretation added by this wrapper.
      */
     final String key;
 
@@ -142,8 +135,9 @@ final class KeyPath implements Path {
      * Creates an absolute path for an object in the S3 storage.
      * This is used when iterating over the files in a pseudo-directory.
      *
-     * <p>When using this constructor, it may happen that the path ends with 
the {@value #SEPARATOR} character
-     * if the S3 object really has that name, and still have the {@link 
#isDirectory} flag set to {@code false}.
+     * <p>When using this constructor, it may happen that the path ends with 
the {@link ClientFileSystem#separator}
+     * character if the S3 object really has that name, and this {@code 
KeyPath} still have the {@link #isDirectory}
+     * flag set to {@code false}.
      * We keep it that way because it describes what is really on the S3 file 
system, even if confusing.</p>
      *
      * @param root      a path from which to inherit the file system and the 
root.
@@ -193,8 +187,8 @@ final class KeyPath implements Path {
     /**
      * Creates a new path by parsing the given components.
      * This is used for creating from character strings or URI.
-     * This constructor accepts the following strings,
-     * where {@code key} can be a path with {@value #SEPARATOR} separators:
+     * This constructor accepts the following strings, where {@code key} can 
be a path
+     * with any number of occurrences of the {@link 
ClientFileSystem#separator} separator:
      *
      * <ul>
      *   <li>{@code "S3://bucket/key"} (note that {@code "accessKey@bucket"} 
is not accepted)</li>
@@ -226,7 +220,7 @@ final class KeyPath implements Path {
             isAbsolute = true;
         } else if (first.isEmpty()) {
             throw emptyPath(first, 0);
-        } else if (first.charAt(0) == SEPARATOR) {
+        } else if (first.startsWith(fs.separator)) {
             isAbsolute = true;
             start = 1;
         }
@@ -236,21 +230,22 @@ final class KeyPath implements Path {
          * if and only if the path is absolute. The bucket name is the string 
before the first '/' character
          * and everything else is the S3 object key. The "everything else" is 
stored in the `StringBuilder`.
          */
+        final int separatorLength = fs.separator.length();
         StringBuilder buffer = null;
         if (isAbsolute) {
-            int end = first.indexOf(SEPARATOR, start);
+            int end = first.indexOf(fs.separator, start);
             if (end < 0) {
                 bucket = first.substring(start);
             } else {
-                bucket = first.substring(start, end);      // Take characters 
before the first '/'.
-                start  = end + 1;                          // Remaining path 
after the first '/'.
+                bucket = first.substring(start, end);       // Take characters 
before the first '/'.
+                start  = end + separatorLength;             // Remaining path 
after the first '/'.
                 end    = first.length();
                 while (start < end) {
-                    if (first.charAt(end - 1) != SEPARATOR) {
+                    if (!first.startsWith(fs.separator, end - 
separatorLength)) {
                         buffer = new StringBuilder(end - start).append(first, 
start, end);
                         break;
                     }
-                    end--;              // Skip trailing '/' characters.
+                    end -= separatorLength;                 // Skip trailing 
'/' characters.
                 }
             }
             start = first.length();     // Tells that there is nothing more 
from `start` to append.
@@ -268,12 +263,12 @@ final class KeyPath implements Path {
             if (more.length == 0) {
                 String path = first;
                 int end = path.length();
-                do path = path.replace("//", "/");
+                do path = path.replace(fs.duplicatedSeparator, fs.separator);
                 while (end > (end = path.length()));
-                if (end == 0) {
+                if ((end -= separatorLength) < 0) {
                     throw emptyPath(first, 0);
                 }
-                isDirectory = (path.charAt(--end) == SEPARATOR);
+                isDirectory = path.endsWith(fs.separator);
                 key = isDirectory ? path.substring(0, end) : path;
                 return;
             }
@@ -286,12 +281,12 @@ final class KeyPath implements Path {
         for (final String component : more) {
             final int end = component.length();
             for (int i=0; i<end; i++) {
-                if (component.charAt(i) != SEPARATOR) {
+                if (!component.startsWith(fs.separator, i)) {
                     if (buffer == null) {
                         buffer = new StringBuilder(first.substring(start));
                     }
                     if (buffer.length() != 0) {
-                        buffer.append(SEPARATOR);
+                        buffer.append(fs.separator);
                     }
                     buffer.append(component, i, end);
                     break;
@@ -306,12 +301,12 @@ final class KeyPath implements Path {
          */
         if (buffer != null) {
             int i = buffer.length();
-            while ((i = buffer.lastIndexOf("//", i)) >= 0) {
-                buffer.deleteCharAt(i + 1);
+            while ((i = buffer.lastIndexOf(fs.duplicatedSeparator, i)) >= 0) {
+                buffer.delete(i, i + separatorLength);
             }
-            i = buffer.length() - 1;
+            i = buffer.length() - separatorLength;
             if (i >= 0) {
-                isDirectory = (buffer.charAt(i) == SEPARATOR);
+                isDirectory = CharSequences.regionMatches(buffer, i, 
fs.separator);
                 if (isDirectory) {
                     if (i == 0) {
                         if (bucket == null) {
@@ -385,7 +380,7 @@ final class KeyPath implements Path {
     final ListObjectsV2Request.Builder request() {
         ListObjectsV2Request.Builder request = 
ListObjectsV2Request.builder().bucket(bucket).delimiter(fs.getSeparator());
         if (key != null) {
-            request = request.prefix(isDirectory ? (key + KeyPath.SEPARATOR) : 
key);
+            request = request.prefix(isDirectory ? key.concat(fs.separator) : 
key);
         }
         return request;
     }
@@ -425,8 +420,9 @@ final class KeyPath implements Path {
     @Override
     public Path getFileName() {
         if (key != null) {
-            final String name = key.substring(key.lastIndexOf(SEPARATOR) + 1);
-            if (bucket != null || !key.equals(name)) {
+            final int i = key.lastIndexOf(fs.separator);
+            if (bucket != null || i >= 0) {
+                final String name = (i >= 0) ? key.substring(i + 
fs.separator.length()) : key;
                 return new KeyPath(fs, name, isDirectory);
             }
         }
@@ -441,7 +437,7 @@ final class KeyPath implements Path {
     @Override
     public Path getParent() {
         if (key != null) {
-            final int i = key.lastIndexOf(SEPARATOR);
+            final int i = key.lastIndexOf(fs.separator);
             if (i >= 0) {
                 return new KeyPath(this, key.substring(0, i), true);
             }
@@ -455,7 +451,7 @@ final class KeyPath implements Path {
      */
     @Override
     public int getNameCount() {
-        int count = CharSequences.count(key, SEPARATOR);
+        int count = CharSequences.count(key, fs.separator);
         if (key    != null) count++;
         if (bucket != null) count++;
         return count;
@@ -480,7 +476,7 @@ final class KeyPath implements Path {
      */
     @Override
     public Path subpath(final int beginIndex, final int endIndex) {
-        int count  = endIndex - beginIndex;
+        int count = endIndex - beginIndex;
         if (beginIndex >= 0 && count > 0) {
             int skip = beginIndex;
             boolean includeRoot = false;
@@ -494,23 +490,28 @@ final class KeyPath implements Path {
                 }
                 skip--;
             }
+            final int separatorLength = fs.separator.length();
 search:     if (key != null) {
-                int offset = 0;
+                int start = 0;
                 while (--skip >= 0) {
-                    offset = key.indexOf(SEPARATOR, offset) + 1;
-                    if (offset == 0) break search;
+                    start = key.indexOf(fs.separator, start);
+                    if (start < 0) break search;
+                    start += separatorLength;
                 }
-                int stop = offset;
-                while (--count >= 0) {
-                    stop = key.indexOf(SEPARATOR, stop + 1);
-                    if (stop < 0 && count != 0) break search;
+                int stop = start;
+                if (--count >= 0) {
+                    stop = key.indexOf(fs.separator, start);
+                    while (--count >= 0) {
+                        if (stop < 0) break search;
+                        stop = key.indexOf(fs.separator, stop + 
separatorLength);
+                    }
                 }
                 boolean isParent = (stop >= 0);
                 if (!isParent && beginIndex == 0) {
                     assert endIndex == getNameCount() : endIndex;
                     return this;
                 }
-                final String part = isParent ? key.substring(offset, stop) : 
key.substring(offset);
+                final String part = isParent ? key.substring(start, stop) : 
key.substring(start);
                 isParent |= isDirectory;
                 if (includeRoot) {
                     return new KeyPath(this, part, isParent);
@@ -554,14 +555,14 @@ search:     if (key != null) {
                             case STARTS_WITH: {
                                 if (key.startsWith(part.key)) {
                                     final int end = part.key.length();
-                                    return end == key.length() || 
key.charAt(end) == SEPARATOR;
+                                    return end == key.length() || 
key.startsWith(fs.separator, end);
                                 }
                                 break;
                             }
                             case ENDS_WITH: {
                                 if (key.endsWith(part.key)) {
                                     final int start = key.length() - 
part.key.length();
-                                    return start == 0 || key.charAt(start - 1) 
== SEPARATOR;
+                                    return start == 0 || 
key.startsWith(fs.separator, start - fs.separator.length());
                                 }
                                 break;
                             }
@@ -620,7 +621,7 @@ search:     if (key != null) {
     }
 
     /**
-     * Resolve the given path against this path. If the given path is 
absolute, then it is returned.
+     * Resolves the given path against this path. If the given path is 
absolute, then it is returned.
      * Otherwise this method returns the concatenation of this path with the 
given path.
      */
     @Override
@@ -634,10 +635,10 @@ search:     if (key != null) {
         }
         part = part.replace(other.getFileSystem().getSeparator(), 
fs.getSeparator());
         if (key != null) {      // If non-null, shall not be empty.
-            if (key.charAt(key.length() - 1) == SEPARATOR || part.charAt(0) == 
SEPARATOR) {
+            if (key.endsWith(fs.separator) || part.startsWith(fs.separator)) {
                 part = key + part;
             } else {
-                part = key + SEPARATOR + part;
+                part = key + fs.separator + part;
             }
         }
         return new KeyPath(this, part, (other instanceof KeyPath) && 
((KeyPath) other).isDirectory);
@@ -681,7 +682,7 @@ search:     if (key != null) {
         if (other instanceof KeyPath) {
             final KeyPath kp = (KeyPath) other;
             if (kp.startsWith(this) && key != null) {
-                final String suffix = kp.key.substring(key.length() + 1);
+                final String suffix = kp.key.substring(key.length() + 
fs.separator.length());
                 if (!suffix.isEmpty()) {
                     return new KeyPath(kp.fs, suffix, kp.isDirectory);
                 }
@@ -715,7 +716,7 @@ search:     if (key != null) {
 
     /**
      * Returns a string representation of this path.
-     * By convention, a trailing {@value #SEPARATOR} is appended to 
directories.
+     * By convention, a trailing {@link ClientFileSystem#separator} is 
appended to directories.
      */
     @Override
     public String toString() {
@@ -728,12 +729,12 @@ search:     if (key != null) {
             if (fs.accessKey != null) {
                 sb.append(fs.accessKey).append('@');
             }
-            sb.append(bucket).append(SEPARATOR);
+            sb.append(bucket).append(fs.separator);
         }
         if (key != null) {
             sb.append(key);
             if (isDirectory) {
-                sb.append(SEPARATOR);
+                sb.append(fs.separator);
             }
         }
         return sb.toString();
@@ -813,7 +814,7 @@ search:     if (key != null) {
          */
         Iter() {
             if (bucket == null) {
-                end = key.indexOf(SEPARATOR);       // If `bucket` is null, 
then `key` shall be non-null.
+                end = key.indexOf(fs.separator);        // If `bucket` is 
null, then `key` shall be non-null.
             }
         }
 
@@ -831,7 +832,7 @@ search:     if (key != null) {
             if (end == 0) {
                 // Set `start` and `end` to the values needed for next element 
(after this one).
                 if (key != null) {
-                    end = key.indexOf(SEPARATOR);
+                    end = key.indexOf(fs.separator);
                 } else {
                     start = -1;
                 }
@@ -843,8 +844,8 @@ search:     if (key != null) {
                 if (end >= 0) {
                     name  = key.substring(start, end);
                     dir   = true;
-                    start = end + 1;
-                    end   = key.indexOf(SEPARATOR, start);
+                    start = end + fs.separator.length();
+                    end   = key.indexOf(fs.separator, start);
                 } else {
                     name  = key.substring(start);
                     dir   = isDirectory;
diff --git 
a/cloud/sis-cloud-S3/src/main/java/org/apache/sis/cloud/aws/s3/KeyPathMatcher.java
 
b/cloud/sis-cloud-S3/src/main/java/org/apache/sis/cloud/aws/s3/KeyPathMatcher.java
index 0504c2ff9b..46a2ec3128 100644
--- 
a/cloud/sis-cloud-S3/src/main/java/org/apache/sis/cloud/aws/s3/KeyPathMatcher.java
+++ 
b/cloud/sis-cloud-S3/src/main/java/org/apache/sis/cloud/aws/s3/KeyPathMatcher.java
@@ -46,10 +46,11 @@ final class KeyPathMatcher implements PathMatcher {
      * Creates a new matcher.
      *
      * @param  syntaxAndPattern  filtering criteria of the {@code 
syntax:pattern}.
+     * @param  separator         the {@link ClientFileSystem#separator} value.
      * @throws PatternSyntaxException if the pattern is invalid.
      * @throws UnsupportedOperationException if the pattern syntax is not 
known to this implementation.
      */
-    KeyPathMatcher(final String syntaxAndPattern) {
+    KeyPathMatcher(final String syntaxAndPattern, final String separator) {
         final int s = syntaxAndPattern.indexOf(':');
         if (s < 0) {
             throw new IllegalArgumentException();
@@ -85,7 +86,13 @@ loop:       for (int i = s; ++i < length;) {
                         final boolean multi = (i+1 < length && 
syntaxAndPattern.charAt(i+1) == '*');
                         if (multi) i++;
                         if (quote) sb.append("\\E");
-                        sb.append(multi ? ".*" : "[^" + KeyPath.SEPARATOR + 
"]*");
+                        if (multi) {
+                            sb.append(".*");
+                        } else if (separator.length() == 1) {
+                            sb.append("[^").append(separator).append("]*");
+                        } else {
+                            throw unsupportedSyntax(syntaxAndPattern, s);
+                        }
                         quote = false;
                         continue;
                     }
@@ -96,12 +103,19 @@ loop:       for (int i = s; ++i < length;) {
             if (quote) sb.append("\\E");
             p = sb.toString();
         } else {
-            throw new UnsupportedOperationException(Errors.format(
-                    Errors.Keys.IllegalArgumentValue_2, "syntax", 
syntaxAndPattern.substring(0, s)));
+            throw unsupportedSyntax(syntaxAndPattern, s);
         }
         pattern = Pattern.compile(p);
     }
 
+    /**
+     * Returns the exception to throw for an unsupported syntax.
+     */
+    private static UnsupportedOperationException unsupportedSyntax(final 
String syntaxAndPattern, final int s) {
+        return new UnsupportedOperationException(Errors.format(
+                Errors.Keys.IllegalArgumentValue_2, "syntax", 
syntaxAndPattern.substring(0, s)));
+    }
+
     /**
      * Tells if given path matches the regular expression pattern.
      */
diff --git 
a/cloud/sis-cloud-S3/src/main/java/org/apache/sis/cloud/aws/s3/PathIterator.java
 
b/cloud/sis-cloud-S3/src/main/java/org/apache/sis/cloud/aws/s3/PathIterator.java
index bf10aecdc8..4298d55ccf 100644
--- 
a/cloud/sis-cloud-S3/src/main/java/org/apache/sis/cloud/aws/s3/PathIterator.java
+++ 
b/cloud/sis-cloud-S3/src/main/java/org/apache/sis/cloud/aws/s3/PathIterator.java
@@ -90,7 +90,7 @@ final class PathIterator implements DirectoryStream<Path>, 
Iterator<Path> {
         this.response  = 
directory.fs.client().listObjectsV2(directory.request().build());
         this.buffer    = new StringBuilder();
         if (directory.key != null) {
-            buffer.append(directory.key).append(KeyPath.SEPARATOR);
+            buffer.append(directory.key).append(directory.fs.separator);
         }
         parentLength = buffer.length();
     }
@@ -118,7 +118,7 @@ final class PathIterator implements DirectoryStream<Path>, 
Iterator<Path> {
             String path = directories.next().prefix();
             int length = path.length();
             if (length > 0) {
-                if (path.charAt(length - 1) == KeyPath.SEPARATOR) {
+                if (path.endsWith(directory.fs.separator)) {
                     if (--length == 0) {
                         continue;
                     }
@@ -185,10 +185,12 @@ verify:     do if (directories == null || 
!nextDirectory()) {
     private boolean accept() throws IOException {
         final String path = next.key;
         if (path != null) {
+            final String separator = directory.fs.separator;
+            final int separatorLength = separator.length();
             int last = path.length();
-            do if (--last < 0) return false;
-            while (path.charAt(last) == KeyPath.SEPARATOR);
-            if (path.regionMatches(0, directory.key, 0, last + 1)) {
+            do if ((last -= separatorLength) < 0) return false;
+            while (path.startsWith(separator, last));
+            if (path.regionMatches(0, directory.key, 0, last + 
separatorLength)) {
                 return false;
             }
         }
diff --git 
a/cloud/sis-cloud-S3/src/test/java/org/apache/sis/cloud/aws/s3/ClientFileSystemTest.java
 
b/cloud/sis-cloud-S3/src/test/java/org/apache/sis/cloud/aws/s3/ClientFileSystemTest.java
index 12c3691bce..ff766b647c 100644
--- 
a/cloud/sis-cloud-S3/src/test/java/org/apache/sis/cloud/aws/s3/ClientFileSystemTest.java
+++ 
b/cloud/sis-cloud-S3/src/test/java/org/apache/sis/cloud/aws/s3/ClientFileSystemTest.java
@@ -55,8 +55,6 @@ public final strictfp class ClientFileSystemTest extends 
TestCase {
      */
     @Test
     public void testGetSeparator() {
-        final String separator = fs.getSeparator();
-        assertEquals(1, separator.length());
-        assertEquals(KeyPath.SEPARATOR, separator.charAt(0));
+        assertEquals("/", fs.getSeparator());
     }
 }
diff --git 
a/cloud/sis-cloud-S3/src/test/java/org/apache/sis/cloud/aws/s3/KeyPathMatcherTest.java
 
b/cloud/sis-cloud-S3/src/test/java/org/apache/sis/cloud/aws/s3/KeyPathMatcherTest.java
index 2ff0915202..64dd7acae5 100644
--- 
a/cloud/sis-cloud-S3/src/test/java/org/apache/sis/cloud/aws/s3/KeyPathMatcherTest.java
+++ 
b/cloud/sis-cloud-S3/src/test/java/org/apache/sis/cloud/aws/s3/KeyPathMatcherTest.java
@@ -36,7 +36,7 @@ public final strictfp class KeyPathMatcherTest extends 
TestCase {
      */
     @Test
     public void testGlob() {
-        final KeyPathMatcher matcher = new 
KeyPathMatcher("glob:bar*foo/fuu/**/f?i");
+        final KeyPathMatcher matcher = new 
KeyPathMatcher("glob:bar*foo/fuu/**/f?i", ClientFileSystem.DEFAULT_SEPARATOR);
         final ClientFileSystem fs = ClientFileSystemTest.create();
         assertTrue (matcher.matches(new KeyPath(fs, 
"bar_skip_foo/fuu/d1/d2/d3/f_i", false)));
         assertFalse(matcher.matches(new KeyPath(fs, 
"bar_sk/p_foo/fuu/d1/d2/d3/f_i", false)));

Reply via email to