Skip to content

Commit 91e416d

Browse files
committed
Add PathMatcherFactory service with directory filtering optimization
This PR adds a comprehensive PathMatcherFactory service to Maven 4 API with directory filtering optimization capabilities, addressing the need for exclude-only pattern matching and performance optimizations. ## New API Features ### PathMatcherFactory Interface - createPathMatcher(baseDirectory, includes, excludes, useDefaultExcludes) - createPathMatcher(baseDirectory, includes, excludes) - convenience overload - createExcludeOnlyMatcher(baseDirectory, excludes, useDefaultExcludes) - createIncludeOnlyMatcher(baseDirectory, includes) - convenience method - deriveDirectoryMatcher(fileMatcher) - directory filtering optimization ### DefaultPathMatcherFactory Implementation - Full implementation of all PathMatcherFactory methods - Delegates to PathSelector for actual pattern matching - Provides directory optimization via PathSelector.couldHoldSelected() - Fail-safe design returning INCLUDES_ALL for unknown matcher types ## PathSelector Enhancements ### Null Safety Improvements - Added @nonnull annotation to constructor directory parameter - Added Objects.requireNonNull() validation with descriptive error message - Moved baseDirectory assignment to beginning for fail-fast behavior - Updated JavaDoc to document NullPointerException behavior ### Directory Filtering Support - Added canFilterDirectories() method to check optimization capability - Made INCLUDES_ALL field package-private for factory reuse - Enhanced couldHoldSelected() method accessibility ## Directory Filtering Optimization The deriveDirectoryMatcher() method enables significant performance improvements by allowing plugins to skip entire directory trees when they definitively won't contain matching files. This preserves Maven 3's optimization behavior. ### Usage Example: ## Comprehensive Testing ### DefaultPathMatcherFactoryTest - Tests all factory methods with various parameter combinations - Verifies null parameter handling (NullPointerException) - Tests directory matcher derivation functionality - Includes edge cases and fail-safe behavior verification ### Backward Compatibility - All existing PathSelector functionality preserved - No breaking changes to existing APIs - Enhanced error handling with better exception types ## Benefits 1. **Plugin Compatibility**: Enables maven-clean-plugin and other plugins to use exclude-only patterns efficiently 2. **Performance**: Directory filtering optimization preserves Maven 3 behavior 3. **Developer Experience**: Clean service interface with comprehensive JavaDoc 4. **Robustness**: Fail-fast null validation and defensive programming 5. **Future-Proof**: Extensible design for additional pattern matching needs ## Related Work This implementation complements PR #10909 by @desruisseaux which addresses PathSelector bug fixes. Both PRs can be merged independently and work together to provide complete exclude-only functionality. Addresses performance optimization suggestions and provides the missing API methods needed by Maven plugins for efficient file filtering.
1 parent 93d3615 commit 91e416d

File tree

4 files changed

+474
-4
lines changed

4 files changed

