From 49c07dcebe234ef72bc7480741092b249cbaf5f5 Mon Sep 17 00:00:00 2001 From: Samuel SCHNEGG Date: Sat, 27 Sep 2025 16:08:06 +0200 Subject: [PATCH 1/5] Hierarchy path predicates --- CHANGELOG.md | 6 + ROADMAP.md | 2 +- .../PathPredicateBuilder.java | 79 +++ .../PathPredicates.java | 93 +++ .../jfiletreeprettyprinter/PathUtils.java | 185 ++++++ .../PathPredicateBuilderTest.java | 534 +++++++++++++++--- 6 files changed, 825 insertions(+), 74 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a3b246a..fa769ae 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,12 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +--- +## [0.0.5] - Unreleased + +### Added +- New path predicates: `hasParentMatching`, `hasAncestorMatching`, `hasDirectChildMatching`, `hasDescendantMatching`, `hasSiblingMatching` + --- ## [0.0.4] - 2025-09-27 diff --git a/ROADMAP.md b/ROADMAP.md index 12266be..a3f3cd8 100644 --- a/ROADMAP.md +++ b/ROADMAP.md @@ -21,7 +21,7 @@ - [x] Option: Line extension (=additional text after the file name) ## To do -- [ ] More `PathPredicates` functions! +- [x] More `PathPredicates` functions! - [ ] Option: custom emojis ## Backlog / To analyze / To implement if requested diff --git a/src/main/java/io/github/computerdaddyguy/jfiletreeprettyprinter/PathPredicateBuilder.java b/src/main/java/io/github/computerdaddyguy/jfiletreeprettyprinter/PathPredicateBuilder.java index 66d03d1..d6c73f7 100644 --- a/src/main/java/io/github/computerdaddyguy/jfiletreeprettyprinter/PathPredicateBuilder.java +++ b/src/main/java/io/github/computerdaddyguy/jfiletreeprettyprinter/PathPredicateBuilder.java @@ -158,4 +158,83 @@ public PathPredicateBuilder isFile() { return pathTest(PathPredicates.isFile()); } + // ---------- Hierarchy ---------- + + /** + * Adds a condition that tests the direct parent of the path. + * + * @param parentPredicate the predicate to apply on the direct parent + * + * @return this builder for chaining + * + * @see PathPredicates#hasParentMatching(Predicate) + */ + public PathPredicateBuilder hasParentMatching(Predicate parentPredicate) { + return pathTest(PathPredicates.hasParentMatching(parentPredicate)); + } + + /** + * Adds a condition that tests all ancestors of the path (stopping at the first match). + * + * The condition is satisfied if the given {@code ancestorPredicate} evaluates + * to {@code true} for any ancestor in the {@link Path#getParent()} chain. + * + * @param ancestorPredicate the predicate to apply on each ancestor + * + * @return this builder for chaining + * + * @see PathPredicates#hasAncestorMatching(Predicate) + */ + public PathPredicateBuilder hasAncestorMatching(Predicate ancestorPredicate) { + return pathTest(PathPredicates.hasAncestorMatching(ancestorPredicate)); + } + + /** + * Adds a condition that tests the direct children of the path. + * + * The condition is satisfied if the given {@code childPredicate} evaluates + * to {@code true} for at least one direct child of the tested path. + * + * @param childPredicate the predicate to apply on each direct child + * + * @return this builder for chaining + * + * @see PathPredicates#hasDirectChildMatching(Predicate) + */ + public PathPredicateBuilder hasDirectChildMatching(Predicate childPredicate) { + return pathTest(PathPredicates.hasDirectChildMatching(childPredicate)); + } + + /** + * Adds a condition that tests all descendants of the path (children at any depth). + * + * The condition is satisfied if the given {@code descendantPredicate} evaluates + * to {@code true} for at least one descendant in the directory tree. + * + * @param descendantPredicate the predicate to apply on each descendant + * + * @return this builder for chaining + * + * @see PathPredicates#hasDescendantMatching(Predicate) + */ + public PathPredicateBuilder hasDescendantMatching(Predicate descendantPredicate) { + return pathTest(PathPredicates.hasDescendantMatching(descendantPredicate)); + } + + /** + * Adds a condition that tests the siblings of the path. + * + * The condition is satisfied if the given {@code siblingPredicate} evaluates + * to {@code true} for at least one sibling of the tested path. + * + * @param siblingPredicate the predicate to apply on each sibling + * + * @return this builder for chaining + * + * @see PathPredicates#hasSiblingMatching(Predicate) + */ + public PathPredicateBuilder hasSiblingMatching(Predicate siblingPredicate) { + return pathTest(PathPredicates.hasSiblingMatching(siblingPredicate)); + } + } \ No newline at end of file diff --git a/src/main/java/io/github/computerdaddyguy/jfiletreeprettyprinter/PathPredicates.java b/src/main/java/io/github/computerdaddyguy/jfiletreeprettyprinter/PathPredicates.java index 3048a37..600f5fa 100644 --- a/src/main/java/io/github/computerdaddyguy/jfiletreeprettyprinter/PathPredicates.java +++ b/src/main/java/io/github/computerdaddyguy/jfiletreeprettyprinter/PathPredicates.java @@ -1,6 +1,7 @@ package io.github.computerdaddyguy.jfiletreeprettyprinter; import java.nio.file.Path; +import java.util.Objects; import java.util.function.Predicate; import java.util.regex.Pattern; import org.jspecify.annotations.NullMarked; @@ -118,4 +119,96 @@ public static Predicate isFile() { return PathUtils::isFile; } + // ---------- Hierarchy ---------- + + /** + * Creates a predicate that evaluates the direct parent of a given path. + * The returned predicate applies the provided {@code parentPredicate} to {@link Path#getParent()} of the tested path. + * If tested path has no parent, the returned predicate will always return {@code false}. + * + * @param parentPredicate the predicate to apply on the parent path (must not be {@code null}) + * + * @return a predicate that returns {@code true} if the parent of the tested path matches the given predicate + * + * @throws NullPointerException if {@code parentPredicate} is {@code null} + */ + public static Predicate hasParentMatching(Predicate parentPredicate) { + Objects.requireNonNull(parentPredicate, "parentPredicate is null"); + return path -> PathUtils.hasParentMatching(path, parentPredicate); + } + + /** + * Creates a predicate that evaluates all ancestors of a given path. + *

