diff --git a/org.springframework.core/src/main/java/org/springframework/util/AntPathMatcher.java b/org.springframework.core/src/main/java/org/springframework/util/AntPathMatcher.java index 01994949d5d..9602d7cc1b0 100644 --- a/org.springframework.core/src/main/java/org/springframework/util/AntPathMatcher.java +++ b/org.springframework.core/src/main/java/org/springframework/util/AntPathMatcher.java @@ -16,6 +16,7 @@ package org.springframework.util; +import java.util.Comparator; import java.util.LinkedHashMap; import java.util.Map; @@ -52,6 +53,7 @@ import java.util.Map; * @author Alef Arendsen * @author Juergen Hoeller * @author Rob Harrop + * @author Arjen Poutsma * @since 16.07.2003 */ public class AntPathMatcher implements PathMatcher { @@ -288,4 +290,73 @@ public class AntPathMatcher implements PathMatcher { return variables; } + /** + * Given a full path, returns a {@link Comparator} suitable for sorting patterns in order of explicitness. + * + *

The returned Comparator will {@linkplain java.util.Collections#sort(java.util.List, + * java.util.Comparator) sort} a list so that more specific patterns (without uri templates or wild cards) come before + * generic patterns. So given a list with the following patterns: + *

    + *
  1. /hotels/new
  2. + *
  3. /hotels/{hotel}
  4. + *
  5. /hotels/*
  6. + *
+ * the returned comparator will sort this list so that the order will be as indicated. + * + * @param path the full path to use for comparison + * @return a comparator capable of sorting patterns in order of explicitness + */ + public Comparator getPatternComparator(String path) { + return new AntPatternComparator(path); + } + + private static class AntPatternComparator implements Comparator{ + + private final String path; + + private AntPatternComparator(String path) { + this.path = path; + } + + public int compare(String pattern1, String pattern2) { + if (pattern1 == null && pattern2 == null) { + return 0; + } + else if (pattern1 == null) { + return 1; + } + else if (pattern2 == null) { + return -1; + } + boolean pattern1EqualsPath = pattern1.equals(path); + boolean pattern2EqualsPath = pattern2.equals(path); + if (pattern1EqualsPath && pattern2EqualsPath) { + return 0; + } + else if (pattern1EqualsPath) { + return -1; + } + else if (pattern2EqualsPath) { + return 1; + } + int wildCardCount1 = StringUtils.countOccurrencesOf(pattern1, "*"); + int wildCardCount2 = StringUtils.countOccurrencesOf(pattern2, "*"); + if (wildCardCount1 < wildCardCount2) { + return -1; + } + else if (wildCardCount2 < wildCardCount1) { + return 1; + } + int bracketCount1 = StringUtils.countOccurrencesOf(pattern1, "{"); + int bracketCount2 = StringUtils.countOccurrencesOf(pattern1, "{"); + if (bracketCount1 < bracketCount2) { + return -1; + } + else if (bracketCount1 < bracketCount2) { + return 1; + } + return 0; + } + } + } diff --git a/org.springframework.core/src/main/java/org/springframework/util/PathMatcher.java b/org.springframework.core/src/main/java/org/springframework/util/PathMatcher.java index 4ebbf32176b..6804af56362 100644 --- a/org.springframework.core/src/main/java/org/springframework/util/PathMatcher.java +++ b/org.springframework.core/src/main/java/org/springframework/util/PathMatcher.java @@ -16,11 +16,12 @@ package org.springframework.util; +import java.util.Comparator; import java.util.Map; /** * Strategy interface for String-based path matching. - * + * *

Used by {@link org.springframework.core.io.support.PathMatchingResourcePatternResolver}, * {@link org.springframework.web.servlet.handler.AbstractUrlHandlerMapping}, * {@link org.springframework.web.servlet.mvc.multiaction.PropertiesMethodNameResolver}, @@ -102,4 +103,16 @@ public interface PathMatcher { * @return a map, containing variable names as keys; variables values as values */ Map extractUriTemplateVariables(String pattern, String path); + + /** + * Given a full path, returns a {@link Comparator} suitable for sorting patterns in order of explicitness. + * + *

The full algorithm used depends on the underlying implementation, but generally, the returned + * Comparator will {@linkplain java.util.Collections#sort(java.util.List, java.util.Comparator) sort} a + * list so that more specific patterns come before generic patterns. + * + * @param path the full path to use for comparison + * @return a comparator capable of sorting patterns in order of explicitness + */ + Comparator getPatternComparator(String path); } diff --git a/org.springframework.core/src/test/java/org/springframework/util/AntPathMatcherTests.java b/org.springframework.core/src/test/java/org/springframework/util/AntPathMatcherTests.java index 26219d36388..c6d76d0e404 100644 --- a/org.springframework.core/src/test/java/org/springframework/util/AntPathMatcherTests.java +++ b/org.springframework.core/src/test/java/org/springframework/util/AntPathMatcherTests.java @@ -16,8 +16,11 @@ package org.springframework.util; +import java.util.ArrayList; import java.util.Collections; +import java.util.Comparator; import java.util.LinkedHashMap; +import java.util.List; import java.util.Map; import static org.junit.Assert.*; @@ -320,5 +323,98 @@ public class AntPathMatcherTests { assertEquals(Collections.singletonMap("B", "b"), result); } + @Test + public void patternComparator() { + Comparator comparator = pathMatcher.getPatternComparator("/hotels/new"); + + assertEquals(0, comparator.compare(null, null)); + assertEquals(1, comparator.compare(null, "/hotels/new")); + assertEquals(-1, comparator.compare("/hotels/new", null)); + + assertEquals(0, comparator.compare("/hotels/new", "/hotels/new")); + + assertEquals(-1, comparator.compare("/hotels/new", "/hotels/*")); + assertEquals(1, comparator.compare("/hotels/*", "/hotels/new")); + assertEquals(0, comparator.compare("/hotels/*", "/hotels/*")); + + assertEquals(-1, comparator.compare("/hotels/new", "/hotels/{hotel}")); + assertEquals(1, comparator.compare("/hotels/{hotel}", "/hotels/new")); + assertEquals(0, comparator.compare("/hotels/{hotel}", "/hotels/{hotel}")); + + assertEquals(-1, comparator.compare("/hotels/{hotel}", "/hotels/*")); + assertEquals(1, comparator.compare("/hotels/*", "/hotels/{hotel}")); + } + + @Test + public void patternComparatorSort() { + Comparator comparator = pathMatcher.getPatternComparator("/hotels/new"); + List paths = new ArrayList(3); + + paths.add(null); + paths.add("/hotels/new"); + Collections.sort(paths, comparator); + assertEquals("/hotels/new", paths.get(0)); + assertNull(paths.get(1)); + paths.clear(); + + paths.add("/hotels/new"); + paths.add(null); + Collections.sort(paths, comparator); + assertEquals("/hotels/new", paths.get(0)); + assertNull(paths.get(1)); + paths.clear(); + + paths.add("/hotels/*"); + paths.add("/hotels/new"); + Collections.sort(paths, comparator); + assertEquals("/hotels/new", paths.get(0)); + assertEquals("/hotels/*", paths.get(1)); + paths.clear(); + + paths.add("/hotels/new"); + paths.add("/hotels/*"); + Collections.sort(paths, comparator); + assertEquals("/hotels/new", paths.get(0)); + assertEquals("/hotels/*", paths.get(1)); + paths.clear(); + + paths.add("/hotels/**"); + paths.add("/hotels/*"); + Collections.sort(paths, comparator); + assertEquals("/hotels/*", paths.get(0)); + assertEquals("/hotels/**", paths.get(1)); + paths.clear(); + + paths.add("/hotels/*"); + paths.add("/hotels/**"); + Collections.sort(paths, comparator); + assertEquals("/hotels/*", paths.get(0)); + assertEquals("/hotels/**", paths.get(1)); + paths.clear(); + + paths.add("/hotels/{hotel}"); + paths.add("/hotels/new"); + Collections.sort(paths, comparator); + assertEquals("/hotels/new", paths.get(0)); + assertEquals("/hotels/{hotel}", paths.get(1)); + paths.clear(); + + paths.add("/hotels/new"); + paths.add("/hotels/{hotel}"); + Collections.sort(paths, comparator); + assertEquals("/hotels/new", paths.get(0)); + assertEquals("/hotels/{hotel}", paths.get(1)); + paths.clear(); + + paths.add("/hotels/*"); + paths.add("/hotels/{hotel}"); + paths.add("/hotels/new"); + Collections.shuffle(paths); + Collections.sort(paths, comparator); + assertEquals("/hotels/new", paths.get(0)); + assertEquals("/hotels/{hotel}", paths.get(1)); + assertEquals("/hotels/*", paths.get(2)); + paths.clear(); + } }