diff --git a/src/main/java/org/codehaus/plexus/components/io/fileselectors/IncludeExcludeFileSelector.java b/src/main/java/org/codehaus/plexus/components/io/fileselectors/IncludeExcludeFileSelector.java index 9f7af5f6..d59bdb28 100644 --- a/src/main/java/org/codehaus/plexus/components/io/fileselectors/IncludeExcludeFileSelector.java +++ b/src/main/java/org/codehaus/plexus/components/io/fileselectors/IncludeExcludeFileSelector.java @@ -93,12 +93,20 @@ public void setIncludes(@Nullable String[] includes) { } private static @Nonnull String getCanonicalName(@Nonnull String pName) { - return pName.replace('/', File.separatorChar).replace('\\', File.separatorChar); + return getCanonicalName(pName, File.separatorChar); } - private String asPattern(@Nonnull String pPattern) { - String pattern = getCanonicalName(pPattern.trim()); - if (pattern.endsWith(File.separator)) { + private static @Nonnull String getCanonicalName(@Nonnull String pName, char separatorChar) { + return pName.replace('/', separatorChar).replace('\\', separatorChar); + } + + private static String asPattern(@Nonnull String pPattern) { + return asPattern(pPattern, File.separatorChar); + } + + private static String asPattern(@Nonnull String pPattern, char separatorChar) { + String pattern = getCanonicalName(pPattern.trim(), separatorChar); + if (pattern.endsWith(String.valueOf(separatorChar))) { pattern += "**"; } return pattern; @@ -132,19 +140,25 @@ private String asPattern(@Nonnull String pPattern) { public void setExcludes(@Nullable String[] excludes) { this.excludes = excludes; final String[] defaultExcludes = useDefaultExcludes ? FileUtils.getDefaultExcludes() : new String[] {}; - if (excludes == null) { - computedExcludes = MatchPatterns.from(defaultExcludes); - } else { - String[] temp = new String[excludes.length + defaultExcludes.length]; - for (int i = 0; i < excludes.length; i++) { - temp[i] = asPattern(excludes[i]); - } + computedExcludes = MatchPatterns.from(asExcludePatterns(excludes, defaultExcludes, File.separatorChar)); + } - if (defaultExcludes.length > 0) { - System.arraycopy(defaultExcludes, 0, temp, excludes.length, defaultExcludes.length); - } - computedExcludes = MatchPatterns.from(temp); + /** + * Converts the user's exclude patterns and the default excludes into patterns + * that use the given separator, the same separator {@link #isSelected(FileInfo)} + * uses for the names it matches. + */ + static String[] asExcludePatterns( + @Nullable String[] excludes, @Nonnull String[] defaultExcludes, char separatorChar) { + final int count = excludes == null ? 0 : excludes.length; + final String[] patterns = new String[count + defaultExcludes.length]; + for (int i = 0; i < count; i++) { + patterns[i] = asPattern(excludes[i], separatorChar); + } + for (int i = 0; i < defaultExcludes.length; i++) { + patterns[count + i] = asPattern(defaultExcludes[i], separatorChar); } + return patterns; } /** diff --git a/src/test/java/org/codehaus/plexus/components/io/fileselectors/IncludeExcludeFileSelectorTest.java b/src/test/java/org/codehaus/plexus/components/io/fileselectors/IncludeExcludeFileSelectorTest.java new file mode 100644 index 00000000..1c9e1229 --- /dev/null +++ b/src/test/java/org/codehaus/plexus/components/io/fileselectors/IncludeExcludeFileSelectorTest.java @@ -0,0 +1,71 @@ +package org.codehaus.plexus.components.io.fileselectors; + +import javax.annotation.Nonnull; + +import java.io.InputStream; +import java.net.URL; + +import org.codehaus.plexus.components.io.resources.AbstractPlexusIoResource; +import org.codehaus.plexus.util.FileUtils; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertArrayEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; + +class IncludeExcludeFileSelectorTest { + + /** + * {@link IncludeExcludeFileSelector#isSelected} matches names that use the platform separator, + * so every exclude pattern, including the default excludes, must use it too. Using a backslash + * here reproduces the Windows behaviour on any platform. + */ + @Test + void excludePatternsUseTheGivenSeparator() { + String[] patterns = IncludeExcludeFileSelector.asExcludePatterns( + new String[] {"bar/*", "foo/"}, new String[] {"**/.svn/**", "**/.git"}, '\\'); + + assertArrayEquals(new String[] {"bar\\*", "foo\\**", "**\\.svn\\**", "**\\.git"}, patterns); + } + + @Test + void defaultExcludesUseTheGivenSeparator() { + String[] patterns = IncludeExcludeFileSelector.asExcludePatterns(null, FileUtils.getDefaultExcludes(), '\\'); + + for (String pattern : patterns) { + assertFalse(pattern.contains("/"), pattern); + } + } + + @Test + void defaultExcludesApplyWithAndWithoutExcludes() { + IncludeExcludeFileSelector selector = new IncludeExcludeFileSelector(); + + selector.setExcludes(null); + assertFalse(selector.isSelected(resource(".svn/entries"))); + assertFalse(selector.isSelected(resource("foo/.git/config"))); + assertTrue(selector.isSelected(resource("foo/bar.txt"))); + + selector.setExcludes(new String[] {"**/*.gif"}); + assertFalse(selector.isSelected(resource(".svn/entries"))); + assertFalse(selector.isSelected(resource("foo/x.gif"))); + assertTrue(selector.isSelected(resource("foo/bar.txt"))); + + selector.setUseDefaultExcludes(false); + assertTrue(selector.isSelected(resource(".svn/entries"))); + assertFalse(selector.isSelected(resource("foo/x.gif"))); + } + + private static AbstractPlexusIoResource resource(String name) { + return new AbstractPlexusIoResource(name, 0, 0, true, false, true) { + @Nonnull + public InputStream getContents() { + throw new IllegalStateException("Not implemented"); + } + + public URL getURL() { + throw new IllegalStateException("Not implemented"); + } + }; + } +}