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);

Reply via email to