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.
This commit is contained in:
parent
62fa20fd6f
commit
08dfce2cb5
|
|
@ -17,6 +17,7 @@
|
|||
package org.springframework.web.util.pattern;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.Comparator;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
|
@ -64,6 +65,7 @@ import org.springframework.util.StringUtils;
|
|||
* </ul>
|
||||
*
|
||||
* @author Andy Clement
|
||||
* @author Rossen Stoyanchev
|
||||
* @since 5.0
|
||||
* @see PathContainer
|
||||
*/
|
||||
|
|
@ -342,38 +344,9 @@ public class PathPattern implements Comparable<PathPattern> {
|
|||
*/
|
||||
@Override
|
||||
public int compareTo(@Nullable PathPattern otherPattern) {
|
||||
// 1) null is sorted last
|
||||
if (otherPattern == null) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
// 2) catchall patterns are sorted last. If both catchall then the
|
||||
// length is considered
|
||||
if (isCatchAll()) {
|
||||
if (otherPattern.isCatchAll()) {
|
||||
int lenDifference = getNormalizedLength() - otherPattern.getNormalizedLength();
|
||||
if (lenDifference != 0) {
|
||||
return (lenDifference < 0) ? +1 : -1;
|
||||
}
|
||||
}
|
||||
else {
|
||||
return +1;
|
||||
}
|
||||
}
|
||||
else if (otherPattern.isCatchAll()) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
// 3) This will sort such that if they differ in terms of wildcards or
|
||||
// captured variable counts, the one with the most will be sorted last
|
||||
int score = getScore() - otherPattern.getScore();
|
||||
if (score != 0) {
|
||||
return (score < 0) ? -1 : +1;
|
||||
}
|
||||
|
||||
// 4) longer is better
|
||||
int lenDifference = getNormalizedLength() - otherPattern.getNormalizedLength();
|
||||
return (lenDifference < 0) ? +1 : (lenDifference == 0 ? 0 : -1);
|
||||
int result = SPECIFICITY_COMPARATOR.compare(this, otherPattern);
|
||||
return (result == 0 && otherPattern != null ?
|
||||
this.patternString.compareTo(otherPattern.patternString) : result);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -718,4 +691,40 @@ public class PathPattern implements Comparable<PathPattern> {
|
|||
return container != null && container.elements().size() > 0;
|
||||
}
|
||||
|
||||
|
||||
public static final Comparator<PathPattern> SPECIFICITY_COMPARATOR = (p1, p2) -> {
|
||||
// 1) null is sorted last
|
||||
if (p2 == null) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
// 2) catchall patterns are sorted last. If both catchall then the
|
||||
// length is considered
|
||||
if (p1.isCatchAll()) {
|
||||
if (p2.isCatchAll()) {
|
||||
int lenDifference = p1.getNormalizedLength() - p2.getNormalizedLength();
|
||||
if (lenDifference != 0) {
|
||||
return (lenDifference < 0) ? +1 : -1;
|
||||
}
|
||||
}
|
||||
else {
|
||||
return +1;
|
||||
}
|
||||
}
|
||||
else if (p2.isCatchAll()) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
// 3) This will sort such that if they differ in terms of wildcards or
|
||||
// captured variable counts, the one with the most will be sorted last
|
||||
int score = p1.getScore() - p2.getScore();
|
||||
if (score != 0) {
|
||||
return (score < 0) ? -1 : +1;
|
||||
}
|
||||
|
||||
// 4) longer is better
|
||||
int lenDifference = p1.getNormalizedLength() - p2.getNormalizedLength();
|
||||
return Integer.compare(0, lenDifference);
|
||||
};
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -387,7 +387,7 @@ public class PathPatternParserTests {
|
|||
// Based purely on catchAll
|
||||
p1 = parse("{*foobar}");
|
||||
p2 = parse("{*goo}");
|
||||
assertEquals(0, p1.compareTo(p2));
|
||||
assertTrue(p1.compareTo(p2) != 0);
|
||||
|
||||
p1 = parse("/{*foobar}");
|
||||
p2 = parse("/abc/{*ww}");
|
||||
|
|
|
|||
|
|
@ -118,7 +118,8 @@ public abstract class AbstractUrlHandlerMapping extends AbstractHandlerMapping {
|
|||
|
||||
return this.handlerMap.entrySet().stream()
|
||||
.filter(entry -> entry.getKey().matches(lookupPath))
|
||||
.sorted(Comparator.comparing(Map.Entry::getKey))
|
||||
.sorted((entry1, entry2) ->
|
||||
PathPattern.SPECIFICITY_COMPARATOR.compare(entry1.getKey(), entry2.getKey()))
|
||||
.findFirst()
|
||||
.map(entry -> {
|
||||
PathPattern pattern = entry.getKey();
|
||||
|
|
|
|||
|
|
@ -166,7 +166,8 @@ public class ResourceUrlProvider implements ApplicationListener<ContextRefreshed
|
|||
private Mono<String> resolveResourceUrl(PathContainer lookupPath) {
|
||||
return this.handlerMap.entrySet().stream()
|
||||
.filter(entry -> entry.getKey().matches(lookupPath))
|
||||
.sorted(Comparator.comparing(Map.Entry::getKey))
|
||||
.sorted((entry1, entry2) ->
|
||||
PathPattern.SPECIFICITY_COMPARATOR.compare(entry1.getKey(), entry2.getKey()))
|
||||
.findFirst()
|
||||
.map(entry -> {
|
||||
PathContainer path = entry.getKey().extractPathWithinPattern(lookupPath);
|
||||
|
|
|
|||
|
|
@ -19,7 +19,6 @@ package org.springframework.web.reactive.result.condition;
|
|||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.Comparator;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
|
@ -61,7 +60,7 @@ public final class PatternsRequestCondition extends AbstractRequestCondition<Pat
|
|||
}
|
||||
|
||||
private static SortedSet<PathPattern> toSortedSet(Collection<PathPattern> patterns) {
|
||||
TreeSet<PathPattern> sorted = new TreeSet<>(getPatternComparator());
|
||||
TreeSet<PathPattern> sorted = new TreeSet<>();
|
||||
sorted.addAll(patterns);
|
||||
return sorted;
|
||||
}
|
||||
|
|
@ -70,13 +69,6 @@ public final class PatternsRequestCondition extends AbstractRequestCondition<Pat
|
|||
this.patterns = patterns;
|
||||
}
|
||||
|
||||
private static Comparator<PathPattern> getPatternComparator() {
|
||||
return (p1, p2) -> {
|
||||
int index = p1.compareTo(p2);
|
||||
return (index != 0 ? index : p1.getPatternString().compareTo(p2.getPatternString()));
|
||||
};
|
||||
}
|
||||
|
||||
public Set<PathPattern> getPatterns() {
|
||||
return this.patterns;
|
||||
}
|
||||
|
|
@ -170,7 +162,7 @@ public final class PatternsRequestCondition extends AbstractRequestCondition<Pat
|
|||
Iterator<PathPattern> iterator = this.patterns.iterator();
|
||||
Iterator<PathPattern> iteratorOther = other.getPatterns().iterator();
|
||||
while (iterator.hasNext() && iteratorOther.hasNext()) {
|
||||
int result = iterator.next().compareTo(iteratorOther.next());
|
||||
int result = PathPattern.SPECIFICITY_COMPARATOR.compare(iterator.next(), iteratorOther.next());
|
||||
if (result != 0) {
|
||||
return result;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -137,7 +137,7 @@ public class PatternsRequestConditionTests {
|
|||
}
|
||||
|
||||
@Test
|
||||
public void compareEqualPatterns() throws Exception {
|
||||
public void compareToConsistentWithEquals() throws Exception {
|
||||
PatternsRequestCondition c1 = createPatternsCondition("/foo*");
|
||||
PatternsRequestCondition c2 = createPatternsCondition("/foo*");
|
||||
|
||||
|
|
@ -155,10 +155,17 @@ public class PatternsRequestConditionTests {
|
|||
|
||||
@Test
|
||||
public void comparePatternSpecificity() throws Exception {
|
||||
ServerWebExchange exchange = get("/foo").toExchange();
|
||||
|
||||
PatternsRequestCondition c1 = createPatternsCondition("/fo*");
|
||||
PatternsRequestCondition c2 = createPatternsCondition("/foo");
|
||||
|
||||
assertEquals(1, c1.compareTo(c2, get("/foo").toExchange()));
|
||||
assertEquals(1, c1.compareTo(c2, exchange));
|
||||
|
||||
c1 = createPatternsCondition("/fo*");
|
||||
c2 = createPatternsCondition("/*oo");
|
||||
|
||||
assertEquals("Patterns are equally specific even if not the same", 0, c1.compareTo(c2, exchange));
|
||||
}
|
||||
|
||||
@Test
|
||||
|
|
|
|||
|
|
@ -160,7 +160,7 @@ public class HandlerMethodMappingTests {
|
|||
|
||||
@Override
|
||||
protected Comparator<String> getMappingComparator(ServerWebExchange exchange) {
|
||||
return Comparator.comparing(o -> parser.parse(o));
|
||||
return (o1, o2) -> PathPattern.SPECIFICITY_COMPARATOR.compare(parser.parse(o1), parser.parse(o2));
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue