Polish PathPattern parser (including package change to web.util.pattern)
Issue: SPR-15531
This commit is contained in:
parent
eaac348c05
commit
67881a5726
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* Copyright 2002-2016 the original author or authors.
|
||||
* Copyright 2002-2017 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
|
@ -25,7 +25,7 @@ import org.springframework.util.PathMatcher;
|
|||
import org.springframework.web.cors.CorsConfiguration;
|
||||
import org.springframework.web.server.ServerWebExchange;
|
||||
import org.springframework.web.server.support.HttpRequestPathHelper;
|
||||
import org.springframework.web.util.ParsingPathMatcher;
|
||||
import org.springframework.web.util.pattern.ParsingPathMatcher;
|
||||
|
||||
/**
|
||||
* Provide a per reactive request {@link CorsConfiguration} instance based on a
|
||||
|
|
|
@ -14,19 +14,21 @@
|
|||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.web.util.patterns;
|
||||
package org.springframework.web.util.pattern;
|
||||
|
||||
import org.springframework.web.util.patterns.PathPattern.MatchingContext;
|
||||
import org.springframework.web.util.pattern.PathPattern.MatchingContext;
|
||||
|
||||
/**
|
||||
* A path element representing capturing the rest of a path. In the pattern
|
||||
* '/foo/{*foobar}' the /{*foobar} is represented as a {@link CaptureTheRestPathElement}.
|
||||
*
|
||||
* @author Andy Clement
|
||||
* @since 5.0
|
||||
*/
|
||||
class CaptureTheRestPathElement extends PathElement {
|
||||
|
||||
private String variableName;
|
||||
private final String variableName;
|
||||
|
||||
|
||||
/**
|
||||
* @param pos position of the path element within the path pattern text
|
||||
|
@ -35,9 +37,10 @@ class CaptureTheRestPathElement extends PathElement {
|
|||
*/
|
||||
CaptureTheRestPathElement(int pos, char[] captureDescriptor, char separator) {
|
||||
super(pos, separator);
|
||||
variableName = new String(captureDescriptor, 2, captureDescriptor.length - 3);
|
||||
this.variableName = new String(captureDescriptor, 2, captureDescriptor.length - 3);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public boolean matches(int candidateIndex, MatchingContext matchingContext) {
|
||||
// No need to handle 'match start' checking as this captures everything
|
||||
|
@ -59,10 +62,6 @@ class CaptureTheRestPathElement extends PathElement {
|
|||
return true;
|
||||
}
|
||||
|
||||
public String toString() {
|
||||
return "CaptureTheRest(/{*" + variableName + "})";
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getNormalizedLength() {
|
||||
return 1;
|
||||
|
@ -77,4 +76,10 @@ class CaptureTheRestPathElement extends PathElement {
|
|||
public int getCaptureCount() {
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public String toString() {
|
||||
return "CaptureTheRest(/{*" + this.variableName + "})";
|
||||
}
|
||||
|
||||
}
|
|
@ -14,11 +14,10 @@
|
|||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.web.util.patterns;
|
||||
package org.springframework.web.util.pattern;
|
||||
|
||||
import java.util.regex.Matcher;
|
||||
|
||||
import org.springframework.web.util.patterns.PathPattern.MatchingContext;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
/**
|
||||
* A path element representing capturing a piece of the path as a variable. In the pattern
|
||||
|
@ -26,12 +25,14 @@ import org.springframework.web.util.patterns.PathPattern.MatchingContext;
|
|||
* must be at least one character to bind to the variable.
|
||||
*
|
||||
* @author Andy Clement
|
||||
* @since 5.0
|
||||
*/
|
||||
class CaptureVariablePathElement extends PathElement {
|
||||
|
||||
private String variableName;
|
||||
private final String variableName;
|
||||
|
||||
private Pattern constraintPattern;
|
||||
|
||||
private java.util.regex.Pattern constraintPattern;
|
||||
|
||||
/**
|
||||
* @param pos the position in the pattern of this capture element
|
||||
|
@ -48,43 +49,47 @@ class CaptureVariablePathElement extends PathElement {
|
|||
}
|
||||
if (colon == -1) {
|
||||
// no constraint
|
||||
variableName = new String(captureDescriptor, 1, captureDescriptor.length - 2);
|
||||
this.variableName = new String(captureDescriptor, 1, captureDescriptor.length - 2);
|
||||
}
|
||||
else {
|
||||
variableName = new String(captureDescriptor, 1, colon - 1);
|
||||
this.variableName = new String(captureDescriptor, 1, colon - 1);
|
||||
if (caseSensitive) {
|
||||
constraintPattern = java.util.regex.Pattern
|
||||
.compile(new String(captureDescriptor, colon + 1, captureDescriptor.length - colon - 2));
|
||||
this.constraintPattern = Pattern.compile(
|
||||
new String(captureDescriptor, colon + 1, captureDescriptor.length - colon - 2));
|
||||
}
|
||||
else {
|
||||
constraintPattern = java.util.regex.Pattern.compile(
|
||||
this.constraintPattern = Pattern.compile(
|
||||
new String(captureDescriptor, colon + 1, captureDescriptor.length - colon - 2),
|
||||
java.util.regex.Pattern.CASE_INSENSITIVE);
|
||||
Pattern.CASE_INSENSITIVE);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public boolean matches(int candidateIndex, MatchingContext matchingContext) {
|
||||
public boolean matches(int candidateIndex, PathPattern.MatchingContext matchingContext) {
|
||||
int nextPos = matchingContext.scanAhead(candidateIndex);
|
||||
// There must be at least one character to capture:
|
||||
if (nextPos == candidateIndex) {
|
||||
return false;
|
||||
}
|
||||
|
||||
CharSequence candidateCapture = null;
|
||||
if (constraintPattern != null) {
|
||||
if (this.constraintPattern != null) {
|
||||
// TODO possible optimization - only regex match if rest of pattern matches? Benefit likely to vary pattern to pattern
|
||||
candidateCapture = new SubSequence(matchingContext.candidate, candidateIndex, nextPos);
|
||||
Matcher m = constraintPattern.matcher(candidateCapture);
|
||||
if (m.groupCount() != 0) {
|
||||
throw new IllegalArgumentException("No capture groups allowed in the constraint regex: " + constraintPattern.pattern());
|
||||
Matcher matcher = constraintPattern.matcher(candidateCapture);
|
||||
if (matcher.groupCount() != 0) {
|
||||
throw new IllegalArgumentException(
|
||||
"No capture groups allowed in the constraint regex: " + this.constraintPattern.pattern());
|
||||
}
|
||||
if (!m.matches()) {
|
||||
if (!matcher.matches()) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
boolean match = false;
|
||||
if (next == null) {
|
||||
if (this.next == null) {
|
||||
if (matchingContext.determineRemainingPath && nextPos > candidateIndex) {
|
||||
matchingContext.remainingPathIndex = nextPos;
|
||||
match = true;
|
||||
|
@ -101,14 +106,16 @@ class CaptureVariablePathElement extends PathElement {
|
|||
}
|
||||
else {
|
||||
if (matchingContext.isMatchStartMatching && nextPos == matchingContext.candidateLength) {
|
||||
match = true; // no more data but matches up to this point
|
||||
match = true; // no more data but matches up to this point
|
||||
}
|
||||
else {
|
||||
match = next.matches(nextPos, matchingContext);
|
||||
match = this.next.matches(nextPos, matchingContext);
|
||||
}
|
||||
}
|
||||
|
||||
if (match && matchingContext.extractingVariables) {
|
||||
matchingContext.set(variableName, new String(matchingContext.candidate, candidateIndex, nextPos - candidateIndex));
|
||||
matchingContext.set(this.variableName,
|
||||
new String(matchingContext.candidate, candidateIndex, nextPos - candidateIndex));
|
||||
}
|
||||
return match;
|
||||
}
|
||||
|
@ -117,10 +124,6 @@ class CaptureVariablePathElement extends PathElement {
|
|||
return this.variableName;
|
||||
}
|
||||
|
||||
public String toString() {
|
||||
return "CaptureVariable({" + variableName + (constraintPattern == null ? "" : ":" + constraintPattern.pattern()) + "})";
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getNormalizedLength() {
|
||||
return 1;
|
||||
|
@ -140,4 +143,11 @@ class CaptureVariablePathElement extends PathElement {
|
|||
public int getScore() {
|
||||
return CAPTURE_VARIABLE_WEIGHT;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public String toString() {
|
||||
return "CaptureVariable({" + this.variableName +
|
||||
(this.constraintPattern != null ? ":" + this.constraintPattern.pattern() : "") + "})";
|
||||
}
|
||||
|
||||
}
|
|
@ -14,12 +14,14 @@
|
|||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.web.util.patterns;
|
||||
package org.springframework.web.util.pattern;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.regex.PatternSyntaxException;
|
||||
|
||||
import org.springframework.web.util.pattern.PatternParseException.PatternMessage;
|
||||
|
||||
/**
|
||||
* Parser for URI template patterns. It breaks the path pattern into a number of
|
||||
* {@link PathElement}s in a linked list. Instances are reusable but are not thread-safe.
|
||||
|
@ -27,7 +29,7 @@ import java.util.regex.PatternSyntaxException;
|
|||
* @author Andy Clement
|
||||
* @since 5.0
|
||||
*/
|
||||
public class InternalPathPatternParser {
|
||||
class InternalPathPatternParser {
|
||||
|
||||
// The expected path separator to split path elements during parsing
|
||||
char separator = PathPatternParser.DEFAULT_SEPARATOR;
|
||||
|
@ -80,13 +82,12 @@ public class InternalPathPatternParser {
|
|||
// The most recently constructed path element in the chain
|
||||
PathElement currentPE;
|
||||
|
||||
|
||||
/**
|
||||
* Create a PatternParser that will use the specified separator instead of
|
||||
* the default.
|
||||
*
|
||||
* @param separator the path separator to look for when parsing
|
||||
* @param caseSensitive true if PathPatterns should be sensitive to case
|
||||
* @param matchOptionalTrailingSlash true if patterns without a trailing slash can match paths that do have a trailing slash
|
||||
* @param matchOptionalTrailingSlash true if patterns without a trailing slash
|
||||
* can match paths that do have a trailing slash
|
||||
*/
|
||||
public InternalPathPatternParser(char separator, boolean caseSensitive, boolean matchOptionalTrailingSlash) {
|
||||
this.separator = separator;
|
||||
|
@ -94,108 +95,111 @@ public class InternalPathPatternParser {
|
|||
this.matchOptionalTrailingSlash = matchOptionalTrailingSlash;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Process the path pattern data, a character at a time, breaking it into
|
||||
* path elements around separator boundaries and verifying the structure at each
|
||||
* stage. Produces a PathPattern object that can be used for fast matching
|
||||
* against paths.
|
||||
*
|
||||
* @param pathPattern the input path pattern, e.g. /foo/{bar}
|
||||
* @return a PathPattern for quickly matching paths against the specified path pattern
|
||||
* @throws PatternParseException in case of parse errors
|
||||
*/
|
||||
public PathPattern parse(String pathPattern) {
|
||||
if (pathPattern == null) {
|
||||
pathPattern = "";
|
||||
}
|
||||
pathPatternData = pathPattern.toCharArray();
|
||||
pathPatternLength = pathPatternData.length;
|
||||
headPE = null;
|
||||
currentPE = null;
|
||||
capturedVariableNames = null;
|
||||
pathElementStart = -1;
|
||||
pos = 0;
|
||||
public PathPattern parse(String pathPattern) throws PatternParseException {
|
||||
this.pathPatternData = pathPattern.toCharArray();
|
||||
this.pathPatternLength = pathPatternData.length;
|
||||
this.headPE = null;
|
||||
this.currentPE = null;
|
||||
this.capturedVariableNames = null;
|
||||
this.pathElementStart = -1;
|
||||
this.pos = 0;
|
||||
resetPathElementState();
|
||||
while (pos < pathPatternLength) {
|
||||
char ch = pathPatternData[pos];
|
||||
if (ch == separator) {
|
||||
if (pathElementStart != -1) {
|
||||
|
||||
while (this.pos < this.pathPatternLength) {
|
||||
char ch = this.pathPatternData[this.pos];
|
||||
if (ch == this.separator) {
|
||||
if (this.pathElementStart != -1) {
|
||||
pushPathElement(createPathElement());
|
||||
}
|
||||
if (peekDoubleWildcard()) {
|
||||
pushPathElement(new WildcardTheRestPathElement(pos, separator));
|
||||
pos += 2;
|
||||
pushPathElement(new WildcardTheRestPathElement(this.pos, this.separator));
|
||||
this.pos += 2;
|
||||
}
|
||||
else {
|
||||
pushPathElement(new SeparatorPathElement(pos, separator));
|
||||
pushPathElement(new SeparatorPathElement(this.pos, this.separator));
|
||||
}
|
||||
}
|
||||
else {
|
||||
if (pathElementStart == -1) {
|
||||
pathElementStart = pos;
|
||||
if (this.pathElementStart == -1) {
|
||||
this.pathElementStart = this.pos;
|
||||
}
|
||||
if (ch == '?') {
|
||||
singleCharWildcardCount++;
|
||||
this.singleCharWildcardCount++;
|
||||
}
|
||||
else if (ch == '{') {
|
||||
if (insideVariableCapture) {
|
||||
throw new PatternParseException(pos, pathPatternData, PatternMessage.ILLEGAL_NESTED_CAPTURE);
|
||||
// If we enforced that adjacent captures weren't allowed,
|
||||
// // this would do it (this would be an error: /foo/{bar}{boo}/)
|
||||
// } else if (pos > 0 && pathPatternData[pos - 1] == '}') {
|
||||
// throw new PatternParseException(pos, pathPatternData,
|
||||
// PatternMessage.CANNOT_HAVE_ADJACENT_CAPTURES);
|
||||
if (this.insideVariableCapture) {
|
||||
throw new PatternParseException(this.pos, this.pathPatternData,
|
||||
PatternMessage.ILLEGAL_NESTED_CAPTURE);
|
||||
}
|
||||
insideVariableCapture = true;
|
||||
variableCaptureStart = pos;
|
||||
// If we enforced that adjacent captures weren't allowed,
|
||||
// this would do it (this would be an error: /foo/{bar}{boo}/)
|
||||
// } else if (pos > 0 && pathPatternData[pos - 1] == '}') {
|
||||
// throw new PatternParseException(pos, pathPatternData,
|
||||
// PatternMessage.CANNOT_HAVE_ADJACENT_CAPTURES);
|
||||
this.insideVariableCapture = true;
|
||||
this.variableCaptureStart = pos;
|
||||
}
|
||||
else if (ch == '}') {
|
||||
if (!insideVariableCapture) {
|
||||
throw new PatternParseException(pos, pathPatternData, PatternMessage.MISSING_OPEN_CAPTURE);
|
||||
if (!this.insideVariableCapture) {
|
||||
throw new PatternParseException(this.pos, this.pathPatternData,
|
||||
PatternMessage.MISSING_OPEN_CAPTURE);
|
||||
}
|
||||
insideVariableCapture = false;
|
||||
if (isCaptureTheRestVariable && (pos + 1) < pathPatternLength) {
|
||||
throw new PatternParseException(pos + 1, pathPatternData,
|
||||
this.insideVariableCapture = false;
|
||||
if (this.isCaptureTheRestVariable && (this.pos + 1) < this.pathPatternLength) {
|
||||
throw new PatternParseException(this.pos + 1, this.pathPatternData,
|
||||
PatternMessage.NO_MORE_DATA_EXPECTED_AFTER_CAPTURE_THE_REST);
|
||||
}
|
||||
variableCaptureCount++;
|
||||
this.variableCaptureCount++;
|
||||
}
|
||||
else if (ch == ':') {
|
||||
if (insideVariableCapture) {
|
||||
if (this.insideVariableCapture) {
|
||||
skipCaptureRegex();
|
||||
insideVariableCapture = false;
|
||||
variableCaptureCount++;
|
||||
this.insideVariableCapture = false;
|
||||
this.variableCaptureCount++;
|
||||
}
|
||||
}
|
||||
else if (ch == '*') {
|
||||
if (insideVariableCapture) {
|
||||
if (variableCaptureStart == pos - 1) {
|
||||
isCaptureTheRestVariable = true;
|
||||
if (this.insideVariableCapture) {
|
||||
if (this.variableCaptureStart == pos - 1) {
|
||||
this.isCaptureTheRestVariable = true;
|
||||
}
|
||||
}
|
||||
wildcard = true;
|
||||
this.wildcard = true;
|
||||
}
|
||||
// Check that the characters used for captured variable names are like java identifiers
|
||||
if (insideVariableCapture) {
|
||||
if ((variableCaptureStart + 1 + (isCaptureTheRestVariable ? 1 : 0)) == pos
|
||||
&& !Character.isJavaIdentifierStart(ch)) {
|
||||
throw new PatternParseException(pos, pathPatternData,
|
||||
if (this.insideVariableCapture) {
|
||||
if ((this.variableCaptureStart + 1 + (this.isCaptureTheRestVariable ? 1 : 0)) == this.pos &&
|
||||
!Character.isJavaIdentifierStart(ch)) {
|
||||
throw new PatternParseException(this.pos, this.pathPatternData,
|
||||
PatternMessage.ILLEGAL_CHARACTER_AT_START_OF_CAPTURE_DESCRIPTOR,
|
||||
Character.toString(ch));
|
||||
|
||||
}
|
||||
else if ((pos > (variableCaptureStart + 1 + (isCaptureTheRestVariable ? 1 : 0))
|
||||
&& !Character.isJavaIdentifierPart(ch))) {
|
||||
throw new PatternParseException(pos, pathPatternData,
|
||||
PatternMessage.ILLEGAL_CHARACTER_IN_CAPTURE_DESCRIPTOR, Character.toString(ch));
|
||||
else if ((this.pos > (this.variableCaptureStart + 1 + (this.isCaptureTheRestVariable ? 1 : 0)) &&
|
||||
!Character.isJavaIdentifierPart(ch))) {
|
||||
throw new PatternParseException(this.pos, this.pathPatternData,
|
||||
PatternMessage.ILLEGAL_CHARACTER_IN_CAPTURE_DESCRIPTOR,
|
||||
Character.toString(ch));
|
||||
}
|
||||
}
|
||||
}
|
||||
pos++;
|
||||
this.pos++;
|
||||
}
|
||||
if (pathElementStart != -1) {
|
||||
if (this.pathElementStart != -1) {
|
||||
pushPathElement(createPathElement());
|
||||
}
|
||||
return new PathPattern(pathPattern, headPE, separator, caseSensitive, matchOptionalTrailingSlash);
|
||||
return new PathPattern(
|
||||
pathPattern, this.headPE, this.separator, this.caseSensitive, this.matchOptionalTrailingSlash);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -207,14 +211,15 @@ public class InternalPathPatternParser {
|
|||
* <p>A separator that should not indicate the end of the regex can be escaped:
|
||||
*/
|
||||
private void skipCaptureRegex() {
|
||||
pos++;
|
||||
int regexStart = pos;
|
||||
this.pos++;
|
||||
int regexStart = this.pos;
|
||||
int curlyBracketDepth = 0; // how deep in nested {...} pairs
|
||||
boolean previousBackslash = false;
|
||||
while (pos < pathPatternLength) {
|
||||
char ch = pathPatternData[pos];
|
||||
|
||||
while (this.pos < this.pathPatternLength) {
|
||||
char ch = this.pathPatternData[pos];
|
||||
if (ch == '\\' && !previousBackslash) {
|
||||
pos++;
|
||||
this.pos++;
|
||||
previousBackslash = true;
|
||||
continue;
|
||||
}
|
||||
|
@ -223,21 +228,24 @@ public class InternalPathPatternParser {
|
|||
}
|
||||
else if (ch == '}' && !previousBackslash) {
|
||||
if (curlyBracketDepth == 0) {
|
||||
if (regexStart == pos) {
|
||||
throw new PatternParseException(regexStart, pathPatternData,
|
||||
if (regexStart == this.pos) {
|
||||
throw new PatternParseException(regexStart, this.pathPatternData,
|
||||
PatternMessage.MISSING_REGEX_CONSTRAINT);
|
||||
}
|
||||
return;
|
||||
}
|
||||
curlyBracketDepth--;
|
||||
}
|
||||
if (ch == separator && !previousBackslash) {
|
||||
throw new PatternParseException(pos, pathPatternData, PatternMessage.MISSING_CLOSE_CAPTURE);
|
||||
if (ch == this.separator && !previousBackslash) {
|
||||
throw new PatternParseException(this.pos, this.pathPatternData,
|
||||
PatternMessage.MISSING_CLOSE_CAPTURE);
|
||||
}
|
||||
pos++;
|
||||
this.pos++;
|
||||
previousBackslash = false;
|
||||
}
|
||||
throw new PatternParseException(pos - 1, pathPatternData, PatternMessage.MISSING_CLOSE_CAPTURE);
|
||||
|
||||
throw new PatternParseException(this.pos - 1, this.pathPatternData,
|
||||
PatternMessage.MISSING_CLOSE_CAPTURE);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -245,13 +253,13 @@ public class InternalPathPatternParser {
|
|||
* (and only ** before the end of the pattern or the next separator)
|
||||
*/
|
||||
private boolean peekDoubleWildcard() {
|
||||
if ((pos + 2) >= pathPatternLength) {
|
||||
if ((this.pos + 2) >= this.pathPatternLength) {
|
||||
return false;
|
||||
}
|
||||
if (pathPatternData[pos + 1] != '*' || pathPatternData[pos + 2] != '*') {
|
||||
if (this.pathPatternData[this.pos + 1] != '*' || this.pathPatternData[this.pos + 2] != '*') {
|
||||
return false;
|
||||
}
|
||||
return (pos + 3 == pathPatternLength);
|
||||
return (this.pos + 3 == this.pathPatternLength);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -261,38 +269,39 @@ public class InternalPathPatternParser {
|
|||
if (newPathElement instanceof CaptureTheRestPathElement) {
|
||||
// There must be a separator ahead of this thing
|
||||
// currentPE SHOULD be a SeparatorPathElement
|
||||
if (currentPE == null) {
|
||||
headPE = newPathElement;
|
||||
currentPE = newPathElement;
|
||||
if (this.currentPE == null) {
|
||||
this.headPE = newPathElement;
|
||||
this.currentPE = newPathElement;
|
||||
}
|
||||
else if (currentPE instanceof SeparatorPathElement) {
|
||||
PathElement peBeforeSeparator = currentPE.prev;
|
||||
else if (this.currentPE instanceof SeparatorPathElement) {
|
||||
PathElement peBeforeSeparator = this.currentPE.prev;
|
||||
if (peBeforeSeparator == null) {
|
||||
// /{*foobar} is at the start
|
||||
headPE = newPathElement;
|
||||
newPathElement.prev = peBeforeSeparator;
|
||||
this.headPE = newPathElement;
|
||||
newPathElement.prev = null;
|
||||
}
|
||||
else {
|
||||
peBeforeSeparator.next = newPathElement;
|
||||
newPathElement.prev = peBeforeSeparator;
|
||||
}
|
||||
currentPE = newPathElement;
|
||||
this.currentPE = newPathElement;
|
||||
}
|
||||
else {
|
||||
throw new IllegalStateException("Expected SeparatorPathElement but was " + currentPE);
|
||||
throw new IllegalStateException("Expected SeparatorPathElement but was " + this.currentPE);
|
||||
}
|
||||
}
|
||||
else {
|
||||
if (headPE == null) {
|
||||
headPE = newPathElement;
|
||||
currentPE = newPathElement;
|
||||
if (this.headPE == null) {
|
||||
this.headPE = newPathElement;
|
||||
this.currentPE = newPathElement;
|
||||
}
|
||||
else {
|
||||
currentPE.next = newPathElement;
|
||||
newPathElement.prev = currentPE;
|
||||
currentPE = newPathElement;
|
||||
this.currentPE.next = newPathElement;
|
||||
newPathElement.prev = this.currentPE;
|
||||
this.currentPE = newPathElement;
|
||||
}
|
||||
}
|
||||
|
||||
resetPathElementState();
|
||||
}
|
||||
|
||||
|
@ -302,61 +311,70 @@ public class InternalPathPatternParser {
|
|||
* @return the new path element
|
||||
*/
|
||||
private PathElement createPathElement() {
|
||||
if (insideVariableCapture) {
|
||||
throw new PatternParseException(pos, pathPatternData, PatternMessage.MISSING_CLOSE_CAPTURE);
|
||||
if (this.insideVariableCapture) {
|
||||
throw new PatternParseException(this.pos, this.pathPatternData, PatternMessage.MISSING_CLOSE_CAPTURE);
|
||||
}
|
||||
char[] pathElementText = new char[pos - pathElementStart];
|
||||
System.arraycopy(pathPatternData, pathElementStart, pathElementText, 0, pos - pathElementStart);
|
||||
|
||||
char[] pathElementText = new char[this.pos - this.pathElementStart];
|
||||
System.arraycopy(this.pathPatternData, this.pathElementStart, pathElementText, 0,
|
||||
this.pos - this.pathElementStart);
|
||||
PathElement newPE = null;
|
||||
if (variableCaptureCount > 0) {
|
||||
if (variableCaptureCount == 1
|
||||
&& pathElementStart == variableCaptureStart && pathPatternData[pos - 1] == '}') {
|
||||
if (isCaptureTheRestVariable) {
|
||||
|
||||
if (this.variableCaptureCount > 0) {
|
||||
if (this.variableCaptureCount == 1 && this.pathElementStart == this.variableCaptureStart &&
|
||||
this.pathPatternData[this.pos - 1] == '}') {
|
||||
if (this.isCaptureTheRestVariable) {
|
||||
// It is {*....}
|
||||
newPE = new CaptureTheRestPathElement(pathElementStart, pathElementText, separator);
|
||||
}
|
||||
else {
|
||||
// It is a full capture of this element (possibly with constraint), for example: /foo/{abc}/
|
||||
try {
|
||||
newPE = new CaptureVariablePathElement(pathElementStart, pathElementText, caseSensitive, separator);
|
||||
newPE = new CaptureVariablePathElement(this.pathElementStart, pathElementText,
|
||||
this.caseSensitive, this.separator);
|
||||
}
|
||||
catch (PatternSyntaxException pse) {
|
||||
throw new PatternParseException(pse, findRegexStart(pathPatternData, pathElementStart)
|
||||
+ pse.getIndex(), pathPatternData, PatternMessage.JDK_PATTERN_SYNTAX_EXCEPTION);
|
||||
throw new PatternParseException(pse,
|
||||
findRegexStart(this.pathPatternData, this.pathElementStart) + pse.getIndex(),
|
||||
this.pathPatternData, PatternMessage.REGEX_PATTERN_SYNTAX_EXCEPTION);
|
||||
}
|
||||
recordCapturedVariable(pathElementStart, ((CaptureVariablePathElement) newPE).getVariableName());
|
||||
recordCapturedVariable(this.pathElementStart,
|
||||
((CaptureVariablePathElement) newPE).getVariableName());
|
||||
}
|
||||
}
|
||||
else {
|
||||
if (isCaptureTheRestVariable) {
|
||||
throw new PatternParseException(pathElementStart, pathPatternData,
|
||||
if (this.isCaptureTheRestVariable) {
|
||||
throw new PatternParseException(this.pathElementStart, this.pathPatternData,
|
||||
PatternMessage.CAPTURE_ALL_IS_STANDALONE_CONSTRUCT);
|
||||
}
|
||||
RegexPathElement newRegexSection = new RegexPathElement(pathElementStart, pathElementText,
|
||||
caseSensitive, pathPatternData, separator);
|
||||
RegexPathElement newRegexSection = new RegexPathElement(this.pathElementStart, pathElementText,
|
||||
this.caseSensitive, this.pathPatternData, this.separator);
|
||||
for (String variableName : newRegexSection.getVariableNames()) {
|
||||
recordCapturedVariable(pathElementStart, variableName);
|
||||
recordCapturedVariable(this.pathElementStart, variableName);
|
||||
}
|
||||
newPE = newRegexSection;
|
||||
}
|
||||
}
|
||||
else {
|
||||
if (wildcard) {
|
||||
if (pos - 1 == pathElementStart) {
|
||||
newPE = new WildcardPathElement(pathElementStart, separator);
|
||||
if (this.wildcard) {
|
||||
if (this.pos - 1 == this.pathElementStart) {
|
||||
newPE = new WildcardPathElement(this.pathElementStart, this.separator);
|
||||
}
|
||||
else {
|
||||
newPE = new RegexPathElement(pathElementStart, pathElementText, caseSensitive, pathPatternData, separator);
|
||||
newPE = new RegexPathElement(this.pathElementStart, pathElementText,
|
||||
this.caseSensitive, this.pathPatternData, this.separator);
|
||||
}
|
||||
}
|
||||
else if (singleCharWildcardCount != 0) {
|
||||
newPE = new SingleCharWildcardedPathElement(pathElementStart, pathElementText,
|
||||
singleCharWildcardCount, caseSensitive, separator);
|
||||
else if (this.singleCharWildcardCount != 0) {
|
||||
newPE = new SingleCharWildcardedPathElement(this.pathElementStart, pathElementText,
|
||||
this.singleCharWildcardCount, this.caseSensitive, this.separator);
|
||||
}
|
||||
else {
|
||||
newPE = new LiteralPathElement(pathElementStart, pathElementText, caseSensitive, separator);
|
||||
newPE = new LiteralPathElement(this.pathElementStart, pathElementText,
|
||||
this.caseSensitive, this.separator);
|
||||
}
|
||||
}
|
||||
|
||||
return newPE;
|
||||
}
|
||||
|
||||
|
@ -383,26 +401,27 @@ public class InternalPathPatternParser {
|
|||
* Reset all the flags and position markers computed during path element processing.
|
||||
*/
|
||||
private void resetPathElementState() {
|
||||
pathElementStart = -1;
|
||||
singleCharWildcardCount = 0;
|
||||
insideVariableCapture = false;
|
||||
variableCaptureCount = 0;
|
||||
wildcard = false;
|
||||
isCaptureTheRestVariable = false;
|
||||
variableCaptureStart = -1;
|
||||
this.pathElementStart = -1;
|
||||
this.singleCharWildcardCount = 0;
|
||||
this.insideVariableCapture = false;
|
||||
this.variableCaptureCount = 0;
|
||||
this.wildcard = false;
|
||||
this.isCaptureTheRestVariable = false;
|
||||
this.variableCaptureStart = -1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Record a new captured variable. If it clashes with an existing one then report an error.
|
||||
*/
|
||||
private void recordCapturedVariable(int pos, String variableName) {
|
||||
if (capturedVariableNames == null) {
|
||||
capturedVariableNames = new ArrayList<>();
|
||||
if (this.capturedVariableNames == null) {
|
||||
this.capturedVariableNames = new ArrayList<>();
|
||||
}
|
||||
if (capturedVariableNames.contains(variableName)) {
|
||||
if (this.capturedVariableNames.contains(variableName)) {
|
||||
throw new PatternParseException(pos, this.pathPatternData,
|
||||
PatternMessage.ILLEGAL_DOUBLE_CAPTURE, variableName);
|
||||
}
|
||||
capturedVariableNames.add(variableName);
|
||||
this.capturedVariableNames.add(variableName);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -14,9 +14,9 @@
|
|||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.web.util.patterns;
|
||||
package org.springframework.web.util.pattern;
|
||||
|
||||
import org.springframework.web.util.patterns.PathPattern.MatchingContext;
|
||||
import org.springframework.web.util.pattern.PathPattern.MatchingContext;
|
||||
|
||||
/**
|
||||
* A literal path element. In the pattern '/foo/bar/goo' there are three
|
||||
|
@ -51,11 +51,12 @@ class LiteralPathElement extends PathElement {
|
|||
@Override
|
||||
public boolean matches(int candidateIndex, MatchingContext matchingContext) {
|
||||
if ((candidateIndex + text.length) > matchingContext.candidateLength) {
|
||||
return false; // not enough data, cannot be a match
|
||||
return false; // not enough data, cannot be a match
|
||||
}
|
||||
if (caseSensitive) {
|
||||
|
||||
if (this.caseSensitive) {
|
||||
for (int i = 0; i < len; i++) {
|
||||
if (matchingContext.candidate[candidateIndex++] != text[i]) {
|
||||
if (matchingContext.candidate[candidateIndex++] != this.text[i]) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
@ -63,12 +64,13 @@ class LiteralPathElement extends PathElement {
|
|||
else {
|
||||
for (int i = 0; i < len; i++) {
|
||||
// TODO revisit performance if doing a lot of case insensitive matching
|
||||
if (Character.toLowerCase(matchingContext.candidate[candidateIndex++]) != text[i]) {
|
||||
if (Character.toLowerCase(matchingContext.candidate[candidateIndex++]) != this.text[i]) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (next == null) {
|
||||
|
||||
if (this.next == null) {
|
||||
if (matchingContext.determineRemainingPath && nextIfExistsIsSeparator(candidateIndex, matchingContext)) {
|
||||
matchingContext.remainingPathIndex = candidateIndex;
|
||||
return true;
|
||||
|
@ -78,17 +80,17 @@ class LiteralPathElement extends PathElement {
|
|||
return true;
|
||||
}
|
||||
else {
|
||||
return matchingContext.isAllowOptionalTrailingSlash() &&
|
||||
(candidateIndex + 1) == matchingContext.candidateLength &&
|
||||
matchingContext.candidate[candidateIndex] == separator;
|
||||
return (matchingContext.isAllowOptionalTrailingSlash() &&
|
||||
(candidateIndex + 1) == matchingContext.candidateLength &&
|
||||
matchingContext.candidate[candidateIndex] == separator);
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
if (matchingContext.isMatchStartMatching && candidateIndex == matchingContext.candidateLength) {
|
||||
return true; // no more data but everything matched so far
|
||||
return true; // no more data but everything matched so far
|
||||
}
|
||||
return next.matches(candidateIndex, matchingContext);
|
||||
return this.next.matches(candidateIndex, matchingContext);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -97,8 +99,9 @@ class LiteralPathElement extends PathElement {
|
|||
return len;
|
||||
}
|
||||
|
||||
|
||||
public String toString() {
|
||||
return "Literal(" + new String(text) + ")";
|
||||
return "Literal(" + String.valueOf(this.text) + ")";
|
||||
}
|
||||
|
||||
}
|
||||
}
|
|
@ -14,7 +14,7 @@
|
|||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.web.util;
|
||||
package org.springframework.web.util.pattern;
|
||||
|
||||
import java.util.Comparator;
|
||||
import java.util.Map;
|
||||
|
@ -22,10 +22,6 @@ import java.util.concurrent.ConcurrentHashMap;
|
|||
import java.util.concurrent.ConcurrentMap;
|
||||
|
||||
import org.springframework.util.PathMatcher;
|
||||
import org.springframework.web.util.patterns.PathPattern;
|
||||
import org.springframework.web.util.patterns.PathPatternParser;
|
||||
import org.springframework.web.util.patterns.PatternComparatorConsideringPath;
|
||||
|
||||
|
||||
/**
|
||||
* {@link PathMatcher} implementation for path patterns parsed
|
||||
|
@ -35,38 +31,50 @@ import org.springframework.web.util.patterns.PatternComparatorConsideringPath;
|
|||
* and quick comparison.
|
||||
*
|
||||
* @author Andy Clement
|
||||
* @author Juergen Hoeller
|
||||
* @since 5.0
|
||||
* @see PathPattern
|
||||
*/
|
||||
public class ParsingPathMatcher implements PathMatcher {
|
||||
|
||||
private final ConcurrentMap<String, PathPattern> cache =
|
||||
new ConcurrentHashMap<>(64);
|
||||
|
||||
private final PathPatternParser parser = new PathPatternParser();
|
||||
|
||||
private final ConcurrentMap<String, PathPattern> cache = new ConcurrentHashMap<>(256);
|
||||
|
||||
|
||||
@Override
|
||||
public boolean isPattern(String path) {
|
||||
// TODO crude, should be smarter, lookup pattern and ask it
|
||||
return (path.indexOf('*') != -1 || path.indexOf('?') != -1);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean match(String pattern, String path) {
|
||||
PathPattern p = getPathPattern(pattern);
|
||||
return p.matches(path);
|
||||
PathPattern pathPattern = getPathPattern(pattern);
|
||||
return pathPattern.matches(path);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean matchStart(String pattern, String path) {
|
||||
PathPattern p = getPathPattern(pattern);
|
||||
return p.matchStart(path);
|
||||
PathPattern pathPattern = getPathPattern(pattern);
|
||||
return pathPattern.matchStart(path);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String extractPathWithinPattern(String pattern, String path) {
|
||||
PathPattern p = getPathPattern(pattern);
|
||||
return p.extractPathWithinPattern(path);
|
||||
PathPattern pathPattern = getPathPattern(pattern);
|
||||
return pathPattern.extractPathWithinPattern(path);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<String, String> extractUriTemplateVariables(String pattern, String path) {
|
||||
PathPattern p = getPathPattern(pattern);
|
||||
return p.matchAndExtract(path);
|
||||
PathPattern pathPattern = getPathPattern(pattern);
|
||||
return pathPattern.matchAndExtract(path);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Comparator<String> getPatternComparator(String path) {
|
||||
return new PathPatternStringComparatorConsideringPath(path);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -75,12 +83,17 @@ public class ParsingPathMatcher implements PathMatcher {
|
|||
return pathPattern.combine(pattern2);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Comparator<String> getPatternComparator(String path) {
|
||||
return new PathPatternStringComparatorConsideringPath(path);
|
||||
private PathPattern getPathPattern(String pattern) {
|
||||
PathPattern pathPattern = this.cache.get(pattern);
|
||||
if (pathPattern == null) {
|
||||
pathPattern = this.parser.parse(pattern);
|
||||
this.cache.put(pattern, pathPattern);
|
||||
}
|
||||
return pathPattern;
|
||||
}
|
||||
|
||||
class PathPatternStringComparatorConsideringPath implements Comparator<String> {
|
||||
|
||||
private class PathPatternStringComparatorConsideringPath implements Comparator<String> {
|
||||
|
||||
private final PatternComparatorConsideringPath ppcp;
|
||||
|
||||
|
@ -100,22 +113,38 @@ public class ParsingPathMatcher implements PathMatcher {
|
|||
PathPattern p2 = getPathPattern(o2);
|
||||
return this.ppcp.compare(p1, p2);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isPattern(String path) {
|
||||
// TODO crude, should be smarter, lookup pattern and ask it
|
||||
return (path.indexOf('*') != -1 || path.indexOf('?') != -1);
|
||||
}
|
||||
|
||||
private PathPattern getPathPattern(String pattern) {
|
||||
PathPattern pathPattern = this.cache.get(pattern);
|
||||
if (pathPattern == null) {
|
||||
pathPattern = this.parser.parse(pattern);
|
||||
this.cache.put(pattern, pathPattern);
|
||||
/**
|
||||
* {@link PathPattern} comparator that takes account of a specified
|
||||
* path and sorts anything that exactly matches it to be first.
|
||||
*/
|
||||
static class PatternComparatorConsideringPath implements Comparator<PathPattern> {
|
||||
|
||||
private final String path;
|
||||
|
||||
public PatternComparatorConsideringPath(String path) {
|
||||
this.path = path;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int compare(PathPattern o1, PathPattern o2) {
|
||||
// Nulls get sorted to the end
|
||||
if (o1 == null) {
|
||||
return (o2 == null ? 0 : +1);
|
||||
}
|
||||
else if (o2 == null) {
|
||||
return -1;
|
||||
}
|
||||
if (o1.getPatternString().equals(this.path)) {
|
||||
return (o2.getPatternString().equals(this.path)) ? 0 : -1;
|
||||
}
|
||||
else if (o2.getPatternString().equals(this.path)) {
|
||||
return +1;
|
||||
}
|
||||
return o1.compareTo(o2);
|
||||
}
|
||||
return pathPattern;
|
||||
}
|
||||
|
||||
}
|
|
@ -14,9 +14,9 @@
|
|||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.web.util.patterns;
|
||||
package org.springframework.web.util.pattern;
|
||||
|
||||
import org.springframework.web.util.patterns.PathPattern.MatchingContext;
|
||||
import org.springframework.web.util.pattern.PathPattern.MatchingContext;
|
||||
|
||||
/**
|
||||
* Common supertype for the Ast nodes created to represent a path pattern.
|
||||
|
@ -31,25 +31,19 @@ abstract class PathElement {
|
|||
|
||||
protected static final int CAPTURE_VARIABLE_WEIGHT = 1;
|
||||
|
||||
/**
|
||||
* Position in the pattern where this path element starts
|
||||
*/
|
||||
protected int pos;
|
||||
|
||||
/**
|
||||
* The next path element in the chain
|
||||
*/
|
||||
// Position in the pattern where this path element starts
|
||||
protected final int pos;
|
||||
|
||||
// The separator used in this path pattern
|
||||
protected final char separator;
|
||||
|
||||
// The next path element in the chain
|
||||
protected PathElement next;
|
||||
|
||||
/**
|
||||
* The previous path element in the chain
|
||||
*/
|
||||
// The previous path element in the chain
|
||||
protected PathElement prev;
|
||||
|
||||
/**
|
||||
* The separator used in this path pattern
|
||||
*/
|
||||
protected char separator;
|
||||
|
||||
|
||||
/**
|
||||
* Create a new path element.
|
||||
|
@ -61,46 +55,47 @@ abstract class PathElement {
|
|||
this.separator = separator;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Attempt to match this path element.
|
||||
*
|
||||
* @param candidatePos the current position within the candidate path
|
||||
* @param matchingContext encapsulates context for the match including the candidate
|
||||
* @return true if matches, otherwise false
|
||||
* @return {@code true} if it matches, otherwise {@code false}
|
||||
*/
|
||||
public abstract boolean matches(int candidatePos, MatchingContext matchingContext);
|
||||
|
||||
/**
|
||||
* @return the length of the path element where captures are considered to be one character long
|
||||
* Return the length of the path element where captures are considered to be one character long.
|
||||
*/
|
||||
public abstract int getNormalizedLength();
|
||||
|
||||
/**
|
||||
* @return the number of variables captured by the path element
|
||||
* Return the number of variables captured by the path element.
|
||||
*/
|
||||
public int getCaptureCount() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the number of wildcard elements (*, ?) in the path element
|
||||
* Return the number of wildcard elements (*, ?) in the path element.
|
||||
*/
|
||||
public int getWildcardCount() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the score for this PathElement, combined score is used to compare parsed patterns.
|
||||
* Return the score for this PathElement, combined score is used to compare parsed patterns.
|
||||
*/
|
||||
public int getScore() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return true if there is no next character, or if there is then it is a separator
|
||||
* Return {@code true} if there is no next character, or if there is then it is a separator.
|
||||
*/
|
||||
protected boolean nextIfExistsIsSeparator(int nextIndex, MatchingContext matchingContext) {
|
||||
return (nextIndex >= matchingContext.candidateLength ||
|
||||
matchingContext.candidate[nextIndex] == separator);
|
||||
matchingContext.candidate[nextIndex] == this.separator);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -14,14 +14,15 @@
|
|||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.web.util.patterns;
|
||||
package org.springframework.web.util.pattern;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import org.springframework.util.PathMatcher;
|
||||
import static org.springframework.util.StringUtils.hasLength;
|
||||
|
||||
import static org.springframework.util.StringUtils.*;
|
||||
|
||||
/**
|
||||
* Represents a parsed path pattern. Includes a chain of path elements
|
||||
|
@ -62,8 +63,6 @@ import static org.springframework.util.StringUtils.hasLength;
|
|||
*/
|
||||
public class PathPattern implements Comparable<PathPattern> {
|
||||
|
||||
private final static Map<String, String> NO_VARIABLES_MAP = Collections.emptyMap();
|
||||
|
||||
/** First path element in the parsed chain of path elements for this pattern */
|
||||
private PathElement head;
|
||||
|
||||
|
@ -88,12 +87,12 @@ public class PathPattern implements Comparable<PathPattern> {
|
|||
* your variable name lengths isn't going to change the length of the active part of the pattern.
|
||||
* Useful when comparing two patterns.
|
||||
*/
|
||||
int normalizedLength;
|
||||
private int normalizedLength;
|
||||
|
||||
/**
|
||||
* Does the pattern end with '<separator>*'
|
||||
*/
|
||||
boolean endsWithSeparatorWildcard = false;
|
||||
private boolean endsWithSeparatorWildcard = false;
|
||||
|
||||
/**
|
||||
* Score is used to quickly compare patterns. Different pattern components are given different
|
||||
|
@ -106,41 +105,58 @@ public class PathPattern implements Comparable<PathPattern> {
|
|||
private int score;
|
||||
|
||||
/** Does the pattern end with {*...} */
|
||||
private boolean isCatchAll = false;
|
||||
private boolean catchAll = false;
|
||||
|
||||
|
||||
PathPattern(String patternText, PathElement head, char separator, boolean caseSensitive,
|
||||
boolean allowOptionalTrailingSlash) {
|
||||
|
||||
public PathPattern(String patternText, PathElement head, char separator, boolean caseSensitive, boolean allowOptionalTrailingSlash) {
|
||||
this.head = head;
|
||||
this.patternString = patternText;
|
||||
this.head = head;
|
||||
this.separator = separator;
|
||||
this.caseSensitive = caseSensitive;
|
||||
this.allowOptionalTrailingSlash = allowOptionalTrailingSlash;
|
||||
|
||||
// Compute fields for fast comparison
|
||||
PathElement s = head;
|
||||
while (s != null) {
|
||||
this.capturedVariableCount += s.getCaptureCount();
|
||||
this.normalizedLength += s.getNormalizedLength();
|
||||
this.score += s.getScore();
|
||||
if (s instanceof CaptureTheRestPathElement || s instanceof WildcardTheRestPathElement) {
|
||||
this.isCatchAll = true;
|
||||
PathElement elem = head;
|
||||
while (elem != null) {
|
||||
this.capturedVariableCount += elem.getCaptureCount();
|
||||
this.normalizedLength += elem.getNormalizedLength();
|
||||
this.score += elem.getScore();
|
||||
if (elem instanceof CaptureTheRestPathElement || elem instanceof WildcardTheRestPathElement) {
|
||||
this.catchAll = true;
|
||||
}
|
||||
if (s instanceof SeparatorPathElement && s.next != null
|
||||
&& s.next instanceof WildcardPathElement && s.next.next == null) {
|
||||
if (elem instanceof SeparatorPathElement && elem.next != null &&
|
||||
elem.next instanceof WildcardPathElement && elem.next.next == null) {
|
||||
this.endsWithSeparatorWildcard = true;
|
||||
}
|
||||
s = s.next;
|
||||
elem = elem.next;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Return the original pattern string that was parsed to create this PathPattern.
|
||||
*/
|
||||
public String getPatternString() {
|
||||
return this.patternString;
|
||||
}
|
||||
|
||||
PathElement getHeadSection() {
|
||||
return this.head;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @param path the candidate path to attempt to match against this pattern
|
||||
* @return true if the path matches this pattern
|
||||
*/
|
||||
public boolean matches(String path) {
|
||||
if (head == null) {
|
||||
if (this.head == null) {
|
||||
return !hasLength(path);
|
||||
}
|
||||
else if (!hasLength(path)) {
|
||||
if (head instanceof WildcardTheRestPathElement || head instanceof CaptureTheRestPathElement) {
|
||||
if (this.head instanceof WildcardTheRestPathElement || this.head instanceof CaptureTheRestPathElement) {
|
||||
path = ""; // Will allow CaptureTheRest to bind the variable to empty
|
||||
}
|
||||
else {
|
||||
|
@ -148,31 +164,31 @@ public class PathPattern implements Comparable<PathPattern> {
|
|||
}
|
||||
}
|
||||
MatchingContext matchingContext = new MatchingContext(path, false);
|
||||
return head.matches(0, matchingContext);
|
||||
return this.head.matches(0, matchingContext);
|
||||
}
|
||||
|
||||
/**
|
||||
* For a given path return the remaining piece that is not covered by this PathPattern.
|
||||
*
|
||||
* @param path a path that may or may not match this path pattern
|
||||
* @return a {@link PathRemainingMatchInfo} describing the match result or null if the path does not match
|
||||
* this pattern
|
||||
* @return a {@link PathRemainingMatchInfo} describing the match result or null if
|
||||
* the path does not match this pattern
|
||||
*/
|
||||
public PathRemainingMatchInfo getPathRemaining(String path) {
|
||||
if (head == null) {
|
||||
if (this.head == null) {
|
||||
if (path == null) {
|
||||
return new PathRemainingMatchInfo(path);
|
||||
return new PathRemainingMatchInfo(null);
|
||||
}
|
||||
else {
|
||||
return new PathRemainingMatchInfo(hasLength(path)?path:"");
|
||||
return new PathRemainingMatchInfo(hasLength(path) ? path : "");
|
||||
}
|
||||
}
|
||||
else if (!hasLength(path)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
MatchingContext matchingContext = new MatchingContext(path, true);
|
||||
matchingContext.setMatchAllowExtraPath();
|
||||
boolean matches = head.matches(0, matchingContext);
|
||||
boolean matches = this.head.matches(0, matchingContext);
|
||||
if (!matches) {
|
||||
return null;
|
||||
}
|
||||
|
@ -194,7 +210,7 @@ public class PathPattern implements Comparable<PathPattern> {
|
|||
* @return true if the pattern matches as much of the path as is supplied
|
||||
*/
|
||||
public boolean matchStart(String path) {
|
||||
if (head == null) {
|
||||
if (this.head == null) {
|
||||
return !hasLength(path);
|
||||
}
|
||||
else if (!hasLength(path)) {
|
||||
|
@ -202,7 +218,7 @@ public class PathPattern implements Comparable<PathPattern> {
|
|||
}
|
||||
MatchingContext matchingContext = new MatchingContext(path, false);
|
||||
matchingContext.setMatchStartMatching(true);
|
||||
return head.matches(0, matchingContext);
|
||||
return this.head.matches(0, matchingContext);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -212,31 +228,19 @@ public class PathPattern implements Comparable<PathPattern> {
|
|||
*/
|
||||
public Map<String, String> matchAndExtract(String path) {
|
||||
MatchingContext matchingContext = new MatchingContext(path, true);
|
||||
if (head != null && head.matches(0, matchingContext)) {
|
||||
if (this.head != null && this.head.matches(0, matchingContext)) {
|
||||
return matchingContext.getExtractedVariables();
|
||||
}
|
||||
else {
|
||||
if (!hasLength(path)) {
|
||||
return NO_VARIABLES_MAP;
|
||||
return Collections.emptyMap();
|
||||
}
|
||||
else {
|
||||
throw new IllegalStateException("Pattern \"" + this.toString()
|
||||
+ "\" is not a match for \"" + path + "\"");
|
||||
throw new IllegalStateException("Pattern \"" + this + "\" is not a match for \"" + path + "\"");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the original pattern string that was parsed to create this PathPattern
|
||||
*/
|
||||
public String getPatternString() {
|
||||
return patternString;
|
||||
}
|
||||
|
||||
public PathElement getHeadSection() {
|
||||
return head;
|
||||
}
|
||||
|
||||
/**
|
||||
* Given a full path, determine the pattern-mapped part. <p>For example: <ul>
|
||||
* <li>'{@code /docs/cvs/commit.html}' and '{@code /docs/cvs/commit.html} -> ''</li>
|
||||
|
@ -253,26 +257,29 @@ public class PathPattern implements Comparable<PathPattern> {
|
|||
*/
|
||||
public String extractPathWithinPattern(String path) {
|
||||
// assert this.matches(path)
|
||||
PathElement s = head;
|
||||
PathElement elem = head;
|
||||
int separatorCount = 0;
|
||||
boolean matchTheRest = false;
|
||||
|
||||
// Find first path element that is pattern based
|
||||
while (s != null) {
|
||||
if (s instanceof SeparatorPathElement || s instanceof CaptureTheRestPathElement
|
||||
|| s instanceof WildcardTheRestPathElement) {
|
||||
while (elem != null) {
|
||||
if (elem instanceof SeparatorPathElement || elem instanceof CaptureTheRestPathElement ||
|
||||
elem instanceof WildcardTheRestPathElement) {
|
||||
separatorCount++;
|
||||
if (s instanceof WildcardTheRestPathElement || s instanceof CaptureTheRestPathElement) {
|
||||
if (elem instanceof WildcardTheRestPathElement || elem instanceof CaptureTheRestPathElement) {
|
||||
matchTheRest = true;
|
||||
}
|
||||
}
|
||||
if (s.getWildcardCount() != 0 || s.getCaptureCount() != 0) {
|
||||
if (elem.getWildcardCount() != 0 || elem.getCaptureCount() != 0) {
|
||||
break;
|
||||
}
|
||||
s = s.next;
|
||||
elem = elem.next;
|
||||
}
|
||||
if (s == null) {
|
||||
return ""; // There is no pattern mapped section
|
||||
|
||||
if (elem == null) {
|
||||
return ""; // there is no pattern mapped section
|
||||
}
|
||||
|
||||
// Now separatorCount indicates how many sections of the path to skip
|
||||
char[] pathChars = path.toCharArray();
|
||||
int len = pathChars.length;
|
||||
|
@ -289,6 +296,7 @@ public class PathPattern implements Comparable<PathPattern> {
|
|||
end--;
|
||||
}
|
||||
}
|
||||
|
||||
// Check if multiple separators embedded in the resulting path, if so trim them out.
|
||||
// Example: aaa////bbb//ccc/d -> aaa/bbb/ccc/d
|
||||
// The stringWithDuplicateSeparatorsRemoved is only computed if necessary
|
||||
|
@ -314,28 +322,31 @@ public class PathPattern implements Comparable<PathPattern> {
|
|||
}
|
||||
c++;
|
||||
}
|
||||
|
||||
if (stringWithDuplicateSeparatorsRemoved != null) {
|
||||
return stringWithDuplicateSeparatorsRemoved.toString();
|
||||
}
|
||||
return pos == len ? "" : path.substring(pos, end);
|
||||
return (pos == len ? "" : path.substring(pos, end));
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Compare this pattern with a supplied pattern. Return -1,0,+1 if this pattern
|
||||
* Compare this pattern with a supplied pattern: return -1,0,+1 if this pattern
|
||||
* is more specific, the same or less specific than the supplied pattern.
|
||||
* The aim is to sort more specific patterns first.
|
||||
*/
|
||||
@Override
|
||||
public int compareTo(PathPattern p) {
|
||||
public int compareTo(PathPattern otherPattern) {
|
||||
// 1) null is sorted last
|
||||
if (p == null) {
|
||||
if (otherPattern == null) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
// 2) catchall patterns are sorted last. If both catchall then the
|
||||
// length is considered
|
||||
if (isCatchAll()) {
|
||||
if (p.isCatchAll()) {
|
||||
int lenDifference = this.getNormalizedLength() - p.getNormalizedLength();
|
||||
if (otherPattern.isCatchAll()) {
|
||||
int lenDifference = getNormalizedLength() - otherPattern.getNormalizedLength();
|
||||
if (lenDifference != 0) {
|
||||
return (lenDifference < 0) ? +1 : -1;
|
||||
}
|
||||
|
@ -344,26 +355,28 @@ public class PathPattern implements Comparable<PathPattern> {
|
|||
return +1;
|
||||
}
|
||||
}
|
||||
else if (p.isCatchAll()) {
|
||||
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 = this.getScore() - p.getScore();
|
||||
int score = getScore() - otherPattern.getScore();
|
||||
if (score != 0) {
|
||||
return (score < 0) ? -1 : +1;
|
||||
}
|
||||
|
||||
// 4) longer is better
|
||||
int lenDifference = this.getNormalizedLength() - p.getNormalizedLength();
|
||||
int lenDifference = getNormalizedLength() - otherPattern.getNormalizedLength();
|
||||
return (lenDifference < 0) ? +1 : (lenDifference == 0 ? 0 : -1);
|
||||
}
|
||||
|
||||
public int getScore() {
|
||||
return score;
|
||||
int getScore() {
|
||||
return this.score;
|
||||
}
|
||||
|
||||
public boolean isCatchAll() {
|
||||
return isCatchAll;
|
||||
boolean isCatchAll() {
|
||||
return this.catchAll;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -371,28 +384,118 @@ public class PathPattern implements Comparable<PathPattern> {
|
|||
* by assuming all capture variables have a normalized length of 1. Effectively this means changing
|
||||
* your variable name lengths isn't going to change the length of the active part of the pattern.
|
||||
* Useful when comparing two patterns.
|
||||
* @return the normalized length of the pattern
|
||||
*/
|
||||
public int getNormalizedLength() {
|
||||
return normalizedLength;
|
||||
int getNormalizedLength() {
|
||||
return this.normalizedLength;
|
||||
}
|
||||
|
||||
public boolean equals(Object o) {
|
||||
if (!(o instanceof PathPattern)) {
|
||||
char getSeparator() {
|
||||
return this.separator;
|
||||
}
|
||||
|
||||
int getCapturedVariableCount() {
|
||||
return this.capturedVariableCount;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Combine this pattern with another. Currently does not produce a new PathPattern, just produces a new string.
|
||||
*/
|
||||
public String combine(String pattern2string) {
|
||||
// If one of them is empty the result is the other. If both empty the result is ""
|
||||
if (!hasLength(this.patternString)) {
|
||||
if (!hasLength(pattern2string)) {
|
||||
return "";
|
||||
}
|
||||
else {
|
||||
return pattern2string;
|
||||
}
|
||||
}
|
||||
else if (!hasLength(pattern2string)) {
|
||||
return this.patternString;
|
||||
}
|
||||
|
||||
// /* + /hotel => /hotel
|
||||
// /*.* + /*.html => /*.html
|
||||
// However:
|
||||
// /usr + /user => /usr/user
|
||||
// /{foo} + /bar => /{foo}/bar
|
||||
if (!this.patternString.equals(pattern2string) &&this. capturedVariableCount == 0 && matches(pattern2string)) {
|
||||
return pattern2string;
|
||||
}
|
||||
|
||||
// /hotels/* + /booking => /hotels/booking
|
||||
// /hotels/* + booking => /hotels/booking
|
||||
if (this.endsWithSeparatorWildcard) {
|
||||
return concat(this.patternString.substring(0, this.patternString.length() - 2), pattern2string);
|
||||
}
|
||||
|
||||
// /hotels + /booking => /hotels/booking
|
||||
// /hotels + booking => /hotels/booking
|
||||
int starDotPos1 = this.patternString.indexOf("*."); // Are there any file prefix/suffix things to consider?
|
||||
if (this.capturedVariableCount != 0 || starDotPos1 == -1 || this.separator == '.') {
|
||||
return concat(this.patternString, pattern2string);
|
||||
}
|
||||
|
||||
// /*.html + /hotel => /hotel.html
|
||||
// /*.html + /hotel.* => /hotel.html
|
||||
String firstExtension = this.patternString.substring(starDotPos1 + 1); // looking for the first extension
|
||||
int dotPos2 = pattern2string.indexOf('.');
|
||||
String file2 = (dotPos2 == -1 ? pattern2string : pattern2string.substring(0, dotPos2));
|
||||
String secondExtension = (dotPos2 == -1 ? "" : pattern2string.substring(dotPos2));
|
||||
boolean firstExtensionWild = (firstExtension.equals(".*") || firstExtension.equals(""));
|
||||
boolean secondExtensionWild = (secondExtension.equals(".*") || secondExtension.equals(""));
|
||||
if (!firstExtensionWild && !secondExtensionWild) {
|
||||
throw new IllegalArgumentException(
|
||||
"Cannot combine patterns: " + this.patternString + " and " + pattern2string);
|
||||
}
|
||||
return file2 + (firstExtensionWild ? secondExtension : firstExtension);
|
||||
}
|
||||
|
||||
/**
|
||||
* Join two paths together including a separator if necessary.
|
||||
* Extraneous separators are removed (if the first path
|
||||
* ends with one and the second path starts with one).
|
||||
* @param path1 first path
|
||||
* @param path2 second path
|
||||
* @return joined path that may include separator if necessary
|
||||
*/
|
||||
private String concat(String path1, String path2) {
|
||||
boolean path1EndsWithSeparator = (path1.charAt(path1.length() - 1) == this.separator);
|
||||
boolean path2StartsWithSeparator = (path2.charAt(0) == this.separator);
|
||||
if (path1EndsWithSeparator && path2StartsWithSeparator) {
|
||||
return path1 + path2.substring(1);
|
||||
}
|
||||
else if (path1EndsWithSeparator || path2StartsWithSeparator) {
|
||||
return path1 + path2;
|
||||
}
|
||||
else {
|
||||
return path1 + this.separator + path2;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public boolean equals(Object other) {
|
||||
if (!(other instanceof PathPattern)) {
|
||||
return false;
|
||||
}
|
||||
PathPattern p = (PathPattern) o;
|
||||
return patternString.equals(p.getPatternString()) && separator == p.getSeparator()
|
||||
&& caseSensitive == p.caseSensitive;
|
||||
PathPattern otherPattern = (PathPattern) other;
|
||||
return (this.patternString.equals(otherPattern.getPatternString()) &&
|
||||
this.separator == otherPattern.getSeparator() &&
|
||||
this.caseSensitive == otherPattern.caseSensitive);
|
||||
}
|
||||
|
||||
public int hashCode() {
|
||||
return (patternString.hashCode() * 17 + separator) * 17 + (caseSensitive ? 1 : 0);
|
||||
return (this.patternString.hashCode() + this.separator) * 17 + (this.caseSensitive ? 1 : 0);
|
||||
}
|
||||
|
||||
public String toChainString() {
|
||||
public String toString() {
|
||||
return this.patternString;
|
||||
}
|
||||
|
||||
String toChainString() {
|
||||
StringBuilder buf = new StringBuilder();
|
||||
PathElement pe = head;
|
||||
PathElement pe = this.head;
|
||||
while (pe != null) {
|
||||
buf.append(pe.toString()).append(" ");
|
||||
pe = pe.next;
|
||||
|
@ -400,17 +503,43 @@ public class PathPattern implements Comparable<PathPattern> {
|
|||
return buf.toString().trim();
|
||||
}
|
||||
|
||||
public char getSeparator() {
|
||||
return separator;
|
||||
|
||||
/**
|
||||
* A holder for the result of a {@link PathPattern#getPathRemaining(String)} call. Holds
|
||||
* information on the path left after the first part has successfully matched a pattern
|
||||
* and any variables bound in that first part that matched.
|
||||
*/
|
||||
public static class PathRemainingMatchInfo {
|
||||
|
||||
private final String pathRemaining;
|
||||
|
||||
private final Map<String, String> matchingVariables;
|
||||
|
||||
PathRemainingMatchInfo(String pathRemaining) {
|
||||
this(pathRemaining, Collections.emptyMap());
|
||||
}
|
||||
|
||||
PathRemainingMatchInfo(String pathRemaining, Map<String, String> matchingVariables) {
|
||||
this.pathRemaining = pathRemaining;
|
||||
this.matchingVariables = matchingVariables;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the part of a path that was not matched by a pattern.
|
||||
*/
|
||||
public String getPathRemaining() {
|
||||
return this.pathRemaining;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return variables that were bound in the part of the path that was successfully matched.
|
||||
* Will be an empty map if no variables were bound
|
||||
*/
|
||||
public Map<String, String> getMatchingVariables() {
|
||||
return this.matchingVariables;
|
||||
}
|
||||
}
|
||||
|
||||
public int getCapturedVariableCount() {
|
||||
return capturedVariableCount;
|
||||
}
|
||||
|
||||
public String toString() {
|
||||
return patternString;
|
||||
}
|
||||
|
||||
/**
|
||||
* Encapsulates context when attempting a match. Includes some fixed state like the
|
||||
|
@ -430,9 +559,9 @@ public class PathPattern implements Comparable<PathPattern> {
|
|||
private Map<String, String> extractedVariables;
|
||||
|
||||
boolean extractingVariables;
|
||||
|
||||
|
||||
boolean determineRemainingPath = false;
|
||||
|
||||
|
||||
// if determineRemaining is true, this is set to the position in
|
||||
// the candidate where the pattern finished matching - i.e. it
|
||||
// points to the remaining path that wasn't consumed
|
||||
|
@ -447,7 +576,7 @@ public class PathPattern implements Comparable<PathPattern> {
|
|||
public void setMatchAllowExtraPath() {
|
||||
determineRemainingPath = true;
|
||||
}
|
||||
|
||||
|
||||
public boolean isAllowOptionalTrailingSlash() {
|
||||
return allowOptionalTrailingSlash;
|
||||
}
|
||||
|
@ -465,7 +594,7 @@ public class PathPattern implements Comparable<PathPattern> {
|
|||
|
||||
public Map<String, String> getExtractedVariables() {
|
||||
if (this.extractedVariables == null) {
|
||||
return NO_VARIABLES_MAP;
|
||||
return Collections.emptyMap();
|
||||
}
|
||||
else {
|
||||
return this.extractedVariables;
|
||||
|
@ -475,7 +604,6 @@ public class PathPattern implements Comparable<PathPattern> {
|
|||
/**
|
||||
* Scan ahead from the specified position for either the next separator
|
||||
* character or the end of the candidate.
|
||||
*
|
||||
* @param pos the starting position for the scan
|
||||
* @return the position of the next separator or the end of the candidate
|
||||
*/
|
||||
|
@ -490,79 +618,4 @@ public class PathPattern implements Comparable<PathPattern> {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Combine this pattern with another. Currently does not produce a new PathPattern, just produces a new string.
|
||||
*/
|
||||
public String combine(String pattern2string) {
|
||||
// If one of them is empty the result is the other. If both empty the result is ""
|
||||
if (!hasLength(patternString)) {
|
||||
if (!hasLength(pattern2string)) {
|
||||
return "";
|
||||
}
|
||||
else {
|
||||
return pattern2string;
|
||||
}
|
||||
}
|
||||
else if (!hasLength(pattern2string)) {
|
||||
return patternString;
|
||||
}
|
||||
|
||||
// /* + /hotel => /hotel
|
||||
// /*.* + /*.html => /*.html
|
||||
// However:
|
||||
// /usr + /user => /usr/user
|
||||
// /{foo} + /bar => /{foo}/bar
|
||||
if (!patternString.equals(pattern2string) && capturedVariableCount == 0 && matches(pattern2string)) {
|
||||
return pattern2string;
|
||||
}
|
||||
|
||||
// /hotels/* + /booking => /hotels/booking
|
||||
// /hotels/* + booking => /hotels/booking
|
||||
if (endsWithSeparatorWildcard) {
|
||||
return concat(patternString.substring(0, patternString.length() - 2), pattern2string);
|
||||
}
|
||||
|
||||
// /hotels + /booking => /hotels/booking
|
||||
// /hotels + booking => /hotels/booking
|
||||
int starDotPos1 = patternString.indexOf("*."); // Are there any file prefix/suffix things to consider?
|
||||
if (capturedVariableCount != 0 || starDotPos1 == -1 || separator == '.') {
|
||||
return concat(patternString, pattern2string);
|
||||
}
|
||||
|
||||
// /*.html + /hotel => /hotel.html
|
||||
// /*.html + /hotel.* => /hotel.html
|
||||
String firstExtension = patternString.substring(starDotPos1 + 1); // looking for the first extension
|
||||
int dotPos2 = pattern2string.indexOf('.');
|
||||
String file2 = (dotPos2 == -1 ? pattern2string : pattern2string.substring(0, dotPos2));
|
||||
String secondExtension = (dotPos2 == -1 ? "" : pattern2string.substring(dotPos2));
|
||||
boolean firstExtensionWild = (firstExtension.equals(".*") || firstExtension.equals(""));
|
||||
boolean secondExtensionWild = (secondExtension.equals(".*") || secondExtension.equals(""));
|
||||
if (!firstExtensionWild && !secondExtensionWild) {
|
||||
throw new IllegalArgumentException("Cannot combine patterns: " + patternString + " and " + pattern2string);
|
||||
}
|
||||
return file2 + (firstExtensionWild ? secondExtension : firstExtension);
|
||||
}
|
||||
|
||||
/**
|
||||
* Join two paths together including a separator if necessary.
|
||||
* Extraneous separators are removed (if the first path
|
||||
* ends with one and the second path starts with one).
|
||||
* @param path1 First path
|
||||
* @param path2 Second path
|
||||
* @return joined path that may include separator if necessary
|
||||
*/
|
||||
private String concat(String path1, String path2) {
|
||||
boolean path1EndsWithSeparator = path1.charAt(path1.length() - 1) == separator;
|
||||
boolean path2StartsWithSeparator = path2.charAt(0) == separator;
|
||||
if (path1EndsWithSeparator && path2StartsWithSeparator) {
|
||||
return path1 + path2.substring(1);
|
||||
}
|
||||
else if (path1EndsWithSeparator || path2StartsWithSeparator) {
|
||||
return path1 + path2;
|
||||
}
|
||||
else {
|
||||
return path1 + separator + path2;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -14,11 +14,11 @@
|
|||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.web.util.patterns;
|
||||
package org.springframework.web.util.pattern;
|
||||
|
||||
/**
|
||||
* Parser for URI template patterns. It breaks the path pattern into a number of
|
||||
* {@link PathElement}s in a linked list.
|
||||
* Parser for URI template patterns. It breaks the path pattern into a number
|
||||
* of {@link PathElement}s in a linked list.
|
||||
*
|
||||
* @author Andy Clement
|
||||
* @since 5.0
|
||||
|
@ -27,62 +27,69 @@ public class PathPatternParser {
|
|||
|
||||
public final static char DEFAULT_SEPARATOR = '/';
|
||||
|
||||
// Is the parser producing case sensitive PathPattern matchers, default true
|
||||
private boolean caseSensitive = true;
|
||||
|
||||
// The expected path separator to split path elements during parsing, default '/'
|
||||
// The expected path separator to split path elements during parsing.
|
||||
private char separator = DEFAULT_SEPARATOR;
|
||||
|
||||
// If true the PathPatterns produced by the parser will allow patterns
|
||||
// that don't have a trailing slash to match paths that may or may not
|
||||
// have a trailing slash
|
||||
// Whether the PathPatterns produced by the parser will allow patterns that don't
|
||||
// have a trailing slash to match paths that may or may not have a trailing slash.
|
||||
private boolean matchOptionalTrailingSlash = false;
|
||||
|
||||
// If the parser produces case-sensitive PathPattern matchers.
|
||||
private boolean caseSensitive = true;
|
||||
|
||||
|
||||
/**
|
||||
* Create a path pattern parser that will use the default separator '/' when
|
||||
* parsing patterns.
|
||||
* Create a path pattern parser that will use the default separator '/'
|
||||
* when parsing patterns.
|
||||
* @see #DEFAULT_SEPARATOR
|
||||
*/
|
||||
public PathPatternParser() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Control behavior of the path patterns produced by this parser. The default
|
||||
* value for matchOptionalTrailingSlash is true but here it can be set to false.
|
||||
* If true then PathPatterns without a trailing slash will match paths with or
|
||||
* without a trailing slash.
|
||||
*
|
||||
* @param matchOptionalTrailingSlash boolean value to override the default value of true
|
||||
*/
|
||||
public void setMatchOptionalTrailingSlash(boolean matchOptionalTrailingSlash) {
|
||||
this.matchOptionalTrailingSlash = matchOptionalTrailingSlash;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Create a path pattern parser that will use the supplied separator when
|
||||
* parsing patterns.
|
||||
* @param separator the separator expected to divide pattern elements parsed by this parser
|
||||
* @param separator the separator expected to divide pattern elements
|
||||
*/
|
||||
public PathPatternParser(char separator) {
|
||||
this.separator = separator;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Control behavior of the path patterns produced by this parser: if {@code true}
|
||||
* then PathPatterns without a trailing slash will match paths with or without
|
||||
* a trailing slash.
|
||||
* <p>The default is {@code true} but here this flag can be set to {@code false}.
|
||||
*/
|
||||
public void setMatchOptionalTrailingSlash(boolean matchOptionalTrailingSlash) {
|
||||
this.matchOptionalTrailingSlash = matchOptionalTrailingSlash;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set whether path patterns are case-sensitive.
|
||||
* <p>The default is {@code true}.
|
||||
*/
|
||||
public void setCaseSensitive(boolean caseSensitive) {
|
||||
this.caseSensitive = caseSensitive;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Process the path pattern data, a character at a time, breaking it into
|
||||
* path elements around separator boundaries and verifying the structure at each
|
||||
* stage. Produces a PathPattern object that can be used for fast matching
|
||||
* against paths. Each invocation of this method delegates to a new instance of
|
||||
* the {@link InternalPathPatternParser} because that class is not thread-safe.
|
||||
*
|
||||
* @param pathPattern the input path pattern, e.g. /foo/{bar}
|
||||
* @return a PathPattern for quickly matching paths against the specified path pattern
|
||||
* @throws PatternParseException in case of parse errors
|
||||
*/
|
||||
public PathPattern parse(String pathPattern) {
|
||||
InternalPathPatternParser ippp = new InternalPathPatternParser(separator, caseSensitive, matchOptionalTrailingSlash);
|
||||
return ippp.parse(pathPattern);
|
||||
public PathPattern parse(String pathPattern) throws PatternParseException {
|
||||
InternalPathPatternParser parserDelegate =
|
||||
new InternalPathPatternParser(this.separator, this.caseSensitive, this.matchOptionalTrailingSlash);
|
||||
return parserDelegate.parse(pathPattern);
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,121 @@
|
|||
/*
|
||||
* Copyright 2002-2017 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.web.util.pattern;
|
||||
|
||||
import java.text.MessageFormat;
|
||||
|
||||
/**
|
||||
* Exception that is thrown when there is a problem with the pattern being parsed.
|
||||
*
|
||||
* @author Andy Clement
|
||||
* @since 5.0
|
||||
*/
|
||||
@SuppressWarnings("serial")
|
||||
public class PatternParseException extends IllegalArgumentException {
|
||||
|
||||
private final int position;
|
||||
|
||||
private final char[] pattern;
|
||||
|
||||
private final PatternMessage messageType;
|
||||
|
||||
private final Object[] inserts;
|
||||
|
||||
|
||||
PatternParseException(int pos, char[] pattern, PatternMessage messageType, Object... inserts) {
|
||||
super(messageType.formatMessage(inserts));
|
||||
this.position = pos;
|
||||
this.pattern = pattern;
|
||||
this.messageType = messageType;
|
||||
this.inserts = inserts;
|
||||
}
|
||||
|
||||
PatternParseException(Throwable cause, int pos, char[] pattern, PatternMessage messageType, Object... inserts) {
|
||||
super(messageType.formatMessage(inserts), cause);
|
||||
this.position = pos;
|
||||
this.pattern = pattern;
|
||||
this.messageType = messageType;
|
||||
this.inserts = inserts;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Return a formatted message with inserts applied.
|
||||
*/
|
||||
@Override
|
||||
public String getMessage() {
|
||||
return this.messageType.formatMessage(this.inserts);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a detailed message that includes the original pattern text
|
||||
* with a pointer to the error position, as well as the error message.
|
||||
*/
|
||||
public String toDetailedString() {
|
||||
StringBuilder buf = new StringBuilder();
|
||||
buf.append(this.pattern).append('\n');
|
||||
for (int i = 0; i < this.position; i++) {
|
||||
buf.append(' ');
|
||||
}
|
||||
buf.append("^\n");
|
||||
buf.append(getMessage());
|
||||
return buf.toString();
|
||||
}
|
||||
|
||||
public int getPosition() {
|
||||
return this.position;
|
||||
}
|
||||
|
||||
public PatternMessage getMessageType() {
|
||||
return this.messageType;
|
||||
}
|
||||
|
||||
public Object[] getInserts() {
|
||||
return this.inserts;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* The messages that can be included in a {@link PatternParseException} when there is a parse failure.
|
||||
*/
|
||||
public enum PatternMessage {
|
||||
|
||||
MISSING_CLOSE_CAPTURE("Expected close capture character after variable name '}'"),
|
||||
MISSING_OPEN_CAPTURE("Missing preceeding open capture character before variable name'{'"),
|
||||
ILLEGAL_NESTED_CAPTURE("Not allowed to nest variable captures"),
|
||||
CANNOT_HAVE_ADJACENT_CAPTURES("Adjacent captures are not allowed"),
|
||||
ILLEGAL_CHARACTER_AT_START_OF_CAPTURE_DESCRIPTOR("Character ''{0}'' is not allowed at start of captured variable name"),
|
||||
ILLEGAL_CHARACTER_IN_CAPTURE_DESCRIPTOR("Character ''{0}'' is not allowed in a captured variable name"),
|
||||
NO_MORE_DATA_EXPECTED_AFTER_CAPTURE_THE_REST("No more pattern data allowed after '{*...}' pattern element"),
|
||||
BADLY_FORMED_CAPTURE_THE_REST("Expected form when capturing the rest of the path is simply '{*...}'"),
|
||||
MISSING_REGEX_CONSTRAINT("Missing regex constraint on capture"),
|
||||
ILLEGAL_DOUBLE_CAPTURE("Not allowed to capture ''{0}'' twice in the same pattern"),
|
||||
REGEX_PATTERN_SYNTAX_EXCEPTION("Exception occurred in regex pattern compilation"),
|
||||
CAPTURE_ALL_IS_STANDALONE_CONSTRUCT("'{*...}' can only be preceeded by a path separator");
|
||||
|
||||
private final String message;
|
||||
|
||||
PatternMessage(String message) {
|
||||
this.message = message;
|
||||
}
|
||||
|
||||
public String formatMessage(Object... inserts) {
|
||||
return MessageFormat.format(this.message, inserts);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -14,14 +14,15 @@
|
|||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.web.util.patterns;
|
||||
package org.springframework.web.util.pattern;
|
||||
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import org.springframework.util.AntPathMatcher;
|
||||
import org.springframework.web.util.patterns.PathPattern.MatchingContext;
|
||||
import org.springframework.web.util.pattern.PathPattern.MatchingContext;
|
||||
|
||||
/**
|
||||
* A regex path element. Used to represent any complicated element of the path.
|
||||
|
@ -33,33 +34,36 @@ import org.springframework.web.util.patterns.PathPattern.MatchingContext;
|
|||
*/
|
||||
class RegexPathElement extends PathElement {
|
||||
|
||||
private final java.util.regex.Pattern GLOB_PATTERN = java.util.regex.Pattern
|
||||
.compile("\\?|\\*|\\{((?:\\{[^/]+?\\}|[^/{}]|\\\\[{}])+?)\\}");
|
||||
private final Pattern GLOB_PATTERN = Pattern.compile("\\?|\\*|\\{((?:\\{[^/]+?\\}|[^/{}]|\\\\[{}])+?)\\}");
|
||||
|
||||
private final String DEFAULT_VARIABLE_PATTERN = "(.*)";
|
||||
|
||||
private final List<String> variableNames = new LinkedList<>();
|
||||
|
||||
private char[] regex;
|
||||
private final char[] regex;
|
||||
|
||||
private java.util.regex.Pattern pattern;
|
||||
private final boolean caseSensitive;
|
||||
|
||||
private boolean caseSensitive;
|
||||
private final Pattern pattern;
|
||||
|
||||
private int wildcardCount;
|
||||
|
||||
private final List<String> variableNames = new LinkedList<>();
|
||||
|
||||
|
||||
RegexPathElement(int pos, char[] regex, boolean caseSensitive, char[] completePattern, char separator) {
|
||||
super(pos, separator);
|
||||
this.regex = regex;
|
||||
this.caseSensitive = caseSensitive;
|
||||
buildPattern(regex, completePattern);
|
||||
this.pattern = buildPattern(regex, completePattern);
|
||||
}
|
||||
|
||||
public void buildPattern(char[] regex, char[] completePattern) {
|
||||
|
||||
public Pattern buildPattern(char[] regex, char[] completePattern) {
|
||||
StringBuilder patternBuilder = new StringBuilder();
|
||||
String text = new String(regex);
|
||||
Matcher matcher = GLOB_PATTERN.matcher(text);
|
||||
int end = 0;
|
||||
|
||||
while (matcher.find()) {
|
||||
patternBuilder.append(quote(text, end, matcher.start()));
|
||||
String match = matcher.group();
|
||||
|
@ -68,16 +72,16 @@ class RegexPathElement extends PathElement {
|
|||
}
|
||||
else if ("*".equals(match)) {
|
||||
patternBuilder.append(".*");
|
||||
wildcardCount++;
|
||||
this.wildcardCount++;
|
||||
}
|
||||
else if (match.startsWith("{") && match.endsWith("}")) {
|
||||
int colonIdx = match.indexOf(':');
|
||||
if (colonIdx == -1) {
|
||||
patternBuilder.append(DEFAULT_VARIABLE_PATTERN);
|
||||
String variableName = matcher.group(1);
|
||||
if (variableNames.contains(variableName)) {
|
||||
throw new PatternParseException(pos, completePattern, PatternMessage.ILLEGAL_DOUBLE_CAPTURE,
|
||||
variableName);
|
||||
if (this.variableNames.contains(variableName)) {
|
||||
throw new PatternParseException(this.pos, completePattern,
|
||||
PatternParseException.PatternMessage.ILLEGAL_DOUBLE_CAPTURE, variableName);
|
||||
}
|
||||
this.variableNames.add(variableName);
|
||||
}
|
||||
|
@ -87,109 +91,112 @@ class RegexPathElement extends PathElement {
|
|||
patternBuilder.append(variablePattern);
|
||||
patternBuilder.append(')');
|
||||
String variableName = match.substring(1, colonIdx);
|
||||
if (variableNames.contains(variableName)) {
|
||||
throw new PatternParseException(pos, completePattern, PatternMessage.ILLEGAL_DOUBLE_CAPTURE,
|
||||
variableName);
|
||||
if (this.variableNames.contains(variableName)) {
|
||||
throw new PatternParseException(this.pos, completePattern,
|
||||
PatternParseException.PatternMessage.ILLEGAL_DOUBLE_CAPTURE, variableName);
|
||||
}
|
||||
this.variableNames.add(variableName);
|
||||
}
|
||||
}
|
||||
end = matcher.end();
|
||||
}
|
||||
|
||||
patternBuilder.append(quote(text, end, text.length()));
|
||||
if (caseSensitive) {
|
||||
pattern = java.util.regex.Pattern.compile(patternBuilder.toString());
|
||||
if (this.caseSensitive) {
|
||||
return Pattern.compile(patternBuilder.toString());
|
||||
}
|
||||
else {
|
||||
pattern = java.util.regex.Pattern.compile(patternBuilder.toString(),
|
||||
java.util.regex.Pattern.CASE_INSENSITIVE);
|
||||
return Pattern.compile(patternBuilder.toString(), Pattern.CASE_INSENSITIVE);
|
||||
}
|
||||
}
|
||||
|
||||
public List<String> getVariableNames() {
|
||||
return variableNames;
|
||||
return this.variableNames;
|
||||
}
|
||||
|
||||
private String quote(String s, int start, int end) {
|
||||
if (start == end) {
|
||||
return "";
|
||||
}
|
||||
return java.util.regex.Pattern.quote(s.substring(start, end));
|
||||
return Pattern.quote(s.substring(start, end));
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean matches(int candidateIndex, MatchingContext matchingContext) {
|
||||
int p = matchingContext.scanAhead(candidateIndex);
|
||||
Matcher m = pattern.matcher(new SubSequence(matchingContext.candidate, candidateIndex, p));
|
||||
boolean matches = m.matches();
|
||||
int pos = matchingContext.scanAhead(candidateIndex);
|
||||
Matcher matcher = this.pattern.matcher(new SubSequence(matchingContext.candidate, candidateIndex, pos));
|
||||
boolean matches = matcher.matches();
|
||||
|
||||
if (matches) {
|
||||
if (next == null) {
|
||||
if (this.next == null) {
|
||||
if (matchingContext.determineRemainingPath &&
|
||||
((this.variableNames.size() == 0) ? true : p > candidateIndex)) {
|
||||
matchingContext.remainingPathIndex = p;
|
||||
((this.variableNames.size() == 0) ? true : pos > candidateIndex)) {
|
||||
matchingContext.remainingPathIndex = pos;
|
||||
matches = true;
|
||||
}
|
||||
else {
|
||||
// No more pattern, is there more data?
|
||||
// If pattern is capturing variables there must be some actual data to bind to them
|
||||
matches = (p == matchingContext.candidateLength &&
|
||||
((this.variableNames.size() == 0) ? true : p > candidateIndex));
|
||||
matches = (pos == matchingContext.candidateLength &&
|
||||
((this.variableNames.size() == 0) ? true : pos > candidateIndex));
|
||||
if (!matches && matchingContext.isAllowOptionalTrailingSlash()) {
|
||||
matches = ((this.variableNames.size() == 0) ? true : p > candidateIndex) &&
|
||||
(p + 1) == matchingContext.candidateLength &&
|
||||
matchingContext.candidate[p] == separator;
|
||||
matches = ((this.variableNames.size() == 0) ? true : pos > candidateIndex) &&
|
||||
(pos + 1) == matchingContext.candidateLength &&
|
||||
matchingContext.candidate[pos] == separator;
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
if (matchingContext.isMatchStartMatching && p == matchingContext.candidateLength) {
|
||||
if (matchingContext.isMatchStartMatching && pos == matchingContext.candidateLength) {
|
||||
return true; // no more data but matches up to this point
|
||||
}
|
||||
matches = next.matches(p, matchingContext);
|
||||
matches = this.next.matches(pos, matchingContext);
|
||||
}
|
||||
}
|
||||
|
||||
if (matches && matchingContext.extractingVariables) {
|
||||
// Process captures
|
||||
if (this.variableNames.size() != m.groupCount()) { // SPR-8455
|
||||
if (this.variableNames.size() != matcher.groupCount()) { // SPR-8455
|
||||
throw new IllegalArgumentException("The number of capturing groups in the pattern segment "
|
||||
+ this.pattern + " does not match the number of URI template variables it defines, "
|
||||
+ "which can occur if capturing groups are used in a URI template regex. "
|
||||
+ "Use non-capturing groups instead.");
|
||||
}
|
||||
for (int i = 1; i <= m.groupCount(); i++) {
|
||||
for (int i = 1; i <= matcher.groupCount(); i++) {
|
||||
String name = this.variableNames.get(i - 1);
|
||||
String value = m.group(i);
|
||||
String value = matcher.group(i);
|
||||
matchingContext.set(name, value);
|
||||
}
|
||||
}
|
||||
return matches;
|
||||
}
|
||||
|
||||
public String toString() {
|
||||
return "Regex(" + new String(regex) + ")";
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getNormalizedLength() {
|
||||
int varsLength = 0;
|
||||
for (String variableName : variableNames) {
|
||||
for (String variableName : this.variableNames) {
|
||||
varsLength += variableName.length();
|
||||
}
|
||||
return regex.length - varsLength - variableNames.size();
|
||||
return (this.regex.length - varsLength - this.variableNames.size());
|
||||
}
|
||||
|
||||
public int getCaptureCount() {
|
||||
return variableNames.size();
|
||||
return this.variableNames.size();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getWildcardCount() {
|
||||
return wildcardCount;
|
||||
return this.wildcardCount;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getScore() {
|
||||
return getCaptureCount() * CAPTURE_VARIABLE_WEIGHT + getWildcardCount() * WILDCARD_WEIGHT;
|
||||
return (getCaptureCount() * CAPTURE_VARIABLE_WEIGHT + getWildcardCount() * WILDCARD_WEIGHT);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public String toString() {
|
||||
return "Regex(" + String.valueOf(this.regex) + ")";
|
||||
}
|
||||
|
||||
}
|
|
@ -14,9 +14,9 @@
|
|||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.web.util.patterns;
|
||||
package org.springframework.web.util.pattern;
|
||||
|
||||
import org.springframework.web.util.patterns.PathPattern.MatchingContext;
|
||||
import org.springframework.web.util.pattern.PathPattern.MatchingContext;
|
||||
|
||||
/**
|
||||
* A separator path element. In the pattern '/foo/bar' the two occurrences
|
||||
|
@ -32,6 +32,7 @@ class SeparatorPathElement extends PathElement {
|
|||
super(pos, separator);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Matching a separator is easy, basically the character at candidateIndex
|
||||
* must be the separator.
|
||||
|
@ -41,7 +42,7 @@ class SeparatorPathElement extends PathElement {
|
|||
boolean matched = false;
|
||||
if (candidateIndex < matchingContext.candidateLength &&
|
||||
matchingContext.candidate[candidateIndex] == separator) {
|
||||
if (next == null) {
|
||||
if (this.next == null) {
|
||||
if (matchingContext.determineRemainingPath) {
|
||||
matchingContext.remainingPathIndex = candidateIndex + 1;
|
||||
matched = true;
|
||||
|
@ -55,19 +56,20 @@ class SeparatorPathElement extends PathElement {
|
|||
if (matchingContext.isMatchStartMatching && candidateIndex == matchingContext.candidateLength) {
|
||||
return true; // no more data but matches up to this point
|
||||
}
|
||||
matched = next.matches(candidateIndex, matchingContext);
|
||||
matched = this.next.matches(candidateIndex, matchingContext);
|
||||
}
|
||||
}
|
||||
return matched;
|
||||
}
|
||||
|
||||
public String toString() {
|
||||
return "Separator(" + separator + ")";
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getNormalizedLength() {
|
||||
return 1;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public String toString() {
|
||||
return "Separator(" + this.separator + ")";
|
||||
}
|
||||
|
||||
}
|
|
@ -14,9 +14,9 @@
|
|||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.web.util.patterns;
|
||||
package org.springframework.web.util.pattern;
|
||||
|
||||
import org.springframework.web.util.patterns.PathPattern.MatchingContext;
|
||||
import org.springframework.web.util.pattern.PathPattern.MatchingContext;
|
||||
|
||||
/**
|
||||
* A literal path element that does includes the single character wildcard '?' one
|
||||
|
@ -27,15 +27,18 @@ import org.springframework.web.util.patterns.PathPattern.MatchingContext;
|
|||
*/
|
||||
class SingleCharWildcardedPathElement extends PathElement {
|
||||
|
||||
private char[] text;
|
||||
private final char[] text;
|
||||
|
||||
private int len;
|
||||
private final int len;
|
||||
|
||||
private int questionMarkCount;
|
||||
private final int questionMarkCount;
|
||||
|
||||
private boolean caseSensitive;
|
||||
private final boolean caseSensitive;
|
||||
|
||||
|
||||
public SingleCharWildcardedPathElement(
|
||||
int pos, char[] literalText, int questionMarkCount, boolean caseSensitive, char separator) {
|
||||
|
||||
public SingleCharWildcardedPathElement(int pos, char[] literalText, int questionMarkCount, boolean caseSensitive, char separator) {
|
||||
super(pos, separator);
|
||||
this.len = literalText.length;
|
||||
this.questionMarkCount = questionMarkCount;
|
||||
|
@ -51,15 +54,17 @@ class SingleCharWildcardedPathElement extends PathElement {
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public boolean matches(int candidateIndex, MatchingContext matchingContext) {
|
||||
if (matchingContext.candidateLength < (candidateIndex + len)) {
|
||||
return false; // There isn't enough data to match
|
||||
return false; // there isn't enough data to match
|
||||
}
|
||||
|
||||
char[] candidate = matchingContext.candidate;
|
||||
if (caseSensitive) {
|
||||
for (int i = 0; i < len; i++) {
|
||||
char t = text[i];
|
||||
if (this.caseSensitive) {
|
||||
for (int i = 0; i <this.len; i++) {
|
||||
char t = this.text[i];
|
||||
if (t != '?' && candidate[candidateIndex] != t) {
|
||||
return false;
|
||||
}
|
||||
|
@ -67,15 +72,16 @@ class SingleCharWildcardedPathElement extends PathElement {
|
|||
}
|
||||
}
|
||||
else {
|
||||
for (int i = 0; i < len; i++) {
|
||||
char t = text[i];
|
||||
for (int i = 0; i < this.len; i++) {
|
||||
char t = this.text[i];
|
||||
if (t != '?' && Character.toLowerCase(candidate[candidateIndex]) != t) {
|
||||
return false;
|
||||
}
|
||||
candidateIndex++;
|
||||
}
|
||||
}
|
||||
if (next == null) {
|
||||
|
||||
if (this.next == null) {
|
||||
if (matchingContext.determineRemainingPath && nextIfExistsIsSeparator(candidateIndex, matchingContext)) {
|
||||
matchingContext.remainingPathIndex = candidateIndex;
|
||||
return true;
|
||||
|
@ -85,27 +91,23 @@ class SingleCharWildcardedPathElement extends PathElement {
|
|||
return true;
|
||||
}
|
||||
else {
|
||||
return matchingContext.isAllowOptionalTrailingSlash() &&
|
||||
(candidateIndex + 1) == matchingContext.candidateLength &&
|
||||
matchingContext.candidate[candidateIndex] == separator;
|
||||
return (matchingContext.isAllowOptionalTrailingSlash() &&
|
||||
(candidateIndex + 1) == matchingContext.candidateLength &&
|
||||
matchingContext.candidate[candidateIndex] == separator);
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
if (matchingContext.isMatchStartMatching && candidateIndex == matchingContext.candidateLength) {
|
||||
return true; // no more data but matches up to this point
|
||||
return true; // no more data but matches up to this point
|
||||
}
|
||||
return next.matches(candidateIndex, matchingContext);
|
||||
return this.next.matches(candidateIndex, matchingContext);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getWildcardCount() {
|
||||
return questionMarkCount;
|
||||
}
|
||||
|
||||
public String toString() {
|
||||
return "SingleCharWildcarding(" + new String(text) + ")";
|
||||
return this.questionMarkCount;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -113,4 +115,9 @@ class SingleCharWildcardedPathElement extends PathElement {
|
|||
return len;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public String toString() {
|
||||
return "SingleCharWildcarding(" + String.valueOf(this.text) + ")";
|
||||
}
|
||||
|
||||
}
|
|
@ -14,21 +14,24 @@
|
|||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.web.util.patterns;
|
||||
package org.springframework.web.util.pattern;
|
||||
|
||||
/**
|
||||
* Used to represent a subsection of an array, useful when wanting to pass that subset of data
|
||||
* to another method (e.g. a java regex matcher) but not wanting to create a new string object to hold
|
||||
* all that data.
|
||||
* to another method (e.g. a java regex matcher) but not wanting to create a new string object
|
||||
* to hold all that data.
|
||||
*
|
||||
* @author Andy Clement
|
||||
* @since 5.0
|
||||
*/
|
||||
class SubSequence implements CharSequence {
|
||||
|
||||
private char[] chars;
|
||||
private final char[] chars;
|
||||
|
||||
private final int start;
|
||||
|
||||
private final int end;
|
||||
|
||||
private int start, end;
|
||||
|
||||
SubSequence(char[] chars, int start, int end) {
|
||||
this.chars = chars;
|
||||
|
@ -36,23 +39,26 @@ class SubSequence implements CharSequence {
|
|||
this.end = end;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public int length() {
|
||||
return end - start;
|
||||
return (this.end - this.start);
|
||||
}
|
||||
|
||||
@Override
|
||||
public char charAt(int index) {
|
||||
return chars[start + index];
|
||||
return this.chars[this.start + index];
|
||||
}
|
||||
|
||||
@Override
|
||||
public CharSequence subSequence(int start, int end) {
|
||||
return new SubSequence(chars, this.start + start, this.start + end);
|
||||
return new SubSequence(this.chars, this.start + start, this.start + end);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return new String(chars, start, end - start);
|
||||
return new String(this.chars, this.start, this.end - this.start);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
|
@ -14,9 +14,9 @@
|
|||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.web.util.patterns;
|
||||
package org.springframework.web.util.pattern;
|
||||
|
||||
import org.springframework.web.util.patterns.PathPattern.MatchingContext;
|
||||
import org.springframework.web.util.pattern.PathPattern.MatchingContext;
|
||||
|
||||
/**
|
||||
* A wildcard path element. In the pattern '/foo/*/goo' the * is
|
||||
|
@ -32,6 +32,7 @@ class WildcardPathElement extends PathElement {
|
|||
super(pos, separator);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Matching on a WildcardPathElement is quite straight forward. Scan the
|
||||
* candidate from the candidateIndex onwards for the next separator or the end of the
|
||||
|
@ -40,7 +41,7 @@ class WildcardPathElement extends PathElement {
|
|||
@Override
|
||||
public boolean matches(int candidateIndex, MatchingContext matchingContext) {
|
||||
int nextPos = matchingContext.scanAhead(candidateIndex);
|
||||
if (next == null) {
|
||||
if (this.next == null) {
|
||||
if (matchingContext.determineRemainingPath) {
|
||||
matchingContext.remainingPathIndex = nextPos;
|
||||
return true;
|
||||
|
@ -50,10 +51,10 @@ class WildcardPathElement extends PathElement {
|
|||
return true;
|
||||
}
|
||||
else {
|
||||
return matchingContext.isAllowOptionalTrailingSlash() && // if optional slash is on...
|
||||
nextPos > candidateIndex && // and there is at least one character to match the *...
|
||||
(nextPos + 1) == matchingContext.candidateLength && // and the nextPos is the end of the candidate...
|
||||
matchingContext.candidate[nextPos] == separator; // and the final character is a separator
|
||||
return (matchingContext.isAllowOptionalTrailingSlash() && // if optional slash is on...
|
||||
nextPos > candidateIndex && // and there is at least one character to match the *...
|
||||
(nextPos + 1) == matchingContext.candidateLength && // and the nextPos is the end of the candidate...
|
||||
matchingContext.candidate[nextPos] == separator); // and the final character is a separator
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -65,7 +66,7 @@ class WildcardPathElement extends PathElement {
|
|||
if (nextPos == candidateIndex) {
|
||||
return false;
|
||||
}
|
||||
return next.matches(nextPos, matchingContext);
|
||||
return this.next.matches(nextPos, matchingContext);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -74,10 +75,6 @@ class WildcardPathElement extends PathElement {
|
|||
return 1;
|
||||
}
|
||||
|
||||
public String toString() {
|
||||
return "Wildcard(*)";
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getWildcardCount() {
|
||||
return 1;
|
||||
|
@ -88,4 +85,9 @@ class WildcardPathElement extends PathElement {
|
|||
return WILDCARD_WEIGHT;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public String toString() {
|
||||
return "Wildcard(*)";
|
||||
}
|
||||
|
||||
}
|
|
@ -14,9 +14,7 @@
|
|||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.web.util.patterns;
|
||||
|
||||
import org.springframework.web.util.patterns.PathPattern.MatchingContext;
|
||||
package org.springframework.web.util.pattern;
|
||||
|
||||
/**
|
||||
* A path element representing wildcarding the rest of a path. In the pattern
|
||||
|
@ -31,8 +29,9 @@ class WildcardTheRestPathElement extends PathElement {
|
|||
super(pos, separator);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public boolean matches(int candidateIndex, MatchingContext matchingContext) {
|
||||
public boolean matches(int candidateIndex, PathPattern.MatchingContext matchingContext) {
|
||||
// If there is more data, it must start with the separator
|
||||
if (candidateIndex < matchingContext.candidateLength &&
|
||||
matchingContext.candidate[candidateIndex] != separator) {
|
||||
|
@ -44,10 +43,6 @@ class WildcardTheRestPathElement extends PathElement {
|
|||
return true;
|
||||
}
|
||||
|
||||
public String toString() {
|
||||
return "WildcardTheRest(" + separator + "**)";
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getNormalizedLength() {
|
||||
return 1;
|
||||
|
@ -58,4 +53,9 @@ class WildcardTheRestPathElement extends PathElement {
|
|||
return 1;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public String toString() {
|
||||
return "WildcardTheRest(" + this.separator + "**)";
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,4 @@
|
|||
/**
|
||||
* Spring's path pattern parser/matcher.
|
||||
*/
|
||||
package org.springframework.web.util.pattern;
|
|
@ -1,40 +0,0 @@
|
|||
/*
|
||||
* Copyright 2002-2017 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.web.util.patterns;
|
||||
|
||||
import java.util.Comparator;
|
||||
|
||||
/**
|
||||
* Basic PathPattern comparator.
|
||||
*
|
||||
* @author Andy Clement
|
||||
*/
|
||||
public class PathPatternComparator implements Comparator<PathPattern> {
|
||||
|
||||
@Override
|
||||
public int compare(PathPattern o1, PathPattern o2) {
|
||||
// Nulls get sorted to the end
|
||||
if (o1 == null) {
|
||||
return (o2 == null ? 0 : +1);
|
||||
}
|
||||
else if (o2 == null) {
|
||||
return -1;
|
||||
}
|
||||
return o1.compareTo(o2);
|
||||
}
|
||||
|
||||
}
|
|
@ -1,58 +0,0 @@
|
|||
/*
|
||||
* Copyright 2002-2017 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.web.util.patterns;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* A holder for the result of a {@link PathPattern#getPathRemaining(String)} call. Holds
|
||||
* information on the path left after the first part has successfully matched a pattern
|
||||
* and any variables bound in that first part that matched.
|
||||
*
|
||||
* @author Andy Clement
|
||||
* @since 5.0
|
||||
*/
|
||||
public class PathRemainingMatchInfo {
|
||||
|
||||
private String pathRemaining;
|
||||
|
||||
private Map<String, String> matchingVariables;
|
||||
|
||||
PathRemainingMatchInfo(String pathRemaining) {
|
||||
this.pathRemaining = pathRemaining;
|
||||
}
|
||||
|
||||
PathRemainingMatchInfo(String pathRemaining, Map<String, String> matchingVariables) {
|
||||
this.pathRemaining = pathRemaining;
|
||||
this.matchingVariables = matchingVariables;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the part of a path that was not matched by a pattern
|
||||
*/
|
||||
public String getPathRemaining() {
|
||||
return pathRemaining;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return variables that were bound in the part of the path that was successfully matched.
|
||||
* Will be an empty map if no variables were bound
|
||||
*/
|
||||
public Map<String, String> getMatchingVariables() {
|
||||
return matchingVariables;
|
||||
}
|
||||
}
|
|
@ -1,54 +0,0 @@
|
|||
/*
|
||||
* Copyright 2002-2017 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.web.util.patterns;
|
||||
|
||||
import java.util.Comparator;
|
||||
|
||||
/**
|
||||
* Similar to {@link PathPatternComparator} but this takes account of a specified path and
|
||||
* sorts anything that exactly matches it to be first.
|
||||
*
|
||||
* @author Andy Clement
|
||||
* @since 5.0
|
||||
*/
|
||||
public class PatternComparatorConsideringPath implements Comparator<PathPattern> {
|
||||
|
||||
private String path;
|
||||
|
||||
public PatternComparatorConsideringPath(String path) {
|
||||
this.path = path;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int compare(PathPattern o1, PathPattern o2) {
|
||||
// Nulls get sorted to the end
|
||||
if (o1 == null) {
|
||||
return (o2 == null ? 0 : +1);
|
||||
}
|
||||
else if (o2 == null) {
|
||||
return -1;
|
||||
}
|
||||
if (o1.getPatternString().equals(path)) {
|
||||
return (o2.getPatternString().equals(path)) ? 0 : -1;
|
||||
}
|
||||
else if (o2.getPatternString().equals(path)) {
|
||||
return +1;
|
||||
}
|
||||
return o1.compareTo(o2);
|
||||
}
|
||||
|
||||
}
|
|
@ -1,54 +0,0 @@
|
|||
/*
|
||||
* Copyright 2002-2017 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.web.util.patterns;
|
||||
|
||||
import java.text.MessageFormat;
|
||||
|
||||
/**
|
||||
* The messages that can be included in a {@link PatternParseException} when there is a parse failure.
|
||||
*
|
||||
* @author Andy Clement
|
||||
* @since 5.0
|
||||
*/
|
||||
public enum PatternMessage {
|
||||
|
||||
// @formatter:off
|
||||
MISSING_CLOSE_CAPTURE("Expected close capture character after variable name '}'"),
|
||||
MISSING_OPEN_CAPTURE("Missing preceeding open capture character before variable name'{'"),
|
||||
ILLEGAL_NESTED_CAPTURE("Not allowed to nest variable captures"),
|
||||
CANNOT_HAVE_ADJACENT_CAPTURES("Adjacent captures are not allowed"),
|
||||
ILLEGAL_CHARACTER_AT_START_OF_CAPTURE_DESCRIPTOR("Character ''{0}'' is not allowed at start of captured variable name"),
|
||||
ILLEGAL_CHARACTER_IN_CAPTURE_DESCRIPTOR("Character ''{0}'' is not allowed in a captured variable name"),
|
||||
NO_MORE_DATA_EXPECTED_AFTER_CAPTURE_THE_REST("No more pattern data allowed after '{*...}' pattern element"),
|
||||
BADLY_FORMED_CAPTURE_THE_REST("Expected form when capturing the rest of the path is simply '{*...}'"),
|
||||
MISSING_REGEX_CONSTRAINT("Missing regex constraint on capture"),
|
||||
ILLEGAL_DOUBLE_CAPTURE("Not allowed to capture ''{0}'' twice in the same pattern"),
|
||||
JDK_PATTERN_SYNTAX_EXCEPTION("Exception occurred in pattern compilation"),
|
||||
CAPTURE_ALL_IS_STANDALONE_CONSTRUCT("'{*...}' can only be preceeded by a path separator");
|
||||
// @formatter:on
|
||||
|
||||
private final String message;
|
||||
|
||||
private PatternMessage(String message) {
|
||||
this.message = message;
|
||||
}
|
||||
|
||||
public String formatMessage(Object... inserts) {
|
||||
return MessageFormat.format(this.message, inserts);
|
||||
}
|
||||
|
||||
}
|
|
@ -1,88 +0,0 @@
|
|||
/*
|
||||
* Copyright 2002-2017 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.web.util.patterns;
|
||||
|
||||
/**
|
||||
* Exception that is thrown when there is a problem with the pattern being parsed.
|
||||
*
|
||||
* @author Andy Clement
|
||||
* @since 5.0
|
||||
*/
|
||||
public class PatternParseException extends RuntimeException {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
private int pos;
|
||||
|
||||
private char[] patternText;
|
||||
|
||||
private final PatternMessage message;
|
||||
|
||||
private final Object[] inserts;
|
||||
|
||||
public PatternParseException(int pos, char[] patternText, PatternMessage message, Object... inserts) {
|
||||
super(message.formatMessage(inserts));
|
||||
this.pos = pos;
|
||||
this.patternText = patternText;
|
||||
this.message = message;
|
||||
this.inserts = inserts;
|
||||
}
|
||||
|
||||
public PatternParseException(Throwable cause, int pos, char[] patternText, PatternMessage message, Object... inserts) {
|
||||
super(message.formatMessage(inserts), cause);
|
||||
this.pos = pos;
|
||||
this.patternText = patternText;
|
||||
this.message = message;
|
||||
this.inserts = inserts;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return a formatted message with inserts applied
|
||||
*/
|
||||
@Override
|
||||
public String getMessage() {
|
||||
return this.message.formatMessage(this.inserts);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return a detailed message that includes the original pattern text with a pointer to the error position,
|
||||
* as well as the error message.
|
||||
*/
|
||||
public String toDetailedString() {
|
||||
StringBuilder buf = new StringBuilder();
|
||||
buf.append(patternText).append('\n');
|
||||
for (int i = 0; i < pos; i++) {
|
||||
buf.append(' ');
|
||||
}
|
||||
buf.append("^\n");
|
||||
buf.append(getMessage());
|
||||
return buf.toString();
|
||||
}
|
||||
|
||||
public Object[] getInserts() {
|
||||
return this.inserts;
|
||||
}
|
||||
|
||||
public int getPosition() {
|
||||
return pos;
|
||||
}
|
||||
|
||||
public PatternMessage getMessageType() {
|
||||
return message;
|
||||
}
|
||||
|
||||
}
|
|
@ -14,7 +14,7 @@
|
|||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.web.util.patterns;
|
||||
package org.springframework.web.util.pattern;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
|
@ -26,7 +26,11 @@ import java.util.Map;
|
|||
import org.junit.Rule;
|
||||
import org.junit.Test;
|
||||
import org.junit.rules.ExpectedException;
|
||||
|
||||
import org.springframework.util.AntPathMatcher;
|
||||
import org.springframework.web.util.pattern.ParsingPathMatcher;
|
||||
import org.springframework.web.util.pattern.PathPattern;
|
||||
import org.springframework.web.util.pattern.PathPatternParser;
|
||||
|
||||
import static org.hamcrest.CoreMatchers.*;
|
||||
import static org.junit.Assert.*;
|
||||
|
@ -38,14 +42,14 @@ import static org.junit.Assert.*;
|
|||
*/
|
||||
public class PathPatternMatcherTests {
|
||||
|
||||
private char separator = PathPatternParser.DEFAULT_SEPARATOR;
|
||||
|
||||
|
||||
@Test
|
||||
public void basicMatching() {
|
||||
checkMatches(null, null);
|
||||
checkMatches("", "");
|
||||
checkMatches("", null);
|
||||
checkNoMatch("/abc", null);
|
||||
checkMatches(null, "");
|
||||
checkNoMatch(null, "/abc");
|
||||
checkMatches("/", "/");
|
||||
checkNoMatch("/", "/a");
|
||||
checkMatches("f", "f");
|
||||
|
@ -267,7 +271,7 @@ public class PathPatternMatcherTests {
|
|||
assertNull(parse("/resource/{*foo}").getPathRemaining("/resourceX"));
|
||||
assertEquals("",parse("/resource/{*foo}").getPathRemaining("/resource").getPathRemaining());
|
||||
|
||||
PathRemainingMatchInfo pri = parse("/aaa/{bbb}/c?d/e*f/*/g").getPathRemaining("/aaa/b/ccd/ef/x/g/i");
|
||||
PathPattern.PathRemainingMatchInfo pri = parse("/aaa/{bbb}/c?d/e*f/*/g").getPathRemaining("/aaa/b/ccd/ef/x/g/i");
|
||||
assertEquals("/i",pri.getPathRemaining());
|
||||
assertEquals("b",pri.getMatchingVariables().get("bbb"));
|
||||
|
||||
|
@ -473,7 +477,7 @@ public class PathPatternMatcherTests {
|
|||
public void pathRemainingEnhancements_spr15419() {
|
||||
// It would be nice to partially match a path and get any bound variables in one step
|
||||
PathPattern pp = parse("/{this}/{one}/{here}");
|
||||
PathRemainingMatchInfo pri = pp.getPathRemaining("/foo/bar/goo/boo");
|
||||
PathPattern.PathRemainingMatchInfo pri = pp.getPathRemaining("/foo/bar/goo/boo");
|
||||
assertEquals("/boo",pri.getPathRemaining());
|
||||
assertEquals("foo",pri.getMatchingVariables().get("this"));
|
||||
assertEquals("bar",pri.getMatchingVariables().get("one"));
|
||||
|
@ -602,9 +606,6 @@ public class PathPatternMatcherTests {
|
|||
checkStartMatches("", "");
|
||||
checkStartMatches("", null);
|
||||
checkStartMatches("/abc", null);
|
||||
checkStartMatches(null, "");
|
||||
checkStartMatches(null, null);
|
||||
checkStartNoMatch(null, "/abc");
|
||||
}
|
||||
|
||||
@Test
|
||||
|
@ -691,7 +692,7 @@ public class PathPatternMatcherTests {
|
|||
@Test
|
||||
public void alternativeDelimiter() {
|
||||
try {
|
||||
separator = '.';
|
||||
this.separator = '.';
|
||||
|
||||
// test exact matching
|
||||
checkMatches("test", "test");
|
||||
|
@ -746,7 +747,7 @@ public class PathPatternMatcherTests {
|
|||
checkNoMatch(".*bla.test", "XXXbl.test");
|
||||
}
|
||||
finally {
|
||||
separator = PathPatternParser.DEFAULT_SEPARATOR;
|
||||
this.separator = PathPatternParser.DEFAULT_SEPARATOR;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -941,9 +942,9 @@ public class PathPatternMatcherTests {
|
|||
@Test
|
||||
public void combine() {
|
||||
TestPathCombiner pathMatcher = new TestPathCombiner();
|
||||
assertEquals("", pathMatcher.combine(null, null));
|
||||
assertEquals("/hotels", pathMatcher.combine("/hotels", null));
|
||||
assertEquals("/hotels", pathMatcher.combine(null, "/hotels"));
|
||||
assertEquals("", pathMatcher.combine("", ""));
|
||||
assertEquals("/hotels", pathMatcher.combine("/hotels", ""));
|
||||
assertEquals("/hotels", pathMatcher.combine("", "/hotels"));
|
||||
assertEquals("/hotels/booking", pathMatcher.combine("/hotels/*", "booking"));
|
||||
assertEquals("/hotels/booking", pathMatcher.combine("/hotels/*", "/booking"));
|
||||
assertEquals("/hotels/**/booking", pathMatcher.combine("/hotels/**", "booking"));
|
||||
|
@ -973,11 +974,6 @@ public class PathPatternMatcherTests {
|
|||
// SPR-10554
|
||||
assertEquals("/hotel", pathMatcher.combine("/", "/hotel")); // SPR-12975
|
||||
assertEquals("/hotel/booking", pathMatcher.combine("/hotel/", "/booking")); // SPR-12975
|
||||
assertEquals("", pathMatcher.combine(null, null));
|
||||
assertEquals("", pathMatcher.combine(null, ""));
|
||||
assertEquals("", pathMatcher.combine("", null));
|
||||
assertEquals("", pathMatcher.combine(null, null));
|
||||
assertEquals("", pathMatcher.combine("", ""));
|
||||
assertEquals("/hotel", pathMatcher.combine("", "/hotel"));
|
||||
assertEquals("/hotel", pathMatcher.combine("/hotel", null));
|
||||
assertEquals("/hotel", pathMatcher.combine("/hotel", ""));
|
||||
|
@ -993,8 +989,7 @@ public class PathPatternMatcherTests {
|
|||
|
||||
@Test
|
||||
public void patternComparator() {
|
||||
Comparator<PathPattern> comparator = new PatternComparatorConsideringPath(
|
||||
"/hotels/new");
|
||||
Comparator<PathPattern> comparator = new ParsingPathMatcher.PatternComparatorConsideringPath("/hotels/new");
|
||||
|
||||
assertEquals(0, comparator.compare(null, null));
|
||||
assertEquals(1, comparator.compare(null, parse("/hotels/new")));
|
||||
|
@ -1058,15 +1053,6 @@ public class PathPatternMatcherTests {
|
|||
assertEquals(1, comparator.compare(parse("*/**"), parse("*")));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void pathPatternComparator() {
|
||||
PathPatternComparator ppc = new PathPatternComparator();
|
||||
assertEquals(0, ppc.compare(null, null));
|
||||
assertEquals(1, ppc.compare(null, parse("")));
|
||||
assertEquals(-1, ppc.compare(parse(""), null));
|
||||
assertEquals(0, ppc.compare(parse(""), parse("")));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void patternCompareTo() {
|
||||
PathPatternParser p = new PathPatternParser();
|
||||
|
@ -1076,8 +1062,8 @@ public class PathPatternMatcherTests {
|
|||
|
||||
@Test
|
||||
public void patternComparatorSort() {
|
||||
Comparator<PathPattern> comparator = new PatternComparatorConsideringPath(
|
||||
"/hotels/new");
|
||||
Comparator<PathPattern> comparator = new ParsingPathMatcher.PatternComparatorConsideringPath("/hotels/new");
|
||||
|
||||
List<PathPattern> paths = new ArrayList<>(3);
|
||||
PathPatternParser pp = new PathPatternParser();
|
||||
paths.add(null);
|
||||
|
@ -1162,7 +1148,7 @@ public class PathPatternMatcherTests {
|
|||
// assertEquals("/hotels/{hotel}", paths.get(1).toPatternString());
|
||||
// paths.clear();
|
||||
|
||||
comparator = new PatternComparatorConsideringPath("/web/endUser/action/login.html");
|
||||
comparator = new ParsingPathMatcher.PatternComparatorConsideringPath("/web/endUser/action/login.html");
|
||||
paths.add(pp.parse("/*/login.*"));
|
||||
paths.add(pp.parse("/*/endUser/action/login.*"));
|
||||
Collections.sort(paths, comparator);
|
||||
|
@ -1181,15 +1167,6 @@ public class PathPatternMatcherTests {
|
|||
assertTrue(p.matches("/group/Sales/members"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void patternmessage() {
|
||||
PatternMessage[] values = PatternMessage.values();
|
||||
assertNotNull(values);
|
||||
for (PatternMessage pm : values) {
|
||||
String name = pm.toString();
|
||||
assertEquals(pm.ordinal(), PatternMessage.valueOf(name).ordinal());
|
||||
}
|
||||
}
|
||||
|
||||
private PathPattern parse(String path) {
|
||||
PathPatternParser pp = new PathPatternParser();
|
||||
|
@ -1197,11 +1174,8 @@ public class PathPatternMatcherTests {
|
|||
return pp.parse(path);
|
||||
}
|
||||
|
||||
private char separator = PathPatternParser.DEFAULT_SEPARATOR;
|
||||
|
||||
private void checkMatches(String uriTemplate, String path) {
|
||||
PathPatternParser parser = (separator == PathPatternParser.DEFAULT_SEPARATOR
|
||||
? new PathPatternParser() : new PathPatternParser(separator));
|
||||
PathPatternParser parser = new PathPatternParser(this.separator);
|
||||
parser.setMatchOptionalTrailingSlash(true);
|
||||
PathPattern p = parser.parse(uriTemplate);
|
||||
assertTrue(p.matches(path));
|
||||
|
@ -1259,6 +1233,7 @@ public class PathPatternMatcherTests {
|
|||
assertEquals(expected, s);
|
||||
}
|
||||
|
||||
|
||||
static class TestPathCombiner {
|
||||
|
||||
PathPatternParser pp = new PathPatternParser();
|
|
@ -14,7 +14,7 @@
|
|||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.web.util.patterns;
|
||||
package org.springframework.web.util.pattern;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
|
@ -23,6 +23,20 @@ import java.util.Map;
|
|||
|
||||
import org.junit.Test;
|
||||
|
||||
import org.springframework.web.util.pattern.CaptureTheRestPathElement;
|
||||
import org.springframework.web.util.pattern.CaptureVariablePathElement;
|
||||
import org.springframework.web.util.pattern.LiteralPathElement;
|
||||
import org.springframework.web.util.pattern.PathElement;
|
||||
import org.springframework.web.util.pattern.PathPattern;
|
||||
import org.springframework.web.util.pattern.PathPatternParser;
|
||||
import org.springframework.web.util.pattern.PatternParseException;
|
||||
import org.springframework.web.util.pattern.PatternParseException.PatternMessage;
|
||||
import org.springframework.web.util.pattern.RegexPathElement;
|
||||
import org.springframework.web.util.pattern.SeparatorPathElement;
|
||||
import org.springframework.web.util.pattern.SingleCharWildcardedPathElement;
|
||||
import org.springframework.web.util.pattern.WildcardPathElement;
|
||||
import org.springframework.web.util.pattern.WildcardTheRestPathElement;
|
||||
|
||||
import static org.junit.Assert.*;
|
||||
|
||||
/**
|
||||
|
@ -32,7 +46,8 @@ import static org.junit.Assert.*;
|
|||
*/
|
||||
public class PathPatternParserTests {
|
||||
|
||||
private PathPattern p;
|
||||
private PathPattern pathPattern;
|
||||
|
||||
|
||||
@Test
|
||||
public void basicPatterns() {
|
||||
|
@ -46,18 +61,18 @@ public class PathPatternParserTests {
|
|||
|
||||
@Test
|
||||
public void singleCharWildcardPatterns() {
|
||||
p = checkStructure("?");
|
||||
assertPathElements(p, SingleCharWildcardedPathElement.class);
|
||||
pathPattern = checkStructure("?");
|
||||
assertPathElements(pathPattern, SingleCharWildcardedPathElement.class);
|
||||
checkStructure("/?/");
|
||||
checkStructure("/?abc?/");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void multiwildcardPattern() {
|
||||
p = checkStructure("/**");
|
||||
assertPathElements(p, WildcardTheRestPathElement.class);
|
||||
p = checkStructure("/**acb"); // this is not double wildcard use, it is / then **acb (an odd, unnecessary use of double *)
|
||||
assertPathElements(p, SeparatorPathElement.class, RegexPathElement.class);
|
||||
pathPattern = checkStructure("/**");
|
||||
assertPathElements(pathPattern, WildcardTheRestPathElement.class);
|
||||
pathPattern = checkStructure("/**acb"); // this is not double wildcard use, it is / then **acb (an odd, unnecessary use of double *)
|
||||
assertPathElements(pathPattern, SeparatorPathElement.class, RegexPathElement.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
|
@ -75,10 +90,10 @@ public class PathPatternParserTests {
|
|||
@Test
|
||||
public void captureTheRestPatterns() {
|
||||
checkError("/{*foobar}x{abc}", 10, PatternMessage.NO_MORE_DATA_EXPECTED_AFTER_CAPTURE_THE_REST);
|
||||
p = checkStructure("{*foobar}");
|
||||
assertPathElements(p, CaptureTheRestPathElement.class);
|
||||
p = checkStructure("/{*foobar}");
|
||||
assertPathElements(p, CaptureTheRestPathElement.class);
|
||||
pathPattern = checkStructure("{*foobar}");
|
||||
assertPathElements(pathPattern, CaptureTheRestPathElement.class);
|
||||
pathPattern = checkStructure("/{*foobar}");
|
||||
assertPathElements(pathPattern, CaptureTheRestPathElement.class);
|
||||
checkError("/{*foobar}/", 10, PatternMessage.NO_MORE_DATA_EXPECTED_AFTER_CAPTURE_THE_REST);
|
||||
checkError("/{*foobar}abc", 10, PatternMessage.NO_MORE_DATA_EXPECTED_AFTER_CAPTURE_THE_REST);
|
||||
checkError("/{*f%obar}", 4, PatternMessage.ILLEGAL_CHARACTER_IN_CAPTURE_DESCRIPTOR);
|
||||
|
@ -118,62 +133,62 @@ public class PathPatternParserTests {
|
|||
public void regexPathElementPatterns() {
|
||||
checkError("/{var:[^/]*}", 8, PatternMessage.MISSING_CLOSE_CAPTURE);
|
||||
checkError("/{var:abc", 8, PatternMessage.MISSING_CLOSE_CAPTURE);
|
||||
checkError("/{var:a{{1,2}}}", 6, PatternMessage.JDK_PATTERN_SYNTAX_EXCEPTION);
|
||||
checkError("/{var:a{{1,2}}}", 6, PatternMessage.REGEX_PATTERN_SYNTAX_EXCEPTION);
|
||||
|
||||
p = checkStructure("/{var:\\\\}");
|
||||
assertEquals(CaptureVariablePathElement.class.getName(), p.getHeadSection().next.getClass().getName());
|
||||
assertTrue(p.matches("/\\"));
|
||||
pathPattern = checkStructure("/{var:\\\\}");
|
||||
assertEquals(CaptureVariablePathElement.class.getName(), pathPattern.getHeadSection().next.getClass().getName());
|
||||
assertTrue(pathPattern.matches("/\\"));
|
||||
|
||||
p = checkStructure("/{var:\\/}");
|
||||
assertEquals(CaptureVariablePathElement.class.getName(), p.getHeadSection().next.getClass().getName());
|
||||
assertFalse(p.matches("/aaa"));
|
||||
pathPattern = checkStructure("/{var:\\/}");
|
||||
assertEquals(CaptureVariablePathElement.class.getName(), pathPattern.getHeadSection().next.getClass().getName());
|
||||
assertFalse(pathPattern.matches("/aaa"));
|
||||
|
||||
p = checkStructure("/{var:a{1,2}}", 1);
|
||||
assertEquals(CaptureVariablePathElement.class.getName(), p.getHeadSection().next.getClass().getName());
|
||||
pathPattern = checkStructure("/{var:a{1,2}}", 1);
|
||||
assertEquals(CaptureVariablePathElement.class.getName(), pathPattern.getHeadSection().next.getClass().getName());
|
||||
|
||||
p = checkStructure("/{var:[^\\/]*}", 1);
|
||||
assertEquals(CaptureVariablePathElement.class.getName(), p.getHeadSection().next.getClass().getName());
|
||||
Map<String, String> result = p.matchAndExtract("/foo");
|
||||
pathPattern = checkStructure("/{var:[^\\/]*}", 1);
|
||||
assertEquals(CaptureVariablePathElement.class.getName(), pathPattern.getHeadSection().next.getClass().getName());
|
||||
Map<String, String> result = pathPattern.matchAndExtract("/foo");
|
||||
assertEquals("foo", result.get("var"));
|
||||
|
||||
p = checkStructure("/{var:\\[*}", 1);
|
||||
assertEquals(CaptureVariablePathElement.class.getName(), p.getHeadSection().next.getClass().getName());
|
||||
result = p.matchAndExtract("/[[[");
|
||||
pathPattern = checkStructure("/{var:\\[*}", 1);
|
||||
assertEquals(CaptureVariablePathElement.class.getName(), pathPattern.getHeadSection().next.getClass().getName());
|
||||
result = pathPattern.matchAndExtract("/[[[");
|
||||
assertEquals("[[[", result.get("var"));
|
||||
|
||||
p = checkStructure("/{var:[\\{]*}", 1);
|
||||
assertEquals(CaptureVariablePathElement.class.getName(), p.getHeadSection().next.getClass().getName());
|
||||
result = p.matchAndExtract("/{{{");
|
||||
pathPattern = checkStructure("/{var:[\\{]*}", 1);
|
||||
assertEquals(CaptureVariablePathElement.class.getName(), pathPattern.getHeadSection().next.getClass().getName());
|
||||
result = pathPattern.matchAndExtract("/{{{");
|
||||
assertEquals("{{{", result.get("var"));
|
||||
|
||||
p = checkStructure("/{var:[\\}]*}", 1);
|
||||
assertEquals(CaptureVariablePathElement.class.getName(), p.getHeadSection().next.getClass().getName());
|
||||
result = p.matchAndExtract("/}}}");
|
||||
pathPattern = checkStructure("/{var:[\\}]*}", 1);
|
||||
assertEquals(CaptureVariablePathElement.class.getName(), pathPattern.getHeadSection().next.getClass().getName());
|
||||
result = pathPattern.matchAndExtract("/}}}");
|
||||
assertEquals("}}}", result.get("var"));
|
||||
|
||||
p = checkStructure("*");
|
||||
assertEquals(WildcardPathElement.class.getName(), p.getHeadSection().getClass().getName());
|
||||
pathPattern = checkStructure("*");
|
||||
assertEquals(WildcardPathElement.class.getName(), pathPattern.getHeadSection().getClass().getName());
|
||||
checkStructure("/*");
|
||||
checkStructure("/*/");
|
||||
checkStructure("*/");
|
||||
checkStructure("/*/");
|
||||
p = checkStructure("/*a*/");
|
||||
assertEquals(RegexPathElement.class.getName(), p.getHeadSection().next.getClass().getName());
|
||||
p = checkStructure("*/");
|
||||
assertEquals(WildcardPathElement.class.getName(), p.getHeadSection().getClass().getName());
|
||||
pathPattern = checkStructure("/*a*/");
|
||||
assertEquals(RegexPathElement.class.getName(), pathPattern.getHeadSection().next.getClass().getName());
|
||||
pathPattern = checkStructure("*/");
|
||||
assertEquals(WildcardPathElement.class.getName(), pathPattern.getHeadSection().getClass().getName());
|
||||
checkError("{foo}_{foo}", 0, PatternMessage.ILLEGAL_DOUBLE_CAPTURE, "foo");
|
||||
checkError("/{bar}/{bar}", 7, PatternMessage.ILLEGAL_DOUBLE_CAPTURE, "bar");
|
||||
checkError("/{bar}/{bar}_{foo}", 7, PatternMessage.ILLEGAL_DOUBLE_CAPTURE, "bar");
|
||||
|
||||
p = checkStructure("{symbolicName:[\\p{L}\\.]+}-sources-{version:[\\p{N}\\.]+}.jar");
|
||||
assertEquals(RegexPathElement.class.getName(), p.getHeadSection().getClass().getName());
|
||||
pathPattern = checkStructure("{symbolicName:[\\p{L}\\.]+}-sources-{version:[\\p{N}\\.]+}.jar");
|
||||
assertEquals(RegexPathElement.class.getName(), pathPattern.getHeadSection().getClass().getName());
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
public void completeCapturingPatterns() {
|
||||
p = checkStructure("{foo}");
|
||||
assertEquals(CaptureVariablePathElement.class.getName(), p.getHeadSection().getClass().getName());
|
||||
pathPattern = checkStructure("{foo}");
|
||||
assertEquals(CaptureVariablePathElement.class.getName(), pathPattern.getHeadSection().getClass().getName());
|
||||
checkStructure("/{foo}");
|
||||
checkStructure("/{f}/");
|
||||
checkStructure("/{foo}/{bar}/{wibble}");
|
||||
|
@ -181,17 +196,17 @@ public class PathPatternParserTests {
|
|||
|
||||
@Test
|
||||
public void completeCaptureWithConstraints() {
|
||||
p = checkStructure("{foo:...}");
|
||||
assertPathElements(p, CaptureVariablePathElement.class);
|
||||
p = checkStructure("{foo:[0-9]*}");
|
||||
assertPathElements(p, CaptureVariablePathElement.class);
|
||||
pathPattern = checkStructure("{foo:...}");
|
||||
assertPathElements(pathPattern, CaptureVariablePathElement.class);
|
||||
pathPattern = checkStructure("{foo:[0-9]*}");
|
||||
assertPathElements(pathPattern, CaptureVariablePathElement.class);
|
||||
checkError("{foo:}", 5, PatternMessage.MISSING_REGEX_CONSTRAINT);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void partialCapturingPatterns() {
|
||||
p = checkStructure("{foo}abc");
|
||||
assertEquals(RegexPathElement.class.getName(), p.getHeadSection().getClass().getName());
|
||||
pathPattern = checkStructure("{foo}abc");
|
||||
assertEquals(RegexPathElement.class.getName(), pathPattern.getHeadSection().getClass().getName());
|
||||
checkStructure("abc{foo}");
|
||||
checkStructure("/abc{foo}");
|
||||
checkStructure("{foo}def/");
|
||||
|
@ -293,19 +308,19 @@ public class PathPatternParserTests {
|
|||
|
||||
@Test
|
||||
public void multipleSeparatorPatterns() {
|
||||
p = checkStructure("///aaa");
|
||||
assertEquals(6, p.getNormalizedLength());
|
||||
assertPathElements(p, SeparatorPathElement.class, SeparatorPathElement.class,
|
||||
pathPattern = checkStructure("///aaa");
|
||||
assertEquals(6, pathPattern.getNormalizedLength());
|
||||
assertPathElements(pathPattern, SeparatorPathElement.class, SeparatorPathElement.class,
|
||||
SeparatorPathElement.class, LiteralPathElement.class);
|
||||
p = checkStructure("///aaa////aaa/b");
|
||||
assertEquals(15, p.getNormalizedLength());
|
||||
assertPathElements(p, SeparatorPathElement.class, SeparatorPathElement.class,
|
||||
pathPattern = checkStructure("///aaa////aaa/b");
|
||||
assertEquals(15, pathPattern.getNormalizedLength());
|
||||
assertPathElements(pathPattern, SeparatorPathElement.class, SeparatorPathElement.class,
|
||||
SeparatorPathElement.class, LiteralPathElement.class, SeparatorPathElement.class,
|
||||
SeparatorPathElement.class, SeparatorPathElement.class, SeparatorPathElement.class,
|
||||
LiteralPathElement.class, SeparatorPathElement.class, LiteralPathElement.class);
|
||||
p = checkStructure("/////**");
|
||||
assertEquals(5, p.getNormalizedLength());
|
||||
assertPathElements(p, SeparatorPathElement.class, SeparatorPathElement.class,
|
||||
pathPattern = checkStructure("/////**");
|
||||
assertEquals(5, pathPattern.getNormalizedLength());
|
||||
assertPathElements(pathPattern, SeparatorPathElement.class, SeparatorPathElement.class,
|
||||
SeparatorPathElement.class, SeparatorPathElement.class, WildcardTheRestPathElement.class);
|
||||
}
|
||||
|
||||
|
@ -344,7 +359,7 @@ public class PathPatternParserTests {
|
|||
patterns.add(p2);
|
||||
patterns.add(p3);
|
||||
patterns.add(p1);
|
||||
Collections.sort(patterns, new PathPatternComparator());
|
||||
Collections.sort(patterns);
|
||||
assertEquals(p1, patterns.get(0));
|
||||
|
||||
// Based purely on length
|
||||
|
@ -356,7 +371,7 @@ public class PathPatternParserTests {
|
|||
patterns.add(p2);
|
||||
patterns.add(p3);
|
||||
patterns.add(p1);
|
||||
Collections.sort(patterns, new PathPatternComparator());
|
||||
Collections.sort(patterns);
|
||||
assertEquals(p3, patterns.get(0));
|
||||
|
||||
// Based purely on 'wildness'
|
||||
|
@ -368,7 +383,7 @@ public class PathPatternParserTests {
|
|||
patterns.add(p2);
|
||||
patterns.add(p3);
|
||||
patterns.add(p1);
|
||||
Collections.sort(patterns, new PathPatternComparator());
|
||||
Collections.sort(patterns);
|
||||
assertEquals(p1, patterns.get(0));
|
||||
|
||||
// Based purely on catchAll
|
||||
|
@ -389,19 +404,11 @@ public class PathPatternParserTests {
|
|||
patterns.add(p2);
|
||||
patterns.add(p3);
|
||||
patterns.add(p1);
|
||||
Collections.sort(patterns, new PathPatternComparator());
|
||||
Collections.sort(patterns);
|
||||
assertEquals(p3, patterns.get(0));
|
||||
assertEquals(p2, patterns.get(1));
|
||||
|
||||
patterns = new ArrayList<>();
|
||||
patterns.add(parse("/abc"));
|
||||
patterns.add(null);
|
||||
patterns.add(parse("/def"));
|
||||
Collections.sort(patterns, new PathPatternComparator());
|
||||
assertNull(patterns.get(2));
|
||||
}
|
||||
|
||||
// ---
|
||||
|
||||
private PathPattern parse(String pattern) {
|
||||
PathPatternParser patternParser = new PathPatternParser();
|
||||
|
@ -428,19 +435,18 @@ public class PathPatternParserTests {
|
|||
}
|
||||
|
||||
private PathPattern checkStructure(String pattern, int expectedSeparatorCount) {
|
||||
p = parse(pattern);
|
||||
assertEquals(pattern, p.getPatternString());
|
||||
// assertEquals(expectedSeparatorCount,p.getSeparatorCount());
|
||||
return p;
|
||||
pathPattern = parse(pattern);
|
||||
assertEquals(pattern, pathPattern.getPatternString());
|
||||
// assertEquals(expectedSeparatorCount, pathPattern.getSeparatorCount());
|
||||
return pathPattern;
|
||||
}
|
||||
|
||||
private void checkError(String pattern, int expectedPos, PatternMessage expectedMessage, String... expectedInserts) {
|
||||
try {
|
||||
p = parse(pattern);
|
||||
pathPattern = parse(pattern);
|
||||
fail("Expected to fail");
|
||||
}
|
||||
catch (PatternParseException ppe) {
|
||||
// System.out.println(ppe.toDetailedString());
|
||||
assertEquals(ppe.toDetailedString(), expectedPos, ppe.getPosition());
|
||||
assertEquals(ppe.toDetailedString(), expectedMessage, ppe.getMessageType());
|
||||
if (expectedInserts.length != 0) {
|
||||
|
@ -455,11 +461,12 @@ public class PathPatternParserTests {
|
|||
@SafeVarargs
|
||||
private final void assertPathElements(PathPattern p, Class<? extends PathElement>... sectionClasses) {
|
||||
PathElement head = p.getHeadSection();
|
||||
for (int i = 0; i < sectionClasses.length; i++) {
|
||||
for (Class<? extends PathElement> sectionClass : sectionClasses) {
|
||||
if (head == null) {
|
||||
fail("Ran out of data in parsed pattern. Pattern is: " + p.toChainString());
|
||||
}
|
||||
assertEquals("Not expected section type. Pattern is: " + p.toChainString(), sectionClasses[i].getSimpleName(), head.getClass().getSimpleName());
|
||||
assertEquals("Not expected section type. Pattern is: " + p.toChainString(),
|
||||
sectionClass.getSimpleName(), head.getClass().getSimpleName());
|
||||
head = head.next;
|
||||
}
|
||||
}
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* Copyright 2002-2016 the original author or authors.
|
||||
* Copyright 2002-2017 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
|
@ -82,7 +82,7 @@ public class PathMatchConfigurer {
|
|||
|
||||
/**
|
||||
* Set the PathMatcher for matching URL paths against registered URL patterns.
|
||||
* <p>Default is {@link org.springframework.web.util.ParsingPathMatcher ParsingPathMatcher}.
|
||||
* <p>The default is a {@link org.springframework.web.util.pattern.ParsingPathMatcher}.
|
||||
*/
|
||||
public PathMatchConfigurer setPathMatcher(PathMatcher pathMatcher) {
|
||||
this.pathMatcher = pathMatcher;
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* Copyright 2002-2016 the original author or authors.
|
||||
* Copyright 2002-2017 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
|
@ -88,10 +88,9 @@ public class ResourceHandlerRegistry {
|
|||
* Add a resource handler for serving static resources based on the specified
|
||||
* URL path patterns. The handler will be invoked for every incoming request
|
||||
* that matches to one of the specified path patterns.
|
||||
*
|
||||
* <p>Patterns like {@code "/static/**"} or {@code "/css/{filename:\\w+\\.css}"}
|
||||
* are allowed. See {@link org.springframework.web.util.ParsingPathMatcher} for more
|
||||
* details on the syntax.
|
||||
* are allowed. See {@link org.springframework.web.util.pattern.ParsingPathMatcher}
|
||||
* for more details on the syntax.
|
||||
* @return A {@link ResourceHandlerRegistration} to use to further
|
||||
* configure the registered resource handler
|
||||
*/
|
||||
|
|
|
@ -28,8 +28,8 @@ import org.springframework.core.io.Resource;
|
|||
import org.springframework.core.io.UrlResource;
|
||||
import org.springframework.util.ResourceUtils;
|
||||
import org.springframework.util.StringUtils;
|
||||
import org.springframework.web.util.patterns.PathPattern;
|
||||
import org.springframework.web.util.patterns.PathPatternParser;
|
||||
import org.springframework.web.util.pattern.PathPattern;
|
||||
import org.springframework.web.util.pattern.PathPatternParser;
|
||||
|
||||
/**
|
||||
* Lookup function used by {@link RouterFunctions#resources(String, Resource)}.
|
||||
|
|
|
@ -41,8 +41,8 @@ import org.springframework.util.Assert;
|
|||
import org.springframework.web.reactive.function.BodyExtractor;
|
||||
import org.springframework.web.server.WebSession;
|
||||
import org.springframework.web.util.UriUtils;
|
||||
import org.springframework.web.util.patterns.PathPattern;
|
||||
import org.springframework.web.util.patterns.PathPatternParser;
|
||||
import org.springframework.web.util.pattern.PathPattern;
|
||||
import org.springframework.web.util.pattern.PathPatternParser;
|
||||
|
||||
/**
|
||||
* Implementations of {@link RequestPredicate} that implement various useful
|
||||
|
|
|
@ -34,7 +34,7 @@ import org.springframework.web.reactive.HandlerMapping;
|
|||
import org.springframework.web.server.ServerWebExchange;
|
||||
import org.springframework.web.server.WebHandler;
|
||||
import org.springframework.web.server.support.HttpRequestPathHelper;
|
||||
import org.springframework.web.util.ParsingPathMatcher;
|
||||
import org.springframework.web.util.pattern.ParsingPathMatcher;
|
||||
|
||||
/**
|
||||
* Abstract base class for {@link org.springframework.web.reactive.HandlerMapping}
|
||||
|
@ -103,8 +103,8 @@ public abstract class AbstractHandlerMapping extends ApplicationObjectSupport im
|
|||
|
||||
/**
|
||||
* Set the PathMatcher implementation to use for matching URL paths
|
||||
* against registered URL patterns. Default is ParsingPathMatcher.
|
||||
* @see org.springframework.web.util.ParsingPathMatcher
|
||||
* against registered URL patterns.
|
||||
* <p>The default is a {@link ParsingPathMatcher}.
|
||||
*/
|
||||
public void setPathMatcher(PathMatcher pathMatcher) {
|
||||
Assert.notNull(pathMatcher, "PathMatcher must not be null");
|
||||
|
|
|
@ -37,7 +37,7 @@ import org.springframework.web.server.ServerWebExchange;
|
|||
* various Ant-style pattern matches, e.g. a registered "/t*" pattern matches
|
||||
* both "/test" and "/team", "/test/*" matches all paths under "/test",
|
||||
* "/test/**" matches all paths below "/test". For details, see the
|
||||
* {@link org.springframework.web.util.ParsingPathMatcher ParsingPathMatcher} javadoc.
|
||||
* {@link org.springframework.web.util.pattern.ParsingPathMatcher} javadoc.
|
||||
*
|
||||
* <p>Will search all path patterns to find the most exact match for the
|
||||
* current request path. The most exact match is defined as the longest
|
||||
|
@ -119,18 +119,15 @@ public abstract class AbstractUrlHandlerMapping extends AbstractHandlerMapping {
|
|||
|
||||
/**
|
||||
* Look up a handler instance for the given URL path.
|
||||
*
|
||||
* <p>Supports direct matches, e.g. a registered "/test" matches "/test",
|
||||
* and various Ant-style pattern matches, e.g. a registered "/t*" matches
|
||||
* both "/test" and "/team". For details, see the AntPathMatcher class.
|
||||
*
|
||||
* <p>Looks for the most exact pattern, where most exact is defined as
|
||||
* the longest path pattern.
|
||||
*
|
||||
* @param urlPath URL the bean is mapped to
|
||||
* @param exchange the current exchange
|
||||
* @return the associated handler instance, or {@code null} if not found
|
||||
* @see org.springframework.web.util.ParsingPathMatcher
|
||||
* @see org.springframework.web.util.pattern.ParsingPathMatcher
|
||||
*/
|
||||
protected Object lookupHandler(String urlPath, ServerWebExchange exchange) throws Exception {
|
||||
// Direct match?
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* Copyright 2002-2016 the original author or authors.
|
||||
* Copyright 2002-2017 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
|
@ -46,7 +46,7 @@ import org.springframework.util.CollectionUtils;
|
|||
* various Ant-style pattern matches, e.g. a registered "/t*" pattern matches
|
||||
* both "/test" and "/team", "/test/*" matches all paths under "/test",
|
||||
* "/test/**" matches all paths below "/test". For details, see the
|
||||
* {@link org.springframework.web.util.ParsingPathMatcher ParsingPathMatcher} javadoc.
|
||||
* {@link org.springframework.web.util.pattern.ParsingPathMatcher} javadoc.
|
||||
*
|
||||
* @author Rossen Stoyanchev
|
||||
* @since 5.0
|
||||
|
@ -59,8 +59,8 @@ public class SimpleUrlHandlerMapping extends AbstractUrlHandlerMapping {
|
|||
/**
|
||||
* Map URL paths to handler bean names.
|
||||
* This is the typical way of configuring this HandlerMapping.
|
||||
* <p>Supports direct URL matches and Ant-style pattern matches. For syntax
|
||||
* details, see the {@link org.springframework.web.util.ParsingPathMatcher} javadoc.
|
||||
* <p>Supports direct URL matches and Ant-style pattern matches. For syntax details,
|
||||
* see the {@link org.springframework.web.util.pattern.ParsingPathMatcher} javadoc.
|
||||
* @param mappings properties with URLs as keys and bean names as values
|
||||
* @see #setUrlMap
|
||||
*/
|
||||
|
@ -71,8 +71,8 @@ public class SimpleUrlHandlerMapping extends AbstractUrlHandlerMapping {
|
|||
/**
|
||||
* Set a Map with URL paths as keys and handler beans (or handler bean names)
|
||||
* as values. Convenient for population with bean references.
|
||||
* <p>Supports direct URL matches and Ant-style pattern matches. For syntax
|
||||
* details, see the {@link org.springframework.web.util.ParsingPathMatcher} javadoc.
|
||||
* <p>Supports direct URL matches and Ant-style pattern matches. For syntax details,
|
||||
* see the {@link org.springframework.web.util.pattern.ParsingPathMatcher} javadoc.
|
||||
* @param urlMap map with URLs as keys and beans as values
|
||||
* @see #setMappings
|
||||
*/
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* Copyright 2002-2016 the original author or authors.
|
||||
* Copyright 2002-2017 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
|
@ -37,7 +37,7 @@ import org.springframework.util.PathMatcher;
|
|||
import org.springframework.web.reactive.handler.SimpleUrlHandlerMapping;
|
||||
import org.springframework.web.server.ServerWebExchange;
|
||||
import org.springframework.web.server.support.HttpRequestPathHelper;
|
||||
import org.springframework.web.util.ParsingPathMatcher;
|
||||
import org.springframework.web.util.pattern.ParsingPathMatcher;
|
||||
|
||||
/**
|
||||
* A central component to use to obtain the public URL path that clients should
|
||||
|
@ -54,7 +54,7 @@ public class ResourceUrlProvider implements ApplicationListener<ContextRefreshed
|
|||
|
||||
protected final Log logger = LogFactory.getLog(getClass());
|
||||
|
||||
private HttpRequestPathHelper urlPathHelper = new HttpRequestPathHelper();
|
||||
private HttpRequestPathHelper pathHelper = new HttpRequestPathHelper();
|
||||
|
||||
private PathMatcher pathMatcher = new ParsingPathMatcher();
|
||||
|
||||
|
@ -64,19 +64,19 @@ public class ResourceUrlProvider implements ApplicationListener<ContextRefreshed
|
|||
|
||||
|
||||
/**
|
||||
* Configure a {@code UrlPathHelper} to use in
|
||||
* Configure a {@code HttpRequestPathHelper} to use in
|
||||
* {@link #getForRequestUrl(ServerWebExchange, String)}
|
||||
* in order to derive the lookup path for a target request URL path.
|
||||
*/
|
||||
public void setUrlPathHelper(HttpRequestPathHelper urlPathHelper) {
|
||||
this.urlPathHelper = urlPathHelper;
|
||||
public void setPathHelper(HttpRequestPathHelper pathHelper) {
|
||||
this.pathHelper = pathHelper;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the configured {@code UrlPathHelper}.
|
||||
* Return the configured {@code HttpRequestPathHelper}.
|
||||
*/
|
||||
public HttpRequestPathHelper getUrlPathHelper() {
|
||||
return this.urlPathHelper;
|
||||
public HttpRequestPathHelper getPathHelper() {
|
||||
return this.pathHelper;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -184,7 +184,7 @@ public class ResourceUrlProvider implements ApplicationListener<ContextRefreshed
|
|||
private int getLookupPathIndex(ServerWebExchange exchange) {
|
||||
ServerHttpRequest request = exchange.getRequest();
|
||||
String requestPath = request.getURI().getPath();
|
||||
String lookupPath = getUrlPathHelper().getLookupPathForRequest(exchange);
|
||||
String lookupPath = getPathHelper().getLookupPathForRequest(exchange);
|
||||
return requestPath.indexOf(lookupPath);
|
||||
}
|
||||
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* Copyright 2002-2016 the original author or authors.
|
||||
* Copyright 2002-2017 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
|
@ -31,7 +31,7 @@ import org.springframework.util.PathMatcher;
|
|||
import org.springframework.util.StringUtils;
|
||||
import org.springframework.web.server.ServerWebExchange;
|
||||
import org.springframework.web.server.support.HttpRequestPathHelper;
|
||||
import org.springframework.web.util.ParsingPathMatcher;
|
||||
import org.springframework.web.util.pattern.ParsingPathMatcher;
|
||||
|
||||
/**
|
||||
* A logical disjunction (' || ') request condition that matches a request
|
||||
|
|
|
@ -24,7 +24,7 @@ import org.junit.Test;
|
|||
|
||||
import org.springframework.http.HttpMethod;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.web.util.patterns.PathPatternParser;
|
||||
import org.springframework.web.util.pattern.PathPatternParser;
|
||||
|
||||
import static org.junit.Assert.*;
|
||||
|
||||
|
|
|
@ -33,11 +33,9 @@ import org.springframework.util.PathMatcher;
|
|||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.method.HandlerMethod;
|
||||
import org.springframework.web.server.ServerWebExchange;
|
||||
import org.springframework.web.util.ParsingPathMatcher;
|
||||
import org.springframework.web.util.pattern.ParsingPathMatcher;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertNotNull;
|
||||
import static org.junit.Assert.assertNull;
|
||||
import static org.junit.Assert.*;
|
||||
|
||||
/**
|
||||
* Unit tests for {@link AbstractHandlerMethodMapping}.
|
||||
|
|
Loading…
Reference in New Issue