Skip to content

Commit 08dfce2

Browse files
committed
Dedicated specificity comparator in PathPattern
The PathPattern compareTo method is now consistent with equals when two patterns are of the same specificity but otherwise different. Separately PathPattern now exposes a Comparator by specificity that offers the current functionality of compareTo. This can be used for actual sorting where we only care about specificity.
1 parent 62fa20f commit 08dfce2

File tree

7 files changed

+58
-48
lines changed

7 files changed

+58
-48
lines changed

spring-web/src/main/java/org/springframework/web/util/pattern/PathPattern.java

Lines changed: 41 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
package org.springframework.web.util.pattern;
1818

1919
import java.util.Collections;
20+
import java.util.Comparator;
2021
import java.util.HashMap;
2122
import java.util.List;
2223
import java.util.Map;
@@ -64,6 +65,7 @@
6465
* </ul>
6566
*
6667
* @author Andy Clement
68+
* @author Rossen Stoyanchev
6769
* @since 5.0
6870
* @see PathContainer
6971
*/
@@ -342,38 +344,9 @@ private String extractPathWithinPattern(String path) {
342344
*/
343345
@Override
344346
public int compareTo(@Nullable PathPattern otherPattern) {
345-
// 1) null is sorted last
346-
if (otherPattern == null) {
347-
return -1;
348-
}
349-
350-
// 2) catchall patterns are sorted last. If both catchall then the
351-
// length is considered
352-
if (isCatchAll()) {
353-
if (otherPattern.isCatchAll()) {
354-
int lenDifference = getNormalizedLength() - otherPattern.getNormalizedLength();
355-
if (lenDifference != 0) {
356-
return (lenDifference < 0) ? +1 : -1;
357-
}
358-
}
359-
else {
360-
return +1;
361-
}
362-
}
363-
else if (otherPattern.isCatchAll()) {
364-
return -1;
365-
}
366-
367-
// 3) This will sort such that if they differ in terms of wildcards or
368-
// captured variable counts, the one with the most will be sorted last
369-
int score = getScore() - otherPattern.getScore();
370-
if (score != 0) {
371-
return (score < 0) ? -1 : +1;
372-
}
373-
374-
// 4) longer is better
375-
int lenDifference = getNormalizedLength() - otherPattern.getNormalizedLength();
376-
return (lenDifference < 0) ? +1 : (lenDifference == 0 ? 0 : -1);
347+
int result = SPECIFICITY_COMPARATOR.compare(this, otherPattern);
348+
return (result == 0 && otherPattern != null ?
349+
this.patternString.compareTo(otherPattern.patternString) : result);
377350
}
378351

379352
/**
@@ -718,4 +691,40 @@ private boolean hasLength(@Nullable PathContainer container) {
718691
return container != null && container.elements().size() > 0;
719692
}
720693

694+
695+
public static final Comparator<PathPattern> SPECIFICITY_COMPARATOR = (p1, p2) -> {
696+
// 1) null is sorted last
697+
if (p2 == null) {
698+
return -1;
699+
}
700+
701+
// 2) catchall patterns are sorted last. If both catchall then the
702+
// length is considered
703+
if (p1.isCatchAll()) {
704+
if (p2.isCatchAll()) {
705+
int lenDifference = p1.getNormalizedLength() - p2.getNormalizedLength();
706+
if (lenDifference != 0) {
707+
return (lenDifference < 0) ? +1 : -1;
708+
}
709+
}
710+
else {
711+
return +1;
712+
}
713+
}
714+
else if (p2.isCatchAll()) {
715+
return -1;
716+
}
717+
718+
// 3) This will sort such that if they differ in terms of wildcards or
719+
// captured variable counts, the one with the most will be sorted last
720+
int score = p1.getScore() - p2.getScore();
721+
if (score != 0) {
722+
return (score < 0) ? -1 : +1;
723+
}
724+
725+
// 4) longer is better
726+
int lenDifference = p1.getNormalizedLength() - p2.getNormalizedLength();
727+
return Integer.compare(0, lenDifference);
728+
};
729+
721730
}

spring-web/src/test/java/org/springframework/web/util/pattern/PathPatternParserTests.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -387,7 +387,7 @@ public void compareTests() {
387387
// Based purely on catchAll
388388
p1 = parse("{*foobar}");
389389
p2 = parse("{*goo}");
390-
assertEquals(0, p1.compareTo(p2));
390+
assertTrue(p1.compareTo(p2) != 0);
391391

392392
p1 = parse("/{*foobar}");
393393
p2 = parse("/abc/{*ww}");

spring-webflux/src/main/java/org/springframework/web/reactive/handler/AbstractUrlHandlerMapping.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -118,7 +118,8 @@ protected Object lookupHandler(PathContainer lookupPath, ServerWebExchange excha
118118

119119
return this.handlerMap.entrySet().stream()
120120
.filter(entry -> entry.getKey().matches(lookupPath))
121-
.sorted(Comparator.comparing(Map.Entry::getKey))
121+
.sorted((entry1, entry2) ->
122+
PathPattern.SPECIFICITY_COMPARATOR.compare(entry1.getKey(), entry2.getKey()))
122123
.findFirst()
123124
.map(entry -> {
124125
PathPattern pattern = entry.getKey();

spring-webflux/src/main/java/org/springframework/web/reactive/resource/ResourceUrlProvider.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -166,7 +166,8 @@ private int getQueryIndex(String path) {
166166
private Mono<String> resolveResourceUrl(PathContainer lookupPath) {
167167
return this.handlerMap.entrySet().stream()
168168
.filter(entry -> entry.getKey().matches(lookupPath))
169-
.sorted(Comparator.comparing(Map.Entry::getKey))
169+
.sorted((entry1, entry2) ->
170+
PathPattern.SPECIFICITY_COMPARATOR.compare(entry1.getKey(), entry2.getKey()))
170171
.findFirst()
171172
.map(entry -> {
172173
PathContainer path = entry.getKey().extractPathWithinPattern(lookupPath);

spring-webflux/src/main/java/org/springframework/web/reactive/result/condition/PatternsRequestCondition.java

Lines changed: 2 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,6 @@
1919
import java.util.ArrayList;
2020
import java.util.Arrays;
2121
import java.util.Collection;
22-
import java.util.Comparator;
2322
import java.util.Iterator;
2423
import java.util.List;
2524
import java.util.Set;
@@ -61,7 +60,7 @@ public PatternsRequestCondition(List<PathPattern> patterns) {
6160
}
6261

6362
private static SortedSet<PathPattern> toSortedSet(Collection<PathPattern> patterns) {
64-
TreeSet<PathPattern> sorted = new TreeSet<>(getPatternComparator());
63+
TreeSet<PathPattern> sorted = new TreeSet<>();
6564
sorted.addAll(patterns);
6665
return sorted;
6766
}
@@ -70,13 +69,6 @@ private PatternsRequestCondition(SortedSet<PathPattern> patterns) {
7069
this.patterns = patterns;
7170
}
7271

73-
private static Comparator<PathPattern> getPatternComparator() {
74-
return (p1, p2) -> {
75-
int index = p1.compareTo(p2);
76-
return (index != 0 ? index : p1.getPatternString().compareTo(p2.getPatternString()));
77-
};
78-
}
79-
8072
public Set<PathPattern> getPatterns() {
8173
return this.patterns;
8274
}
@@ -170,7 +162,7 @@ public int compareTo(PatternsRequestCondition other, ServerWebExchange exchange)
170162
Iterator<PathPattern> iterator = this.patterns.iterator();
171163
Iterator<PathPattern> iteratorOther = other.getPatterns().iterator();
172164
while (iterator.hasNext() && iteratorOther.hasNext()) {
173-
int result = iterator.next().compareTo(iteratorOther.next());
165+
int result = PathPattern.SPECIFICITY_COMPARATOR.compare(iterator.next(), iteratorOther.next());
174166
if (result != 0) {
175167
return result;
176168
}

spring-webflux/src/test/java/org/springframework/web/reactive/result/condition/PatternsRequestConditionTests.java

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -137,7 +137,7 @@ public void matchPatternContainsExtension() throws Exception {
137137
}
138138

139139
@Test
140-
public void compareEqualPatterns() throws Exception {
140+
public void compareToConsistentWithEquals() throws Exception {
141141
PatternsRequestCondition c1 = createPatternsCondition("/foo*");
142142
PatternsRequestCondition c2 = createPatternsCondition("/foo*");
143143

@@ -155,10 +155,17 @@ public void equallyMatchingPatternsAreBothPresent() throws Exception {
155155

156156
@Test
157157
public void comparePatternSpecificity() throws Exception {
158+
ServerWebExchange exchange = get("/foo").toExchange();
159+
158160
PatternsRequestCondition c1 = createPatternsCondition("/fo*");
159161
PatternsRequestCondition c2 = createPatternsCondition("/foo");
160162

161-
assertEquals(1, c1.compareTo(c2, get("/foo").toExchange()));
163+
assertEquals(1, c1.compareTo(c2, exchange));
164+
165+
c1 = createPatternsCondition("/fo*");
166+
c2 = createPatternsCondition("/*oo");
167+
168+
assertEquals("Patterns are equally specific even if not the same", 0, c1.compareTo(c2, exchange));
162169
}
163170

164171
@Test

spring-webflux/src/test/java/org/springframework/web/reactive/result/method/HandlerMethodMappingTests.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -160,7 +160,7 @@ protected String getMatchingMapping(String pattern, ServerWebExchange exchange)
160160

161161
@Override
162162
protected Comparator<String> getMappingComparator(ServerWebExchange exchange) {
163-
return Comparator.comparing(o -> parser.parse(o));
163+
return (o1, o2) -> PathPattern.SPECIFICITY_COMPARATOR.compare(parser.parse(o1), parser.parse(o2));
164164
}
165165

166166
}

0 commit comments

Comments
 (0)