software.amazon.nio.spi.s3.S3Path Maven / Gradle / Ivy
/*
* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
* SPDX-License-Identifier: Apache-2.0
*/
package software.amazon.nio.spi.s3;
import software.amazon.awssdk.services.s3.model.S3Object;
import java.io.File;
import java.io.IOError;
import java.net.URI;
import java.nio.file.*;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Objects;
import static java.nio.file.LinkOption.NOFOLLOW_LINKS;
public class S3Path implements Path {
public static final String PATH_SEPARATOR = "/";
private final S3FileSystem fileSystem;
private final PosixLikePathRepresentation pathRepresentation;
private S3Path(S3FileSystem fileSystem, PosixLikePathRepresentation pathRepresentation){
this.fileSystem = fileSystem;
this.pathRepresentation = pathRepresentation;
}
/**
* Construct a path using the same filesystem (bucket) as this path
*/
private S3Path from(String path){
return getPath(this.fileSystem, path);
}
/**
* Construct a path from an S3 object in the bucket represented by the filesystem
* @param fs the filesystem that holds (or will hold) the object represented by {@code s3Object}
* @param s3Object the object
* @return a new {@code S3Path}
*/
public static S3Path getPath(S3FileSystem fs, S3Object s3Object){
return getPath(fs, s3Object.key());
}
/**
* Construct a Path in the parent FileSystem using the POSIX style.
* The path string is assumed to follow the POSIX form
* with the "root" of the bucket being represented by "/". The supplied path should not
* be a URI. It should not start with the string "s3:". For example, if this S3FileSystem
* represents "{@code s3://my-bucket}" then "{@code s3://my-bucket/foo.txt}" should be addressed by the path
* "/foo.txt" or by a path relative to the current working directory following POSIX conventions.
* Further, although folders or directories don't technically exist in S3
* the presence of a directory is implicit if "{@code s3://my-bucket/someFolder/}" contains
* objects and the Path to this folder is therefore valid.
*
* This library DOES NOT support S3 Paths that are not compliant with POSIX conventions. For example,
* the URI {@code s3://my-bucket/../foo.txt} is legal in S3 but due to POSIX conventions it will be
* unreachable through this API due to the special meaning of the .. directory alias in POSIX.
*
* @param fsForBucket the filesystem for the bucket that holds this path
* @param first the path string or initial part of the path string, may not be null. It may not be empty unless more is also null has zero length
* @param more additional strings to be joined to form the path string
* @throws InvalidPathException if the Path cannot be constructed
* @return a new S3Path
*/
public static S3Path getPath(S3FileSystem fsForBucket, String first, String... more) {
if(fsForBucket == null) throw new IllegalArgumentException("The S3FileSystem may not be null");
if(first == null ){
throw new IllegalArgumentException("first element of the path may not be null");
}
first = first.trim();
if((first.isEmpty()) && !(more == null || more.length == 0)) throw new IllegalArgumentException("The first element of the path may not be empty when more exists");
if( first.startsWith(S3FileSystemProvider.SCHEME+":/")) {
first = first.replaceFirst(S3FileSystemProvider.SCHEME+":/", "");
}
return new S3Path(fsForBucket, PosixLikePathRepresentation.of(first, more));
}
/**
* Returns the file system that created this object.
*
* @return the file system that created this object
*/
@Override
public S3FileSystem getFileSystem() {
return fileSystem;
}
/**
* The name of the S3 bucket that represents the root ("/") of this Path
* @return the bucketName, equivalent to getFileSystem().bucketName()
*/
public String bucketName() {
return fileSystem.bucketName();
}
/**
* Tells whether this path is absolute.
*
* An absolute path is complete in that it doesn't need to be combined
* with other path information in order to locate a file.
*
* @return {@code true} if, and only if, this path is absolute
*/
@Override
public boolean isAbsolute() {
return pathRepresentation.isAbsolute();
}
/**
* Is the path inferred to be an S3 directory?
* @return true if the path can be inferrred to be a directory
*/
public boolean isDirectory() {
return pathRepresentation.isDirectory();
}
/**
* If the path is absolute then returns the root of the path (e.g. "/") otherwise {@code null}
*
* @return a path representing the root component of this path,
* or {@code null}
*/
@Override
public S3Path getRoot() {
return isAbsolute() ? new S3Path(fileSystem, PosixLikePathRepresentation.ROOT) : null;
}
/**
* Returns the name of the file or directory denoted by this path as a
* {@code Path} object. The file name is the farthest element from
* the root in the directory hierarchy.
*
* @return a path representing the name of the file or directory, or
* {@code null} if this path has zero elements
*/
@Override
public S3Path getFileName() {
final List elements = pathRepresentation.elements();
int size = elements.size();
if(size == 0) return null;
if(pathRepresentation.hasTrailingSeparator()) {
return from(elements.get(size -1) + PATH_SEPARATOR);
} else {
return from(elements.get(size -1));
}
}
/**
* Returns the parent path, or {@code null} if this path does not
* have a parent.
*
* The parent of this path object consists of this path's root
* component, if any, and each element in the path except for the
* farthest from the root in the directory hierarchy. This method
* does not access the file system; the path or its parent may not exist.
* Furthermore, this method does not eliminate special names such as "."
* and ".." that may be used in some implementations. On UNIX for example,
* the parent of "{@code /a/b/c}" is "{@code /a/b}", and the parent of
* {@code "x/y/.}" is "{@code x/y}". This method may be used with the {@link
* #normalize normalize} method, to eliminate redundant names, for cases where
* shell-like navigation is required.
*
*
If this path has one or more elements, and no root component, then
* this method is equivalent to evaluating the expression:
*
* subpath(0, getNameCount()-1);
*
*
* @return a path representing the path's parent
*/
@Override
public S3Path getParent() {
int size = pathRepresentation.elements().size();
if (this.equals(getRoot()) || size < 1) return null;
if (pathRepresentation.isAbsolute() && size == 1) return getRoot();
return subpath(0, getNameCount()-1);
}
/**
* Returns the number of name elements in the path.
*
* @return the number of elements in the path, or {@code 0} if this path
* only represents a root component
*/
@Override
public int getNameCount() {
return pathRepresentation.elements().size();
}
/**
* Returns a name element of this path as a {@code Path} object.
*
* The {@code index} parameter is the index of the name element to return.
* The element that is closest to the root in the directory hierarchy
* has the index {@code 0}. The element that is farthest from the root
* has the index {@link #getNameCount count}{@code -1}.
*
* @param index the index of the element
* @return the name element
* @throws IllegalArgumentException if {@code index} is negative, {@code index} is greater than or
* equal to the number of elements, or this path has zero name
* elements
*/
@Override
public S3Path getName(int index) {
final List elements = pathRepresentation.elements();
if(index < 0 || index >= elements.size()) throw new IllegalArgumentException("index must be >= 0 and <= the number of path elements");
return subpath(index, index+1);
}
/**
* Returns a relative {@code Path} that is a subsequence of the name
* elements of this path.
*
* The {@code beginIndex} and {@code endIndex} parameters specify the
* subsequence of name elements. The name that is closest to the root
* in the directory hierarchy has the index {@code 0}. The name that is
* farthest from the root has the index {@link #getNameCount
* count}{@code -1}. The returned {@code Path} object has the name elements
* that begin at {@code beginIndex} and extend to the element at index {@code
* endIndex-1}.
*
* @param beginIndex the index of the first element, inclusive
* @param endIndex the index of the last element, exclusive
* @return a new {@code Path} object that is a subsequence of the name
* elements in this {@code Path}
* @throws IllegalArgumentException if {@code beginIndex} is negative, or greater than or equal to
* the number of elements. If {@code endIndex} is less than or
* equal to {@code beginIndex}, or larger than the number of elements.
*/
@Override
public S3Path subpath(int beginIndex, int endIndex) {
final int size = pathRepresentation.elements().size();
if(beginIndex < 0) throw new IllegalArgumentException("begin index may not be < 0");
if(beginIndex >= size) throw new IllegalArgumentException("begin index may not be >= the number of path elements");
if(endIndex > size) throw new IllegalArgumentException("end index may not be > the number of path elements");
if(endIndex <= beginIndex) throw new IllegalArgumentException("end index may not be <= the begin index");
String path = String.join(PATH_SEPARATOR, pathRepresentation.elements().subList(beginIndex, endIndex));
if (this.isAbsolute() && beginIndex == 0) path = PATH_SEPARATOR+path;
if (endIndex == size && !pathRepresentation.hasTrailingSeparator()) {
return from(path);
} else {
return from(path+PATH_SEPARATOR);
}
}
/**
* Tests if this path starts with the given path.
*
*
This path starts with the given path if this path's root
* component starts with the root component of the given path,
* and this path starts with the same name elements as the given path.
* If the given path has more name elements than this path then {@code false}
* is returned.
*
*
If this path does
* not have a root component and the given path has a root component then
* this path does not start with the given path.
*
*
If the given path is associated with a different {@code FileSystem} (s3 bucket)
* to this path then {@code false} is returned.
*
* @param other the given path
* @return {@code true} if this path starts with the given path; otherwise
* {@code false}
*/
@Override
public boolean startsWith(Path other) {
return this.equals(other) ||
this.fileSystem.equals(other.getFileSystem()) &&
this.isAbsolute() == other.isAbsolute() &&
this.getNameCount() >= other.getNameCount() &&
this.subpath(0, other.getNameCount()).equals(other);
}
/**
* Tests if this path starts with a {@code Path}, constructed by converting
* the given path string, in exactly the manner specified by the {@link
* #startsWith(Path) startsWith(Path)} method.
*
* @param other the given path string
* @return {@code true} if this path starts with the given path; otherwise
* {@code false}
* @throws InvalidPathException If the path string cannot be converted to a Path.
*/
@Override
public boolean startsWith(String other) {
return startsWith(from(other));
}
/**
* Tests if this path ends with the given path.
*
*
If the given path has N elements, and no root component,
* and this path has N or more elements, then this path ends with
* the given path if the last N elements of each path, starting at
* the element farthest from the root, are equal.
*
*
If the given path has a root component then this path ends with the
* given path if the root component of this path ends with the root
* component of the given path, and the corresponding elements of both paths
* are equal. If the two paths are equal then they can be said to end with each other. If this path
* does not have a root component and the given path has a root component
* then this path does not end with the given path.
*
*
If the given path is associated with a different {@code FileSystem}
* to this path then {@code false} is returned.
*
* @param other the given path
* @return {@code true} if this path ends with the given path; otherwise
* {@code false}
*/
@Override
public boolean endsWith(Path other) {
return this.equals(other) ||
this.fileSystem == other.getFileSystem() &&
this.getNameCount() >= other.getNameCount() &&
this.subpath(this.getNameCount() - other.getNameCount(), this.getNameCount()).equals(other);
}
/**
* Tests if this path ends with a {@code Path}, constructed by converting
* the given path string, in exactly the manner specified by the {@link
* #endsWith(Path) endsWith(Path)} method. On UNIX for example, the path
* "{@code foo/bar}" ends with "{@code foo/bar}" and "{@code bar}". It does
* not end with "{@code r}" or "{@code /bar}". Note that trailing separators
* are not taken into account, and so invoking this method on the {@code
* Path}"{@code foo/bar}" with the {@code String} "{@code bar/}" returns
* {@code true}.
*
* @param other the given path string
* @return {@code true} if this path ends with the given path; otherwise
* {@code false}
* @throws InvalidPathException If the path string cannot be converted to a Path.
*/
@Override
public boolean endsWith(String other) {
return endsWith(from(other));
}
/**
* Returns a path that is this path with redundant name elements eliminated.
* All occurrences of "{@code .}" are considered redundant. If a "{@code ..}" is preceded by a
* non-"{@code ..}" name then both names are considered redundant (the
* process to identify such names is repeated until it is no longer
* applicable).
*
*
This method does not access the file system; the path may not locate
* a file that exists. Eliminating "{@code ..}" and a preceding name from a
* path may result in the path that locates a different file than the original
* path. This can arise when the preceding name is a symbolic link.
*
* @return the resulting path or this path if it does not contain
* redundant name elements; an empty path is returned if this path
* does have a root component and all name elements are redundant
* @see #getParent
* @see #toRealPath
*/
@Override
public S3Path normalize() {
if (pathRepresentation.isRoot()) {
return this;
}
boolean directory = pathRepresentation.isDirectory();
final List elements = pathRepresentation.elements();
final LinkedList realElements = new LinkedList<>();
for (String element : elements) {
if (element.equals(".")) continue;
if (element.equals("..")){
if (!realElements.isEmpty()){
realElements.removeLast();
}
continue;
}
if (directory) {
realElements.addLast(element + "/");
} else {
realElements.addLast(element);
}
}
return S3Path.getPath(fileSystem, String.join(PATH_SEPARATOR, realElements));
}
/**
* Resolve the given path against this path.
*
* If the {@code other} parameter is an {@link #isAbsolute() absolute}
* path then this method trivially returns {@code other}. If {@code other}
* is an empty path then this method trivially returns this path.
* Otherwise, this method considers this path to be a directory and resolves
* the given path against this path by
* joining the given path to this path with the addition of a separator ('/') and returns a resulting path
* that {@link #endsWith ends} with the given (other) path.
*
* @param other the path to resolve against this path
* @return the resulting path
* @throws ProviderMismatchException if {@code other} is {@code null} or if it is not an instance of {@code S3Path}
* @throws IllegalArgumentException if {@code other} is NOT and instance of an {@code S3Path}
* @see #relativize
*/
@Override
public S3Path resolve(Path other) {
if(!(other instanceof S3Path)) throw new ProviderMismatchException("a non s3 path cannot be resolved against and S3Path");
S3Path s3Other = (S3Path) other;
if(!this.bucketName().equals(s3Other.bucketName())) throw new IllegalArgumentException("S3Paths cannot be resolved when they are from different buckets");
if (s3Other.isAbsolute()) return s3Other;
if (s3Other.isEmpty()) return this;
String concatenatedPath;
if (!this.pathRepresentation.hasTrailingSeparator()) {
concatenatedPath = this + PATH_SEPARATOR + s3Other;
} else {
concatenatedPath = this.toString() + s3Other;
}
return from(concatenatedPath);
}
/**
* Converts a given path string to a {@code S3Path} and resolves it against
* this {@code S3Path} in exactly the manner specified by the {@link
* #resolve(Path) resolve} method.
*
* @param other the path string to resolve against this path
* @return the resulting path
* @throws InvalidPathException if the path string cannot be converted to a Path.
* @see FileSystem#getPath
*/
@Override
public S3Path resolve(String other) {
return resolve(from(other));
}
/**
* Resolves the given path against this path's {@link #getParent parent}
* path. This is useful where a file name needs to be replaced with
* another file name. For example, suppose that the name separator is
* "{@code /}" and a path represents "{@code dir1/dir2/foo}", then invoking
* this method with the {@code Path} "{@code bar}" will result in the {@code
* Path} "{@code dir1/dir2/bar}". If this path does not have a parent path,
* or {@code other} is {@link #isAbsolute() absolute}, then this method
* returns {@code other}. If {@code other} is an empty path then this method
* returns this path's parent, or where this path doesn't have a parent, the
* empty path.
*
* @param other the path to resolve against this path's parent
* @return the resulting path
* @see #resolve(Path)
*/
@Override
public S3Path resolveSibling(Path other) {
return getParent().resolve(other);
}
/**
* Converts a given path string to a {@code Path} and resolves it against
* this path's {@link #getParent parent} path in exactly the manner
* specified by the {@link #resolveSibling(Path) resolveSibling} method.
*
* @param other the path string to resolve against this path's parent
* @return the resulting path
* @throws InvalidPathException if the path string cannot be converted to a Path.
* @see FileSystem#getPath
*/
@Override
public S3Path resolveSibling(String other) {
return getParent().resolve(other);
}
/**
* Constructs a relative path between this path and a given path.
*
*
Relativization is the inverse of {@link #resolve(Path) resolution}.
* This method attempts to construct a {@link #isAbsolute relative} path
* that when {@link #resolve(Path) resolved} against this path, yields a
* path that locates the same file as the given path. For example, on UNIX,
* if this path is {@code "/a/b"} and the given path is {@code "/a/b/c/d"}
* then the resulting relative path would be {@code "c/d"}. Where this
* path and the given path do not have a {@link #getRoot root} component,
* then a relative path can be constructed. A relative path cannot be
* constructed if only one of the paths have a root component. Where both
* paths have a root component then it is implementation dependent if a
* relative path can be constructed. If this path and the given path are
* {@link #equals equal} then an empty path is returned.
*
* @param other the path to relativize against this path
* @return the resulting relative path, or an empty path if both paths are
* equal
* @throws IllegalArgumentException if {@code other} is not a {@code Path} that can be relativized
* against this path
*/
@Override
public S3Path relativize(Path other) {
if(!(other instanceof S3Path)) throw new IllegalArgumentException("path is not an S3Path");
if(this.equals(other)) return from("");
if(this.isAbsolute() != other.isAbsolute()) throw new IllegalArgumentException("to obtain a relative path both must be absolute or both must be relative");
if(!Objects.equals(this.bucketName(), ((S3Path) other).bucketName())) throw new IllegalArgumentException("cannot relativize S3Paths from different buckets");
S3Path otherPath = (S3Path) other;
if(this.isEmpty()) return otherPath;
int nameCount = this.getNameCount();
int otherNameCount = other.getNameCount();
int limit = Math.min(nameCount, otherNameCount);
int differenceCount = getDifferenceCount(other, limit);
int parentDirCount = nameCount - differenceCount;
if (differenceCount < otherNameCount) {
return getRelativePathFromDifference(otherPath, otherNameCount, differenceCount, parentDirCount);
}
char[] relativePath = new char[parentDirCount*3 - 1];
int index = 0;
while (parentDirCount > 0) {
relativePath[index++] = '.';
relativePath[index++] = '.';
if (parentDirCount > 1)
relativePath[index++] = '/';
parentDirCount--;
}
return new S3Path(getFileSystem(), new PosixLikePathRepresentation(relativePath));
}
private S3Path getRelativePathFromDifference(S3Path otherPath, int otherNameCount, int differenceCount, int parentDirCount) {
Objects.requireNonNull(otherPath);
S3Path remainingSubPath = otherPath.subpath(differenceCount, otherNameCount);
if (parentDirCount == 0) return remainingSubPath;
// we need to pop up some directories (each of which needs three characters ../) then append the remaining sub-path
int relativePathSize = parentDirCount * 3 + remainingSubPath.pathRepresentation.toString().length();
if (otherPath.isEmpty()) relativePathSize--;
char[] relativePath = new char[relativePathSize];
int index = 0;
while (parentDirCount > 0) {
relativePath[index++] = '.';
relativePath[index++] = '.';
if (otherPath.isEmpty()) {
if (parentDirCount > 1) relativePath[index++] = '/';
} else {
relativePath[index++] = '/';
}
parentDirCount--;
}
System.arraycopy(remainingSubPath.pathRepresentation.chars(), 0, relativePath, index, remainingSubPath.pathRepresentation.chars().length);
return new S3Path(getFileSystem(), new PosixLikePathRepresentation(relativePath));
}
private int getDifferenceCount(Path other, int limit) {
int i = 0;
while (i < limit) {
if (!this.getName(i).equals(other.getName(i)))
break;
i++;
}
return i;
}
private boolean isEmpty(){
return pathRepresentation.toString().isEmpty();
}
/**
* Returns a URI to represent this path.
*
*
This method constructs an absolute and normalized {@link URI} with a {@link
* URI#getScheme() scheme} equal to the URI scheme that identifies the
* provider (s3).
*
* @return the URI representing this path
* @throws IOError if an I/O error occurs obtaining the absolute path, or where a
* file system is constructed to access the contents of a file as
* a file system, and the URI of the enclosing file system cannot be
* obtained
* @throws SecurityException In the case of the default provider, and a security manager
* is installed, the {@link #toAbsolutePath toAbsolutePath} method
* throws a security exception.
*/
@Override
public URI toUri() {
return URI.create(
fileSystem.provider().getScheme() + "://"
+ bucketName()
+ this.toAbsolutePath().toRealPath(NOFOLLOW_LINKS));
}
/**
* Returns a {@code Path} object representing the absolute path of this
* path.
*
*
If this path is already {@link Path#isAbsolute absolute} then this
* method simply returns this path. Otherwise, this method resolves the path
* by resolving the path against the root (the top level of the bucket). The resulting path may contain redundancies
* and may point to a non-existent location.
*
* @return a {@code Path} object representing the absolute path
*/
@Override
public S3Path toAbsolutePath() {
if (isAbsolute()) return this;
return new S3Path(fileSystem, PosixLikePathRepresentation.of(PATH_SEPARATOR, pathRepresentation.toString()));
}
/**
* Returns the real path of an existing file.
*
*
If this path is relative then its absolute path is first obtained,
* as if by invoking the {@link #toAbsolutePath toAbsolutePath} method.
* When deriving the real path, and a
* "{@code ..}" (or equivalent) is preceded by a non-"{@code ..}" name then
* an implementation will cause both names to be removed.
*
* @param options options indicating how symbolic links are handled. S3 has no links so this will be ignored.
* @return an absolute path represent the real path of the file
* located by this object
*/
@Override
public S3Path toRealPath(LinkOption... options) {
S3Path p = this;
if(!isAbsolute()) p = toAbsolutePath();
return S3Path.getPath(fileSystem, PATH_SEPARATOR, p.normalize().toString());
}
/**
* S3 Objects cannot be represented in the local file system
* @throws UnsupportedOperationException always
*/
@Override
public File toFile() {
throw new UnsupportedOperationException("S3 Objects cannot be represented in the local (default) file system");
}
/**
* Currently not implemented
* @throws UnsupportedOperationException always
*/
@Override
public WatchKey register(WatchService watcher, WatchEvent.Kind>[] events, WatchEvent.Modifier... modifiers) throws UnsupportedOperationException {
throw new UnsupportedOperationException("This method is not yet supported. Please raise a feature request describing your use case");
}
/**
* Currently not implemented
* @throws UnsupportedOperationException always
*/
@Override
public WatchKey register(WatchService watcher, WatchEvent.Kind>... events) throws UnsupportedOperationException {
throw new UnsupportedOperationException("This method is not yet supported. Please raise a feature request describing your use case");
}
/**
* Returns an iterator over the name elements of this path.
*
*
The first element returned by the iterator represents the name
* element that is closest to the root in the directory hierarchy, the
* second element is the next closest, and so on. The last element returned
* is the name of the file or directory denoted by this path. The {@link
* #getRoot root} component, if present, is not returned by the iterator.
*
* @return an iterator over the name elements of this path.
*/
@Override
public Iterator iterator() {
return new S3PathIterator(pathRepresentation.elements().iterator(), pathRepresentation.isAbsolute(), pathRepresentation.hasTrailingSeparator());
}
/**
* Compares two abstract paths lexicographically. The ordering defined by
* this method is provider specific, and in the case of the default
* provider, platform specific. This method does not access the file system
* and neither file is required to exist.
*
* This method may not be used to compare paths that are associated
* with different file system providers.
*
* @param other the path compared to this path.
* @return zero if the argument is {@link #equals equal} to this path, a
* value less than zero if this path is lexicographically less than
* the argument, or a value greater than zero if this path is
* lexicographically greater than the argument
* @throws ClassCastException if the paths are associated with different providers
*/
@Override
public int compareTo(Path other) {
if(!(other instanceof S3Path)) throw new ClassCastException("compared paths must be from the same provider");
S3Path o = (S3Path) other;
if(o.fileSystem != this.fileSystem) throw new ClassCastException("compared S3 paths must be from the same bucket");
return this.toRealPath(NOFOLLOW_LINKS).toString().compareTo(
o.toRealPath(NOFOLLOW_LINKS).toString());
}
/**
* Tests this path for equality with the given object.
*
* {@code true} if {@code other} is also an {@code S3Path} from the same bucket and the two paths have the same
* real path.
* @param other the object to which this object is to be compared
* @return {@code true} if, and only if, the given object is a {@code Path}
* that is identical to this {@code Path}
*/
@Override
public boolean equals(Object other) {
if (this == other) return true;
return other instanceof S3Path
&& Objects.equals(((S3Path) other).bucketName(), this.bucketName())
&& Objects.equals(((S3Path) other).toRealPath(NOFOLLOW_LINKS).pathRepresentation,
this.toRealPath(NOFOLLOW_LINKS).pathRepresentation);
}
/**
* Computes a hash code for this path.
*
*
The hash code is based upon the components of the path, and
* satisfies the general contract of the {@link Object#hashCode
* Object.hashCode} method.
*
* @return the hash-code value for this path
*/
@Override
public int hashCode() {
return toRealPath(NOFOLLOW_LINKS).pathRepresentation.hashCode();
}
/**
* Returns the string representation of this path.
*
* @return the string representation of this path
*/
@Override
public String toString() {
return pathRepresentation.toString();
}
/**
* The key of the object for S3. Essentially the "real path" with the "/" prefix removed.
* @return the key
*/
public String getKey(){
if(isEmpty()) return "";
return toRealPath(NOFOLLOW_LINKS).toString().substring(1);
}
private final class S3PathIterator implements Iterator {
private final Iterator delegate;
boolean first;
boolean isAbsolute;
boolean hasTrailingSeparator;
public S3PathIterator(Iterator delegate, boolean isAbsolute, boolean hasTrailingSeparator){
this.delegate = delegate;
this.isAbsolute = isAbsolute;
this.hasTrailingSeparator = hasTrailingSeparator;
first = true;
}
@Override
public Path next() {
String pathString = delegate.next();
if(isAbsolute() && first){
first = false;
pathString = PATH_SEPARATOR+pathString;
if (!hasNext() && hasTrailingSeparator) {
pathString = pathString+PATH_SEPARATOR;
}
}
if(hasNext() || hasTrailingSeparator) {
pathString = pathString+PATH_SEPARATOR;
}
return from(pathString);
}
@Override
public boolean hasNext() {
return delegate.hasNext();
}
}
}