This is an automated email from the ASF dual-hosted git repository.
lukaszlenart pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/struts.git
The following commit(s) were added to refs/heads/main by this push:
new cd6fbb315 WW-5743 fix(convention): rank path-spanning ** patterns
after single-segment ones (#1962)
cd6fbb315 is described below
commit cd6fbb315f9d92e0ec6ee942e0858bc659a486bd
Author: Lukasz Lenart <[email protected]>
AuthorDate: Thu Sep 17 09:06:07 2026 +0200
WW-5743 fix(convention): rank path-spanning ** patterns after
single-segment ones (#1962)
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) <[email protected]>
---
.../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);