diff --git a/extensions/qute/deployment/src/main/java/io/quarkus/qute/deployment/QuteProcessor.java b/extensions/qute/deployment/src/main/java/io/quarkus/qute/deployment/QuteProcessor.java index 193fd8f588f78..51c396c94d29b 100644 --- a/extensions/qute/deployment/src/main/java/io/quarkus/qute/deployment/QuteProcessor.java +++ b/extensions/qute/deployment/src/main/java/io/quarkus/qute/deployment/QuteProcessor.java @@ -99,9 +99,8 @@ import io.quarkus.maven.dependency.DependencyFlags; import io.quarkus.maven.dependency.ResolvedDependency; import io.quarkus.panache.common.deployment.PanacheEntityClassesBuildItem; -import io.quarkus.paths.FilteredPathTree; -import io.quarkus.paths.PathFilter; import io.quarkus.paths.PathTree; +import io.quarkus.paths.PathTreeUtils; import io.quarkus.qute.CheckedTemplate; import io.quarkus.qute.Engine; import io.quarkus.qute.EngineBuilder; @@ -2179,14 +2178,22 @@ private void scanPathTree(PathTree pathTree, TemplateRootsBuildItem templateRoot BuildProducer nativeImageResources, QuteConfig config) { for (String templateRoot : templateRoots) { - pathTree.accept(templateRoot, visit -> { - if (visit != null) { - // if template root is found in this tree then walk over its subtree - scanTemplateRootSubtree( - new FilteredPathTree(pathTree, PathFilter.forIncludes(List.of(templateRoot + "/**"))), - visit.getRelativePath(), watchedPaths, templatePaths, nativeImageResources, config); - } - }); + if (PathTreeUtils.containsCaseSensitivePath(pathTree, templateRoot)) { + pathTree.walkIfContains(templateRoot, visit -> { + if (Files.isRegularFile(visit.getPath())) { + LOGGER.debugf("Found template: %s", visit.getPath()); + // remove templateRoot + / + final String relativePath = visit.getRelativePath(); + String templatePath = relativePath.substring(templateRoot.length() + 1); + if (config.templatePathExclude.matcher(templatePath).matches()) { + LOGGER.debugf("Template file excluded: %s", visit.getPath()); + return; + } + produceTemplateBuildItems(templatePaths, watchedPaths, nativeImageResources, + relativePath, templatePath, visit.getPath(), config); + } + }); + } } } @@ -3414,27 +3421,6 @@ private static void produceTemplateBuildItems(BuildProducer watchedPaths, - BuildProducer templatePaths, - BuildProducer nativeImageResources, - QuteConfig config) { - pathTree.walk(visit -> { - if (Files.isRegularFile(visit.getPath())) { - LOGGER.debugf("Found template: %s", visit.getPath()); - // remove templateRoot + / - final String relativePath = visit.getRelativePath(); - String templatePath = relativePath.substring(templateRoot.length() + 1); - if (config.templatePathExclude.matcher(templatePath).matches()) { - LOGGER.debugf("Template file excluded: %s", visit.getPath()); - return; - } - produceTemplateBuildItems(templatePaths, watchedPaths, nativeImageResources, - relativePath, templatePath, visit.getPath(), config); - } - }); - } - private static boolean isExcluded(TypeCheck check, Iterable> excludes) { for (Predicate exclude : excludes) { if (exclude.test(check)) { diff --git a/extensions/vertx-http/deployment/src/main/java/io/quarkus/vertx/http/deployment/StaticResourcesProcessor.java b/extensions/vertx-http/deployment/src/main/java/io/quarkus/vertx/http/deployment/StaticResourcesProcessor.java index 3b64ea89e1c7c..77897012f602c 100644 --- a/extensions/vertx-http/deployment/src/main/java/io/quarkus/vertx/http/deployment/StaticResourcesProcessor.java +++ b/extensions/vertx-http/deployment/src/main/java/io/quarkus/vertx/http/deployment/StaticResourcesProcessor.java @@ -20,8 +20,6 @@ import io.quarkus.deployment.builditem.LaunchModeBuildItem; import io.quarkus.deployment.builditem.nativeimage.NativeImageResourceBuildItem; import io.quarkus.deployment.pkg.steps.NativeOrNativeSourcesBuild; -import io.quarkus.paths.FilteredPathTree; -import io.quarkus.paths.PathFilter; import io.quarkus.paths.PathVisitor; import io.quarkus.vertx.core.deployment.CoreVertxBuildItem; import io.quarkus.vertx.http.deployment.spi.AdditionalStaticResourceBuildItem; @@ -85,7 +83,7 @@ public void nativeImageResource(Optional staticResourc final Set collectedDirs = new HashSet<>(); visitRuntimeMetaInfResources(visit -> { if (Files.isDirectory(visit.getPath())) { - final String relativePath = visit.getRelativePath("/"); + final String relativePath = visit.getRelativePath(); if (collectedDirs.add(relativePath)) { producer.produce(new NativeImageResourceBuildItem(relativePath)); } @@ -104,7 +102,7 @@ private Set getClasspathResources() { visitRuntimeMetaInfResources(visit -> { if (!Files.isDirectory(visit.getPath())) { knownPaths.add(new StaticResourcesBuildItem.Entry( - visit.getRelativePath("/").substring(StaticResourcesRecorder.META_INF_RESOURCES.length()), + visit.getRelativePath().substring(StaticResourcesRecorder.META_INF_RESOURCES.length()), false)); } }); @@ -120,13 +118,10 @@ private static void visitRuntimeMetaInfResources(PathVisitor visitor) { final List elements = QuarkusClassLoader.getElements(StaticResourcesRecorder.META_INF_RESOURCES, false); if (!elements.isEmpty()) { - final PathFilter filter = PathFilter.forIncludes(List.of( - StaticResourcesRecorder.META_INF_RESOURCES + "/**", - StaticResourcesRecorder.META_INF_RESOURCES)); for (var element : elements) { if (element.isRuntime()) { element.apply(tree -> { - new FilteredPathTree(tree, filter).walk(visitor); + tree.walkIfContains(StaticResourcesRecorder.META_INF_RESOURCES, visitor); return null; }); } diff --git a/independent-projects/bootstrap/app-model/src/main/java/io/quarkus/paths/ArchivePathTree.java b/independent-projects/bootstrap/app-model/src/main/java/io/quarkus/paths/ArchivePathTree.java index 507dce20e771a..f4c094678c879 100644 --- a/independent-projects/bootstrap/app-model/src/main/java/io/quarkus/paths/ArchivePathTree.java +++ b/independent-projects/bootstrap/app-model/src/main/java/io/quarkus/paths/ArchivePathTree.java @@ -95,7 +95,25 @@ public Collection getRoots() { public void walk(PathVisitor visitor) { try (FileSystem fs = openFs()) { final Path dir = fs.getPath("/"); - PathTreeVisit.walk(archive, dir, pathFilter, getMultiReleaseMapping(), visitor); + PathTreeVisit.walk(archive, dir, dir, pathFilter, getMultiReleaseMapping(), visitor); + } catch (IOException e) { + throw new UncheckedIOException("Failed to read " + archive, e); + } + } + + @Override + public void walkIfContains(String relativePath, PathVisitor visitor) { + ensureResourcePath(relativePath); + if (!PathFilter.isVisible(pathFilter, relativePath)) { + return; + } + try (FileSystem fs = openFs()) { + for (Path root : fs.getRootDirectories()) { + final Path walkDir = root.resolve(relativePath); + if (Files.exists(walkDir)) { + PathTreeVisit.walk(archive, root, walkDir, pathFilter, getMultiReleaseMapping(), visitor); + } + } } catch (IOException e) { throw new UncheckedIOException("Failed to read " + archive, e); } @@ -293,6 +311,17 @@ public void walk(PathVisitor visitor) { } } + @Override + public void walkIfContains(String relativePath, PathVisitor visitor) { + lock.readLock().lock(); + try { + ensureOpen(); + super.walkIfContains(relativePath, visitor); + } finally { + lock.readLock().unlock(); + } + } + @Override public boolean contains(String relativePath) { lock.readLock().lock(); diff --git a/independent-projects/bootstrap/app-model/src/main/java/io/quarkus/paths/CachingPathTree.java b/independent-projects/bootstrap/app-model/src/main/java/io/quarkus/paths/CachingPathTree.java deleted file mode 100644 index 47bde335702f4..0000000000000 --- a/independent-projects/bootstrap/app-model/src/main/java/io/quarkus/paths/CachingPathTree.java +++ /dev/null @@ -1,234 +0,0 @@ -package io.quarkus.paths; - -import java.io.IOException; -import java.net.MalformedURLException; -import java.net.URL; -import java.nio.file.Path; -import java.util.Collection; -import java.util.LinkedHashMap; -import java.util.Objects; -import java.util.function.Consumer; -import java.util.function.Function; -import java.util.jar.Manifest; - -public class CachingPathTree implements OpenPathTree { - - public static CachingPathTree of(PathTree pathTree) { - return new CachingPathTree(pathTree); - } - - private final PathTree delegate; - private volatile LinkedHashMap walkSnapshot; - - private CachingPathTree(PathTree delegate) { - this.delegate = delegate; - } - - @Override - public Collection getRoots() { - return delegate.getRoots(); - } - - @Override - public Manifest getManifest() { - return delegate.getManifest(); - } - - @Override - public void walk(PathVisitor visitor) { - final LinkedHashMap snapshot = walkSnapshot; - if (snapshot != null) { - final PathVisitWrapper wrapper = new PathVisitWrapper(); - for (PathVisitSnapshot visit : snapshot.values()) { - wrapper.target = visit; - visitor.visitPath(wrapper); - if (wrapper.stopWalking) { - break; - } - } - return; - } - - final LinkedHashMap walkSnapshot = new LinkedHashMap<>(); - final PathVisitWrapper wrapper = new PathVisitWrapper(); - delegate.walk(new PathVisitor() { - @Override - public void visitPath(PathVisit visit) { - final PathVisitSnapshot snapshot = new PathVisitSnapshot(visit); - walkSnapshot.put(snapshot.getRelativePath("/"), snapshot); - if (wrapper.stopWalking) { - return; - } - wrapper.target = snapshot; - visitor.visitPath(wrapper); - } - }); - if (this.walkSnapshot == null) { - this.walkSnapshot = walkSnapshot; - } - } - - @Override - public T apply(String relativePath, Function func) { - final LinkedHashMap snapshot = walkSnapshot; - if (snapshot != null) { - return func.apply((PathVisit) snapshot.get(relativePath)); - } - return delegate.apply(relativePath, func); - } - - @Override - public void accept(String relativePath, Consumer func) { - final LinkedHashMap snapshot = walkSnapshot; - if (snapshot != null) { - func.accept((PathVisit) snapshot.get(relativePath)); - return; - } - delegate.accept(relativePath, func); - } - - @Override - public void acceptAll(String relativePath, Consumer func) { - delegate.acceptAll(relativePath, func); - } - - @Override - public boolean contains(String relativePath) { - final LinkedHashMap snapshot = walkSnapshot; - if (snapshot != null) { - return snapshot.get(relativePath) != null; - } - return delegate.contains(relativePath); - } - - @Override - public Path getPath(String relativePath) { - final LinkedHashMap snapshot = walkSnapshot; - if (snapshot != null) { - final PathVisitSnapshot visit = snapshot.get(relativePath); - return visit == null ? null : visit.getPath(); - } - if (delegate instanceof OpenPathTree) { - return ((OpenPathTree) delegate).getPath(relativePath); - } - throw new UnsupportedOperationException(); - } - - @Override - public OpenPathTree open() { - return this; - } - - @Override - public boolean isOpen() { - return true; - } - - @Override - public void close() throws IOException { - walkSnapshot = null; - if (delegate instanceof OpenPathTree) { - ((OpenPathTree) delegate).close(); - } - } - - @Override - public PathTree getOriginalTree() { - return delegate instanceof OpenPathTree ? ((OpenPathTree) delegate).getOriginalTree() : delegate; - } - - @Override - public int hashCode() { - return Objects.hash(delegate); - } - - @Override - public boolean equals(Object obj) { - if (this == obj) - return true; - if (obj == null) - return false; - if (getClass() != obj.getClass()) - return false; - CachingPathTree other = (CachingPathTree) obj; - return Objects.equals(delegate, other.delegate); - } - - private static class PathVisitWrapper implements PathVisit { - PathVisit target; - boolean stopWalking; - - @Override - public Path getRoot() { - return target.getRoot(); - } - - @Override - public Path getPath() { - return target.getPath(); - } - - @Override - public String getRelativePath(String separator) { - return target.getRelativePath(separator); - } - - @Override - public URL getUrl() { - return target.getUrl(); - } - - @Override - public void stopWalking() { - stopWalking = true; - } - } - - private static class PathVisitSnapshot implements PathVisit { - - private final Path root; - private final Path path; - private final String relativePathStr; - private volatile URL url; - - private PathVisitSnapshot(PathVisit visit) { - this.root = visit.getRoot(); - this.path = visit.getPath(); - this.relativePathStr = visit.getRelativePath("/"); - } - - @Override - public Path getRoot() { - return root; - } - - @Override - public Path getPath() { - return path; - } - - @Override - public URL getUrl() { - if (url != null) { - return url; - } - try { - return url = path.toUri().toURL(); - } catch (MalformedURLException e) { - throw new RuntimeException("Failed to translate " + path.toUri() + " to a URL", e); - } - } - - @Override - public String getRelativePath(String separator) { - if (!path.getFileSystem().getSeparator().equals("/")) { - return relativePathStr.replace("/", path.getFileSystem().getSeparator()); - } - return relativePathStr; - } - - @Override - public void stopWalking() { - } - } -} diff --git a/independent-projects/bootstrap/app-model/src/main/java/io/quarkus/paths/DirectoryPathTree.java b/independent-projects/bootstrap/app-model/src/main/java/io/quarkus/paths/DirectoryPathTree.java index 36a1c41e9cff7..14ceb795ca6a1 100644 --- a/independent-projects/bootstrap/app-model/src/main/java/io/quarkus/paths/DirectoryPathTree.java +++ b/independent-projects/bootstrap/app-model/src/main/java/io/quarkus/paths/DirectoryPathTree.java @@ -8,7 +8,7 @@ import java.nio.file.Path; import java.nio.file.Paths; import java.util.Collection; -import java.util.Collections; +import java.util.List; import java.util.Objects; import java.util.function.Consumer; import java.util.function.Function; @@ -81,12 +81,25 @@ protected DirectoryPathTree(Path dir, PathFilter pathFilter, PathTreeWithManifes @Override public Collection getRoots() { - return Collections.singletonList(dir); + return List.of(dir); } @Override public void walk(PathVisitor visitor) { - PathTreeVisit.walk(dir, dir, pathFilter, getMultiReleaseMapping(), visitor); + PathTreeVisit.walk(dir, dir, dir, pathFilter, getMultiReleaseMapping(), visitor); + } + + @Override + public void walkIfContains(String relativePath, PathVisitor visitor) { + ensureResourcePath(relativePath); + if (!PathFilter.isVisible(pathFilter, relativePath)) { + return; + } + final Path walkDir = dir.resolve(manifestEnabled ? toMultiReleaseRelativePath(relativePath) : relativePath); + if (!Files.exists(walkDir)) { + return; + } + PathTreeVisit.walk(dir, dir, walkDir, pathFilter, getMultiReleaseMapping(), visitor); } private void ensureResourcePath(String path) { @@ -189,5 +202,4 @@ public boolean equals(Object obj) { return Objects.equals(dir, other.dir) && Objects.equals(pathFilter, other.pathFilter) && manifestEnabled == other.manifestEnabled; } - } diff --git a/independent-projects/bootstrap/app-model/src/main/java/io/quarkus/paths/EmptyPathTree.java b/independent-projects/bootstrap/app-model/src/main/java/io/quarkus/paths/EmptyPathTree.java index b57c35f57bf8a..d1efedc41dad6 100644 --- a/independent-projects/bootstrap/app-model/src/main/java/io/quarkus/paths/EmptyPathTree.java +++ b/independent-projects/bootstrap/app-model/src/main/java/io/quarkus/paths/EmptyPathTree.java @@ -30,6 +30,10 @@ public Manifest getManifest() { public void walk(PathVisitor visitor) { } + @Override + public void walkIfContains(String relativePath, PathVisitor visitor) { + } + @Override public T apply(String relativePath, Function func) { return func.apply(null); diff --git a/independent-projects/bootstrap/app-model/src/main/java/io/quarkus/paths/FilePathTree.java b/independent-projects/bootstrap/app-model/src/main/java/io/quarkus/paths/FilePathTree.java index fcdbd6a9e21d0..348b887742a27 100644 --- a/independent-projects/bootstrap/app-model/src/main/java/io/quarkus/paths/FilePathTree.java +++ b/independent-projects/bootstrap/app-model/src/main/java/io/quarkus/paths/FilePathTree.java @@ -66,6 +66,11 @@ public String getRelativePath(String separator) { }); } + @Override + public void walkIfContains(String relativePath, PathVisitor visitor) { + throw new UnsupportedOperationException(); + } + @Override public T apply(String relativePath, Function func) { if (relativePath.isEmpty()) { diff --git a/independent-projects/bootstrap/app-model/src/main/java/io/quarkus/paths/FilteredPathTree.java b/independent-projects/bootstrap/app-model/src/main/java/io/quarkus/paths/FilteredPathTree.java index 8265ef59771f1..47b03c66d8031 100644 --- a/independent-projects/bootstrap/app-model/src/main/java/io/quarkus/paths/FilteredPathTree.java +++ b/independent-projects/bootstrap/app-model/src/main/java/io/quarkus/paths/FilteredPathTree.java @@ -37,6 +37,18 @@ public void walk(PathVisitor visitor) { }); } + @Override + public void walkIfContains(String relativePath, PathVisitor visitor) { + if (!PathFilter.isVisible(filter, relativePath)) { + return; + } + original.walkIfContains(relativePath, visit -> { + if (visit != null && filter.isVisible(visit.getRelativePath())) { + visitor.visitPath(visit); + } + }); + } + @Override public T apply(String relativePath, Function func) { if (!PathFilter.isVisible(filter, relativePath)) { diff --git a/independent-projects/bootstrap/app-model/src/main/java/io/quarkus/paths/MultiRootPathTree.java b/independent-projects/bootstrap/app-model/src/main/java/io/quarkus/paths/MultiRootPathTree.java index e09b70ce12168..9919662363250 100644 --- a/independent-projects/bootstrap/app-model/src/main/java/io/quarkus/paths/MultiRootPathTree.java +++ b/independent-projects/bootstrap/app-model/src/main/java/io/quarkus/paths/MultiRootPathTree.java @@ -52,6 +52,16 @@ public void walk(PathVisitor visitor) { } } + @Override + public void walkIfContains(String relativePath, PathVisitor visitor) { + if (trees.length == 0) { + return; + } + for (PathTree t : trees) { + t.walkIfContains(relativePath, visitor); + } + } + @Override public T apply(String relativePath, Function func) { for (PathTree tree : trees) { diff --git a/independent-projects/bootstrap/app-model/src/main/java/io/quarkus/paths/PathTree.java b/independent-projects/bootstrap/app-model/src/main/java/io/quarkus/paths/PathTree.java index 5b68db900bc93..92c288e10db04 100644 --- a/independent-projects/bootstrap/app-model/src/main/java/io/quarkus/paths/PathTree.java +++ b/independent-projects/bootstrap/app-model/src/main/java/io/quarkus/paths/PathTree.java @@ -112,6 +112,19 @@ default boolean isEmpty() { */ void walk(PathVisitor visitor); + /** + * Walks a subtree of this tree that begins with a passed in {@code relativePath}, + * if the tree contains {@code relativePath}. If the tree does not contain {@code relativePath} + * then the method returns without an error. + *

+ * This method does not create a new {@link PathTree} with the root at {@code relativePath}. + * It simply applies an inclusion filter to this {@link PathTree} instance, keeping the same root. + * + * @param relativePath relative path from which the walk should begin + * @param visitor path visitor + */ + void walkIfContains(String relativePath, PathVisitor visitor); + /** * Applies a function to a given path relative to the root of the tree. * If the path isn't found in the tree, the {@link PathVisit} argument @@ -167,4 +180,14 @@ default void acceptAll(String relativePath, Consumer consumer) { * @return an instance of {@link OpenPathTree} for this path tree */ OpenPathTree open(); + + /** + * Creates a new {@link PathTree} instance applying a path filter to this {@link PathTree} instance. + * + * @param filter path filter to apply + * @return a new {@link PathTree} instance applying a path filter to this {@link PathTree} instance + */ + default PathTree filter(PathFilter filter) { + return new FilteredPathTree(this, filter); + } } diff --git a/independent-projects/bootstrap/app-model/src/main/java/io/quarkus/paths/PathTreeUtils.java b/independent-projects/bootstrap/app-model/src/main/java/io/quarkus/paths/PathTreeUtils.java new file mode 100644 index 0000000000000..f805b9f8dda02 --- /dev/null +++ b/independent-projects/bootstrap/app-model/src/main/java/io/quarkus/paths/PathTreeUtils.java @@ -0,0 +1,100 @@ +package io.quarkus.paths; + +import java.io.File; +import java.io.IOException; +import java.io.UncheckedIOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.stream.Stream; + +public interface PathTreeUtils { + + /** + * Returns a path as a string using the specified separator. + * + * @param path path to convert to a string + * @param separator path element separator + * @return string representation of a path + */ + static String asString(final Path path, String separator) { + if (path.getFileSystem().getSeparator().equals(separator)) { + return path.toString(); + } + final int nameCount = path.getNameCount(); + if (nameCount == 0) { + return ""; + } + if (nameCount == 1) { + return path.getName(0).toString(); + } + final StringBuilder s = new StringBuilder(); + s.append(path.getName(0)); + for (int i = 1; i < nameCount; ++i) { + s.append(separator).append(path.getName(i)); + } + return s.toString(); + } + + /** + * Checks whether a path tree contains a given relative path respecting case sensitivity even on Windows. + * + *

+ * Path API on Windows may resolve {@code templates} to {@code Templates}. This method + * helps verify whether a given relative path actually exists. + * + * @param pathTree path tree + * @param relativePath relative path to check + * @return true if a path tree contains a given relative path + */ + static boolean containsCaseSensitivePath(PathTree pathTree, String relativePath) { + if (!pathTree.contains(relativePath)) { + return false; + } + // if it's not Windows, we don't need to check further + if (File.separatorChar != '\\') { + return true; + } + // this should not be necessary, since relatvePath is meant to be a resource path, not an FS path but just in case + relativePath = relativePath.replace(File.separatorChar, '/'); + final String[] pathElements = relativePath.split("/"); + try (var openTree = pathTree.open()) { + for (var root : openTree.getRoots()) { + if (containsCaseSensitivePath(root, pathElements)) { + return true; + } + } + } catch (IOException e) { + throw new UncheckedIOException(e); + } + return false; + } + + private static boolean containsCaseSensitivePath(Path root, String[] pathElements) { + var parent = root; + for (String pathElement : pathElements) { + if (!Files.isDirectory(parent)) { + return false; + } + try (Stream stream = Files.list(parent)) { + var i = stream.iterator(); + Path match = null; + while (i.hasNext()) { + final Path next = i.next(); + if (pathElement.equals(next.getFileName().toString())) { + match = next; + break; + } + } + if (match == null) { + return false; + } + parent = match; + } catch (IOException e) { + throw new UncheckedIOException(e); + } catch (Exception e) { + throw e; + } + } + return true; + } +} diff --git a/independent-projects/bootstrap/app-model/src/main/java/io/quarkus/paths/PathTreeVisit.java b/independent-projects/bootstrap/app-model/src/main/java/io/quarkus/paths/PathTreeVisit.java index 27d466f1e5d4f..605124615ea74 100644 --- a/independent-projects/bootstrap/app-model/src/main/java/io/quarkus/paths/PathTreeVisit.java +++ b/independent-projects/bootstrap/app-model/src/main/java/io/quarkus/paths/PathTreeVisit.java @@ -13,10 +13,10 @@ class PathTreeVisit implements PathVisit { - static void walk(Path root, Path rootDir, PathFilter pathFilter, Map multiReleaseMapping, + static void walk(Path root, Path rootDir, Path walkDir, PathFilter pathFilter, Map multiReleaseMapping, PathVisitor visitor) { final PathTreeVisit visit = new PathTreeVisit(root, rootDir, pathFilter, multiReleaseMapping); - try (Stream files = Files.walk(rootDir)) { + try (Stream files = Files.walk(walkDir)) { final Iterator i = files.iterator(); while (i.hasNext()) { if (!visit.setCurrent(i.next())) { @@ -89,7 +89,7 @@ boolean isStopWalking() { @Override public String getRelativePath(String separator) { if (relativePath == null) { - return PathUtils.asString(baseDir.relativize(current), separator); + return PathTreeUtils.asString(baseDir.relativize(current), separator); } if (!current.getFileSystem().getSeparator().equals(separator)) { return relativePath.replace(current.getFileSystem().getSeparator(), separator); diff --git a/independent-projects/bootstrap/app-model/src/main/java/io/quarkus/paths/PathUtils.java b/independent-projects/bootstrap/app-model/src/main/java/io/quarkus/paths/PathUtils.java deleted file mode 100644 index a87a7f83e3e0a..0000000000000 --- a/independent-projects/bootstrap/app-model/src/main/java/io/quarkus/paths/PathUtils.java +++ /dev/null @@ -1,26 +0,0 @@ -package io.quarkus.paths; - -import java.nio.file.Path; - -public interface PathUtils { - - static String asString(final Path path, String separator) { - if (path.getFileSystem().getSeparator().equals(separator)) { - return path.toString(); - } - final int nameCount = path.getNameCount(); - if (nameCount == 0) { - return ""; - } - if (nameCount == 1) { - return path.getName(0).toString(); - } - final StringBuilder s = new StringBuilder(); - s.append(path.getName(0)); - for (int i = 1; i < nameCount; ++i) { - s.append('/').append(path.getName(i).toString()); - } - return s.toString(); - } - -} diff --git a/independent-projects/bootstrap/app-model/src/main/java/io/quarkus/paths/SharedArchivePathTree.java b/independent-projects/bootstrap/app-model/src/main/java/io/quarkus/paths/SharedArchivePathTree.java index edfef2d0c1d49..a8cc3f4b41962 100644 --- a/independent-projects/bootstrap/app-model/src/main/java/io/quarkus/paths/SharedArchivePathTree.java +++ b/independent-projects/bootstrap/app-model/src/main/java/io/quarkus/paths/SharedArchivePathTree.java @@ -153,6 +153,11 @@ public void walk(PathVisitor visitor) { delegate.walk(visitor); } + @Override + public void walkIfContains(String relativePath, PathVisitor visitor) { + delegate.walkIfContains(relativePath, visitor); + } + @Override public T apply(String relativePath, Function func) { return delegate.apply(relativePath, func); diff --git a/independent-projects/bootstrap/app-model/src/test/java/io/quarkus/paths/WalkSubtreeTest.java b/independent-projects/bootstrap/app-model/src/test/java/io/quarkus/paths/WalkSubtreeTest.java new file mode 100644 index 0000000000000..b1b19f35280c5 --- /dev/null +++ b/independent-projects/bootstrap/app-model/src/test/java/io/quarkus/paths/WalkSubtreeTest.java @@ -0,0 +1,120 @@ +package io.quarkus.paths; + +import static org.assertj.core.api.Assertions.assertThat; + +import java.io.File; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.ArrayList; + +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.io.TempDir; + +import io.quarkus.fs.util.ZipUtils; + +public class WalkSubtreeTest { + + @TempDir + static Path testDir; + static Path testZip; + + private static void createFile(String path) throws Exception { + var file = testDir.resolve(path); + Files.createDirectories(file.getParent()); + Files.createFile(file); + } + + @BeforeAll + public static void createFiles() throws Exception { + createFile("a/1.txt"); + createFile("a/aa/1.txt"); + createFile("a/aa/aaa/1.txt"); + createFile("a/aa/aaa/2.txt"); + createFile("a/aa/aaa/aaaa/1.txt"); + + createFile("b/aa/aaa/1.txt"); + createFile("b/aa/aaa/2.txt"); + createFile("b/aa/aaa/aaaa/1.txt"); + + testZip = testDir.resolve("archive.zip"); + ZipUtils.zip(testDir, testZip); + } + + @Test + public void walkDirectorySubtree() { + var list = new ArrayList(); + PathTree.ofDirectoryOrArchive(testDir) + .walkIfContains("a/aa", visit -> list.add(visit.getRelativePath())); + assertThat(list).containsExactlyInAnyOrder( + "a/aa", + "a/aa/1.txt", + "a/aa/aaa", + "a/aa/aaa/1.txt", + "a/aa/aaa/aaaa", + "a/aa/aaa/aaaa/1.txt", + "a/aa/aaa/2.txt"); + } + + @Test + public void walkJarSubtree() { + var list = new ArrayList(); + PathTree.ofDirectoryOrArchive(testZip) + .walkIfContains("a/aa", visit -> list.add(visit.getRelativePath())); + assertThat(list).containsExactlyInAnyOrder( + "a/aa", + "a/aa/1.txt", + "a/aa/aaa", + "a/aa/aaa/1.txt", + "a/aa/aaa/aaaa", + "a/aa/aaa/aaaa/1.txt", + "a/aa/aaa/2.txt"); + } + + @Test + public void walkMultirootTree() { + var list = new ArrayList(); + new MultiRootPathTree( + PathTree.ofDirectoryOrArchive(testDir.resolve("a/aa")), + PathTree.ofDirectoryOrArchive(testDir.resolve("b/aa"))) + .walk(visit -> list.add(ensureForwardSlash(testDir.relativize(visit.getPath()).toString()))); + assertThat(list).containsExactlyInAnyOrder( + "a/aa", + "a/aa/1.txt", + "a/aa/aaa", + "a/aa/aaa/1.txt", + "a/aa/aaa/aaaa", + "a/aa/aaa/aaaa/1.txt", + "a/aa/aaa/2.txt", + "b/aa", + "b/aa/aaa", + "b/aa/aaa/1.txt", + "b/aa/aaa/aaaa", + "b/aa/aaa/aaaa/1.txt", + "b/aa/aaa/2.txt"); + } + + @Test + public void walkMultirootSubtree() { + var list = new ArrayList(); + new MultiRootPathTree( + PathTree.ofDirectoryOrArchive(testDir.resolve("a/aa")), + PathTree.ofDirectoryOrArchive(testDir.resolve("b/aa"))) + .walkIfContains("aaa", visit -> list.add(ensureForwardSlash(testDir.relativize(visit.getPath()).toString()))); + assertThat(list).containsExactlyInAnyOrder( + "a/aa/aaa", + "a/aa/aaa/1.txt", + "a/aa/aaa/2.txt", + "a/aa/aaa/aaaa", + "a/aa/aaa/aaaa/1.txt", + "b/aa/aaa", + "b/aa/aaa/1.txt", + "b/aa/aaa/2.txt", + "b/aa/aaa/aaaa", + "b/aa/aaa/aaaa/1.txt"); + } + + private static String ensureForwardSlash(String path) { + return File.separatorChar == '/' ? path : path.replace(File.separatorChar, '/'); + } +}