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)));