+474
-4
lines changed
Lines changed: 141 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,141 @@
1+
/*
2+
* Licensed to the Apache Software Foundation (ASF) under one
3+
* or more contributor license agreements. See the NOTICE file
4+
* distributed with this work for additional information
5+
* regarding copyright ownership. The ASF licenses this file
6+
* to you under the Apache License, Version 2.0 (the
7+
* "License"); you may not use this file except in compliance
8+
* with the License. You may obtain a copy of the License at
9+
*
10+
* http://www.apache.org/licenses/LICENSE-2.0
11+
*
12+
* Unless required by applicable law or agreed to in writing,
13+
* software distributed under the License is distributed on an
14+
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15+
* KIND, either express or implied. See the License for the
16+
* specific language governing permissions and limitations
17+
* under the License.
18+
*/
19+
package org.apache.maven.api.services;
20+
21+
import java.nio.file.Path;
22+
import java.nio.file.PathMatcher;
23+
import java.util.Collection;
24+
25+
import org.apache.maven.api.Service;
26+
import org.apache.maven.api.annotations.Experimental;
27+
import org.apache.maven.api.annotations.Nonnull;
28+
29+
/**
30+
* Service for creating {@link PathMatcher} objects that can be used to filter files
31+
* based on include/exclude patterns. This service provides a clean API for plugins
32+
* to create path matchers without directly depending on implementation classes.
33+
* <p>
34+
* The path matchers created by this service support Maven's traditional include/exclude
35+
* pattern syntax, which is compatible with the behavior of Maven 3 plugins like
36+
* maven-compiler-plugin and maven-clean-plugin.
37+
* <p>
38+
* Pattern syntax supports:
39+
* <ul>
40+
* <li>Standard glob patterns with {@code *}, {@code ?}, and {@code **} wildcards</li>
41+
* <li>Explicit syntax prefixes like {@code "glob:"} or {@code "regex:"}</li>
42+
* <li>Maven 3 compatible behavior for patterns without explicit syntax</li>
43+
* <li>Default exclusion patterns for SCM files when requested</li>
44+
* </ul>
45+
*
46+
* @since 4.0.0
47+
* @see PathMatcher
48+
*/
49+
@Experimental
50+
public interface PathMatcherFactory extends Service {
51+
52+
/**
53+
* Creates a path matcher for filtering files based on include and exclude patterns.
54+
* <p>
55+
* The pathnames used for matching will be relative to the specified base directory
56+
* and use {@code '/'} as separator, regardless of the hosting operating system.
57+
*
58+
* @param baseDirectory the base directory for relativizing paths during matching
59+
* @param includes the patterns of files to include, or null/empty for including all files
60+
* @param excludes the patterns of files to exclude, or null/empty for no exclusion
61+
* @param useDefaultExcludes whether to augment excludes with default SCM exclusion patterns
62+
* @return a PathMatcher that can be used to test if paths should be included
63+
* @throws NullPointerException if baseDirectory is null
64+
*/
65+
@Nonnull
66+
PathMatcher createPathMatcher(
67+
@Nonnull Path baseDirectory,
68+
Collection<String> includes,
69+
Collection<String> excludes,
70+
boolean useDefaultExcludes);
71+
72+
/**
73+
* Creates a path matcher for filtering files based on include and exclude patterns,
74+
* without using default exclusion patterns.
75+
* <p>
76+
* This is equivalent to calling {@link #createPathMatcher(Path, Collection, Collection, boolean)}
77+
* with {@code useDefaultExcludes = false}.
78+
*
79+
* @param baseDirectory the base directory for relativizing paths during matching
80+
* @param includes the patterns of files to include, or null/empty for including all files
81+
* @param excludes the patterns of files to exclude, or null/empty for no exclusion
82+
* @return a PathMatcher that can be used to test if paths should be included
83+
* @throws NullPointerException if baseDirectory is null
84+
*/
85+
@Nonnull
86+
default PathMatcher createPathMatcher(
87+
@Nonnull Path baseDirectory, Collection<String> includes, Collection<String> excludes) {
88+
return createPathMatcher(baseDirectory, includes, excludes, false);
89+
}
90+
91+
/**
92+
* Creates a path matcher that includes all files except those matching the exclude patterns.
93+
* <p>
94+
* This is equivalent to calling {@link #createPathMatcher(Path, Collection, Collection, boolean)}
95+
* with {@code includes = null}.
96+
*
97+
* @param baseDirectory the base directory for relativizing paths during matching
98+
* @param excludes the patterns of files to exclude, or null/empty for no exclusion
99+
* @param useDefaultExcludes whether to augment excludes with default SCM exclusion patterns
100+
* @return a PathMatcher that can be used to test if paths should be included
101+
* @throws NullPointerException if baseDirectory is null
102+
*/
103+
@Nonnull
104+
default PathMatcher createExcludeOnlyMatcher(
105+
@Nonnull Path baseDirectory, Collection<String> excludes, boolean useDefaultExcludes) {
106+
return createPathMatcher(baseDirectory, null, excludes, useDefaultExcludes);
107+
}
108+
109+
/**
110+
* Creates a path matcher that only includes files matching the include patterns.
111+
* <p>
112+
* This is equivalent to calling {@link #createPathMatcher(Path, Collection, Collection, boolean)}
113+
* with {@code excludes = null} and {@code useDefaultExcludes = false}.
114+
*
115+
* @param baseDirectory the base directory for relativizing paths during matching
116+
* @param includes the patterns of files to include, or null/empty for including all files
117+
* @return a PathMatcher that can be used to test if paths should be included
118+
* @throws NullPointerException if baseDirectory is null
119+
*/
120+
@Nonnull
121+
default PathMatcher createIncludeOnlyMatcher(@Nonnull Path baseDirectory, Collection<String> includes) {
122+
return createPathMatcher(baseDirectory, includes, null, false);
123+
}
124+
125+
/**
126+
* Returns a filter for directories that may contain paths accepted by the given matcher.
127+
* The given path matcher should be an instance created by this service.
128+
* The path matcher returned by this method expects <em>directory</em> paths.
129+
* If that matcher returns {@code false}, then the directory will definitively not contain
130+
* the paths selected by the matcher given in argument to this method.
131+
* In such case, the whole directory and all its sub-directories can be skipped.
132+
* In case of doubt, or if the matcher given in argument is not recognized by this method,
133+
* then the matcher returned by this method will return {@code true}.
134+
*
135+
* @param fileMatcher a matcher created by one of the other methods of this interface
136+
* @return filter for directories that may contain the selected files
137+
* @throws NullPointerException if fileMatcher is null
138+
*/
139+
@Nonnull
140+
PathMatcher deriveDirectoryMatcher(@Nonnull PathMatcher fileMatcher);
141+
}
Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
/*
2+
* Licensed to the Apache Software Foundation (ASF) under one
3+
* or more contributor license agreements. See the NOTICE file
4+
* distributed with this work for additional information
5+
* regarding copyright ownership. The ASF licenses this file
6+
* to you under the Apache License, Version 2.0 (the
7+
* "License"); you may not use this file except in compliance
8+
* with the License. You may obtain a copy of the License at
9+
*
10+
* http://www.apache.org/licenses/LICENSE-2.0
11+
*
12+
* Unless required by applicable law or agreed to in writing,
13+
* software distributed under the License is distributed on an
14+
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15+
* KIND, either express or implied. See the License for the
16+
* specific language governing permissions and limitations
17+
* under the License.
18+
*/
19+
package org.apache.maven.impl;
20+
21+
import java.nio.file.Path;
22+
import java.nio.file.PathMatcher;
23+
import java.util.Collection;
24+
import java.util.Objects;
25+
26+
import org.apache.maven.api.annotations.Nonnull;
27+
import org.apache.maven.api.di.Named;
28+
import org.apache.maven.api.di.Singleton;
29+
import org.apache.maven.api.services.PathMatcherFactory;
30+
31+
import static java.util.Objects.requireNonNull;
32+
33+
/**
34+
* Default implementation of {@link PathMatcherFactory} that creates {@link PathSelector}
35+
* instances for filtering files based on include/exclude patterns.
36+
* <p>
37+
* This implementation provides Maven's traditional include/exclude pattern behavior,
38+
* compatible with Maven 3 plugins like maven-compiler-plugin and maven-clean-plugin.
39+
*
40+
* @since 4.0.0
41+
*/
42+
@Named
43+
@Singleton
44+
public class DefaultPathMatcherFactory implements PathMatcherFactory {
45+
46+
@Nonnull
47+
@Override
48+
public PathMatcher createPathMatcher(
49+
@Nonnull Path baseDirectory,
50+
Collection<String> includes,
51+
Collection<String> excludes,
52+
boolean useDefaultExcludes) {
53+
requireNonNull(baseDirectory, "baseDirectory cannot be null");
54+
55+
return new PathSelector(baseDirectory, includes, excludes, useDefaultExcludes);
56+
}
57+
58+
@Nonnull
59+
@Override
60+
public PathMatcher createExcludeOnlyMatcher(
61+
@Nonnull Path baseDirectory, Collection<String> excludes, boolean useDefaultExcludes) {
62+
return createPathMatcher(baseDirectory, null, excludes, useDefaultExcludes);
63+
}
64+
65+
@Nonnull
66+
@Override
67+
public PathMatcher deriveDirectoryMatcher(@Nonnull PathMatcher fileMatcher) {
68+
if (Objects.requireNonNull(fileMatcher) instanceof PathSelector selector) {
69+
if (selector.canFilterDirectories()) {
70+
return selector::couldHoldSelected;
71+
}
72+
}
73+
return PathSelector.INCLUDES_ALL;
74+
}
75+
}

