You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@sis.apache.org by de...@apache.org on 2022/05/06 17:58:26 UTC
[sis] 01/03: Make the name separator configurable when using a S3 storage service.
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 <ma...@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)));