diff --git a/src/java.base/share/classes/java/nio/file/Path.java b/src/java.base/share/classes/java/nio/file/Path.java index e5699a96849..6a47e1ba82b 100644 --- a/src/java.base/share/classes/java/nio/file/Path.java +++ b/src/java.base/share/classes/java/nio/file/Path.java @@ -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. + * + *

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. + * + *

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 replaced with diff --git a/src/java.base/unix/classes/sun/nio/fs/UnixPath.java b/src/java.base/unix/classes/sun/nio/fs/UnixPath.java index 1a9b61fd803..3a4c47fb9db 100644 --- a/src/java.base/unix/classes/sun/nio/fs/UnixPath.java +++ b/src/java.base/unix/classes/sun/nio/fs/UnixPath.java @@ -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); diff --git a/test/jdk/java/nio/file/Path/PathOps.java b/test/jdk/java/nio/file/Path/PathOps.java index aa37ddb84ac..eab74101769 100644 --- a/test/jdk/java/nio/file/Path/PathOps.java +++ b/test/jdk/java/nio/file/Path/PathOps.java @@ -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) { }