diff --git a/spring-core/src/main/java/org/springframework/util/AntPathMatcher.java b/spring-core/src/main/java/org/springframework/util/AntPathMatcher.java index 1c0f7d845b..886327e13c 100644 --- a/spring-core/src/main/java/org/springframework/util/AntPathMatcher.java +++ b/spring-core/src/main/java/org/springframework/util/AntPathMatcher.java @@ -594,15 +594,16 @@ public class AntPathMatcher implements PathMatcher { @Override public int compare(String pattern1, String pattern2) { - boolean pattern1NullCaptureAll = isNullOrCaptureAllPattern(pattern1); - boolean pattern2NullCaptureAll = isNullOrCaptureAllPattern(pattern2); - if (pattern1NullCaptureAll && pattern2NullCaptureAll) { + PatternInfo info1 = new PatternInfo(pattern1); + PatternInfo info2 = new PatternInfo(pattern2); + + if (info1.isLeastSpecific() && info2.isLeastSpecific()) { return 0; } - else if (pattern1NullCaptureAll) { + else if (info1.isLeastSpecific()) { return 1; } - else if (pattern2NullCaptureAll) { + else if (info2.isLeastSpecific()) { return -1; } @@ -618,93 +619,126 @@ public class AntPathMatcher implements PathMatcher { return 1; } - PatternElements pattern1Elements = new PatternElements(pattern1); - PatternElements pattern2Elements = new PatternElements(pattern2); - - if(pattern1Elements.endsWithCatchAll && pattern2Elements.catchAllCount == 0) { + if (info1.isPrefixPattern() && info2.getDoubleWildcards() == 0) { return 1; } - else if(pattern2Elements.endsWithCatchAll && pattern1Elements.catchAllCount == 0) { + else if (info2.isPrefixPattern() && info1.getDoubleWildcards() == 0) { return -1; } - int totalCount1 = pattern1Elements.bracketCount + pattern1Elements.wildcardsCount; - int totalCount2 = pattern2Elements.bracketCount + pattern2Elements.wildcardsCount; - - if (totalCount1 != totalCount2) { - return totalCount1 - totalCount2; + if (info1.getTotalCount() != info2.getTotalCount()) { + return info1.getTotalCount() - info2.getTotalCount(); } - int pattern1Length = getPatternLength(pattern1); - int pattern2Length = getPatternLength(pattern2); - - if (pattern1Length != pattern2Length) { - return pattern2Length - pattern1Length; + if (info1.getLength() != info2.getLength()) { + return info2.getLength() - info1.getLength(); } - if (pattern1Elements.wildcardsCount < pattern2Elements.wildcardsCount) { + if (info1.getSingleWildcards() < info2.getSingleWildcards()) { return -1; } - else if (pattern2Elements.wildcardsCount < pattern1Elements.wildcardsCount) { + else if (info2.getSingleWildcards() < info1.getSingleWildcards()) { return 1; } - if (pattern1Elements.bracketCount < pattern2Elements.bracketCount) { + if (info1.getUriVars() < info2.getUriVars()) { return -1; } - else if (pattern2Elements.bracketCount < pattern1Elements.bracketCount) { + else if (info2.getUriVars() < info1.getUriVars()) { return 1; } return 0; } - private boolean isNullOrCaptureAllPattern(String pattern) { - return pattern == null || "/**".equals(pattern); - } - /** - * Returns the length of the given pattern, where template variables are considered to be 1 long. + * Value class that holds information about the pattern, e.g. number of + * occurrences of "*", "**", and "{" pattern elements. */ - private int getPatternLength(String pattern) { - return VARIABLE_PATTERN.matcher(pattern).replaceAll("#").length(); - } + private static class PatternInfo { - /** - * Value class that holds the number of occurrences for "*", "**", and "{" pattern elements - */ - private class PatternElements { - int bracketCount = 0; - int wildcardsCount = 0; - int catchAllCount = 0; - boolean endsWithCatchAll; + private final String pattern; - public PatternElements(String pattern) { + private int uriVars; - if(pattern == null || pattern.length() == 0) { - return; + private int singleWildcards; + + private int doubleWildcards; + + private boolean catchAllPattern; + + private boolean prefixPattern; + + private Integer length; + + + public PatternInfo(String pattern) { + this.pattern = pattern; + if (this.pattern != null) { + initCounters(); + this.catchAllPattern = this.pattern.equals("/**"); + this.prefixPattern = !this.catchAllPattern && this.pattern.endsWith("/**"); } - int pos = 0; + if (this.uriVars == 0) { + this.length = (this.pattern != null ? this.pattern.length() : 0); + } + } - while(pos < pattern.length()) { - if(pattern.charAt(pos) == '{') { - bracketCount++; + protected void initCounters() { + int pos = 0; + while (pos < this.pattern.length()) { + if(this.pattern.charAt(pos) == '{') { + this.uriVars++; pos++; - } else if(pattern.charAt(pos) == '*') { - if(pos + 1 < pattern.length() && pattern.charAt(pos + 1) == '*') { - catchAllCount++; + } + else if(this.pattern.charAt(pos) == '*') { + if(pos + 1 < this.pattern.length() && this.pattern.charAt(pos + 1) == '*') { + this.doubleWildcards++; pos += 2; - } else { - wildcardsCount++; + } + else { + this.singleWildcards++; pos++; } } else { pos++; } } - endsWithCatchAll = pattern.endsWith("**"); } + public int getUriVars() { + return this.uriVars; + } + + public int getSingleWildcards() { + return this.singleWildcards; + } + + public int getDoubleWildcards() { + return this.doubleWildcards; + } + + public boolean isLeastSpecific() { + return (this.pattern == null || this.catchAllPattern); + } + + public boolean isPrefixPattern() { + return this.prefixPattern; + } + + public int getTotalCount() { + return this.uriVars + this.singleWildcards + (2 * this.doubleWildcards); + } + + /** + * Returns the length of the given pattern, where template variables are considered to be 1 long. + */ + public int getLength() { + if (this.length == null) { + this.length = VARIABLE_PATTERN.matcher(this.pattern).replaceAll("#").length(); + } + return this.length; + } } } diff --git a/spring-core/src/test/java/org/springframework/util/AntPathMatcherTests.java b/spring-core/src/test/java/org/springframework/util/AntPathMatcherTests.java index 120e771581..28673c2643 100644 --- a/spring-core/src/test/java/org/springframework/util/AntPathMatcherTests.java +++ b/spring-core/src/test/java/org/springframework/util/AntPathMatcherTests.java @@ -457,8 +457,8 @@ public class AntPathMatcherTests { assertEquals(1, comparator.compare("/hotels/**", "/hotels/{hotel}/bookings/{booking}/cutomers/{customer}")); assertEquals(1, comparator.compare("/hotels/foo/bar/**", "/hotels/{hotel}")); assertEquals(-1, comparator.compare("/hotels/{hotel}", "/hotels/foo/bar/**")); - assertEquals(-12, comparator.compare("/hotels/**/bookings/**", "/hotels/**")); - assertEquals(12, comparator.compare("/hotels/**", "/hotels/**/bookings/**")); + assertEquals(2, comparator.compare("/hotels/**/bookings/**", "/hotels/**")); + assertEquals(-2, comparator.compare("/hotels/**", "/hotels/**/bookings/**")); //SPR-8683 assertEquals(1, comparator.compare("/**", "/hotels/{hotel}"));