You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@struts.apache.org by lu...@apache.org on 2026/09/17 06:44:28 UTC
(struts) 01/01: WW-5743 fix(convention): rank path-spanning ** patterns after single-segment ones
This is an automated email from the ASF dual-hosted git repository.
lukaszlenart pushed a commit to branch feature/WW-5743-path-wildcard-ranking
in repository https://gitbox.apache.org/repos/asf/struts.git
commit b0eed791ac2e8e91482873e92ff5f0c3e241cac7
Author: Lukasz Lenart <lu...@apache.org>
AuthorDate: Thu Sep 17 08:44:15 2026 +0200
WW-5743 fix(convention): rank path-spanning ** patterns after single-segment ones
ActionNameSpecificityComparator compared the raw wildcard-token count before
the ** count, so `a/**` (one token) sorted ahead of `a/*/*` (two tokens) and
shadowed it for `a/x/y`, although `**` crosses `/` and `*` does not. The **
count is now the first key, then the token count, then the literal count.
Both `*` (WildcardHelper MATCH_FILE) and `{var}` (`([^/]+)` in
NamedVariablePatternMatcher) are single-segment, so the same rule covers
both matchers; the tests pin one pair for each.
Co-Authored-By: Claude Opus 5 (1M context) <no...@anthropic.com>
---
.../struts2/convention/ActionNameSpecificityComparator.java | 11 ++++++-----
.../convention/ActionNameSpecificityComparatorTest.java | 12 ++++++++++++
2 files changed, 18 insertions(+), 5 deletions(-)
diff --git a/plugins/convention/src/main/java/org/apache/struts2/convention/ActionNameSpecificityComparator.java b/plugins/convention/src/main/java/org/apache/struts2/convention/ActionNameSpecificityComparator.java
index a0455dced..f3d771ab9 100644
--- a/plugins/convention/src/main/java/org/apache/struts2/convention/ActionNameSpecificityComparator.java
+++ b/plugins/convention/src/main/java/org/apache/struts2/convention/ActionNameSpecificityComparator.java
@@ -27,9 +27,10 @@ import java.util.Comparator;
*
* <p>Ordering keys, applied in order:</p>
* <ol>
+ * <li>fewer path-spanning {@code **} tokens first — {@code **} crosses {@code /} while {@code *}
+ * and <code>{var}</code> never do, so any pattern relying on it is broader than one that does not;</li>
* <li>fewer wildcard tokens first (a {@code *}/{@code **} run, or a <code>{var}</code> group);</li>
* <li>more literal characters first;</li>
- * <li>fewer path-spanning {@code **} tokens first;</li>
* <li>natural (alphabetical) order of the pattern, for deterministic tie-breaking.</li>
* </ol>
*
@@ -45,6 +46,10 @@ public class ActionNameSpecificityComparator implements Comparator<String> {
Counts ca = count(a);
Counts cb = count(b);
+ int byPathWildcards = Integer.compare(ca.pathWildcards, cb.pathWildcards);
+ if (byPathWildcards != 0) {
+ return byPathWildcards;
+ }
int byWildcards = Integer.compare(ca.wildcards, cb.wildcards);
if (byWildcards != 0) {
return byWildcards;
@@ -53,10 +58,6 @@ public class ActionNameSpecificityComparator implements Comparator<String> {
if (byLiterals != 0) {
return byLiterals;
}
- int byPathWildcards = Integer.compare(ca.pathWildcards, cb.pathWildcards);
- if (byPathWildcards != 0) {
- return byPathWildcards;
- }
return a.compareTo(b);
}
diff --git a/plugins/convention/src/test/java/org/apache/struts2/convention/ActionNameSpecificityComparatorTest.java b/plugins/convention/src/test/java/org/apache/struts2/convention/ActionNameSpecificityComparatorTest.java
index f97a1d1d4..38ac38662 100644
--- a/plugins/convention/src/test/java/org/apache/struts2/convention/ActionNameSpecificityComparatorTest.java
+++ b/plugins/convention/src/test/java/org/apache/struts2/convention/ActionNameSpecificityComparatorTest.java
@@ -50,6 +50,18 @@ public class ActionNameSpecificityComparatorTest {
assertTrue(comparator.compare("a/*", "a/**") < 0);
}
+ @Test
+ public void twoSingleStarsBeatOnePathStar() {
+ // "a/**" spans any depth, "a/*/*" exactly two segments -> the ** pattern is broader and must lose
+ assertTrue(comparator.compare("a/*/*", "a/**") < 0);
+ }
+
+ @Test
+ public void twoNamedVariablesBeatOnePathStar() {
+ // {var} matches a single segment like *, so the same rule applies for NamedVariablePatternMatcher users
+ assertTrue(comparator.compare("a/{x}/{y}", "a/**") < 0);
+ }
+
@Test
public void namedVariablesCountAsWildcards() {
assertTrue(comparator.compare("some/usefull/{id}", "some/{id}") < 0);