+ * The returned predicate applies the provided {@code ancestorPredicate} + * to each parent in the chain obtained via successive calls to {@link Path#getParent()}. + * If any ancestor matches, the predicate returns {@code true}. + *

+ * If the tested path has no parent, the returned predicate always returns {@code false}. + * + * @param ancestorPredicate the predicate to apply on each ancestor path (must not be {@code null}) + * + * @return a predicate that returns {@code true} if any ancestor of the tested path matches the given predicate + * + * @throws NullPointerException if {@code ancestorPredicate} is {@code null} + */ + public static Predicate hasAncestorMatching(Predicate ancestorPredicate) { + Objects.requireNonNull(ancestorPredicate, "ancestorPredicate is null"); + return path -> PathUtils.hasAncestorMatching(path, ancestorPredicate); + } + + /** + * Creates a predicate that evaluates the direct children of a given path. + *

+ * The returned predicate applies the provided {@code childPredicate} to each + * direct child of the tested path. The predicate returns {@code true} if at least + * one child matches. If the path is not a directory, the predicate always returns {@code false}. + * + * @param childPredicate the predicate to apply on each direct child + * + * @return a predicate that returns {@code true} if at least one direct child matches + * + * @throws NullPointerException if {@code childPredicate} is {@code null} + */ + public static Predicate hasDirectChildMatching(Predicate childPredicate) { + Objects.requireNonNull(childPredicate, "childPredicate is null"); + return path -> PathUtils.hasDirectChildMatching(path, childPredicate); + } + + /** + * Creates a predicate that evaluates all descendants (children at any depth) of a given path. + *

+ * The returned predicate applies the provided {@code descendantPredicate} recursively to each + * child and sub-child. It returns {@code true} if at least one descendant matches. + * + * @param descendantPredicate the predicate to apply on each descendant + * + * @return a predicate that returns {@code true} if at least one descendant matches + * + * @throws NullPointerException if {@code descendantPredicate} is {@code null} + */ + public static Predicate hasDescendantMatching(Predicate descendantPredicate) { + Objects.requireNonNull(descendantPredicate, "descendantPredicate is null"); + return path -> PathUtils.hasDescendantMatching(path, descendantPredicate); + } + + /** + * Creates a predicate that evaluates the siblings of a given path. + *

+ * The returned predicate applies the provided {@code siblingPredicate} to each + * sibling of the tested path (other files/directories in the same parent). + * The predicate returns {@code true} if at least one sibling matches. + * If the tested path has no parent, the predicate always returns {@code false}. + * + * @param siblingPredicate the predicate to apply on each sibling + * + * @return a predicate that returns {@code true} if at least one sibling matches + * + * @throws NullPointerException if {@code siblingPredicate} is {@code null} + */ + public static Predicate hasSiblingMatching(Predicate siblingPredicate) { + Objects.requireNonNull(siblingPredicate, "siblingPredicate is null"); + return path -> PathUtils.hasSiblingMatching(path, siblingPredicate); + } + } diff --git a/src/main/java/io/github/computerdaddyguy/jfiletreeprettyprinter/PathUtils.java b/src/main/java/io/github/computerdaddyguy/jfiletreeprettyprinter/PathUtils.java index 74ed7ef..77e0689 100644 --- a/src/main/java/io/github/computerdaddyguy/jfiletreeprettyprinter/PathUtils.java +++ b/src/main/java/io/github/computerdaddyguy/jfiletreeprettyprinter/PathUtils.java @@ -1,7 +1,14 @@ package io.github.computerdaddyguy.jfiletreeprettyprinter; +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.Objects; +import java.util.function.Predicate; import java.util.regex.Pattern; +import java.util.stream.Stream; /** * Utility class providing common {@link Predicate} implementations @@ -28,8 +35,12 @@ private PathUtils() { * @param name the expected file name (without parent directories) * * @return {@code true} if the path's file name equals {@code name} + * + * @throws NullPointerException if {@code path} or {@code name} is {@code null} */ public static boolean hasName(Path path, String name) { + Objects.requireNonNull(path, "path is null"); + Objects.requireNonNull(name, "name is null"); return path.getFileName().toString().equals(name); } @@ -41,8 +52,12 @@ public static boolean hasName(Path path, String name) { * @param name the expected file name (case-insensitive) * * @return {@code true} if the path's file name equals {@code name}, ignoring case + * + * @throws NullPointerException if {@code path} or {@code name} is {@code null} */ public static boolean hasNameIgnoreCase(Path path, String name) { + Objects.requireNonNull(path, "path is null"); + Objects.requireNonNull(name, "name is null"); return path.getFileName().toString().equalsIgnoreCase(name); } @@ -53,8 +68,12 @@ public static boolean hasNameIgnoreCase(Path path, String name) { * @param pattern the regex pattern to apply to the file name * * @return {@code true} if the file name matches the pattern + * + * @throws NullPointerException if {@code path} or {@code pattern} is {@code null} */ public static boolean hasNameMatching(Path path, Pattern pattern) { + Objects.requireNonNull(path, "path is null"); + Objects.requireNonNull(pattern, "pattern is null"); return pattern.matcher(path.getFileName().toString()).matches(); } @@ -65,8 +84,12 @@ public static boolean hasNameMatching(Path path, Pattern pattern) { * @param suffix the suffix to test (e.g. ".log", ".txt") * * @return {@code true} if the file name ends with the given suffix + * + * @throws NullPointerException if {@code path} or {@code suffix} is {@code null} */ public static boolean hasNameEndingWith(Path path, String suffix) { + Objects.requireNonNull(path, "path is null"); + Objects.requireNonNull(suffix, "suffix is null"); return path.getFileName().toString().endsWith(suffix); } @@ -81,8 +104,12 @@ public static boolean hasNameEndingWith(Path path, String suffix) { * @param extension the extension to test (without the dot) * * @return {@code true} if the file name ends with {@code "." + extension} + * + * @throws NullPointerException if {@code path} or {@code extension} is {@code null} */ public static boolean hasExtension(Path path, String extension) { + Objects.requireNonNull(path, "path is null"); + Objects.requireNonNull(extension, "extension is null"); return hasNameEndingWith(path, "." + extension); } @@ -94,8 +121,11 @@ public static boolean hasExtension(Path path, String extension) { * @param path the path to test * * @return {@code true} if the path is a directory + * + * @throws NullPointerException if {@code path} is {@code null} */ public static boolean isDirectory(Path path) { + Objects.requireNonNull(path, "path is null"); return path.toFile().isDirectory(); } @@ -105,9 +135,164 @@ public static boolean isDirectory(Path path) { * @param path the path to test * * @return {@code true} if the path is a file + * + * @throws NullPointerException if {@code path} is {@code null} */ public static boolean isFile(Path path) { + Objects.requireNonNull(path, "path is null"); return path.toFile().isFile(); } + // ---------- Hierarchy ---------- + + /** + * Tests whether the direct parent of the given path matches the provided predicate. + * + * @param path the path whose parent should be tested (must not be {@code null}) + * @param parentPredicate the predicate to apply to the direct parent (must not be {@code null}) + * + * @return {@code true} if the path has a parent and that parent matches the predicate, + * {@code false} otherwise + * + * @throws NullPointerException if {@code path} or {@code parentPredicate} is {@code null} + */ + public static boolean hasParentMatching(Path path, Predicate parentPredicate) { + Objects.requireNonNull(path, "path is null"); + Objects.requireNonNull(parentPredicate, "parentPredicate is null"); + + if (path.getParent() == null) { + return false; + } + return parentPredicate.test(path.getParent()); + } + + /** + * Tests whether any ancestor of the given path matches the provided predicate. + * + *

The test is applied recursively up the parent chain (using {@link Path#getParent()}) + * until the root is reached or the predicate returns {@code true}. + * + * @param path the path whose ancestors should be tested (must not be {@code null}) + * @param ancestorPredicate the predicate to apply to each ancestor (must not be {@code null}) + * + * @return {@code true} if any ancestor of the path matches the predicate, + * {@code false} otherwise + * + * @throws NullPointerException if {@code path} or {@code ancestorPredicate} is {@code null} + */ + public static boolean hasAncestorMatching(Path path, Predicate ancestorPredicate) { + Objects.requireNonNull(path, "path is null"); + Objects.requireNonNull(ancestorPredicate, "ancestorPredicate is null"); + + Path parent = path.getParent(); + while (parent != null) { + if (ancestorPredicate.test(parent)) { + return true; + } + parent = parent.getParent(); + } + return false; + } + + /** + * Tests whether the given path has at least one direct child that matches the provided predicate. + * + *

The method checks only immediate children of the path (not recursive). + * If the path is not a directory, the result is always {@code false}. + * + * @param path the directory whose direct children should be tested (must not be {@code null}) + * @param childPredicate the predicate to apply to each direct child (must not be {@code null}) + * + * @return {@code true} if any direct child matches the predicate, + * {@code false} if none match, or if the path is not a directory + * + * @throws NullPointerException if {@code path} or {@code childPredicate} is {@code null} + */ + public static boolean hasDirectChildMatching(Path path, Predicate childPredicate) { + Objects.requireNonNull(path, "path is null"); + Objects.requireNonNull(childPredicate, "childPredicate is null"); + + File file = path.toFile(); + if (!file.isDirectory()) { + return false; + } + File[] children = file.listFiles(); + if (children == null) { // Only if I/O error occurred when listing files + return false; + } + for (File child : children) { + if (childPredicate.test(child.toPath())) { + return true; + } + } + return false; + } + + /** + * Tests whether the given path has at least one descendant (child, grandchild, etc.) + * that matches the provided predicate. + * + *

The method walks the file tree starting at the given path, excluding the path itself, + * and applies the predicate to all discovered descendants. + * If the path is not a directory, the result is always {@code false}. + * + * @param path the directory whose descendants should be tested (must not be {@code null}) + * @param descendantPredicate the predicate to apply to each descendant (must not be {@code null}) + * + * @return {@code true} if any descendant matches the predicate, + * {@code false} if none match or if the path is not a directory + * + * @throws NullPointerException if {@code path} or {@code descendantPredicate} is {@code null} + * @throws UncheckedIOException if an I/O error occurs while traversing the directory + */ + public static boolean hasDescendantMatching(Path path, Predicate descendantPredicate) { + Objects.requireNonNull(path, "path is null"); + Objects.requireNonNull(descendantPredicate, "descendantPredicate is null"); + File file = path.toFile(); + if (!file.isDirectory()) { + return false; + } + try (Stream stream = Files.walk(path)) { + return stream + .skip(1) // skip the root path itself + .anyMatch(descendantPredicate); + } catch (IOException e) { + throw new UncheckedIOException("Exception while walking files of " + path, e); + } + } + + /** + * Tests whether the given path has at least one sibling that matches the provided predicate. + * + *

The siblings are the other entries in the same parent directory. + * The path itself is excluded from testing. If the path has no parent, + * the result is always {@code false}. + * + * @param path the path whose siblings should be tested (must not be {@code null}) + * @param siblingPredicate the predicate to apply to each sibling (must not be {@code null}) + * + * @return {@code true} if any sibling matches the predicate, + * {@code false} if none match or if the path has no parent + * + * @throws NullPointerException if {@code path} or {@code siblingPredicate} is {@code null} + */ + public static boolean hasSiblingMatching(Path path, Predicate siblingPredicate) { + Objects.requireNonNull(path, "path is null"); + Objects.requireNonNull(siblingPredicate, "siblingPredicate is null"); + Path parent = path.getParent(); + if (parent == null) { + return false; + } + File[] siblings = parent.toFile().listFiles(); + if (siblings == null) { // Only if I/O error occurred when listing files + return false; + } + for (File sibling : siblings) { + if (!sibling.toPath().equals(path) && siblingPredicate.test(sibling.toPath())) { + return true; + } + } + return false; + } + } diff --git a/src/test/java/io/github/computerdaddyguy/jfiletreeprettyprinter/PathPredicateBuilderTest.java b/src/test/java/io/github/computerdaddyguy/jfiletreeprettyprinter/PathPredicateBuilderTest.java index 62ecfcd..7f1285e 100644 --- a/src/test/java/io/github/computerdaddyguy/jfiletreeprettyprinter/PathPredicateBuilderTest.java +++ b/src/test/java/io/github/computerdaddyguy/jfiletreeprettyprinter/PathPredicateBuilderTest.java @@ -1,10 +1,13 @@ package io.github.computerdaddyguy.jfiletreeprettyprinter; import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatNullPointerException; import java.io.IOException; +import java.nio.file.Files; import java.nio.file.Path; import java.util.regex.Pattern; +import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.io.TempDir; @@ -73,104 +76,489 @@ void fileTest_noMatch() { // ---------- Name ---------- - @Test - void hasName_match() { - var path = createTempFile("myFile.java"); - var filter = PathPredicates.builder().hasName("myFile.java").build(); - assertThat(filter.test(path)).isTrue(); - } + @Nested + class HasName { - @Test - void hasName_is_case_sensitive() { - var path = createTempFile("myFile.java"); - var filter = PathPredicates.builder().hasName("myfile.java").build(); - assertThat(filter.test(path)).isFalse(); - } + @Test + void match() { + var path = createTempFile("myFile.java"); + var filter = PathPredicates.builder().hasName("myFile.java").build(); + assertThat(filter.test(path)).isTrue(); + } + + @Test + void no_match_case_sensitive() { + var path = createTempFile("myFile.java"); + var filter = PathPredicates.builder().hasName("MYFILE.JAVA").build(); + assertThat(filter.test(path)).isFalse(); + } + + @Test + void no_match() { + var path = createTempFile("myFile.java"); + var filter = PathPredicates.builder().hasName("myFile.php").build(); + assertThat(filter.test(path)).isFalse(); + } - @Test - void hasNameIgnoreCase_match() { - var path = createTempFile("myFile.java"); - var filter = PathPredicates.builder().hasNameIgnoreCase("myFile.java").build(); - assertThat(filter.test(path)).isTrue(); } - @Test - void hasNameIgnoreCase_is_case_insensitive() { - var path = createTempFile("myFile.java"); - var filter = PathPredicates.builder().hasNameIgnoreCase("myfile.java").build(); - assertThat(filter.test(path)).isTrue(); + @Nested + class HasNameIgnoreCase { + + @Test + void match() { + var path = createTempFile("myFile.java"); + var filter = PathPredicates.builder().hasNameIgnoreCase("myFile.java").build(); + assertThat(filter.test(path)).isTrue(); + } + + @Test + void match_case_sensitive() { + var path = createTempFile("myFile.java"); + var filter = PathPredicates.builder().hasNameIgnoreCase("MYFILE.JAVA").build(); + assertThat(filter.test(path)).isTrue(); + } + + @Test + void no_match() { + var path = createTempFile("myFile.java"); + var filter = PathPredicates.builder().hasNameIgnoreCase("myFile.php").build(); + assertThat(filter.test(path)).isFalse(); + } + } - @Test - void hasNameMatching_match() { - var path = createTempFile("myFile.java"); - var filter = PathPredicates.builder().hasNameMatching(Pattern.compile("my.*")).build(); - assertThat(filter.test(path)).isTrue(); + @Nested + class HasNameMatching { + + @Test + void match() { + var path = createTempFile("myFile.java"); + var filter = PathPredicates.builder().hasNameMatching(Pattern.compile("my.*")).build(); + assertThat(filter.test(path)).isTrue(); + } + + @Test + void no_match() { + var path = createTempFile("myFile.java"); + var filter = PathPredicates.builder().hasNameMatching(Pattern.compile("ma.*")).build(); + assertThat(filter.test(path)).isFalse(); + } + } - @Test - void hasNameMatching_noMatch() { - var path = createTempFile("myFile.java"); - var filter = PathPredicates.builder().hasNameMatching(Pattern.compile("ma.*")).build(); - assertThat(filter.test(path)).isFalse(); + @Nested + class HasNameEndingWith { + + @Test + void match() { + var path = createTempFile("myFile.java"); + var filter = PathPredicates.builder().hasNameEndingWith(".java").build(); + assertThat(filter.test(path)).isTrue(); + } + + @Test + void no_match() { + var path = createTempFile("myFile.java"); + var filter = PathPredicates.builder().hasNameEndingWith(".php").build(); + assertThat(filter.test(path)).isFalse(); + } + + @Test + void no_match_case_sensitive() { + var path = createTempFile("myFile.java"); + var filter = PathPredicates.builder().hasNameEndingWith(".Java").build(); + assertThat(filter.test(path)).isFalse(); + } + } - @Test - void hasNameEndingWith_match() { - var path = createTempFile("myFile.java"); - var filter = PathPredicates.builder().hasNameEndingWith(".java").build(); - assertThat(filter.test(path)).isTrue(); + @Nested + class HasExtension { + + @Test + void match() { + var path = createTempFile("myFile.java"); + var filter = PathPredicates.builder().hasExtension("java").build(); + assertThat(filter.test(path)).isTrue(); + } + + @Test + void no_match() { + var path = createTempFile("myFile.java"); + var filter = PathPredicates.builder().hasExtension("php").build(); + assertThat(filter.test(path)).isFalse(); + } + + @Test + void no_match_case_sensitive() { + var path = createTempFile("myFile.java"); + var filter = PathPredicates.builder().hasExtension("Java").build(); + assertThat(filter.test(path)).isFalse(); + } + + @Test + void no_match_no_extension() { + var path = createTempFile("myFilejava"); + var filter = PathPredicates.builder().hasExtension("java").build(); + assertThat(filter.test(path)).isFalse(); + } + } - @Test - void hasNameEndingWith_is_case_sensitive() { - var path = createTempFile("myFile.java"); - var filter = PathPredicates.builder().hasNameEndingWith(".Java").build(); - assertThat(filter.test(path)).isFalse(); + // ---------- Type ---------- + + @Nested + class IsDirectory { + + @Test + void match() { + var path = createTempDir("myDir"); + var filter = PathPredicates.builder().isDirectory().build(); + assertThat(filter.test(path)).isTrue(); + } + + @Test + void no_match() { + var path = createTempFile("myFile.java"); + var filter = PathPredicates.builder().isDirectory().build(); + assertThat(filter.test(path)).isFalse(); + } + } - @Test - void hasExtension_match() { - var path = createTempFile("myFile.java"); - var filter = PathPredicates.builder().hasExtension("java").build(); - assertThat(filter.test(path)).isTrue(); + @Nested + class IsFile { + + @Test + void match() { + var path = createTempFile("myFile.java"); + var filter = PathPredicates.builder().isFile().build(); + assertThat(filter.test(path)).isTrue(); + } + + @Test + void no_match() { + var path = createTempDir("myDir"); + var filter = PathPredicates.builder().isFile().build(); + assertThat(filter.test(path)).isFalse(); + } + } - @Test - void hasExtension_is_case_sensitive() { - var path = createTempFile("myFile.java"); - var filter = PathPredicates.builder().hasExtension("Java").build(); - assertThat(filter.test(path)).isFalse(); + // ---------- Hierarchy ---------- + + @Nested + class HasParentMatching { + + @Test + void null_predicate_throws_NPE() { + assertThatNullPointerException() + .isThrownBy(() -> PathPredicates.builder().hasParentMatching(null)); + } + + @Test + void matches_direct_parent() throws IOException { + var parent = createTempDir("parent"); + var child = Files.createDirectory(parent.resolve("child")); + var grandChild = Files.createDirectory(child.resolve("grandchild")); + + var filter = PathPredicates.builder().hasParentMatching( + p -> p.getFileName().toString().equals("child") + ).build(); + + assertThat(filter.test(grandChild)).isTrue(); + } + + @Test + void does_not_match_grandparent() throws IOException { + var parent = createTempDir("parent"); + var child = Files.createDirectory(parent.resolve("child")); + var grandChild = Files.createDirectory(child.resolve("grandchild")); + + var filter = PathPredicates.builder().hasParentMatching( + p -> p.getFileName().toString().equals("parent") + ).build(); + + assertThat(filter.test(grandChild)).isFalse(); + } + + @Test + void does_not_match_sibling() throws IOException { + var parent = createTempDir("parent"); + var child = Files.createDirectory(parent.resolve("child")); + var grandChild1 = Files.createDirectory(child.resolve("grandchild1")); + var grandChild2 = Files.createDirectory(child.resolve("grandchild2")); + + var filter = PathPredicates.builder().hasParentMatching( + p -> p.getFileName().toString().equals("grandchild2") + ).build(); + + assertThat(filter.test(grandChild1)).isFalse(); + } + + @Test + void root_has_no_parent() throws IOException { + var root = Path.of("root"); + + var filter = PathPredicates.builder().hasParentMatching( + p -> true + ).build(); + + assertThat(filter.test(root)).isFalse(); + } + } - // ---------- Type ---------- + @Nested + class HasAncestorMatching { + + @Test + void null_predicate_throws_NPE() { + assertThatNullPointerException() + .isThrownBy(() -> PathPredicates.builder().hasAncestorMatching(null)); + } + + @Test + void matches_direct_parent() throws IOException { + var parent = createTempDir("parent"); + var child = Files.createDirectory(parent.resolve("child")); + var grandChild = Files.createDirectory(child.resolve("grandchild")); + + var filter = PathPredicates.builder().hasAncestorMatching( + p -> p.getFileName().toString().equals("child") + ).build(); + + assertThat(filter.test(grandChild)).isTrue(); + } + + @Test + void matches_grandparent() throws IOException { + var parent = createTempDir("parent"); + var child = Files.createDirectory(parent.resolve("child")); + var grandChild = Files.createDirectory(child.resolve("grandchild")); + + var filter = PathPredicates.builder().hasAncestorMatching( + p -> p.getFileName().toString().equals("parent") + ).build(); + + assertThat(filter.test(grandChild)).isTrue(); + } + + @Test + void does_not_match_sibling() throws IOException { + var parent = createTempDir("parent"); + var child = Files.createDirectory(parent.resolve("child")); + var grandChild1 = Files.createDirectory(child.resolve("grandchild1")); + var grandChild2 = Files.createDirectory(child.resolve("grandchild2")); + + var filter = PathPredicates.builder().hasAncestorMatching( + p -> p.getNameCount() > 0 && p.getFileName().toString().equals("grandchild2") + ).build(); + + assertThat(filter.test(grandChild1)).isFalse(); + } + + @Test + void root_has_no_parent() throws IOException { + var root = Path.of("root"); + + var filter = PathPredicates.builder().hasAncestorMatching( + p -> true + ).build(); + + assertThat(filter.test(root)).isFalse(); + } - @Test - void isDirectory_match() { - var path = createTempDir("myDir"); - var filter = PathPredicates.builder().isDirectory().build(); - assertThat(filter.test(path)).isTrue(); } - @Test - void isDirectory_noMatch() { - var path = createTempFile("myFile.java"); - var filter = PathPredicates.builder().isDirectory().build(); - assertThat(filter.test(path)).isFalse(); + @Nested + class HasDirectChildMatching { + + @Test + void null_predicate_throws_NPE() { + assertThatNullPointerException() + .isThrownBy(() -> PathPredicates.builder().hasDirectChildMatching(null)); + } + + @Test + void matches_direct_child() throws IOException { + var parent = createTempDir("parent"); + var child = Files.createDirectory(parent.resolve("child")); + + var filter = PathPredicates.builder().hasDirectChildMatching( + p -> p.getFileName().toString().equals("child") + ).build(); + + assertThat(filter.test(parent)).isTrue(); + } + + @Test + void does_not_match_grandchild() throws IOException { + var parent = createTempDir("parent"); + var child = Files.createDirectory(parent.resolve("child")); + var grandchild = Files.createDirectory(child.resolve("grandchild")); + + var filter = PathPredicates.builder().hasDirectChildMatching( + p -> p.getFileName().toString().equals("grandchild") + ).build(); + + assertThat(filter.test(parent)).isFalse(); + } + + @Test + void does_not_match_if_not_directory() throws IOException { + var parent = createTempFile("parent"); + + var filter = PathPredicates.builder().hasDirectChildMatching( + p -> true + ).build(); + + assertThat(filter.test(parent)).isFalse(); + } + + @Test + void does_not_match_if_no_child() throws IOException { + var parent = createTempDir("parent"); + + var filter = PathPredicates.builder().hasDirectChildMatching( + p -> true + ).build(); + + assertThat(filter.test(parent)).isFalse(); + } + } - @Test - void isFile_match() { - var path = createTempFile("myFile.java"); - var filter = PathPredicates.builder().isFile().build(); - assertThat(filter.test(path)).isTrue(); + @Nested + class HasDescendantMatching { + + @Test + void null_predicate_throws_NPE() { + assertThatNullPointerException() + .isThrownBy(() -> PathPredicates.builder().hasDescendantMatching(null)); + } + + @Test + void matches_grandchild() throws IOException { + var parent = createTempDir("parent"); + var child = Files.createDirectory(parent.resolve("child")); + var grandchild = Files.createDirectory(child.resolve("grandchild")); + + var filter = PathPredicates.builder().hasDescendantMatching( + p -> p.getFileName().toString().equals("grandchild") + ).build(); + + assertThat(filter.test(parent)).isTrue(); + } + + @Test + void does_not_match_nonexistent_descendant() { + var parent = createTempDir("parent"); + parent.resolve("child"); + + var filter = PathPredicates.builder().hasDescendantMatching( + p -> p.getFileName().toString().equals("missing") + ).build(); + + assertThat(filter.test(parent)).isFalse(); + } + + @Test + void does_not_match_if_not_directory() throws IOException { + var parent = createTempFile("parent"); + + var filter = PathPredicates.builder().hasDescendantMatching( + p -> true + ).build(); + + assertThat(filter.test(parent)).isFalse(); + } + + @Test + void does_not_match_if_no_child() throws IOException { + var parent = createTempDir("parent"); + + var filter = PathPredicates.builder().hasDescendantMatching( + p -> true + ).build(); + + assertThat(filter.test(parent)).isFalse(); + } + } - @Test - void isFile_noMatch() { - var path = createTempDir("myDir"); - var filter = PathPredicates.builder().isFile().build(); - assertThat(filter.test(path)).isFalse(); + @Nested + class HasSiblingMatching { + + @Test + void null_predicate_throws_NPE() { + assertThatNullPointerException() + .isThrownBy(() -> PathPredicates.builder().hasSiblingMatching(null)); + } + + @Test + void matches_sibling() throws IOException { + var parent = createTempDir("parent"); + var child1 = Files.createDirectory(parent.resolve("child1")); + var child2 = Files.createDirectory(parent.resolve("child2")); + + var filter = PathPredicates.builder().hasSiblingMatching( + p -> p.getFileName().toString().equals("child2") + ).build(); + + assertThat(filter.test(child1)).isTrue(); + } + + @Test + void matches_sibling_failed() throws IOException { + var parent = createTempDir("parent"); + var child1 = Files.createDirectory(parent.resolve("child1")); + var child2 = Files.createDirectory(parent.resolve("child2")); + + var filter = PathPredicates.builder().hasSiblingMatching( + p -> p.getFileName().toString().equals("otherChild") + ).build(); + + assertThat(filter.test(child1)).isFalse(); + } + + @Test + void root_has_no_sibling() throws IOException { + var root = Path.of("root"); + + var filter = PathPredicates.builder().hasSiblingMatching( + p -> true + ).build(); + + assertThat(filter.test(root)).isFalse(); + } + + @Test + void does_not_match_self() throws IOException { + var parent = createTempDir("parent"); + var child = Files.createDirectory(parent.resolve("child")); + + var filter = PathPredicates.builder().hasSiblingMatching( + p -> true + ).build(); + + assertThat(filter.test(child)).isFalse(); + } + + @Test + void does_not_match_parent_or_child() throws IOException { + var parent = createTempDir("parent"); + var child = Files.createDirectory(parent.resolve("child")); + var grandchild = Files.createDirectory(child.resolve("grandchild")); + + var filter = PathPredicates.builder().hasSiblingMatching( + p -> true + ).build(); + + assertThat(filter.test(child)).isFalse(); + } + } } From 351b2d0883e0b60a3899e397b1d460ba6f2abd45 Mon Sep 17 00:00:00 2001 From: Samuel SCHNEGG Date: Sat, 27 Sep 2025 17:15:12 +0200 Subject: [PATCH 2/5] Rework PathUtils/PathPredicates --- CHANGELOG.md | 3 + README.md | 18 +- .../example/ChildLimitDynamic.java | 3 +- .../example/Filtering.java | 3 +- .../example/LineExtension.java | 10 +- .../ChildLimitBuilder.java | 4 +- .../PathPredicateBuilder.java | 34 +- .../PathPredicates.java | 285 +++++++++++------ .../jfiletreeprettyprinter/PathUtils.java | 298 ------------------ .../scanner/DefaultPathToTreeScanner.java | 2 +- .../ChildLimitDynamicTest.java | 4 +- .../FileTreePrettyPrinterTest.java | 2 +- .../jfiletreeprettyprinter/FilteringTest.java | 14 +- .../LineExtensionTest.java | 14 +- 14 files changed, 255 insertions(+), 439 deletions(-) delete mode 100644 src/main/java/io/github/computerdaddyguy/jfiletreeprettyprinter/PathUtils.java diff --git a/CHANGELOG.md b/CHANGELOG.md index fa769ae..3b02375 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Added - New path predicates: `hasParentMatching`, `hasAncestorMatching`, `hasDirectChildMatching`, `hasDescendantMatching`, `hasSiblingMatching` +### Changed +- `PathUtils` removed, `PathPredicates`rework + --- ## [0.0.4] - 2025-09-27 diff --git a/README.md b/README.md index 9c6cedc..d3c118c 100644 --- a/README.md +++ b/README.md @@ -178,13 +178,14 @@ child_limit_static/ Or you can also set a limitation function, to dynamically choose the number of children displayed in each directory. It avoids cluttering the whole console with known large folders (e.g. `node_modules`) but continue to pretty print normally other folders. -Use the `ChildLimitBuilder` and `PathPredicates` classes to help you build the limit function that fits your needs.. +Use the `ChildLimitBuilder` and `PathPredicates` classes to help you build the limit function that fits your needs. ```java // Example: ChildLimitDynamic.java +var isNodeModulePredicate = PathPredicates.builder().hasName("node_modules").build(); var childLimit = ChildLimitBuilder.builder() .defaultLimit(ChildLimitBuilder.UNLIMITED) - .limit(PathPredicates.hasName("node_modules"), 0) + .limit(isNodeModulePredicate, 0) .build(); var prettyPrinter = FileTreePrettyPrinter.builder() .customizeOptions(options -> options.withChildLimit(childLimit)) @@ -278,12 +279,13 @@ Files and directories can be selectively included or excluded using a custom `Pr Filtering is **recursive by default**: directory's contents will always be traversed. However, if a directory does not match and none of its children match, the directory itself will not be displayed. -The `PathPredicates` class provides several ready-to-use `Predicate` implementations for common cases, as well as a builder for creating more advanced predicates. +The `PathPredicates` class provides several ready-to-use methods for creating common predicates, as well as a builder for creating more advanced predicates. ```java // Example: Filtering.java +var hasJavaExtensionPredicate = PathPredicates.builder().hasExtension("java").build(); var prettyPrinter = FileTreePrettyPrinter.builder() - .customizeOptions(options -> options.filter(PathPredicates.hasExtension("java"))) + .customizeOptions(options -> options.filter(hasJavaExtensionPredicate)) .build(); ``` ``` @@ -308,16 +310,16 @@ If the function returns `null`, nothing is added. ```java // Example: LineExtension.java Function lineExtension = path -> { - if (PathUtils.isDirectory(path) && PathUtils.hasName(path, "api")) { + if (PathPredicates.isDirectory(path) && PathPredicates.hasName(path, "api")) { return "\t\t\t// All API code: controllers, etc."; } - if (PathUtils.isDirectory(path) && PathUtils.hasName(path, "domain")) { + if (PathPredicates.isDirectory(path) && PathPredicates.hasName(path, "domain")) { return "\t\t\t// All domain code: value objects, etc."; } - if (PathUtils.isDirectory(path) && PathUtils.hasName(path, "infra")) { + if (PathPredicates.isDirectory(path) && PathPredicates.hasName(path, "infra")) { return "\t\t\t// All infra code: database, email service, etc."; } - if (PathUtils.isFile(path) && PathUtils.hasName(path, "application.properties")) { + if (PathPredicates.isFile(path) && PathPredicates.hasName(path, "application.properties")) { return "\t// Config file"; } return null; diff --git a/src/example/java/io/github/computerdaddyguy/jfiletreeprettyprinter/example/ChildLimitDynamic.java b/src/example/java/io/github/computerdaddyguy/jfiletreeprettyprinter/example/ChildLimitDynamic.java index 1d7d7f6..d7483e9 100644 --- a/src/example/java/io/github/computerdaddyguy/jfiletreeprettyprinter/example/ChildLimitDynamic.java +++ b/src/example/java/io/github/computerdaddyguy/jfiletreeprettyprinter/example/ChildLimitDynamic.java @@ -7,9 +7,10 @@ public class ChildLimitDynamic { public static void main(String[] args) { + var isNodeModulePredicate = PathPredicates.builder().hasName("node_modules").build(); var childLimit = ChildLimitBuilder.builder() .defaultLimit(ChildLimitBuilder.UNLIMITED) - .limit(PathPredicates.hasName("node_modules"), 0) + .limit(isNodeModulePredicate, 0) .build(); var prettyPrinter = FileTreePrettyPrinter.builder() .customizeOptions(options -> options.withChildLimit(childLimit)) diff --git a/src/example/java/io/github/computerdaddyguy/jfiletreeprettyprinter/example/Filtering.java b/src/example/java/io/github/computerdaddyguy/jfiletreeprettyprinter/example/Filtering.java index 860e474..81d9699 100644 --- a/src/example/java/io/github/computerdaddyguy/jfiletreeprettyprinter/example/Filtering.java +++ b/src/example/java/io/github/computerdaddyguy/jfiletreeprettyprinter/example/Filtering.java @@ -6,8 +6,9 @@ public class Filtering { public static void main(String[] args) { + var hasJavaExtensionPredicate = PathPredicates.builder().hasExtension("java").build(); var prettyPrinter = FileTreePrettyPrinter.builder() - .customizeOptions(options -> options.filter(PathPredicates.hasExtension("java"))) + .customizeOptions(options -> options.filter(hasJavaExtensionPredicate)) .build(); var tree = prettyPrinter.prettyPrint("src/example/resources/filtering"); diff --git a/src/example/java/io/github/computerdaddyguy/jfiletreeprettyprinter/example/LineExtension.java b/src/example/java/io/github/computerdaddyguy/jfiletreeprettyprinter/example/LineExtension.java index 99795d2..2364051 100644 --- a/src/example/java/io/github/computerdaddyguy/jfiletreeprettyprinter/example/LineExtension.java +++ b/src/example/java/io/github/computerdaddyguy/jfiletreeprettyprinter/example/LineExtension.java @@ -1,7 +1,7 @@ package io.github.computerdaddyguy.jfiletreeprettyprinter.example; import io.github.computerdaddyguy.jfiletreeprettyprinter.FileTreePrettyPrinter; -import io.github.computerdaddyguy.jfiletreeprettyprinter.PathUtils; +import io.github.computerdaddyguy.jfiletreeprettyprinter.PathPredicates; import java.nio.file.Path; import java.util.function.Function; @@ -9,16 +9,16 @@ public class LineExtension { public static void main(String[] args) { Function lineExtension = path -> { - if (PathUtils.isDirectory(path) && PathUtils.hasName(path, "api")) { + if (PathPredicates.isDirectory(path) && PathPredicates.hasName(path, "api")) { return "\t\t\t// All API code: controllers, etc."; } - if (PathUtils.isDirectory(path) && PathUtils.hasName(path, "domain")) { + if (PathPredicates.isDirectory(path) && PathPredicates.hasName(path, "domain")) { return "\t\t\t// All domain code: value objects, etc."; } - if (PathUtils.isDirectory(path) && PathUtils.hasName(path, "infra")) { + if (PathPredicates.isDirectory(path) && PathPredicates.hasName(path, "infra")) { return "\t\t\t// All infra code: database, email service, etc."; } - if (PathUtils.isFile(path) && PathUtils.hasName(path, "application.properties")) { + if (PathPredicates.isFile(path) && PathPredicates.hasName(path, "application.properties")) { return "\t// Config file"; } return null; diff --git a/src/main/java/io/github/computerdaddyguy/jfiletreeprettyprinter/ChildLimitBuilder.java b/src/main/java/io/github/computerdaddyguy/jfiletreeprettyprinter/ChildLimitBuilder.java index afd2723..f66f26b 100644 --- a/src/main/java/io/github/computerdaddyguy/jfiletreeprettyprinter/ChildLimitBuilder.java +++ b/src/main/java/io/github/computerdaddyguy/jfiletreeprettyprinter/ChildLimitBuilder.java @@ -26,8 +26,8 @@ *

{@code
  * var childLimit = ChildLimitBuilder.builder()
  *     .defaultLimit(ChildLimit.UNLIMITED)   // unlimited unless specified
- *     .limit(path -> PathUtils.hasName("bigDir"), 10)  // max 10 children in "bigDir"
- *     .limit(path -> PathUtils.hasName("emptyDir"), 0) // disallow children in "emptyDir"
+ *     .limit(path -> PathPredicates.hasName(path, "bigDir"), 10)  // max 10 children in "bigDir"
+ *     .limit(path -> PathPredicates.hasName(path, "emptyDir"), 0) // disallow children in "emptyDir"
  *     .build();
  *
  * }
diff --git a/src/main/java/io/github/computerdaddyguy/jfiletreeprettyprinter/PathPredicateBuilder.java b/src/main/java/io/github/computerdaddyguy/jfiletreeprettyprinter/PathPredicateBuilder.java index d6c73f7..55cb211 100644 --- a/src/main/java/io/github/computerdaddyguy/jfiletreeprettyprinter/PathPredicateBuilder.java +++ b/src/main/java/io/github/computerdaddyguy/jfiletreeprettyprinter/PathPredicateBuilder.java @@ -86,7 +86,8 @@ public PathPredicateBuilder fileTest(Predicate predicate) { * @return this builder for chaining */ public PathPredicateBuilder hasName(String name) { - return pathTest(PathPredicates.hasName(name)); + Objects.requireNonNull(name, "name is null"); + return pathTest(path -> PathPredicates.hasName(path, name)); } /** @@ -98,7 +99,8 @@ public PathPredicateBuilder hasName(String name) { * @return this builder for chaining */ public PathPredicateBuilder hasNameIgnoreCase(String name) { - return pathTest(PathPredicates.hasNameIgnoreCase(name)); + Objects.requireNonNull(name, "name is null"); + return pathTest(path -> PathPredicates.hasNameIgnoreCase(path, name)); } /** @@ -109,7 +111,8 @@ public PathPredicateBuilder hasNameIgnoreCase(String name) { * @return this builder for chaining */ public PathPredicateBuilder hasNameMatching(Pattern pattern) { - return pathTest(PathPredicates.hasNameMatching(pattern)); + Objects.requireNonNull(pattern, "pattern is null"); + return pathTest(path -> PathPredicates.hasNameMatching(path, pattern)); } /** @@ -120,7 +123,8 @@ public PathPredicateBuilder hasNameMatching(Pattern pattern) { * @return this builder for chaining */ public PathPredicateBuilder hasNameEndingWith(String suffix) { - return pathTest(PathPredicates.hasNameEndingWith(suffix)); + Objects.requireNonNull(suffix, "suffix is null"); + return pathTest(path -> PathPredicates.hasNameEndingWith(path, suffix)); } /** @@ -135,7 +139,8 @@ public PathPredicateBuilder hasNameEndingWith(String suffix) { * @return this builder for chaining */ public PathPredicateBuilder hasExtension(String extension) { - return pathTest(PathPredicates.hasExtension(extension)); + Objects.requireNonNull(extension, "extension is null"); + return pathTest(path -> PathPredicates.hasExtension(path, extension)); } // ---------- Type ---------- @@ -146,7 +151,7 @@ public PathPredicateBuilder hasExtension(String extension) { * @return this builder for chaining */ public PathPredicateBuilder isDirectory() { - return pathTest(PathPredicates.isDirectory()); + return pathTest(path -> PathPredicates.isDirectory(path)); } /** @@ -155,7 +160,7 @@ public PathPredicateBuilder isDirectory() { * @return this builder for chaining */ public PathPredicateBuilder isFile() { - return pathTest(PathPredicates.isFile()); + return pathTest(path -> PathPredicates.isFile(path)); } // ---------- Hierarchy ---------- @@ -170,7 +175,8 @@ public PathPredicateBuilder isFile() { * @see PathPredicates#hasParentMatching(Predicate) */ public PathPredicateBuilder hasParentMatching(Predicate parentPredicate) { - return pathTest(PathPredicates.hasParentMatching(parentPredicate)); + Objects.requireNonNull(parentPredicate, "parentPredicate is null"); + return pathTest(path -> PathPredicates.hasParentMatching(path, parentPredicate)); } /** @@ -186,7 +192,8 @@ public PathPredicateBuilder hasParentMatching(Predicate parentPredicate) { * @see PathPredicates#hasAncestorMatching(Predicate) */ public PathPredicateBuilder hasAncestorMatching(Predicate ancestorPredicate) { - return pathTest(PathPredicates.hasAncestorMatching(ancestorPredicate)); + Objects.requireNonNull(ancestorPredicate, "ancestorPredicate is null"); + return pathTest(path -> PathPredicates.hasAncestorMatching(path, ancestorPredicate)); } /** @@ -202,7 +209,8 @@ public PathPredicateBuilder hasAncestorMatching(Predicate ancestorPredicat * @see PathPredicates#hasDirectChildMatching(Predicate) */ public PathPredicateBuilder hasDirectChildMatching(Predicate childPredicate) { - return pathTest(PathPredicates.hasDirectChildMatching(childPredicate)); + Objects.requireNonNull(childPredicate, "childPredicate is null"); + return pathTest(path -> PathPredicates.hasDirectChildMatching(path, childPredicate)); } /** @@ -218,7 +226,8 @@ public PathPredicateBuilder hasDirectChildMatching(Predicate childPredicat * @see PathPredicates#hasDescendantMatching(Predicate) */ public PathPredicateBuilder hasDescendantMatching(Predicate descendantPredicate) { - return pathTest(PathPredicates.hasDescendantMatching(descendantPredicate)); + Objects.requireNonNull(descendantPredicate, "descendantPredicate is null"); + return pathTest(path -> PathPredicates.hasDescendantMatching(path, descendantPredicate)); } /** @@ -234,7 +243,8 @@ public PathPredicateBuilder hasDescendantMatching(Predicate descendantPred * @see PathPredicates#hasSiblingMatching(Predicate) */ public PathPredicateBuilder hasSiblingMatching(Predicate siblingPredicate) { - return pathTest(PathPredicates.hasSiblingMatching(siblingPredicate)); + Objects.requireNonNull(siblingPredicate, "siblingPredicate is null"); + return pathTest(path -> PathPredicates.hasSiblingMatching(path, siblingPredicate)); } } \ No newline at end of file diff --git a/src/main/java/io/github/computerdaddyguy/jfiletreeprettyprinter/PathPredicates.java b/src/main/java/io/github/computerdaddyguy/jfiletreeprettyprinter/PathPredicates.java index 600f5fa..ee3be33 100644 --- a/src/main/java/io/github/computerdaddyguy/jfiletreeprettyprinter/PathPredicates.java +++ b/src/main/java/io/github/computerdaddyguy/jfiletreeprettyprinter/PathPredicates.java @@ -1,9 +1,14 @@ package io.github.computerdaddyguy.jfiletreeprettyprinter; +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.Objects; import java.util.function.Predicate; import java.util.regex.Pattern; +import java.util.stream.Stream; import org.jspecify.annotations.NullMarked; /** @@ -37,178 +42,270 @@ public static PathPredicateBuilder builder() { // ---------- Name ---------- /** - * Creates a predicate that tests whether the path has the specified file name - * + * Tests whether the given path has exactly the specified file name. + * + * @param path the path to test * @param name the expected file name (without parent directories) * - * @return a predicate testing for file names matching the given name + * @return {@code true} if the path's file name equals {@code name} + * + * @throws NullPointerException if {@code path} or {@code name} is {@code null} */ - public static Predicate hasName(String name) { - return path -> PathUtils.hasName(path, name); + public static boolean hasName(Path path, String name) { + Objects.requireNonNull(path, "path is null"); + Objects.requireNonNull(name, "name is null"); + return path.getFileName().toString().equals(name); } /** - * Creates a predicate that tests whether the path has the specified - * file name, ignoring case. + * Tests whether the given path has the specified file name, + * ignoring case. * - * @param name the expected file name (without parent directories), case-insensitive + * @param path the path to test + * @param name the expected file name (case-insensitive) * - * @return a predicate testing for file names matching the given name, case-insensitive + * @return {@code true} if the path's file name equals {@code name}, ignoring case + * + * @throws NullPointerException if {@code path} or {@code name} is {@code null} */ - public static Predicate hasNameIgnoreCase(String name) { - return path -> PathUtils.hasNameIgnoreCase(path, name); + public static boolean hasNameIgnoreCase(Path path, String name) { + Objects.requireNonNull(path, "path is null"); + Objects.requireNonNull(name, "name is null"); + return path.getFileName().toString().equalsIgnoreCase(name); } /** - * Creates a predicate that tests whether a path's file name matches - * the provided pattern. + * Tests whether the given path's file name matches the provided pattern. * + * @param path the path to test * @param pattern the regex pattern to apply to the file name * - * @return a predicate testing for file names matching the pattern + * @return {@code true} if the file name matches the pattern + * + * @throws NullPointerException if {@code path} or {@code pattern} is {@code null} */ - public static Predicate hasNameMatching(Pattern pattern) { - return path -> PathUtils.hasNameMatching(path, pattern); + public static boolean hasNameMatching(Path path, Pattern pattern) { + Objects.requireNonNull(path, "path is null"); + Objects.requireNonNull(pattern, "pattern is null"); + return pattern.matcher(path.getFileName().toString()).matches(); } /** - * Creates a predicate that tests whether a path's file name ends - * with the specified suffix. + * Tests whether the given path's file name ends with the specified suffix. * + * @param path the path to test * @param suffix the suffix to test (e.g. ".log", ".txt") * - * @return a predicate testing for file names ending with the given suffix + * @return {@code true} if the file name ends with the given suffix + * + * @throws NullPointerException if {@code path} or {@code suffix} is {@code null} */ - public static Predicate hasNameEndingWith(String suffix) { - return path -> PathUtils.hasNameEndingWith(path, suffix); + public static boolean hasNameEndingWith(Path path, String suffix) { + Objects.requireNonNull(path, "path is null"); + Objects.requireNonNull(suffix, "suffix is null"); + return path.getFileName().toString().endsWith(suffix); } /** - * Creates a predicate that tests whether a path's file name has - * the specified extension. + * Tests whether the given path's file name has the specified extension. *

* The extension should be provided without a leading dot, e.g. * {@code "txt"} or {@code "pdf"}. *

* + * @param path the path to test * @param extension the extension to test (without the dot) * - * @return a predicate testing for file names with the given extension + * @return {@code true} if the file name ends with {@code "." + extension} + * + * @throws NullPointerException if {@code path} or {@code extension} is {@code null} */ - public static Predicate hasExtension(String extension) { - return path -> PathUtils.hasExtension(path, extension); + public static boolean hasExtension(Path path, String extension) { + Objects.requireNonNull(path, "path is null"); + Objects.requireNonNull(extension, "extension is null"); + return hasNameEndingWith(path, "." + extension); } // ---------- Type ---------- /** - * Creates a predicate that tests whether the path represents a directory. + * Tests whether the given path represents a directory. * - * @return a predicate testing for files to represent a directory + * @param path the path to test + * + * @return {@code true} if the path is a directory + * + * @throws NullPointerException if {@code path} is {@code null} */ - public static Predicate isDirectory() { - return PathUtils::isDirectory; + public static boolean isDirectory(Path path) { + Objects.requireNonNull(path, "path is null"); + return path.toFile().isDirectory(); } /** - * Creates a predicate that tests whether the path represents a file. + * Tests whether the given path represents a file. * - * @return a predicate testing for files to represent a file + * @param path the path to test + * + * @return {@code true} if the path is a file + * + * @throws NullPointerException if {@code path} is {@code null} */ - public static Predicate isFile() { - return PathUtils::isFile; + public static boolean isFile(Path path) { + Objects.requireNonNull(path, "path is null"); + return path.toFile().isFile(); } // ---------- Hierarchy ---------- /** - * Creates a predicate that evaluates the direct parent of a given path. - * The returned predicate applies the provided {@code parentPredicate} to {@link Path#getParent()} of the tested path. - * If tested path has no parent, the returned predicate will always return {@code false}. + * Tests whether the direct parent of the given path matches the provided predicate. * - * @param parentPredicate the predicate to apply on the parent path (must not be {@code null}) - * - * @return a predicate that returns {@code true} if the parent of the tested path matches the given predicate - * - * @throws NullPointerException if {@code parentPredicate} is {@code null} + * @param path the path whose parent should be tested (must not be {@code null}) + * @param parentPredicate the predicate to apply to the direct parent (must not be {@code null}) + * + * @return {@code true} if the path has a parent and that parent matches the predicate, + * {@code false} otherwise + * + * @throws NullPointerException if {@code path} or {@code parentPredicate} is {@code null} */ - public static Predicate hasParentMatching(Predicate parentPredicate) { + public static boolean hasParentMatching(Path path, Predicate parentPredicate) { + Objects.requireNonNull(path, "path is null"); Objects.requireNonNull(parentPredicate, "parentPredicate is null"); - return path -> PathUtils.hasParentMatching(path, parentPredicate); + + if (path.getParent() == null) { + return false; + } + return parentPredicate.test(path.getParent()); } /** - * Creates a predicate that evaluates all ancestors of a given path. - *

- * The returned predicate applies the provided {@code ancestorPredicate} - * to each parent in the chain obtained via successive calls to {@link Path#getParent()}. - * If any ancestor matches, the predicate returns {@code true}. - *

- * If the tested path has no parent, the returned predicate always returns {@code false}. + * Tests whether any ancestor of the given path matches the provided predicate. * - * @param ancestorPredicate the predicate to apply on each ancestor path (must not be {@code null}) - * - * @return a predicate that returns {@code true} if any ancestor of the tested path matches the given predicate - * - * @throws NullPointerException if {@code ancestorPredicate} is {@code null} + *

The test is applied recursively up the parent chain (using {@link Path#getParent()}) + * until the root is reached or the predicate returns {@code true}. + * + * @param path the path whose ancestors should be tested (must not be {@code null}) + * @param ancestorPredicate the predicate to apply to each ancestor (must not be {@code null}) + * + * @return {@code true} if any ancestor of the path matches the predicate, + * {@code false} otherwise + * + * @throws NullPointerException if {@code path} or {@code ancestorPredicate} is {@code null} */ - public static Predicate hasAncestorMatching(Predicate ancestorPredicate) { + public static boolean hasAncestorMatching(Path path, Predicate ancestorPredicate) { + Objects.requireNonNull(path, "path is null"); Objects.requireNonNull(ancestorPredicate, "ancestorPredicate is null"); - return path -> PathUtils.hasAncestorMatching(path, ancestorPredicate); + + Path parent = path.getParent(); + while (parent != null) { + if (ancestorPredicate.test(parent)) { + return true; + } + parent = parent.getParent(); + } + return false; } /** - * Creates a predicate that evaluates the direct children of a given path. - *

- * The returned predicate applies the provided {@code childPredicate} to each - * direct child of the tested path. The predicate returns {@code true} if at least - * one child matches. If the path is not a directory, the predicate always returns {@code false}. + * Tests whether the given path has at least one direct child that matches the provided predicate. * - * @param childPredicate the predicate to apply on each direct child - * - * @return a predicate that returns {@code true} if at least one direct child matches - * - * @throws NullPointerException if {@code childPredicate} is {@code null} + *

The method checks only immediate children of the path (not recursive). + * If the path is not a directory, the result is always {@code false}. + * + * @param path the directory whose direct children should be tested (must not be {@code null}) + * @param childPredicate the predicate to apply to each direct child (must not be {@code null}) + * + * @return {@code true} if any direct child matches the predicate, + * {@code false} if none match, or if the path is not a directory + * + * @throws NullPointerException if {@code path} or {@code childPredicate} is {@code null} */ - public static Predicate hasDirectChildMatching(Predicate childPredicate) { + public static boolean hasDirectChildMatching(Path path, Predicate childPredicate) { + Objects.requireNonNull(path, "path is null"); Objects.requireNonNull(childPredicate, "childPredicate is null"); - return path -> PathUtils.hasDirectChildMatching(path, childPredicate); + + File file = path.toFile(); + if (!file.isDirectory()) { + return false; + } + File[] children = file.listFiles(); + if (children == null) { // Only if I/O error occurred when listing files + return false; + } + for (File child : children) { + if (childPredicate.test(child.toPath())) { + return true; + } + } + return false; } /** - * Creates a predicate that evaluates all descendants (children at any depth) of a given path. - *

- * The returned predicate applies the provided {@code descendantPredicate} recursively to each - * child and sub-child. It returns {@code true} if at least one descendant matches. + * Tests whether the given path has at least one descendant (child, grandchild, etc.) + * that matches the provided predicate. * - * @param descendantPredicate the predicate to apply on each descendant - * - * @return a predicate that returns {@code true} if at least one descendant matches - * - * @throws NullPointerException if {@code descendantPredicate} is {@code null} + *

The method walks the file tree starting at the given path, excluding the path itself, + * and applies the predicate to all discovered descendants. + * If the path is not a directory, the result is always {@code false}. + * + * @param path the directory whose descendants should be tested (must not be {@code null}) + * @param descendantPredicate the predicate to apply to each descendant (must not be {@code null}) + * + * @return {@code true} if any descendant matches the predicate, + * {@code false} if none match or if the path is not a directory + * + * @throws NullPointerException if {@code path} or {@code descendantPredicate} is {@code null} + * @throws UncheckedIOException if an I/O error occurs while traversing the directory */ - public static Predicate hasDescendantMatching(Predicate descendantPredicate) { + public static boolean hasDescendantMatching(Path path, Predicate descendantPredicate) { + Objects.requireNonNull(path, "path is null"); Objects.requireNonNull(descendantPredicate, "descendantPredicate is null"); - return path -> PathUtils.hasDescendantMatching(path, descendantPredicate); + File file = path.toFile(); + if (!file.isDirectory()) { + return false; + } + try (Stream stream = Files.walk(path)) { + return stream + .skip(1) // skip the root path itself + .anyMatch(descendantPredicate); + } catch (IOException e) { + throw new UncheckedIOException("Exception while walking files of " + path, e); + } } /** - * Creates a predicate that evaluates the siblings of a given path. - *

- * The returned predicate applies the provided {@code siblingPredicate} to each - * sibling of the tested path (other files/directories in the same parent). - * The predicate returns {@code true} if at least one sibling matches. - * If the tested path has no parent, the predicate always returns {@code false}. + * Tests whether the given path has at least one sibling that matches the provided predicate. * - * @param siblingPredicate the predicate to apply on each sibling - * - * @return a predicate that returns {@code true} if at least one sibling matches - * - * @throws NullPointerException if {@code siblingPredicate} is {@code null} + *

The siblings are the other entries in the same parent directory. + * The path itself is excluded from testing. If the path has no parent, + * the result is always {@code false}. + * + * @param path the path whose siblings should be tested (must not be {@code null}) + * @param siblingPredicate the predicate to apply to each sibling (must not be {@code null}) + * + * @return {@code true} if any sibling matches the predicate, + * {@code false} if none match or if the path has no parent + * + * @throws NullPointerException if {@code path} or {@code siblingPredicate} is {@code null} */ - public static Predicate hasSiblingMatching(Predicate siblingPredicate) { + public static boolean hasSiblingMatching(Path path, Predicate siblingPredicate) { + Objects.requireNonNull(path, "path is null"); Objects.requireNonNull(siblingPredicate, "siblingPredicate is null"); - return path -> PathUtils.hasSiblingMatching(path, siblingPredicate); + Path parent = path.getParent(); + if (parent == null) { + return false; + } + File[] siblings = parent.toFile().listFiles(); + if (siblings == null) { // Only if I/O error occurred when listing files + return false; + } + for (File sibling : siblings) { + if (!sibling.toPath().equals(path) && siblingPredicate.test(sibling.toPath())) { + return true; + } + } + return false; } } diff --git a/src/main/java/io/github/computerdaddyguy/jfiletreeprettyprinter/PathUtils.java b/src/main/java/io/github/computerdaddyguy/jfiletreeprettyprinter/PathUtils.java deleted file mode 100644 index 77e0689..0000000 --- a/src/main/java/io/github/computerdaddyguy/jfiletreeprettyprinter/PathUtils.java +++ /dev/null @@ -1,298 +0,0 @@ -package io.github.computerdaddyguy.jfiletreeprettyprinter; - -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.Objects; -import java.util.function.Predicate; -import java.util.regex.Pattern; -import java.util.stream.Stream; - -/** - * Utility class providing common {@link Predicate} implementations - * and helper methods for testing files and directories. - *

- * All methods are {@code static} and can be used directly or wrapped into - * a {@link Predicate} for filtering paths. - *

- * - * This class is not instantiable. - */ -public final class PathUtils { - - private PathUtils() { - // Helper class - } - - // ---------- Name ---------- - - /** - * Tests whether the given path has exactly the specified file name. - * - * @param path the path to test - * @param name the expected file name (without parent directories) - * - * @return {@code true} if the path's file name equals {@code name} - * - * @throws NullPointerException if {@code path} or {@code name} is {@code null} - */ - public static boolean hasName(Path path, String name) { - Objects.requireNonNull(path, "path is null"); - Objects.requireNonNull(name, "name is null"); - return path.getFileName().toString().equals(name); - } - - /** - * Tests whether the given path has the specified file name, - * ignoring case. - * - * @param path the path to test - * @param name the expected file name (case-insensitive) - * - * @return {@code true} if the path's file name equals {@code name}, ignoring case - * - * @throws NullPointerException if {@code path} or {@code name} is {@code null} - */ - public static boolean hasNameIgnoreCase(Path path, String name) { - Objects.requireNonNull(path, "path is null"); - Objects.requireNonNull(name, "name is null"); - return path.getFileName().toString().equalsIgnoreCase(name); - } - - /** - * Tests whether the given path's file name matches the provided pattern. - * - * @param path the path to test - * @param pattern the regex pattern to apply to the file name - * - * @return {@code true} if the file name matches the pattern - * - * @throws NullPointerException if {@code path} or {@code pattern} is {@code null} - */ - public static boolean hasNameMatching(Path path, Pattern pattern) { - Objects.requireNonNull(path, "path is null"); - Objects.requireNonNull(pattern, "pattern is null"); - return pattern.matcher(path.getFileName().toString()).matches(); - } - - /** - * Tests whether the given path's file name ends with the specified suffix. - * - * @param path the path to test - * @param suffix the suffix to test (e.g. ".log", ".txt") - * - * @return {@code true} if the file name ends with the given suffix - * - * @throws NullPointerException if {@code path} or {@code suffix} is {@code null} - */ - public static boolean hasNameEndingWith(Path path, String suffix) { - Objects.requireNonNull(path, "path is null"); - Objects.requireNonNull(suffix, "suffix is null"); - return path.getFileName().toString().endsWith(suffix); - } - - /** - * Tests whether the given path's file name has the specified extension. - *

- * The extension should be provided without a leading dot, e.g. - * {@code "txt"} or {@code "pdf"}. - *

- * - * @param path the path to test - * @param extension the extension to test (without the dot) - * - * @return {@code true} if the file name ends with {@code "." + extension} - * - * @throws NullPointerException if {@code path} or {@code extension} is {@code null} - */ - public static boolean hasExtension(Path path, String extension) { - Objects.requireNonNull(path, "path is null"); - Objects.requireNonNull(extension, "extension is null"); - return hasNameEndingWith(path, "." + extension); - } - - // ---------- Type ---------- - - /** - * Tests whether the given path represents a directory. - * - * @param path the path to test - * - * @return {@code true} if the path is a directory - * - * @throws NullPointerException if {@code path} is {@code null} - */ - public static boolean isDirectory(Path path) { - Objects.requireNonNull(path, "path is null"); - return path.toFile().isDirectory(); - } - - /** - * Tests whether the given path represents a file. - * - * @param path the path to test - * - * @return {@code true} if the path is a file - * - * @throws NullPointerException if {@code path} is {@code null} - */ - public static boolean isFile(Path path) { - Objects.requireNonNull(path, "path is null"); - return path.toFile().isFile(); - } - - // ---------- Hierarchy ---------- - - /** - * Tests whether the direct parent of the given path matches the provided predicate. - * - * @param path the path whose parent should be tested (must not be {@code null}) - * @param parentPredicate the predicate to apply to the direct parent (must not be {@code null}) - * - * @return {@code true} if the path has a parent and that parent matches the predicate, - * {@code false} otherwise - * - * @throws NullPointerException if {@code path} or {@code parentPredicate} is {@code null} - */ - public static boolean hasParentMatching(Path path, Predicate parentPredicate) { - Objects.requireNonNull(path, "path is null"); - Objects.requireNonNull(parentPredicate, "parentPredicate is null"); - - if (path.getParent() == null) { - return false; - } - return parentPredicate.test(path.getParent()); - } - - /** - * Tests whether any ancestor of the given path matches the provided predicate. - * - *

The test is applied recursively up the parent chain (using {@link Path#getParent()}) - * until the root is reached or the predicate returns {@code true}. - * - * @param path the path whose ancestors should be tested (must not be {@code null}) - * @param ancestorPredicate the predicate to apply to each ancestor (must not be {@code null}) - * - * @return {@code true} if any ancestor of the path matches the predicate, - * {@code false} otherwise - * - * @throws NullPointerException if {@code path} or {@code ancestorPredicate} is {@code null} - */ - public static boolean hasAncestorMatching(Path path, Predicate ancestorPredicate) { - Objects.requireNonNull(path, "path is null"); - Objects.requireNonNull(ancestorPredicate, "ancestorPredicate is null"); - - Path parent = path.getParent(); - while (parent != null) { - if (ancestorPredicate.test(parent)) { - return true; - } - parent = parent.getParent(); - } - return false; - } - - /** - * Tests whether the given path has at least one direct child that matches the provided predicate. - * - *

The method checks only immediate children of the path (not recursive). - * If the path is not a directory, the result is always {@code false}. - * - * @param path the directory whose direct children should be tested (must not be {@code null}) - * @param childPredicate the predicate to apply to each direct child (must not be {@code null}) - * - * @return {@code true} if any direct child matches the predicate, - * {@code false} if none match, or if the path is not a directory - * - * @throws NullPointerException if {@code path} or {@code childPredicate} is {@code null} - */ - public static boolean hasDirectChildMatching(Path path, Predicate childPredicate) { - Objects.requireNonNull(path, "path is null"); - Objects.requireNonNull(childPredicate, "childPredicate is null"); - - File file = path.toFile(); - if (!file.isDirectory()) { - return false; - } - File[] children = file.listFiles(); - if (children == null) { // Only if I/O error occurred when listing files - return false; - } - for (File child : children) { - if (childPredicate.test(child.toPath())) { - return true; - } - } - return false; - } - - /** - * Tests whether the given path has at least one descendant (child, grandchild, etc.) - * that matches the provided predicate. - * - *

The method walks the file tree starting at the given path, excluding the path itself, - * and applies the predicate to all discovered descendants. - * If the path is not a directory, the result is always {@code false}. - * - * @param path the directory whose descendants should be tested (must not be {@code null}) - * @param descendantPredicate the predicate to apply to each descendant (must not be {@code null}) - * - * @return {@code true} if any descendant matches the predicate, - * {@code false} if none match or if the path is not a directory - * - * @throws NullPointerException if {@code path} or {@code descendantPredicate} is {@code null} - * @throws UncheckedIOException if an I/O error occurs while traversing the directory - */ - public static boolean hasDescendantMatching(Path path, Predicate descendantPredicate) { - Objects.requireNonNull(path, "path is null"); - Objects.requireNonNull(descendantPredicate, "descendantPredicate is null"); - File file = path.toFile(); - if (!file.isDirectory()) { - return false; - } - try (Stream stream = Files.walk(path)) { - return stream - .skip(1) // skip the root path itself - .anyMatch(descendantPredicate); - } catch (IOException e) { - throw new UncheckedIOException("Exception while walking files of " + path, e); - } - } - - /** - * Tests whether the given path has at least one sibling that matches the provided predicate. - * - *

The siblings are the other entries in the same parent directory. - * The path itself is excluded from testing. If the path has no parent, - * the result is always {@code false}. - * - * @param path the path whose siblings should be tested (must not be {@code null}) - * @param siblingPredicate the predicate to apply to each sibling (must not be {@code null}) - * - * @return {@code true} if any sibling matches the predicate, - * {@code false} if none match or if the path has no parent - * - * @throws NullPointerException if {@code path} or {@code siblingPredicate} is {@code null} - */ - public static boolean hasSiblingMatching(Path path, Predicate siblingPredicate) { - Objects.requireNonNull(path, "path is null"); - Objects.requireNonNull(siblingPredicate, "siblingPredicate is null"); - Path parent = path.getParent(); - if (parent == null) { - return false; - } - File[] siblings = parent.toFile().listFiles(); - if (siblings == null) { // Only if I/O error occurred when listing files - return false; - } - for (File sibling : siblings) { - if (!sibling.toPath().equals(path) && siblingPredicate.test(sibling.toPath())) { - return true; - } - } - return false; - } - -} diff --git a/src/main/java/io/github/computerdaddyguy/jfiletreeprettyprinter/scanner/DefaultPathToTreeScanner.java b/src/main/java/io/github/computerdaddyguy/jfiletreeprettyprinter/scanner/DefaultPathToTreeScanner.java index d4aa425..f903a6c 100644 --- a/src/main/java/io/github/computerdaddyguy/jfiletreeprettyprinter/scanner/DefaultPathToTreeScanner.java +++ b/src/main/java/io/github/computerdaddyguy/jfiletreeprettyprinter/scanner/DefaultPathToTreeScanner.java @@ -124,7 +124,7 @@ private List handleLeftOverChildren(int depth, Iterator pathIte private Iterator directoryStreamToIterator(DirectoryStream childrenStream, @Nullable Predicate filter) { var stream = StreamSupport.stream(childrenStream.spliterator(), false); if (filter != null) { - var recursiveFilter = PathPredicates.isDirectory().or(filter); + var recursiveFilter = PathPredicates.builder().isDirectory().build().or(filter); stream = stream.filter(recursiveFilter); } return stream diff --git a/src/test/java/io/github/computerdaddyguy/jfiletreeprettyprinter/ChildLimitDynamicTest.java b/src/test/java/io/github/computerdaddyguy/jfiletreeprettyprinter/ChildLimitDynamicTest.java index d4cc058..d4ac90a 100644 --- a/src/test/java/io/github/computerdaddyguy/jfiletreeprettyprinter/ChildLimitDynamicTest.java +++ b/src/test/java/io/github/computerdaddyguy/jfiletreeprettyprinter/ChildLimitDynamicTest.java @@ -17,8 +17,8 @@ class ChildLimitDynamicTest { .customizeOptions( // @formatter:off options -> options.withChildLimit( - p -> PathUtils.hasName(p, "limit_1") ? 1 : - PathUtils.hasName(p, "limit_3") ? 3 : + p -> PathPredicates.hasName(p, "limit_1") ? 1 : + PathPredicates.hasName(p, "limit_3") ? 3 : -1 ) // @formatter:on diff --git a/src/test/java/io/github/computerdaddyguy/jfiletreeprettyprinter/FileTreePrettyPrinterTest.java b/src/test/java/io/github/computerdaddyguy/jfiletreeprettyprinter/FileTreePrettyPrinterTest.java index c6b003c..f257345 100644 --- a/src/test/java/io/github/computerdaddyguy/jfiletreeprettyprinter/FileTreePrettyPrinterTest.java +++ b/src/test/java/io/github/computerdaddyguy/jfiletreeprettyprinter/FileTreePrettyPrinterTest.java @@ -26,7 +26,7 @@ void prettyPrintWithFilter_by_path_and_string_are_same() { var path = FileStructures.simpleDirectoryWithFilesAndFolders(root, 3, 3); FileTreePrettyPrinter printer = FileTreePrettyPrinter.builder() - .customizeOptions(options -> options.filter(PathPredicates.isFile())) + .customizeOptions(options -> options.filter(PathPredicates::isFile)) .build(); assertThat(printer.prettyPrint(path)).isEqualTo(printer.prettyPrint(path.toString())); diff --git a/src/test/java/io/github/computerdaddyguy/jfiletreeprettyprinter/FilteringTest.java b/src/test/java/io/github/computerdaddyguy/jfiletreeprettyprinter/FilteringTest.java index 91ab0bd..1a46e3b 100644 --- a/src/test/java/io/github/computerdaddyguy/jfiletreeprettyprinter/FilteringTest.java +++ b/src/test/java/io/github/computerdaddyguy/jfiletreeprettyprinter/FilteringTest.java @@ -10,7 +10,7 @@ class FilteringTest { @Test void example() { - var filter = PathPredicates.hasExtension("java"); + var filter = PathPredicates.builder().hasExtension("java").build(); FileTreePrettyPrinter printer = FileTreePrettyPrinter.builder() .customizeOptions(options -> options.filter(filter)) .build(); @@ -32,7 +32,7 @@ void example() { @Test void example_dir_match() { - var filter = PathPredicates.hasNameEndingWith("no_java_file"); + var filter = PathPredicates.builder().hasNameEndingWith("no_java_file").build(); FileTreePrettyPrinter printer = FileTreePrettyPrinter.builder() .customizeOptions(options -> options.filter(filter)) .build(); @@ -49,7 +49,7 @@ void example_dir_match() { @Test void example_and_sorting() { - var filter = PathPredicates.hasExtension("java"); + var filter = PathPredicates.builder().hasExtension("java").build(); FileTreePrettyPrinter printer = FileTreePrettyPrinter.builder() .customizeOptions(options -> options.sort(Sorts.BY_NAME.reversed())) .customizeOptions(options -> options.filter(filter)) @@ -72,7 +72,7 @@ void example_and_sorting() { @Test void example_childLimit_1() { - var filter = PathPredicates.hasExtension("java"); + var filter = PathPredicates.builder().hasExtension("java").build(); FileTreePrettyPrinter printer = FileTreePrettyPrinter.builder() .customizeOptions(options -> options.withChildLimit(1)) .customizeOptions(options -> options.filter(filter)) @@ -91,7 +91,7 @@ void example_childLimit_1() { @Test void example_childLimit_2() { - var filter = PathPredicates.hasExtension("java"); + var filter = PathPredicates.builder().hasExtension("java").build(); FileTreePrettyPrinter printer = FileTreePrettyPrinter.builder() .customizeOptions(options -> options.withChildLimit(2)) .customizeOptions(options -> options.filter(filter)) @@ -114,7 +114,7 @@ void example_childLimit_2() { @Test void example_childLimit_3() { - var filter = PathPredicates.hasExtension("java"); + var filter = PathPredicates.builder().hasExtension("java").build(); FileTreePrettyPrinter printer = FileTreePrettyPrinter.builder() .customizeOptions(options -> options.withChildLimit(3)) .customizeOptions(options -> options.filter(filter)) @@ -137,7 +137,7 @@ void example_childLimit_3() { @Test void example_compact_dir() { - var filter = PathPredicates.hasExtension("java"); + var filter = PathPredicates.builder().hasExtension("java").build(); FileTreePrettyPrinter printer = FileTreePrettyPrinter.builder() .customizeOptions(options -> options.withCompactDirectories(true)) .customizeOptions(options -> options.filter(filter)) diff --git a/src/test/java/io/github/computerdaddyguy/jfiletreeprettyprinter/LineExtensionTest.java b/src/test/java/io/github/computerdaddyguy/jfiletreeprettyprinter/LineExtensionTest.java index 029ae29..480a450 100644 --- a/src/test/java/io/github/computerdaddyguy/jfiletreeprettyprinter/LineExtensionTest.java +++ b/src/test/java/io/github/computerdaddyguy/jfiletreeprettyprinter/LineExtensionTest.java @@ -30,16 +30,16 @@ void emptyDir() { void example_dir_match() { Function lineExtension = path -> { - if (PathUtils.isDirectory(path) && PathUtils.hasName(path, "api")) { + if (PathPredicates.isDirectory(path) && PathPredicates.hasName(path, "api")) { return "\t\t\t// All API code: controllers, etc."; } - if (PathUtils.isDirectory(path) && PathUtils.hasName(path, "domain")) { + if (PathPredicates.isDirectory(path) && PathPredicates.hasName(path, "domain")) { return "\t\t\t// All domain code: value objects, etc."; } - if (PathUtils.isDirectory(path) && PathUtils.hasName(path, "infra")) { + if (PathPredicates.isDirectory(path) && PathPredicates.hasName(path, "infra")) { return "\t\t\t// All infra code: database, email service, etc."; } - if (PathUtils.isFile(path) && PathUtils.hasName(path, "application.properties")) { + if (PathPredicates.isFile(path) && PathPredicates.hasName(path, "application.properties")) { return "\t// Config file"; } return null; @@ -70,7 +70,7 @@ void example_dir_match() { void compact_dir_first_dir() { Function lineExtension = p -> { - if (PathUtils.hasName(p, "dirA")) { + if (PathPredicates.hasName(p, "dirA")) { return " // 1"; } return null; @@ -92,7 +92,7 @@ void compact_dir_first_dir() { void compact_dir_middle_dir() { Function lineExtension = p -> { - if (PathUtils.hasName(p, "dirB")) { + if (PathPredicates.hasName(p, "dirB")) { return " // 2"; } return null; @@ -114,7 +114,7 @@ void compact_dir_middle_dir() { void compact_dir_last_dir() { Function lineExtension = p -> { - if (PathUtils.hasName(p, "dirC")) { + if (PathPredicates.hasName(p, "dirC")) { return " // 3"; } return null; From 1f5d3b15a5d4b53fd80410c4db506d4ce8069f1b Mon Sep 17 00:00:00 2001 From: Samuel SCHNEGG Date: Sat, 27 Sep 2025 17:57:01 +0200 Subject: [PATCH 3/5] New "glob" path matching methods --- CHANGELOG.md | 2 +- README.md | 8 +- .../example/LineExtension.java | 8 +- .../PathPredicateBuilder.java | 47 +++++++ .../PathPredicates.java | 68 ++++++++++ .../LineExtensionTest.java | 8 +- .../PathPredicateBuilderTest.java | 116 +++++++++++++++--- 7 files changed, 224 insertions(+), 33 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3b02375..328406e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,7 +9,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [0.0.5] - Unreleased ### Added -- New path predicates: `hasParentMatching`, `hasAncestorMatching`, `hasDirectChildMatching`, `hasDescendantMatching`, `hasSiblingMatching` +- New path predicates: `hasParentMatching`, `hasAncestorMatching`, `hasDirectChildMatching`, `hasDescendantMatching`, `hasSiblingMatching`, `hasFullPathMatchingGlob`, `hasFullPathMatching`, `hasNameMatchingGlob` ### Changed - `PathUtils` removed, `PathPredicates`rework diff --git a/README.md b/README.md index d3c118c..f30e5ee 100644 --- a/README.md +++ b/README.md @@ -310,16 +310,16 @@ If the function returns `null`, nothing is added. ```java // Example: LineExtension.java Function lineExtension = path -> { - if (PathPredicates.isDirectory(path) && PathPredicates.hasName(path, "api")) { + if (PathPredicates.hasFullPathMatchingGlob(path, "**/src/main/java/api")) { return "\t\t\t// All API code: controllers, etc."; } - if (PathPredicates.isDirectory(path) && PathPredicates.hasName(path, "domain")) { + if (PathPredicates.hasFullPathMatchingGlob(path, "**/src/main/java/domain")) { return "\t\t\t// All domain code: value objects, etc."; } - if (PathPredicates.isDirectory(path) && PathPredicates.hasName(path, "infra")) { + if (PathPredicates.hasFullPathMatchingGlob(path, "**/src/main/java/infra")) { return "\t\t\t// All infra code: database, email service, etc."; } - if (PathPredicates.isFile(path) && PathPredicates.hasName(path, "application.properties")) { + if (PathPredicates.hasNameMatchingGlob(path, "*.properties")) { return "\t// Config file"; } return null; diff --git a/src/example/java/io/github/computerdaddyguy/jfiletreeprettyprinter/example/LineExtension.java b/src/example/java/io/github/computerdaddyguy/jfiletreeprettyprinter/example/LineExtension.java index 2364051..c9b3f2e 100644 --- a/src/example/java/io/github/computerdaddyguy/jfiletreeprettyprinter/example/LineExtension.java +++ b/src/example/java/io/github/computerdaddyguy/jfiletreeprettyprinter/example/LineExtension.java @@ -9,16 +9,16 @@ public class LineExtension { public static void main(String[] args) { Function lineExtension = path -> { - if (PathPredicates.isDirectory(path) && PathPredicates.hasName(path, "api")) { + if (PathPredicates.hasFullPathMatchingGlob(path, "**/src/main/java/api")) { return "\t\t\t// All API code: controllers, etc."; } - if (PathPredicates.isDirectory(path) && PathPredicates.hasName(path, "domain")) { + if (PathPredicates.hasFullPathMatchingGlob(path, "**/src/main/java/domain")) { return "\t\t\t// All domain code: value objects, etc."; } - if (PathPredicates.isDirectory(path) && PathPredicates.hasName(path, "infra")) { + if (PathPredicates.hasFullPathMatchingGlob(path, "**/src/main/java/infra")) { return "\t\t\t// All infra code: database, email service, etc."; } - if (PathPredicates.isFile(path) && PathPredicates.hasName(path, "application.properties")) { + if (PathPredicates.hasNameMatchingGlob(path, "*.properties")) { return "\t// Config file"; } return null; diff --git a/src/main/java/io/github/computerdaddyguy/jfiletreeprettyprinter/PathPredicateBuilder.java b/src/main/java/io/github/computerdaddyguy/jfiletreeprettyprinter/PathPredicateBuilder.java index 55cb211..34ac6a1 100644 --- a/src/main/java/io/github/computerdaddyguy/jfiletreeprettyprinter/PathPredicateBuilder.java +++ b/src/main/java/io/github/computerdaddyguy/jfiletreeprettyprinter/PathPredicateBuilder.java @@ -2,6 +2,7 @@ import java.io.File; import java.nio.file.Path; +import java.nio.file.PathMatcher; import java.util.Objects; import java.util.function.Predicate; import java.util.regex.Pattern; @@ -75,6 +76,32 @@ public PathPredicateBuilder fileTest(Predicate predicate) { return pathTest(path -> predicate.test(path.toFile())); } + // ---------- PathMatcher ---------- + + /** + * Adds a condition that tests whether the path matches the specified glob pattern. + * + * @param glob the glob pattern to match; must not be {@code null} + * + * @return this builder for chaining + */ + public PathPredicateBuilder hasFullPathMatchingGlob(String glob) { + Objects.requireNonNull(glob, "glob is null"); + return pathTest(path -> PathPredicates.hasFullPathMatchingGlob(path, glob)); + } + + /** + * Adds a condition that tests whether the path matches the provided {@link PathMatcher}. + * + * @param matcher the {@code PathMatcher} to use; must not be {@code null} + * + * @return this builder for chaining + */ + public PathPredicateBuilder hasFullPathMatching(PathMatcher matcher) { + Objects.requireNonNull(matcher, "matcher is null"); + return pathTest(path -> PathPredicates.hasFullPathMatching(path, matcher)); + } + // ---------- Name ---------- /** @@ -115,6 +142,26 @@ public PathPredicateBuilder hasNameMatching(Pattern pattern) { return pathTest(path -> PathPredicates.hasNameMatching(path, pattern)); } + /** + * Adds a condition that tests whether the file name of the given path + * matches the specified glob pattern. + * + *

Note: Only the file name (the last element of the path) is tested, + * not the entire path. For example, {@code "*.txt"} will match {@code "file.txt"}. + * + *

The glob syntax follows {@link java.nio.file.FileSystem#getPathMatcher(String)} conventions. + * + * @param glob the glob pattern to match against the file name; must not be {@code null} + * + * @return this builder for chaining + * + * @see #hasFullPathMatchingGlob(String) + */ + public PathPredicateBuilder hasNameMatchingGlob(String glob) { + Objects.requireNonNull(glob, "glob is null"); + return pathTest(path -> PathPredicates.hasNameMatchingGlob(path, glob)); + } + /** * Adds a condition that tests whether the given path's file name ends with the specified suffix. * diff --git a/src/main/java/io/github/computerdaddyguy/jfiletreeprettyprinter/PathPredicates.java b/src/main/java/io/github/computerdaddyguy/jfiletreeprettyprinter/PathPredicates.java index ee3be33..bf6f4e5 100644 --- a/src/main/java/io/github/computerdaddyguy/jfiletreeprettyprinter/PathPredicates.java +++ b/src/main/java/io/github/computerdaddyguy/jfiletreeprettyprinter/PathPredicates.java @@ -5,6 +5,7 @@ import java.io.UncheckedIOException; import java.nio.file.Files; import java.nio.file.Path; +import java.nio.file.PathMatcher; import java.util.Objects; import java.util.function.Predicate; import java.util.regex.Pattern; @@ -39,6 +40,53 @@ public static PathPredicateBuilder builder() { return new PathPredicateBuilder(); } + // ---------- PathMatcher ---------- + + /** + * Tests whether the given {@link Path} matches the specified glob pattern. + * + *

The glob syntax follows {@link java.nio.file.FileSystem#getPathMatcher(String)} conventions. + * + * @param path the path to test; must not be {@code null} + * @param glob the glob pattern; must not be {@code null} + * + * @return {@code true} if the path matches the glob pattern, {@code false} otherwise + * + * @throws NullPointerException if {@code path} or {@code glob} is {@code null} + * + * @see #hasNameMatchingGlob(Path, String) + * @see #hasFullPathMatching(Path, PathMatcher) + */ + public static boolean hasFullPathMatchingGlob(Path path, String glob) { + Objects.requireNonNull(path, "path is null"); + Objects.requireNonNull(glob, "glob is null"); + + // From Files.newDirectoryStream(Path dir, String glob) + if (glob.equals("*")) { + return true; + } + var matcher = path.getFileSystem().getPathMatcher("glob:" + glob); + return matcher.matches(path); + } + + /** + * Checks if the given {@link Path} matches the provided {@link PathMatcher}. + * + * @param path the path to test; must not be {@code null} + * @param matcher the {@code PathMatcher} to use; must not be {@code null} + * + * @return {@code true} if the path matches the matcher, {@code false} otherwise + * + * @throws NullPointerException if {@code path} or {@code matcher} is {@code null} + * + * @see #hasFullPathMatchingGlob(Path, String) + */ + public static boolean hasFullPathMatching(Path path, PathMatcher matcher) { + Objects.requireNonNull(path, "path is null"); + Objects.requireNonNull(matcher, "matcher is null"); + return matcher.matches(path); + } + // ---------- Name ---------- /** @@ -90,6 +138,26 @@ public static boolean hasNameMatching(Path path, Pattern pattern) { return pattern.matcher(path.getFileName().toString()).matches(); } + /** + * Tests whether the given path's file name matches the provided glob. + * + *

The glob syntax follows {@link java.nio.file.FileSystem#getPathMatcher(String)} conventions. + * + * @param path the path to test + * @param glob the glob pattern to match against the file name; must not be {@code null} + * + * @return {@code true} if the file name matches the glob + * + * @throws NullPointerException if {@code path} or {@code glob} is {@code null} + * + * @see #hasFullPathMatchingGlob(Path, String) + */ + public static boolean hasNameMatchingGlob(Path path, String glob) { + Objects.requireNonNull(path, "path is null"); + Objects.requireNonNull(glob, "glob is null"); + return hasFullPathMatchingGlob(path.getFileName(), glob); + } + /** * Tests whether the given path's file name ends with the specified suffix. * diff --git a/src/test/java/io/github/computerdaddyguy/jfiletreeprettyprinter/LineExtensionTest.java b/src/test/java/io/github/computerdaddyguy/jfiletreeprettyprinter/LineExtensionTest.java index 480a450..7010040 100644 --- a/src/test/java/io/github/computerdaddyguy/jfiletreeprettyprinter/LineExtensionTest.java +++ b/src/test/java/io/github/computerdaddyguy/jfiletreeprettyprinter/LineExtensionTest.java @@ -30,16 +30,16 @@ void emptyDir() { void example_dir_match() { Function lineExtension = path -> { - if (PathPredicates.isDirectory(path) && PathPredicates.hasName(path, "api")) { + if (PathPredicates.hasFullPathMatchingGlob(path, "**/src/main/java/api")) { return "\t\t\t// All API code: controllers, etc."; } - if (PathPredicates.isDirectory(path) && PathPredicates.hasName(path, "domain")) { + if (PathPredicates.hasFullPathMatchingGlob(path, "**/src/main/java/domain")) { return "\t\t\t// All domain code: value objects, etc."; } - if (PathPredicates.isDirectory(path) && PathPredicates.hasName(path, "infra")) { + if (PathPredicates.hasFullPathMatchingGlob(path, "**/src/main/java/infra")) { return "\t\t\t// All infra code: database, email service, etc."; } - if (PathPredicates.isFile(path) && PathPredicates.hasName(path, "application.properties")) { + if (PathPredicates.hasNameMatchingGlob(path, "*.properties")) { return "\t// Config file"; } return null; diff --git a/src/test/java/io/github/computerdaddyguy/jfiletreeprettyprinter/PathPredicateBuilderTest.java b/src/test/java/io/github/computerdaddyguy/jfiletreeprettyprinter/PathPredicateBuilderTest.java index 7f1285e..971b283 100644 --- a/src/test/java/io/github/computerdaddyguy/jfiletreeprettyprinter/PathPredicateBuilderTest.java +++ b/src/test/java/io/github/computerdaddyguy/jfiletreeprettyprinter/PathPredicateBuilderTest.java @@ -46,32 +46,82 @@ void noPredicate_then_nulll() { assertThat(filter).isNull(); } - @Test - void pathTest_match() { - var path = createTempFile("myFile.java"); - var filter = PathPredicates.builder().pathTest(p -> p.equals(path)).build(); - assertThat(filter.test(path)).isTrue(); + @Nested + class PathTest { + + @Test + void match() { + var path = createTempFile("myFile.java"); + var filter = PathPredicates.builder().pathTest(p -> p.equals(path)).build(); + assertThat(filter.test(path)).isTrue(); + } + + @Test + void no_match() { + var path = createTempFile("myFile.java"); + var filter = PathPredicates.builder().pathTest(p -> p.equals(new Object())).build(); + assertThat(filter.test(path)).isFalse(); + } + } - @Test - void pathTest_noMatch() { - var path = createTempFile("myFile.java"); - var filter = PathPredicates.builder().pathTest(p -> p.equals(new Object())).build(); - assertThat(filter.test(path)).isFalse(); + @Nested + class FileTest { + + @Test + void match() { + var path = createTempFile("myFile.java"); + var filter = PathPredicates.builder().fileTest(p -> p.equals(path.toFile())).build(); + assertThat(filter.test(path)).isTrue(); + } + + @Test + void no_match() { + var path = createTempFile("myFile.java"); + var filter = PathPredicates.builder().fileTest(p -> p.equals(new Object())).build(); + assertThat(filter.test(path)).isFalse(); + } + } - @Test - void fileTest_match() { - var path = createTempFile("myFile.java"); - var filter = PathPredicates.builder().fileTest(p -> p.equals(path.toFile())).build(); - assertThat(filter.test(path)).isTrue(); + // ---------- PathMatcher ---------- + + @Nested + class HasFullPathMatchingGlob { + + @Test + void match_wildcard() { + var path = createTempFile("myFile.java"); + var filter = PathPredicates.builder().hasFullPathMatchingGlob("*").build(); + assertThat(filter.test(path)).isTrue(); + } + + @Test + void match() { + var path = createTempFile("myFile.java"); + var filter = PathPredicates.builder().hasFullPathMatchingGlob("**/*.java").build(); + assertThat(filter.test(path)).isTrue(); + } + + @Test + void no_match_because_full_path() { + var path = createTempFile("myFile.java"); + var filter = PathPredicates.builder().hasFullPathMatchingGlob("*.java").build(); + assertThat(filter.test(path)).isFalse(); + } + + @Test + void no_match() { + var path = createTempFile("myFile.java"); + var filter = PathPredicates.builder().hasFullPathMatchingGlob("**/*.php").build(); + assertThat(filter.test(path)).isFalse(); + } + } - @Test - void fileTest_noMatch() { - var path = createTempFile("myFile.java"); - var filter = PathPredicates.builder().fileTest(p -> p.equals(new Object())).build(); - assertThat(filter.test(path)).isFalse(); + @Nested + class MatchesPathMatcher { + } // ---------- Name ---------- @@ -147,6 +197,32 @@ void no_match() { } + @Nested + class HasNameMatchingGlob { + + @Test + void match() { + var path = createTempFile("myFile.java"); + var filter = PathPredicates.builder().hasNameMatchingGlob("my*").build(); + assertThat(filter.test(path)).isTrue(); + } + + @Test + void no_match() { + var path = createTempFile("myFile.java"); + var filter = PathPredicates.builder().hasNameMatchingGlob("ma*").build(); + assertThat(filter.test(path)).isFalse(); + } + + @Test + void no_match_dir() { + var path = createTempFile("myFile.java"); + var filter = PathPredicates.builder().hasNameMatchingGlob("*/my*").build(); + assertThat(filter.test(path)).isFalse(); + } + + } + @Nested class HasNameEndingWith { From b51be57629d154782d74866a0d3aaa3bc780a911 Mon Sep 17 00:00:00 2001 From: Samuel SCHNEGG Date: Sat, 27 Sep 2025 18:03:47 +0200 Subject: [PATCH 4/5] Fix Sonar issue --- .../PathPredicateBuilder.java | 4 +-- .../PathPredicateBuilderTest.java | 30 ++++++++++++------- 2 files changed, 21 insertions(+), 13 deletions(-) diff --git a/src/main/java/io/github/computerdaddyguy/jfiletreeprettyprinter/PathPredicateBuilder.java b/src/main/java/io/github/computerdaddyguy/jfiletreeprettyprinter/PathPredicateBuilder.java index 34ac6a1..08d903b 100644 --- a/src/main/java/io/github/computerdaddyguy/jfiletreeprettyprinter/PathPredicateBuilder.java +++ b/src/main/java/io/github/computerdaddyguy/jfiletreeprettyprinter/PathPredicateBuilder.java @@ -198,7 +198,7 @@ public PathPredicateBuilder hasExtension(String extension) { * @return this builder for chaining */ public PathPredicateBuilder isDirectory() { - return pathTest(path -> PathPredicates.isDirectory(path)); + return pathTest(PathPredicates::isDirectory); } /** @@ -207,7 +207,7 @@ public PathPredicateBuilder isDirectory() { * @return this builder for chaining */ public PathPredicateBuilder isFile() { - return pathTest(path -> PathPredicates.isFile(path)); + return pathTest(PathPredicates::isFile); } // ---------- Hierarchy ---------- diff --git a/src/test/java/io/github/computerdaddyguy/jfiletreeprettyprinter/PathPredicateBuilderTest.java b/src/test/java/io/github/computerdaddyguy/jfiletreeprettyprinter/PathPredicateBuilderTest.java index 971b283..ae2e584 100644 --- a/src/test/java/io/github/computerdaddyguy/jfiletreeprettyprinter/PathPredicateBuilderTest.java +++ b/src/test/java/io/github/computerdaddyguy/jfiletreeprettyprinter/PathPredicateBuilderTest.java @@ -360,6 +360,7 @@ void does_not_match_grandparent() throws IOException { } @Test + @SuppressWarnings("unused") void does_not_match_sibling() throws IOException { var parent = createTempDir("parent"); var child = Files.createDirectory(parent.resolve("child")); @@ -374,14 +375,14 @@ void does_not_match_sibling() throws IOException { } @Test - void root_has_no_parent() throws IOException { - var root = Path.of("root"); + void root_has_no_parent() { + var detachedRoot = Path.of("root"); var filter = PathPredicates.builder().hasParentMatching( p -> true ).build(); - assertThat(filter.test(root)).isFalse(); + assertThat(filter.test(detachedRoot)).isFalse(); } } @@ -422,6 +423,7 @@ void matches_grandparent() throws IOException { } @Test + @SuppressWarnings("unused") void does_not_match_sibling() throws IOException { var parent = createTempDir("parent"); var child = Files.createDirectory(parent.resolve("child")); @@ -436,14 +438,14 @@ void does_not_match_sibling() throws IOException { } @Test - void root_has_no_parent() throws IOException { - var root = Path.of("root"); + void root_has_no_parent() { + var detachedRoot = Path.of("root"); var filter = PathPredicates.builder().hasAncestorMatching( p -> true ).build(); - assertThat(filter.test(root)).isFalse(); + assertThat(filter.test(detachedRoot)).isFalse(); } } @@ -458,6 +460,7 @@ void null_predicate_throws_NPE() { } @Test + @SuppressWarnings("unused") void matches_direct_child() throws IOException { var parent = createTempDir("parent"); var child = Files.createDirectory(parent.resolve("child")); @@ -470,6 +473,7 @@ void matches_direct_child() throws IOException { } @Test + @SuppressWarnings("unused") void does_not_match_grandchild() throws IOException { var parent = createTempDir("parent"); var child = Files.createDirectory(parent.resolve("child")); @@ -483,7 +487,7 @@ void does_not_match_grandchild() throws IOException { } @Test - void does_not_match_if_not_directory() throws IOException { + void does_not_match_if_not_directory() { var parent = createTempFile("parent"); var filter = PathPredicates.builder().hasDirectChildMatching( @@ -494,7 +498,7 @@ void does_not_match_if_not_directory() throws IOException { } @Test - void does_not_match_if_no_child() throws IOException { + void does_not_match_if_no_child() { var parent = createTempDir("parent"); var filter = PathPredicates.builder().hasDirectChildMatching( @@ -516,6 +520,7 @@ void null_predicate_throws_NPE() { } @Test + @SuppressWarnings("unused") void matches_grandchild() throws IOException { var parent = createTempDir("parent"); var child = Files.createDirectory(parent.resolve("child")); @@ -541,7 +546,7 @@ void does_not_match_nonexistent_descendant() { } @Test - void does_not_match_if_not_directory() throws IOException { + void does_not_match_if_not_directory() { var parent = createTempFile("parent"); var filter = PathPredicates.builder().hasDescendantMatching( @@ -552,7 +557,7 @@ void does_not_match_if_not_directory() throws IOException { } @Test - void does_not_match_if_no_child() throws IOException { + void does_not_match_if_no_child() { var parent = createTempDir("parent"); var filter = PathPredicates.builder().hasDescendantMatching( @@ -574,6 +579,7 @@ void null_predicate_throws_NPE() { } @Test + @SuppressWarnings("unused") void matches_sibling() throws IOException { var parent = createTempDir("parent"); var child1 = Files.createDirectory(parent.resolve("child1")); @@ -587,6 +593,7 @@ void matches_sibling() throws IOException { } @Test + @SuppressWarnings("unused") void matches_sibling_failed() throws IOException { var parent = createTempDir("parent"); var child1 = Files.createDirectory(parent.resolve("child1")); @@ -600,7 +607,7 @@ void matches_sibling_failed() throws IOException { } @Test - void root_has_no_sibling() throws IOException { + void root_has_no_sibling() { var root = Path.of("root"); var filter = PathPredicates.builder().hasSiblingMatching( @@ -623,6 +630,7 @@ void does_not_match_self() throws IOException { } @Test + @SuppressWarnings("unused") void does_not_match_parent_or_child() throws IOException { var parent = createTempDir("parent"); var child = Files.createDirectory(parent.resolve("child")); From 1ec406c93c2753941f1b8e6c49767bc276d87787 Mon Sep 17 00:00:00 2001 From: Samuel SCHNEGG Date: Sat, 27 Sep 2025 18:06:08 +0200 Subject: [PATCH 5/5] Fix Sonar issue --- .../jfiletreeprettyprinter/PathPredicateBuilderTest.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/test/java/io/github/computerdaddyguy/jfiletreeprettyprinter/PathPredicateBuilderTest.java b/src/test/java/io/github/computerdaddyguy/jfiletreeprettyprinter/PathPredicateBuilderTest.java index ae2e584..dba75d4 100644 --- a/src/test/java/io/github/computerdaddyguy/jfiletreeprettyprinter/PathPredicateBuilderTest.java +++ b/src/test/java/io/github/computerdaddyguy/jfiletreeprettyprinter/PathPredicateBuilderTest.java @@ -608,13 +608,13 @@ void matches_sibling_failed() throws IOException { @Test void root_has_no_sibling() { - var root = Path.of("root"); + var detachedRoot = Path.of("root"); var filter = PathPredicates.builder().hasSiblingMatching( p -> true ).build(); - assertThat(filter.test(root)).isFalse(); + assertThat(filter.test(detachedRoot)).isFalse(); } @Test