diff --git a/archunit/src/main/java/com/tngtech/archunit/base/PackageMatcher.java b/archunit/src/main/java/com/tngtech/archunit/base/PackageMatcher.java
index 7d539264c2..4310d02ea1 100644
--- a/archunit/src/main/java/com/tngtech/archunit/base/PackageMatcher.java
+++ b/archunit/src/main/java/com/tngtech/archunit/base/PackageMatcher.java
@@ -41,6 +41,12 @@
*
{@code '*.*.pack*..'} matches {@code 'a.b.packfix.c.d'},
* but neither {@code 'a.packfix.b'} nor {@code 'a.b.prepack.d'}
*
+ * You can also use alternations with the '|' operator. For example
+ *
+ * - {@code 'pack.[a.c|b*].d'} matches {@code 'pack.a.c.d'} or {@code 'pack.bar.d'}, but neither
+ * {@code 'pack.a.d'} nor {@code 'pack.b.c.d'}
+ *
+ *
* Furthermore, the use of capturing groups is supported. In this case '(*)' matches any sequence of characters,
* but not the dot '.', while '(**)' matches any sequence including the dot.
* For example
@@ -49,6 +55,7 @@
*
{@code '..service.(**)'} matches {@code 'a.service.hello.more'} and group 1 would be {@code 'hello.more'}
* {@code 'my.(*)..service.(**)'} matches {@code 'my.company.some.service.hello.more'}
* and group 1 would be {@code 'company'}, while group 2 would be {@code 'hello.more'}
+ * {@code '..service.(a|b*)..'} matches {@code 'a.service.bar.more'} and group 1 would be {@code 'bar'}
*
* Create via {@link PackageMatcher#of(String) PackageMatcher.of(packageIdentifier)}
*/
@@ -62,7 +69,7 @@ public final class PackageMatcher {
private static final String TWO_STAR_CAPTURE_REGEX = "(\\w+(?:\\.\\w+)*)";
static final String TWO_STAR_REGEX_MARKER = "#%#%#";
- private static final Set PACKAGE_CONTROL_SYMBOLS = ImmutableSet.of('*', '(', ')', '.');
+ private static final Set PACKAGE_CONTROL_SYMBOLS = ImmutableSet.of('*', '(', ')', '.', '|', '[', ']');
private final String packageIdentifier;
private final Pattern packagePattern;
@@ -84,6 +91,10 @@ private void validate(String packageIdentifier) {
if (packageIdentifier.contains("(..)")) {
throw new IllegalArgumentException("Package Identifier does not support capturing via (..), use (**) instead");
}
+ if (Pattern.compile("([\\)\\]]\\|)|(\\|[\\(\\[])").matcher(packageIdentifier).find()) {
+ throw new IllegalArgumentException("Package Identifier does neither support nested alternations nor " +
+ "alternation of capturing and non capturing alternatives");
+ }
validateCharacters(packageIdentifier);
}
@@ -100,6 +111,7 @@ private void validateCharacters(String packageIdentifier) {
private String convertToRegex(String packageIdentifier) {
return packageIdentifier.
+ replaceAll("\\[([\\w.*]*)((?:\\|[\\w.*]*)+)]", "(?:$1$2)"). // replacing all ' [..|..]' with '(?:..|..)'
replace(TWO_STAR_CAPTURE_LITERAL, TWO_STAR_REGEX_MARKER).
replace("*", "\\w+").
replace(".", "\\.").
diff --git a/archunit/src/test/java/com/tngtech/archunit/base/PackageMatcherTest.java b/archunit/src/test/java/com/tngtech/archunit/base/PackageMatcherTest.java
index 76958715f1..863a067136 100644
--- a/archunit/src/test/java/com/tngtech/archunit/base/PackageMatcherTest.java
+++ b/archunit/src/test/java/com/tngtech/archunit/base/PackageMatcherTest.java
@@ -20,34 +20,40 @@ public class PackageMatcherTest {
@Test
@DataProvider(value = {
- "some.arbitrary.pkg | some.arbitrary.pkg | true",
- "some.arbitrary.pkg | some.thing.different | false",
- "some..pkg | some.arbitrary.pkg | true",
- "some..middle..pkg | some.arbitrary.middle.more.pkg | true",
- "*..pkg | some.arbitrary.pkg | true",
- "some..* | some.arbitrary.pkg | true",
- "*..pkg | some.arbitrary.pkg.toomuch | false",
- "toomuch.some..* | some.arbitrary.pkg | false",
- "*..wrong | some.arbitrary.pkg | false",
- "some..* | wrong.arbitrary.pkg | false",
- "..some | some | true",
- "some.. | some | true",
- "*..some | some | false",
- "some..* | some | false",
- "..some | asome | false",
- "some.. | somea | false",
- "*.*.* | wrong.arbitrary.pkg | true",
- "*.*.* | wrong.arbitrary.pkg.toomuch | false",
- "some.arbi*.pk*.. | some.arbitrary.pkg.whatever | true",
- "some.arbi*.. | some.brbitrary.pkg | false",
- "some.*rary.*kg.. | some.arbitrary.pkg.whatever | true",
- "some.*rary.. | some.arbitrarz.pkg | false",
- "some.pkg | someepkg | false",
- "..pkg.. | some.random.pkg.maybe.anywhere | true",
- "..p.. | s.r.p.m.a | true",
- "*..pkg..* | some.random.pkg.maybe.anywhere | true",
- "*..p..* | s.r.p.m.a | true"
- }, splitBy = "\\|")
+ "some.arbitrary.pkg , some.arbitrary.pkg , true",
+ "some.arbitrary.pkg , some.thing.different , false",
+ "some..pkg , some.arbitrary.pkg , true",
+ "some..middle..pkg , some.arbitrary.middle.more.pkg , true",
+ "*..pkg , some.arbitrary.pkg , true",
+ "some..* , some.arbitrary.pkg , true",
+ "*..pkg , some.arbitrary.pkg.toomuch , false",
+ "toomuch.some..* , some.arbitrary.pkg , false",
+ "*..wrong , some.arbitrary.pkg , false",
+ "some..* , wrong.arbitrary.pkg , false",
+ "..some , some , true",
+ "some.. , some , true",
+ "*..some , some , false",
+ "some..* , some , false",
+ "..some , asome , false",
+ "some.. , somea , false",
+ "*.*.* , wrong.arbitrary.pkg , true",
+ "*.*.* , wrong.arbitrary.pkg.toomuch , false",
+ "some.arbi*.pk*.. , some.arbitrary.pkg.whatever , true",
+ "some.arbi*.. , some.brbitrary.pkg , false",
+ "some.*rary.*kg.. , some.arbitrary.pkg.whatever , true",
+ "some.*rary.. , some.arbitrarz.pkg , false",
+ "some.pkg , someepkg , false",
+ "..pkg.. , some.random.pkg.maybe.anywhere , true",
+ "..p.. , s.r.p.m.a , true",
+ "*..pkg..* , some.random.pkg.maybe.anywhere , true",
+ "*..p..* , s.r.p.m.a , true",
+ "..[a|b|c].pk*.. , some.a.pkg.whatever , true",
+ "..[c|b].pk*.. , some.a.pkg.whatever , false",
+ "..[a|b*].pk*.. , some.bitrary.pkg.whatever , true",
+ "..[a|b*].pk*.. , some.a.pkg.whatever , true",
+ "..[a|b*].pk*.. , some.arbitrary.pkg.whatever , false",
+ "..[*c|d*].pk*.. , some.arbitrary.pkg.whatever , false",
+ })
public void match(String matcher, String target, boolean matches) {
assertThat(PackageMatcher.of(matcher).matches(target))
.as("package matches")
@@ -56,26 +62,30 @@ public void match(String matcher, String target, boolean matches) {
@Test
@DataProvider(value = {
- "some.(*).pkg | some.arbitrary.pkg | arbitrary",
- "some.arb(*)ry.pkg | some.arbitrary.pkg | itra",
- "some.arb(*)ry.pkg | some.arbit.rary.pkg | null",
- "some.(*).matches.(*).pkg | some.first.matches.second.pkg | first:second",
- "(*).matches.(*) | start.matches.end | start:end",
- "(*).(*).(*).(*) | a.b.c.d | a:b:c:d",
- "(*) | some | some",
- "some.(*).pkg | some.in.between.pkg | null",
- "some.(**).pkg | some.in.between.pkg | in.between",
- "some.(**).pkg.(*) | some.in.between.pkg.addon | in.between:addon",
- "some(**)pkg | somerandom.in.between.longpkg | random.in.between.long",
- "some.(**).pkg | somer.in.between.pkg | null",
- "some.(**).pkg | some.in.between.gpkg | null",
- "so(*)me.(**)pkg.an(*).more | soinfme.in.between.gpkg.and.more | inf:in.between.g:d",
- "so(*)me.(**)pkg.an(*).more | soinfme.in.between.gpkg.an.more | null",
- "so(**)me | some | null",
- "so(*)me | some | null",
- "(**)so | awe.some.aso | awe.some.a",
- "so(**) | soan.some.we | an.some.we",
- }, splitBy = "\\|")
+ "some.(*).pkg , some.arbitrary.pkg , arbitrary",
+ "some.arb(*)ry.pkg , some.arbitrary.pkg , itra",
+ "some.arb(*)ry.pkg , some.arbit.rary.pkg , null",
+ "some.(*).matches.(*).pkg , some.first.matches.second.pkg , first:second",
+ "(*).matches.(*) , start.matches.end , start:end",
+ "(*).(*).(*).(*) , a.b.c.d , a:b:c:d",
+ "(*) , some , some",
+ "some.(*).pkg , some.in.between.pkg , null",
+ "some.(**).pkg , some.in.between.pkg , in.between",
+ "some.(**).pkg.(*) , some.in.between.pkg.addon , in.between:addon",
+ "some(**)pkg , somerandom.in.between.longpkg , random.in.between.long",
+ "some.(**).pkg , somer.in.between.pkg , null",
+ "some.(**).pkg , some.in.between.gpkg , null",
+ "so(*)me.(**)pkg.an(*).more , soinfme.in.between.gpkg.and.more , inf:in.between.g:d",
+ "so(*)me.(**)pkg.an(*).more , soinfme.in.between.gpkg.an.more , null",
+ "so(**)me , some , null",
+ "so(*)me , some , null",
+ "(**)so , awe.some.aso , awe.some.a",
+ "so(**) , soan.some.we , an.some.we",
+ "..(a|b).pk*.(c|d).. , some.a.pkg.d.whatever , a:d",
+ "..[a|b|c].pk*.(c|d).. , some.c.pkg.d.whatever , d",
+ "..(a|b*|cd).pk*.(**).end , some.bitrary.pkg.in.between.end, bitrary:in.between",
+ "..(a.b|c.d).pk* , some.a.b.pkg , a.b",
+ }, splitBy = ",")
public void capture_groups(String matcher, String target, String groupString) {
assertThat(PackageMatcher.of(matcher).match(target).isPresent())
.as("'%s' matching '%s'", matcher, target)
@@ -113,6 +123,24 @@ public void should_reject_capturing_with_two_dots() {
PackageMatcher.of("some.(..).package");
}
+ @Test
+ public void should_reject_nested_alternations() {
+ thrown.expect(IllegalArgumentException.class);
+ thrown.expectMessage("Package Identifier does neither support nested alternations nor alternation of " +
+ "capturing and non capturing alternatives");
+
+ PackageMatcher.of("some.(a|b)|(c|d).package");
+ }
+
+ @Test
+ public void should_reject_alternation_of_capturing_and_non_capturing_alternatives() {
+ thrown.expect(IllegalArgumentException.class);
+ thrown.expectMessage("Package Identifier does neither support nested alternations nor alternation of " +
+ "capturing and non capturing alternatives");
+
+ PackageMatcher.of("some.[a|(c)].package");
+ }
+
@Test
public void should_reject_illegal_characters() {
String illegalPackageIdentifier = "some" + PackageMatcher.TWO_STAR_REGEX_MARKER + "package";