impl/maven-impl/src/main/java/org/apache/maven/impl/PathSelector.java

Lines changed: 22 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -28,8 +28,11 @@
2828
import java.util.Iterator;
2929
import java.util.LinkedHashSet;
3030
import java.util.List;
31+
import java.util.Objects;
3132
import java.util.Set;
3233

34+
import org.apache.maven.api.annotations.Nonnull;
35+
3336
/**
3437
* Determines whether a path is selected according to include/exclude patterns.
3538
* The pathnames used for method parameters will be relative to some base directory
@@ -163,7 +166,7 @@ public class PathSelector implements PathMatcher {
163166
*
164167
* @see #simplify()
165168
*/
166-
private static final PathMatcher INCLUDES_ALL = (path) -> true;
169+
static final PathMatcher INCLUDES_ALL = (path) -> true;
167170

168171
/**
169172
* String representations of the normalized include filters.
@@ -219,13 +222,17 @@ public class PathSelector implements PathMatcher {
219222
* @param includes the patterns of the files to include, or null or empty for including all files
220223
* @param excludes the patterns of the files to exclude, or null or empty for no exclusion
221224
* @param useDefaultExcludes whether to augment the excludes with a default set of <abbr>SCM</abbr> patterns
225+
* @throws NullPointerException if directory is null
222226
*/
223227
public PathSelector(
224-
Path directory, Collection<String> includes, Collection<String> excludes, boolean useDefaultExcludes) {
228+
@Nonnull Path directory,
229+
Collection<String> includes,
230+
Collection<String> excludes,
231+
boolean useDefaultExcludes) {
232+
baseDirectory = Objects.requireNonNull(directory, "directory cannot be null");
225233
includePatterns = normalizePatterns(includes, false);
226234
excludePatterns = normalizePatterns(effectiveExcludes(excludes, includePatterns, useDefaultExcludes), true);
227-
baseDirectory = directory;
228-
FileSystem system = directory.getFileSystem();
235+
FileSystem system = baseDirectory.getFileSystem();
229236
this.includes = matchers(system, includePatterns);
230237
this.excludes = matchers(system, excludePatterns);
231238
dirIncludes = matchers(system, directoryPatterns(includePatterns, false));
@@ -570,6 +577,17 @@ private static boolean isMatched(Path path, PathMatcher[] matchers) {
570577
return false;
571578
}
572579

580+
/**
581+
* Returns whether {@link #couldHoldSelected(Path)} may return {@code false} for some directories.
582+
* This method can be used to determine if directory filtering optimization is possible.
583+
*
584+
* @return {@code true} if directory filtering is possible, {@code false} if all directories
585+
* will be considered as potentially containing selected files
586+
*/
587+
boolean canFilterDirectories() {
588+
return dirIncludes.length != 0 || dirExcludes.length != 0;
589+
}
590+
573591
/**
574592
* Determines whether a directory could contain selected paths.
575593
*

0 commit comments

Comments
 (0)