8262742: (fs) Add Path::resolve with varargs string

Reviewed-by: alanb
This commit is contained in:
Brian Burkhalter 2023-07-24 19:59:17 +00:00
parent 8008e27c55
commit 2bdfa836ad
3 changed files with 239 additions and 2 deletions
src/java.base
share/classes/java/nio/file
unix/classes/sun/nio/fs
test/jdk/java/nio/file/Path

@ -516,6 +516,90 @@ public interface Path
return resolve(getFileSystem().getPath(other));
}
/**
* Resolves a path against this path, and then iteratively resolves any
* additional paths.
*
* <p> This method resolves {@code first} against this {@code Path} as if
* by calling {@link #resolve(Path)}. If {@code more} has one or more
* elements then it resolves the first element against the result, then
* iteratively resolves all subsequent elements. This method returns the
* result from the final resolve.
*
* @implSpec
* The default implementation is equivalent to the result obtained with:
* {@snippet lang=java :
* Path result = resolve(first);
* for (Path p : more) {
* result = result.resolve(p);
* }
* }
*
* @param first
* the first path to resolve against this path
* @param more
* additional paths to iteratively resolve
*
* @return the resulting path
*
* @see #resolve(Path)
* @since 22
*/
default Path resolve(Path first, Path... more) {
Path result = resolve(first);
for (Path p : more) {
result = result.resolve(p);
}
return result;
}
/**
* Converts a path string to a path, resolves that path against this path,
* and then iteratively performs the same procedure for any additional
* path strings.
*
* <p> This method converts {@code first} to a {@code Path} and resolves
* that {@code Path} against this {@code Path} as if by calling
* {@link #resolve(String)}. If {@code more} has one or more elements
* then it converts the first element to a path, resolves that path against
* the result, then iteratively converts and resolves all subsequent
* elements. This method returns the result from the final resolve.
*
* @implSpec
* The default implementation is equivalent to the result obtained with:
* {@snippet lang=java :
* Path result = resolve(first);
* for (String s : more) {
* result = result.resolve(s);
* }
* }
*
* @param first
* the first path string to convert to a path and
* resolve against this path
*
* @param more
* additional path strings to be iteratively converted to
* paths and resolved
*
* @return the resulting path
*
* @throws InvalidPathException
* if a path string cannot be converted to a Path.
*
* @see #resolve(Path,Path...)
* @see #resolve(String)
*
* @since 22
*/
default Path resolve(String first, String... more) {
Path result = resolve(first);
for (String s : more) {
result = result.resolve(s);
}
return result;
}
/**
* Resolves the given path against this path's {@link #getParent parent}
* path. This is useful where a file name needs to be <i>replaced</i> with

@ -397,6 +397,83 @@ class UnixPath implements Path {
return resolve(new UnixPath(getFileSystem(), other));
}
private static final byte[] resolve(byte[] base, byte[]... children) {
// 'start' is either zero, indicating the base, or indicates which
// child is that last one which is an absolute path
int start = 0;
int resultLength = base.length;
// Locate the last child which is an absolute path and calculate
// the total number of bytes in the resolved path
final int count = children.length;
if (count > 0) {
for (int i = 0; i < count; i++) {
byte[] b = children[i];
if (b.length > 0) {
if (b[0] == '/') {
start = i + 1;
resultLength = b.length;
} else {
if (resultLength > 0)
resultLength++;
resultLength += b.length;
}
}
}
}
// If the base is not being superseded by a child which is an
// absolute path, then if at least one child is non-empty and
// the base consists only of a '/', then decrement resultLength to
// account for an extra '/' added in the resultLength computation.
if (start == 0 && resultLength > base.length && base.length == 1 && base[0] == '/')
resultLength--;
// Allocate the result array and return if empty.
byte[] result = new byte[resultLength];
if (result.length == 0)
return result;
// Prepend the base if it is non-empty and would not later be
// overwritten by an absolute child
int offset = 0;
if (start == 0 && base.length > 0) {
System.arraycopy(base, 0, result, 0, base.length);
offset += base.length;
}
// Append children starting with the last one which is an
// absolute path
if (count > 0) {
int idx = Math.max(0, start - 1);
for (int i = idx; i < count; i++) {
byte[] b = children[i];
if (b.length > 0) {
if (offset > 0 && result[offset - 1] != '/')
result[offset++] = '/';
System.arraycopy(b, 0, result, offset, b.length);
offset += b.length;
}
}
}
return result;
}
@Override
public UnixPath resolve(Path first, Path... more) {
if (more.length == 0)
return resolve(first);
byte[][] children = new byte[1 + more.length][];
children[0] = toUnixPath(first).path;
for (int i = 0; i < more.length; i++)
children[i + 1] = toUnixPath(more[i]).path;
byte[] result = resolve(path, children);
return new UnixPath(getFileSystem(), result);
}
@Override
public UnixPath relativize(Path obj) {
UnixPath child = toUnixPath(obj);

@ -22,7 +22,8 @@
*/
/* @test
* @bug 4313887 6838333 6925932 7006126 8037945 8072495 8140449 8254876 8298478
* @bug 4313887 6838333 6925932 7006126 8037945 8072495 8140449 8254876 8262742
* 8298478
* @summary Unit test for java.nio.file.Path path operations
*/
@ -182,6 +183,27 @@ public class PathOps {
return this;
}
// Note: "expected" is first parameter here
PathOps resolve(String expected, String first, String... more) {
out.format("test resolve %s varargs (String)\n", path());
checkPath();
check(path.resolve(first, more), expected);
Path[] others = new Path[more.length];
int i = 0;
for (String s : more) {
others[i++] = Path.of(s);
}
return resolve(expected, Path.of(first), others);
}
// Note: "expected" is first parameter here
PathOps resolve(String expected, Path first, Path... more) {
out.format("test resolve %s varargs (Path)\n", path());
checkPath();
check(path.resolve(first, more), expected);
return this;
}
PathOps resolveSibling(String other, String expected) {
out.format("test resolveSibling %s\n", other);
checkPath();
@ -543,6 +565,35 @@ public class PathOps {
.resolve("C:foo", "C:foo")
.resolve("\\\\server\\share\\bar", "\\\\server\\share\\bar");
// resolve - varargs
test("C:\\tmp")
.resolve("C:\\tmp\\foo\\bar\\gus", "foo", "bar", "gus")
.resolve("C:\\gus", "\\foo", "bar", "\\gus")
.resolve("C:\\tmp\\baz", "", "", "baz");
test("C:\\tmp\\foo")
.resolve("C:\\tmp\\foo\\bar\\gus", "", "bar\\gus", "")
.resolve("C:\\tmp\\foo\\bar\\gus\\foo\\baz",
"", "bar\\gus", "foo\\baz")
.resolve("C:\\bar\\gus\\baz", "", "C:\\bar\\gus", "baz")
.resolve("C:\\tmp\\bar", "C:\\bar\\gus", "baz", "C:\\tmp\\bar");
test("tmp")
.resolve("tmp\\foo\\bar\\gus", "foo", "bar", "gus")
.resolve("\\gus", "\\foo", "bar", "\\gus")
.resolve("tmp\\baz", "", "", "baz");
test("")
.resolve("", "", "")
.resolve("\\bar", "foo", "\\bar", "")
.resolve("foo\\bar\\gus", "foo", "bar", "gus")
.resolve("baz", "", "", "baz");
test("\\")
.resolve("\\foo", "foo", "")
.resolve("\\foo", "", "foo")
.resolve("\\bar", "foo", "", "\\bar");
test("C:")
.resolve("C:foo\\bar\\gus", "foo", "bar", "gus")
.resolve("C:baz", "", "baz")
.resolve("C:", "", "");
// resolveSibling
test("foo")
.resolveSibling("bar", "bar")
@ -1669,6 +1720,31 @@ public class PathOps {
.resolve("foo", "foo")
.resolve("/foo", "/foo");
// resolve - varargs
test("/tmp")
.resolve("/tmp/foo/bar/gus", "foo", "bar", "gus")
.resolve("/gus", "/foo", "bar", "/gus")
.resolve("/tmp/baz", "", "", "baz");
test("/tmp/foo")
.resolve("/tmp/foo/bar/gus", "", "bar/gus", "")
.resolve("/tmp/foo/bar/gus/foo/baz", "", "bar/gus", "foo/baz")
.resolve("/bar/gus/baz", "", "/bar/gus", "baz")
.resolve("/tmp/bar", "/bar/gus", "baz", "/tmp/bar");
test("tmp")
.resolve("tmp/foo/bar/gus", "foo", "bar", "gus")
.resolve("/gus", "/foo", "bar", "/gus")
.resolve("tmp/baz", "", "", "baz");
test("")
.resolve("", "", "")
.resolve("/bar", "foo", "/bar", "")
.resolve("foo/bar/gus", "foo", "bar", "gus")
.resolve("baz", "", "", "baz");
test("/")
.resolve("/foo", "", "", "foo", "")
.resolve("/foo", "foo", "")
.resolve("/foo", "", "foo")
.resolve("/bar", "foo", "", "/bar");
// resolveSibling
test("foo")
.resolveSibling("bar", "bar")
@ -2077,7 +2153,7 @@ public class PathOps {
}
try {
Path.of("foo", null);
Path.of("foo", (String[])null);
throw new RuntimeException("NullPointerException not thrown");
} catch (NullPointerException npe) {
}