|
12 | 12 | * A builder for creating a {@code ToIntFunction<Path>} that defines |
13 | 13 | * how many child entries (files or directories) are allowed under a given path. |
14 | 14 | * <p> |
15 | | - * The resulting function evaluates added rules in the order they were defined. |
16 | | - * The first matching rule determines the child limit for a given path. |
| 15 | + * Rules are evaluated in the order they are added (first-match-wins). |
| 16 | + * The first rule that matches a path determines the limit. |
17 | 17 | * If no rules match, the default limit is applied. |
18 | 18 | * </p> |
19 | 19 | * |
20 | 20 | * <p> |
21 | | - * A limit of {@link #UNLIMITED} ({@code -1}) means no restriction. |
22 | | - * A limit of {@code 0} means the directory cannot have any children. |
| 21 | + * Special values: |
| 22 | + * <ul> |
| 23 | + * <li>{@link #UNLIMITED} ({@code -1}) — no restriction on children.</li> |
| 24 | + * <li>{@code 0} — directory cannot contain any children.</li> |
| 25 | + * <li>Any positive number — maximum number of children allowed.</li> |
| 26 | + * </ul> |
23 | 27 | * </p> |
24 | 28 | * |
25 | 29 | * <h2>Example usage:</h2> |
26 | 30 | * <pre>{@code |
27 | | - * var childLimit = ChildLimitBuilder.builder() |
28 | | - * .defaultLimit(ChildLimit.UNLIMITED) // unlimited unless specified |
29 | | - * .limit(PathMatchers.hasName("bigDir"), 10) // max 10 children in "bigDir" |
30 | | - * .limit(PathMatchers.hasName("emptyDir"), 0) // disallow children in "emptyDir" |
| 31 | + * var childLimit = ChildLimitBuilder.newInstance() |
| 32 | + * .setDefault(ChildLimitBuilder.UNLIMITED) // unlimited unless specified |
| 33 | + * .add(PathMatchers.hasName("bigDir"), 10) // max 10 children in "bigDir" |
| 34 | + * .add(PathMatchers.hasName("emptyDir"), 0) // disallow children in "emptyDir" |
31 | 35 | * .build(); |
32 | | - * |
33 | 36 | * }</pre> |
34 | 37 | */ |
35 | 38 | @NullMarked |
36 | 39 | public class ChildLimitBuilder { |
37 | 40 |
|
38 | | - /** |
39 | | - * Unlimited children. |
| 41 | + /** |
| 42 | + * Special value indicating unlimited children ({@code -1}). |
40 | 43 | */ |
41 | 44 | public static final int UNLIMITED = -1; |
42 | 45 |
|
43 | | - private static final ChildControl UNLIMITED_CONTROL = new ChildControl(p -> true, UNLIMITED); |
| 46 | + private List<ToIntFunction<Path>> limits; |
| 47 | + private int defaultLimit; |
44 | 48 |
|
45 | | - private List<ChildControl> controls; |
46 | | - private ChildControl defaultControl; |
| 49 | + private ChildLimitBuilder(int defaultLimit) { |
| 50 | + this.limits = new ArrayList<>(); |
| 51 | + this.defaultLimit = defaultLimit; |
| 52 | + } |
47 | 53 |
|
48 | 54 | /** |
49 | | - * Creates a new builder. |
| 55 | + * Returns a new {@link ChildLimitBuilder}. |
| 56 | + * |
| 57 | + * @return a fresh builder instance |
50 | 58 | */ |
51 | | - private ChildLimitBuilder() { |
52 | | - this.controls = new ArrayList<>(); |
53 | | - this.defaultControl = UNLIMITED_CONTROL; |
| 59 | + public static ChildLimitBuilder newInstance() { |
| 60 | + return newInstance(UNLIMITED); |
54 | 61 | } |
55 | 62 |
|
56 | | - private record ChildControl(PathMatcher pathMatcher, int limit) { |
57 | | - |
58 | | - } |
59 | | - |
60 | | - public static ChildLimitBuilder builder() { |
61 | | - return new ChildLimitBuilder(); |
| 63 | + public static ChildLimitBuilder newInstance(int defaultLimit) { |
| 64 | + return new ChildLimitBuilder(defaultLimit); |
62 | 65 | } |
63 | 66 |
|
64 | 67 | /** |
65 | | - * Builds the child limit function based on the configured rules. |
| 68 | + * Builds the final child limit function based on the configured rules. |
66 | 69 | * <p> |
67 | | - * Rules are tested in the order they were added. The first matching rule |
68 | | - * provides the limit. If no rule matches, the default limit is used. |
| 70 | + * Rules are evaluated in insertion order. The first rule that matches a path |
| 71 | + * determines its child limit. If no rules match, the default limit is returned. |
69 | 72 | * </p> |
70 | 73 | * |
71 | 74 | * @return a function mapping a {@link Path} to its maximum number of children |
72 | 75 | */ |
73 | 76 | public ToIntFunction<Path> build() { |
74 | | - var immutControls = List.copyOf(controls); |
75 | | - var immutDefaultControl = this.defaultControl; |
76 | | - return p -> immutControls.stream() |
77 | | - .filter(control -> control.pathMatcher().matches(p)) |
78 | | - .findFirst() |
79 | | - .orElse(immutDefaultControl) |
80 | | - .limit(); |
| 77 | + var immutLimits = List.copyOf(limits); |
| 78 | + return path -> { |
| 79 | + int result = defaultLimit; |
| 80 | + for (var rule : immutLimits) { |
| 81 | + result = rule.applyAsInt(path); |
| 82 | + if (result >= 0) { |
| 83 | + break; |
| 84 | + } |
| 85 | + } |
| 86 | + return result; |
| 87 | + }; |
81 | 88 | } |
82 | 89 |
|
83 | 90 | /** |
84 | 91 | * Sets the default child limit to apply when no specific rule matches. |
85 | 92 | * |
86 | 93 | * @param limit the default limit (use {@link #UNLIMITED} for no restriction) |
87 | | - * |
88 | 94 | * @return this builder for chaining |
89 | 95 | */ |
90 | | - public ChildLimitBuilder defaultLimit(int limit) { |
91 | | - this.defaultControl = new ChildControl(p -> true, limit); |
| 96 | + public ChildLimitBuilder setDefault(int limit) { |
| 97 | + this.defaultLimit = limit; |
| 98 | + return this; |
| 99 | + } |
| 100 | + |
| 101 | + /** |
| 102 | + * Adds a custom rule expressed as a {@link ToIntFunction}. |
| 103 | + * <p> |
| 104 | + * This function should return: |
| 105 | + * <ul> |
| 106 | + * <li>{@link #UNLIMITED} ({@code -1}) if it does not apply</li> |
| 107 | + * <li>Any non-negative integer as the effective child limit if it applies</li> |
| 108 | + * </ul> |
| 109 | + * </p> |
| 110 | + * |
| 111 | + * @param childLimit the function mapping a path to a child limit |
| 112 | + * @return this builder for chaining |
| 113 | + * @throws NullPointerException if {@code childLimit} is null |
| 114 | + */ |
| 115 | + public ChildLimitBuilder add(ToIntFunction<Path> childLimit) { |
| 116 | + Objects.requireNonNull(childLimit, "childLimit is null"); |
| 117 | + this.limits.add(childLimit); |
92 | 118 | return this; |
93 | 119 | } |
94 | 120 |
|
95 | 121 | /** |
96 | 122 | * Adds a child limit rule for paths matching the given matcher. |
97 | 123 | * <p> |
98 | | - * Rules are evaluated in the order they are added. The first matching rule wins. |
| 124 | + * If the path matches, this rule applies the given limit. |
| 125 | + * Otherwise, it yields {@link #UNLIMITED} so that subsequent rules may apply. |
| 126 | + * Because rules are evaluated in insertion order, the first matching rule wins. |
99 | 127 | * </p> |
100 | 128 | * |
101 | | - * @param pathMatcher the condition for paths |
102 | | - * @param limit the maximum number of children (use {@link #UNLIMITED} for no restriction) |
103 | | - * |
| 129 | + * @param pathMatcher the matcher used to test paths (non-null) |
| 130 | + * @param limit the maximum number of children for matching paths |
| 131 | + * (use {@link #UNLIMITED} for no restriction) |
104 | 132 | * @return this builder for chaining |
105 | | - * |
106 | 133 | * @throws NullPointerException if {@code pathMatcher} is null |
107 | 134 | */ |
108 | | - public ChildLimitBuilder limit(PathMatcher pathMatcher, int limit) { |
| 135 | + public ChildLimitBuilder add(PathMatcher pathMatcher, int limit) { |
109 | 136 | Objects.requireNonNull(pathMatcher, "pathMatcher is null"); |
110 | | - this.controls.add(new ChildControl(pathMatcher, limit)); |
111 | | - return this; |
| 137 | + return add(path -> pathMatcher.matches(path) ? limit : UNLIMITED); |
112 | 138 | } |
113 | 139 |
|
114 | 140 | } |
0 commit comments