diff --git a/spring-web/src/main/java/org/springframework/web/util/ParsingPathMatcher.java b/spring-web/src/main/java/org/springframework/web/util/ParsingPathMatcher.java index fff0d2af73..5f7aa0379b 100644 --- a/spring-web/src/main/java/org/springframework/web/util/ParsingPathMatcher.java +++ b/spring-web/src/main/java/org/springframework/web/util/ParsingPathMatcher.java @@ -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. @@ -13,6 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ + package org.springframework.web.util; import java.util.Comparator; @@ -26,16 +27,22 @@ import org.springframework.web.util.patterns.PatternComparatorConsideringPath; /** - * + * {@link PathMatcher} implementation for path patterns parsed + * as {@link PathPatternParser} and compiled as {@link PathPattern}s. + * + *

Once parsed, {@link PathPattern}s are tailored for fast matching + * and quick comparison. + * * @author Andy Clement * @since 5.0 + * @see PathPattern */ public class ParsingPathMatcher implements PathMatcher { - Map cache = new HashMap<>(); - + Map cache = new HashMap<>(); + PathPatternParser parser; - + public ParsingPathMatcher() { parser = new PathPatternParser(); } @@ -74,27 +81,28 @@ public class ParsingPathMatcher implements PathMatcher { public Comparator getPatternComparator(String path) { return new PathPatternStringComparatorConsideringPath(path); } - + class PathPatternStringComparatorConsideringPath implements Comparator { - + PatternComparatorConsideringPath ppcp; - + public PathPatternStringComparatorConsideringPath(String path) { ppcp = new PatternComparatorConsideringPath(path); } - + @Override public int compare(String o1, String o2) { if (o1 == null) { - return (o2==null?0:+1); - } else if (o2 == null) { + return (o2 == null ? 0 : +1); + } + else if (o2 == null) { return -1; } PathPattern p1 = getPathPattern(o1); PathPattern p2 = getPathPattern(o2); - return ppcp.compare(p1,p2); + return ppcp.compare(p1, p2); } - + } @Override diff --git a/spring-web/src/main/java/org/springframework/web/util/patterns/CaptureTheRestPathElement.java b/spring-web/src/main/java/org/springframework/web/util/patterns/CaptureTheRestPathElement.java index 659b28e74f..9e4fad18a3 100644 --- a/spring-web/src/main/java/org/springframework/web/util/patterns/CaptureTheRestPathElement.java +++ b/spring-web/src/main/java/org/springframework/web/util/patterns/CaptureTheRestPathElement.java @@ -1,5 +1,5 @@ /* - * Copyright 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. @@ -13,6 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ + package org.springframework.web.util.patterns; import org.springframework.web.util.patterns.PathPattern.MatchingContext; @@ -20,13 +21,13 @@ import org.springframework.web.util.patterns.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 */ class CaptureTheRestPathElement extends PathElement { private String variableName; - + private char separator; /** @@ -45,14 +46,14 @@ class CaptureTheRestPathElement extends PathElement { // No need to handle 'match start' checking as this captures everything // anyway and cannot be followed by anything else // assert next == null - + // If there is more data, it must start with the separator - if (candidateIndexPathPatterns match URL paths using the following rules:
+ *

+ * + *

Examples

+ * + * * @author Andy Clement + * @since 5.0 */ public class PathPattern implements Comparable { - private final static Map NO_VARIABLES_MAP = Collections.emptyMap(); + private final static Map NO_VARIABLES_MAP = Collections.emptyMap(); /** First path element in the parsed chain of path elements for this pattern */ private PathElement head; /** The text of the parsed pattern */ private String patternString; - + /** The separator used when parsing the pattern */ private char separator; @@ -54,7 +85,7 @@ public class PathPattern implements Comparable { * Useful when comparing two patterns. */ int normalizedLength; - + /** * Does the pattern end with '<separator>*' */ @@ -87,8 +118,9 @@ public class PathPattern implements Comparable { if (s instanceof CaptureTheRestPathElement || s instanceof WildcardTheRestPathElement) { this.isCatchAll = true; } - if (s instanceof SeparatorPathElement && s.next!=null && s.next instanceof WildcardPathElement && s.next.next == null) { - this.endsWithSeparatorWildcard=true; + if (s instanceof SeparatorPathElement && s.next != null + && s.next instanceof WildcardPathElement && s.next.next == null) { + this.endsWithSeparatorWildcard = true; } s = s.next; } @@ -101,28 +133,31 @@ public class PathPattern implements Comparable { public boolean matches(String path) { if (head == null) { return (path == null) || (path.length() == 0); - } else if (path == null || path.length() == 0) { + } + else if (path == null || path.length() == 0) { if (head instanceof WildcardTheRestPathElement || head instanceof CaptureTheRestPathElement) { path = ""; // Will allow CaptureTheRest to bind the variable to empty - } else { + } + else { return false; } } - MatchingContext matchingContext = new MatchingContext(path,false); + MatchingContext matchingContext = new MatchingContext(path, false); return head.matches(0, matchingContext); } - + /** * @param path the path to check against the pattern * @return true if the pattern matches as much of the path as is supplied */ public boolean matchStart(String path) { if (head == null) { - return (path==null || path.length() == 0); - } else if (path == null || path.length() == 0) { + return (path == null || path.length() == 0); + } + else if (path == null || path.length() == 0) { return true; } - MatchingContext matchingContext = new MatchingContext(path,false); + MatchingContext matchingContext = new MatchingContext(path, false); matchingContext.setMatchStartMatching(true); return head.matches(0, matchingContext); } @@ -132,14 +167,17 @@ public class PathPattern implements Comparable { * @return a map of extracted variables - an empty map if no variables extracted. */ public Map matchAndExtract(String path) { - MatchingContext matchingContext = new MatchingContext(path,true); + MatchingContext matchingContext = new MatchingContext(path, true); if (head != null && head.matches(0, matchingContext)) { return matchingContext.getExtractedVariables(); - } else { - if (path== null || path.length()==0) { + } + else { + if (path == null || path.length() == 0) { return NO_VARIABLES_MAP; - } else { - throw new IllegalStateException("Pattern \"" + this.toString() + "\" is not a match for \"" + path + "\""); + } + else { + throw new IllegalStateException("Pattern \"" + this.toString() + + "\" is not a match for \"" + path + "\""); } } } @@ -150,7 +188,7 @@ public class PathPattern implements Comparable { public String getPatternString() { return patternString; } - + public PathElement getHeadSection() { return head; } @@ -168,17 +206,18 @@ public class PathPattern implements Comparable { * the returned path. * @param path a path that matches this pattern * @return the subset of the path that is matched by pattern or "" if none of it is matched by pattern elements - */ + */ public String extractPathWithinPattern(String path) { // assert this.matches(path) PathElement s = head; int separatorCount = 0; // Find first path element that is pattern based while (s != null) { - if (s instanceof SeparatorPathElement || s instanceof CaptureTheRestPathElement || s instanceof WildcardTheRestPathElement) { + if (s instanceof SeparatorPathElement || s instanceof CaptureTheRestPathElement + || s instanceof WildcardTheRestPathElement) { separatorCount++; } - if (s.getWildcardCount()!=0 || s.getCaptureCount()!=0) { + if (s.getWildcardCount() != 0 || s.getCaptureCount() != 0) { break; } s = s.next; @@ -201,7 +240,7 @@ public class PathPattern implements Comparable { } int end = len; // Trim trailing separators - while (path.charAt(end-1) == separator) { + while (path.charAt(end - 1) == separator) { end--; } // Check if multiple separators embedded in the resulting path, if so trim them out. @@ -209,19 +248,19 @@ public class PathPattern implements Comparable { // The stringWithDuplicateSeparatorsRemoved is only computed if necessary int c = pos; StringBuilder stringWithDuplicateSeparatorsRemoved = null; - while (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 * is more specific, the same or less specific than the supplied pattern. @@ -254,10 +293,12 @@ public class PathPattern implements Comparable { if (lenDifference != 0) { return (lenDifference < 0) ? +1 : -1; } - } else { + } + else { return +1; } - } else if (p.isCatchAll()) { + } + else if (p.isCatchAll()) { return -1; } // 3) This will sort such that if they differ in terms of wildcards or @@ -306,7 +347,7 @@ public class PathPattern implements Comparable { public String toChainString() { StringBuilder buf = new StringBuilder(); PathElement pe = head; - while (pe!=null) { + while (pe != null) { buf.append(pe.toString()).append(" "); pe = pe.next; } @@ -320,7 +361,7 @@ public class PathPattern implements Comparable { public int getCapturedVariableCount() { return capturedVariableCount; } - + public String toString() { return patternString; } @@ -340,7 +381,7 @@ public class PathPattern implements Comparable { boolean isMatchStartMatching = false; - private Map extractedVariables; + private Map extractedVariables; public boolean extractingVariables; @@ -361,10 +402,11 @@ public class PathPattern implements Comparable { extractedVariables.put(key, value); } - public Map getExtractedVariables() { + public Map getExtractedVariables() { if (this.extractedVariables == null) { return NO_VARIABLES_MAP; - } else { + } + else { return this.extractedVariables; } } @@ -372,7 +414,7 @@ public class PathPattern implements Comparable { /** * 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 */ @@ -392,62 +434,65 @@ public class PathPattern implements Comparable { */ public String combine(String pattern2string) { // If one of them is empty the result is the other. If both empty the result is "" - if (patternString == null || patternString.length()==0) { - if (pattern2string == null || pattern2string.length()==0) { + if (patternString == null || patternString.length() == 0) { + if (pattern2string == null || pattern2string.length() == 0) { return ""; - } else { + } + else { return pattern2string; } - } else if (pattern2string == null || pattern2string.length()==0) { + } + else if (pattern2string == null || pattern2string.length() == 0) { return patternString; } - + // /* + /hotel => /hotel // /*.* + /*.html => /*.html // However: // /usr + /user => /usr/user // /{foo} + /bar => /{foo}/bar - if (!patternString.equals(pattern2string) && capturedVariableCount==0 && matches(pattern2string)) { + 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); + 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); + 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 + 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)); + 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); + return file2 + (firstExtensionWild ? secondExtension : firstExtension); } /** - * Join two paths together including a separator if necessary. Extraneous separators are removed (if the first path + * 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; + boolean path1EndsWithSeparator = path1.charAt(path1.length() - 1) == separator; + boolean path2StartsWithSeparator = path2.charAt(0) == separator; if (path1EndsWithSeparator && path2StartsWithSeparator) { return path1 + path2.substring(1); } diff --git a/spring-web/src/main/java/org/springframework/web/util/patterns/PathPatternComparator.java b/spring-web/src/main/java/org/springframework/web/util/patterns/PathPatternComparator.java index 167774dba1..9470fc1d7d 100644 --- a/spring-web/src/main/java/org/springframework/web/util/patterns/PathPatternComparator.java +++ b/spring-web/src/main/java/org/springframework/web/util/patterns/PathPatternComparator.java @@ -1,5 +1,5 @@ /* - * Copyright 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. @@ -13,13 +13,14 @@ * 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 { @@ -28,8 +29,9 @@ public class PathPatternComparator implements Comparator { 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 (o2 == null ? 0 : +1); + } + else if (o2 == null) { return -1; } return o1.compareTo(o2); diff --git a/spring-web/src/main/java/org/springframework/web/util/patterns/PathPatternParser.java b/spring-web/src/main/java/org/springframework/web/util/patterns/PathPatternParser.java index 31317e267a..fbdd5b9eaf 100644 --- a/spring-web/src/main/java/org/springframework/web/util/patterns/PathPatternParser.java +++ b/spring-web/src/main/java/org/springframework/web/util/patterns/PathPatternParser.java @@ -1,5 +1,5 @@ /* - * Copyright 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. @@ -13,6 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ + package org.springframework.web.util.patterns; import java.util.ArrayList; @@ -21,9 +22,10 @@ import java.util.regex.PatternSyntaxException; /** * Parser for URI template patterns. It breaks the path pattern into a number of - * path elements in a linked list. - * + * {@link PathElement}s in a linked list. + * * @author Andy Clement + * @since 5.0 */ public class PathPatternParser { @@ -85,7 +87,7 @@ public class PathPatternParser { /** * Create a PatternParser that will use the specified separator instead of * the default. - * + * * @param separator the path separator to look for when parsing. */ public PathPatternParser(char separator) { @@ -101,7 +103,7 @@ public class PathPatternParser { * 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 */ @@ -128,32 +130,37 @@ public class PathPatternParser { pushPathElement(createPathElement()); } // Skip over multiple separators - while ((pos+1) < pathPatternLength && pathPatternData[pos+1] == separator) { + while ((pos + 1) < pathPatternLength && pathPatternData[pos + 1] == separator) { pos++; } if (peekDoubleWildcard()) { - pushPathElement(new WildcardTheRestPathElement(pos,separator)); - pos+=2; - } else { + pushPathElement(new WildcardTheRestPathElement(pos, separator)); + pos += 2; + } + else { pushPathElement(new SeparatorPathElement(pos, separator)); } - } else { + } + else { if (pathElementStart == -1) { pathElementStart = pos; } if (ch == '?') { singleCharWildcardCount++; - } else if (ch == '{') { + } + 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}/) + // 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); } insideVariableCapture = true; variableCaptureStart = pos; - } else if (ch == '}') { + } + else if (ch == '}') { if (!insideVariableCapture) { throw new PatternParseException(pos, pathPatternData, PatternMessage.MISSING_OPEN_CAPTURE); } @@ -163,13 +170,15 @@ public class PathPatternParser { PatternMessage.NO_MORE_DATA_EXPECTED_AFTER_CAPTURE_THE_REST); } variableCaptureCount++; - } else if (ch == ':') { + } + else if (ch == ':') { if (insideVariableCapture) { skipCaptureRegex(); insideVariableCapture = false; variableCaptureCount++; } - } else if (ch == '*') { + } + else if (ch == '*') { if (insideVariableCapture) { if (variableCaptureStart == pos - 1) { isCaptureTheRestVariable = true; @@ -185,7 +194,8 @@ public class PathPatternParser { PatternMessage.ILLEGAL_CHARACTER_AT_START_OF_CAPTURE_DESCRIPTOR, Character.toString(ch)); - } else if ((pos > (variableCaptureStart + 1 + (isCaptureTheRestVariable ? 1 : 0)) + } + 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)); @@ -219,10 +229,11 @@ public class PathPatternParser { pos++; previousBackslash = true; continue; - } + } if (ch == '{' && !previousBackslash) { curlyBracketDepth++; - } else if (ch == '}' && !previousBackslash) { + } + else if (ch == '}' && !previousBackslash) { if (curlyBracketDepth == 0) { if (regexStart == pos) { throw new PatternParseException(regexStart, pathPatternData, @@ -236,7 +247,7 @@ public class PathPatternParser { throw new PatternParseException(pos, pathPatternData, PatternMessage.MISSING_CLOSE_CAPTURE); } pos++; - previousBackslash=false; + previousBackslash = false; } throw new PatternParseException(pos - 1, pathPatternData, PatternMessage.MISSING_CLOSE_CAPTURE); } @@ -265,25 +276,30 @@ public class PathPatternParser { if (currentPE == null) { headPE = newPathElement; currentPE = newPathElement; - } else if (currentPE instanceof SeparatorPathElement) { + } + else if (currentPE instanceof SeparatorPathElement) { PathElement peBeforeSeparator = currentPE.prev; if (peBeforeSeparator == null) { // /{*foobar} is at the start headPE = newPathElement; newPathElement.prev = peBeforeSeparator; - } else { + } + else { peBeforeSeparator.next = newPathElement; newPathElement.prev = peBeforeSeparator; } currentPE = newPathElement; - } else { - throw new IllegalStateException("Expected SeparatorPathElement but was "+currentPE); } - } else { + else { + throw new IllegalStateException("Expected SeparatorPathElement but was " + currentPE); + } + } + else { if (headPE == null) { headPE = newPathElement; currentPE = newPathElement; - } else { + } + else { currentPE.next = newPathElement; newPathElement.prev = currentPE; currentPE = newPathElement; @@ -305,39 +321,51 @@ public class PathPatternParser { System.arraycopy(pathPatternData, pathElementStart, pathElementText, 0, pos - pathElementStart); PathElement newPE = null; if (variableCaptureCount > 0) { - if (variableCaptureCount == 1 && pathElementStart == variableCaptureStart && pathPatternData[pos - 1] == '}') { + if (variableCaptureCount == 1 + && pathElementStart == variableCaptureStart && pathPatternData[pos - 1] == '}') { if (isCaptureTheRestVariable) { // It is {*....} newPE = new CaptureTheRestPathElement(pathElementStart, pathElementText, separator); - } else { + } + else { // It is a full capture of this element (possibly with constraint), for example: /foo/{abc}/ try { newPE = new CaptureVariablePathElement(pathElementStart, pathElementText, caseSensitive); - } catch (PatternSyntaxException pse) { - throw new PatternParseException(pse, findRegexStart(pathPatternData,pathElementStart)+pse.getIndex(), pathPatternData, PatternMessage.JDK_PATTERN_SYNTAX_EXCEPTION); + } + catch (PatternSyntaxException pse) { + throw new PatternParseException(pse, findRegexStart(pathPatternData, pathElementStart) + + pse.getIndex(), pathPatternData, PatternMessage.JDK_PATTERN_SYNTAX_EXCEPTION); } recordCapturedVariable(pathElementStart, ((CaptureVariablePathElement) newPE).getVariableName()); } - } else { + } + else { if (isCaptureTheRestVariable) { - throw new PatternParseException(pathElementStart, pathPatternData, PatternMessage.CAPTURE_ALL_IS_STANDALONE_CONSTRUCT); + throw new PatternParseException(pathElementStart, pathPatternData, + PatternMessage.CAPTURE_ALL_IS_STANDALONE_CONSTRUCT); } - RegexPathElement newRegexSection = new RegexPathElement(pathElementStart, pathElementText, caseSensitive, pathPatternData); + RegexPathElement newRegexSection = new RegexPathElement(pathElementStart, pathElementText, + caseSensitive, pathPatternData); for (String variableName : newRegexSection.getVariableNames()) { recordCapturedVariable(pathElementStart, variableName); } newPE = newRegexSection; } - } else { + } + else { if (wildcard) { if (pos - 1 == pathElementStart) { newPE = new WildcardPathElement(pathElementStart); - } else { + } + else { newPE = new RegexPathElement(pathElementStart, pathElementText, caseSensitive, pathPatternData); } - } else if (singleCharWildcardCount!=0) { - newPE = new SingleCharWildcardedPathElement(pathElementStart, pathElementText, singleCharWildcardCount, caseSensitive); - } else { + } + else if (singleCharWildcardCount != 0) { + newPE = new SingleCharWildcardedPathElement(pathElementStart, pathElementText, + singleCharWildcardCount, caseSensitive); + } + else { newPE = new LiteralPathElement(pathElementStart, pathElementText, caseSensitive); } } @@ -349,11 +377,12 @@ public class PathPatternParser { * Assumes there is a constraint pattern. * @param data a complete path expression, e.g. /aaa/bbb/{ccc:...} * @param offset the start of the capture pattern of interest - * @return the index of the character after the ':' within the pattern expression relative to the start of the whole expression + * @return the index of the character after the ':' within + * the pattern expression relative to the start of the whole expression */ private int findRegexStart(char[] data, int offset) { int pos = offset; - while (pos(); } if (capturedVariableNames.contains(variableName)) { - throw new PatternParseException(pos, this.pathPatternData, PatternMessage.ILLEGAL_DOUBLE_CAPTURE, variableName); + throw new PatternParseException(pos, this.pathPatternData, + PatternMessage.ILLEGAL_DOUBLE_CAPTURE, variableName); } capturedVariableNames.add(variableName); } diff --git a/spring-web/src/main/java/org/springframework/web/util/patterns/PatternComparatorConsideringPath.java b/spring-web/src/main/java/org/springframework/web/util/patterns/PatternComparatorConsideringPath.java index 06fb69251c..77be7bb055 100644 --- a/spring-web/src/main/java/org/springframework/web/util/patterns/PatternComparatorConsideringPath.java +++ b/spring-web/src/main/java/org/springframework/web/util/patterns/PatternComparatorConsideringPath.java @@ -1,5 +1,5 @@ /* - * Copyright 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. @@ -13,6 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ + package org.springframework.web.util.patterns; import java.util.Comparator; @@ -20,13 +21,14 @@ 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 { private String path; - + public PatternComparatorConsideringPath(String path) { this.path = path; } @@ -35,13 +37,15 @@ public class PatternComparatorConsideringPath implements Comparator 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 (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 (o2.getPatternString().equals(path)) ? 0 : -1; + } + else if (o2.getPatternString().equals(path)) { return +1; } return o1.compareTo(o2); diff --git a/spring-web/src/main/java/org/springframework/web/util/patterns/PatternMessage.java b/spring-web/src/main/java/org/springframework/web/util/patterns/PatternMessage.java index a3b8836533..0cb53f80d5 100644 --- a/spring-web/src/main/java/org/springframework/web/util/patterns/PatternMessage.java +++ b/spring-web/src/main/java/org/springframework/web/util/patterns/PatternMessage.java @@ -1,5 +1,5 @@ /* - * Copyright 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. @@ -13,20 +13,22 @@ * 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'{'"), + 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"), diff --git a/spring-web/src/main/java/org/springframework/web/util/patterns/PatternParseException.java b/spring-web/src/main/java/org/springframework/web/util/patterns/PatternParseException.java index 7e40476c04..dc8ba52523 100644 --- a/spring-web/src/main/java/org/springframework/web/util/patterns/PatternParseException.java +++ b/spring-web/src/main/java/org/springframework/web/util/patterns/PatternParseException.java @@ -1,5 +1,5 @@ /* - * Copyright 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. @@ -13,12 +13,14 @@ * 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 { @@ -41,7 +43,7 @@ public class PatternParseException extends RuntimeException { } public PatternParseException(Throwable cause, int pos, char[] patternText, PatternMessage message, Object... inserts) { - super(message.formatMessage(inserts),cause); + super(message.formatMessage(inserts), cause); this.pos = pos; this.patternText = patternText; this.message = message; diff --git a/spring-web/src/main/java/org/springframework/web/util/patterns/RegexPathElement.java b/spring-web/src/main/java/org/springframework/web/util/patterns/RegexPathElement.java index a164718905..fe6a8a6424 100644 --- a/spring-web/src/main/java/org/springframework/web/util/patterns/RegexPathElement.java +++ b/spring-web/src/main/java/org/springframework/web/util/patterns/RegexPathElement.java @@ -1,5 +1,5 @@ /* - * Copyright 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. @@ -13,6 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ + package org.springframework.web.util.patterns; import java.util.LinkedList; @@ -26,8 +27,9 @@ import org.springframework.web.util.patterns.PathPattern.MatchingContext; * A regex path element. Used to represent any complicated element of the path. * For example in '/foo/*_*/*_{foobar}' both *_* and *_{foobar} * are {@link RegexPathElement} path elements. Derived from the general {@link AntPathMatcher} approach. - * + * * @author Andy Clement + * @since 5.0 */ class RegexPathElement extends PathElement { @@ -63,10 +65,12 @@ class RegexPathElement extends PathElement { String match = matcher.group(); if ("?".equals(match)) { patternBuilder.append('.'); - } else if ("*".equals(match)) { + } + else if ("*".equals(match)) { patternBuilder.append(".*"); wildcardCount++; - } else if (match.startsWith("{") && match.endsWith("}")) { + } + else if (match.startsWith("{") && match.endsWith("}")) { int colonIdx = match.indexOf(':'); if (colonIdx == -1) { patternBuilder.append(DEFAULT_VARIABLE_PATTERN); @@ -76,7 +80,8 @@ class RegexPathElement extends PathElement { variableName); } this.variableNames.add(variableName); - } else { + } + else { String variablePattern = match.substring(colonIdx + 1, match.length() - 1); patternBuilder.append('('); patternBuilder.append(variablePattern); @@ -94,7 +99,8 @@ class RegexPathElement extends PathElement { patternBuilder.append(quote(text, end, text.length())); if (caseSensitive) { pattern = java.util.regex.Pattern.compile(patternBuilder.toString()); - } else { + } + else { pattern = java.util.regex.Pattern.compile(patternBuilder.toString(), java.util.regex.Pattern.CASE_INSENSITIVE); } @@ -120,7 +126,8 @@ class RegexPathElement extends PathElement { if (next == null) { // No more pattern, is there more data? matches = (p == matchingContext.candidateLength); - } else { + } + else { if (matchingContext.isMatchStartMatching && p == matchingContext.candidateLength) { return true; // no more data but matches up to this point } @@ -165,10 +172,10 @@ class RegexPathElement extends PathElement { public int getWildcardCount() { return wildcardCount; } - + @Override public int getScore() { - return getCaptureCount()*CAPTURE_VARIABLE_WEIGHT + getWildcardCount()*WILDCARD_WEIGHT; + return getCaptureCount() * CAPTURE_VARIABLE_WEIGHT + getWildcardCount() * WILDCARD_WEIGHT; } } \ No newline at end of file diff --git a/spring-web/src/main/java/org/springframework/web/util/patterns/SeparatorPathElement.java b/spring-web/src/main/java/org/springframework/web/util/patterns/SeparatorPathElement.java index d85a1cbb4e..25aaea68b1 100644 --- a/spring-web/src/main/java/org/springframework/web/util/patterns/SeparatorPathElement.java +++ b/spring-web/src/main/java/org/springframework/web/util/patterns/SeparatorPathElement.java @@ -1,5 +1,5 @@ /* - * Copyright 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. @@ -13,6 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ + package org.springframework.web.util.patterns; import org.springframework.web.util.patterns.PathPattern.MatchingContext; @@ -21,8 +22,9 @@ import org.springframework.web.util.patterns.PathPattern.MatchingContext; * A separator path element. In the pattern '/foo/bar' the two occurrences * of '/' will be represented by a SeparatorPathElement (if the default * separator of '/' is being used). - * + * * @author Andy Clement + * @since 5.0 */ class SeparatorPathElement extends PathElement { @@ -44,13 +46,14 @@ class SeparatorPathElement extends PathElement { if (matchingContext.candidate[candidateIndex] == separator) { // Skip further separators in the path (they are all 'matched' // by a single SeparatorPathElement) - while ((candidateIndex+1) extracted = checkCapture("/abc","/abc"); - assertEquals(0,extracted.size()); + Map extracted = checkCapture("/abc", "/abc"); + assertEquals(0, extracted.size()); } @Test @@ -565,7 +568,7 @@ public class PathPatternMatcherTests { assertEquals("2010", result.get("year")); assertEquals("02", result.get("month")); assertEquals("20", result.get("day")); - + p = pp.parse("{symbolicName:[\\p{L}\\.]+}-sources-{version:[\\p{N}\\.\\{\\}]+}.jar"); result = p.matchAndExtract("com.example-sources-1.0.0.{12}.jar"); assertEquals("com.example", result.get("symbolicName")); @@ -580,7 +583,7 @@ public class PathPatternMatcherTests { exception.expectMessage(containsString("The number of capturing groups in the pattern")); pathMatcher.matchAndExtract("/web/foobar_goo"); } - + @Rule public final ExpectedException exception = ExpectedException.none(); @@ -619,14 +622,14 @@ 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", "")); + 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", "")); // TODO Do we need special handling when patterns contain multiple dots? } @@ -703,21 +706,21 @@ public class PathPatternMatcherTests { assertEquals(-1, comparator.compare(parse("*"), parse("*/**"))); 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(""))); + 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(); PathPattern pp = p.parse("/abc"); - assertEquals(-1,pp.compareTo(null)); + assertEquals(-1, pp.compareTo(null)); } @Test @@ -816,7 +819,7 @@ public class PathPatternMatcherTests { assertEquals("/*/login.*", paths.get(1).getPatternString()); paths.clear(); } - + @Test // SPR-13286 public void caseInsensitive() { PathPatternParser pp = new PathPatternParser(); @@ -831,12 +834,12 @@ public class PathPatternMatcherTests { public void patternmessage() { PatternMessage[] values = PatternMessage.values(); assertNotNull(values); - for (PatternMessage pm: values) { + for (PatternMessage pm : values) { String name = pm.toString(); - assertEquals(pm.ordinal(),PatternMessage.valueOf(name).ordinal()); + assertEquals(pm.ordinal(), PatternMessage.valueOf(name).ordinal()); } } - + private PathPattern parse(String path) { PathPatternParser pp = new PathPatternParser(); return pp.parse(path); @@ -869,7 +872,7 @@ public class PathPatternMatcherTests { assertFalse(pattern.matches(path)); } - private Map checkCapture(String uriTemplate, String path, String... keyValues) { + private Map checkCapture(String uriTemplate, String path, String... keyValues) { PathPatternParser parser = new PathPatternParser(); PathPattern pattern = parser.parse(uriTemplate); Map matchResults = pattern.matchAndExtract(path); @@ -898,7 +901,7 @@ public class PathPatternMatcherTests { PathPatternParser ppp = new PathPatternParser(); PathPattern pp = ppp.parse(pattern); String s = pp.extractPathWithinPattern(path); - assertEquals(expected,s); + assertEquals(expected, s); } static class TestPathCombiner { diff --git a/spring-web/src/test/java/org/springframework/web/util/patterns/PathPatternParserTests.java b/spring-web/src/test/java/org/springframework/web/util/patterns/PathPatternParserTests.java index 22bc6b4ac7..9729648dc0 100644 --- a/spring-web/src/test/java/org/springframework/web/util/patterns/PathPatternParserTests.java +++ b/spring-web/src/test/java/org/springframework/web/util/patterns/PathPatternParserTests.java @@ -1,5 +1,5 @@ /* - * Copyright 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. @@ -13,6 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ + package org.springframework.web.util.patterns; import java.util.ArrayList; @@ -26,13 +27,13 @@ import static org.junit.Assert.*; /** * Exercise the {@link PathPatternParser}. - * + * * @author Andy Clement */ public class PathPatternParserTests { private PathPattern p; - + @Test public void basicPatterns() { checkStructure("/"); @@ -42,23 +43,23 @@ public class PathPatternParserTests { checkStructure("/foo/"); checkStructure("//"); } - + @Test public void singleCharWildcardPatterns() { p = checkStructure("?"); - assertPathElements(p , SingleCharWildcardedPathElement.class); + assertPathElements(p, SingleCharWildcardedPathElement.class); checkStructure("/?/"); checkStructure("//?abc?/"); } - + @Test public void multiwildcardPattern() { p = checkStructure("/**"); - assertPathElements(p,WildcardTheRestPathElement.class); + 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); + assertPathElements(p, SeparatorPathElement.class, RegexPathElement.class); } - + @Test public void toStringTests() { assertEquals("CaptureTheRest(/{*foobar})", checkStructure("/{*foobar}").toChainString()); @@ -70,7 +71,7 @@ public class PathPatternParserTests { assertEquals("Wildcard(*)", checkStructure("*").toChainString()); assertEquals("WildcardTheRest(/**)", checkStructure("/**").toChainString()); } - + @Test public void captureTheRestPatterns() { checkError("/{*foobar}x{abc}", 10, PatternMessage.NO_MORE_DATA_EXPECTED_AFTER_CAPTURE_THE_REST); @@ -81,13 +82,13 @@ public class PathPatternParserTests { 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); - checkError("/{*foobar}abc",10,PatternMessage.NO_MORE_DATA_EXPECTED_AFTER_CAPTURE_THE_REST); - checkError("/{f*oobar}",3,PatternMessage.ILLEGAL_CHARACTER_IN_CAPTURE_DESCRIPTOR); - checkError("/{*foobar}/abc",10,PatternMessage.NO_MORE_DATA_EXPECTED_AFTER_CAPTURE_THE_REST); - checkError("/{abc}{*foobar}",1,PatternMessage.CAPTURE_ALL_IS_STANDALONE_CONSTRUCT); - checkError("/{abc}{*foobar}{foo}",15,PatternMessage.NO_MORE_DATA_EXPECTED_AFTER_CAPTURE_THE_REST); + checkError("/{*foobar}abc", 10, PatternMessage.NO_MORE_DATA_EXPECTED_AFTER_CAPTURE_THE_REST); + checkError("/{f*oobar}", 3, PatternMessage.ILLEGAL_CHARACTER_IN_CAPTURE_DESCRIPTOR); + checkError("/{*foobar}/abc", 10, PatternMessage.NO_MORE_DATA_EXPECTED_AFTER_CAPTURE_THE_REST); + checkError("/{abc}{*foobar}", 1, PatternMessage.CAPTURE_ALL_IS_STANDALONE_CONSTRUCT); + checkError("/{abc}{*foobar}{foo}", 15, PatternMessage.NO_MORE_DATA_EXPECTED_AFTER_CAPTURE_THE_REST); } - + @Test public void equalsAndHashcode() { PathPatternParser caseInsensitiveParser = new PathPatternParser(); @@ -96,23 +97,23 @@ public class PathPatternParserTests { PathPattern pp1 = caseInsensitiveParser.parse("/abc"); PathPattern pp2 = caseInsensitiveParser.parse("/abc"); PathPattern pp3 = caseInsensitiveParser.parse("/def"); - assertEquals(pp1,pp2); - assertEquals(pp1.hashCode(),pp2.hashCode()); + assertEquals(pp1, pp2); + assertEquals(pp1.hashCode(), pp2.hashCode()); assertNotEquals(pp1, pp3); assertFalse(pp1.equals("abc")); - + pp1 = caseInsensitiveParser.parse("/abc"); pp2 = caseSensitiveParser.parse("/abc"); assertFalse(pp1.equals(pp2)); - assertNotEquals(pp1.hashCode(),pp2.hashCode()); - + assertNotEquals(pp1.hashCode(), pp2.hashCode()); + PathPatternParser alternateSeparatorParser = new PathPatternParser(':'); pp1 = caseInsensitiveParser.parse("abc"); pp2 = alternateSeparatorParser.parse("abc"); assertFalse(pp1.equals(pp2)); - assertNotEquals(pp1.hashCode(),pp2.hashCode()); + assertNotEquals(pp1.hashCode(), pp2.hashCode()); } - + @Test public void regexPathElementPatterns() { checkError("/{var:[^/]*}", 8, PatternMessage.MISSING_CLOSE_CAPTURE); @@ -120,78 +121,77 @@ public class PathPatternParserTests { checkError("/{var:a{{1,2}}}", 6, PatternMessage.JDK_PATTERN_SYNTAX_EXCEPTION); p = checkStructure("/{var:\\\\}"); - assertEquals(CaptureVariablePathElement.class.getName(),p.getHeadSection().next.getClass().getName()); + assertEquals(CaptureVariablePathElement.class.getName(), p.getHeadSection().next.getClass().getName()); assertTrue(p.matches("/\\")); p = checkStructure("/{var:\\/}"); - assertEquals(CaptureVariablePathElement.class.getName(),p.getHeadSection().next.getClass().getName()); - assertFalse(p.matches("/aaa")); - - p = checkStructure("/{var:a{1,2}}",1); - assertEquals(CaptureVariablePathElement.class.getName(),p.getHeadSection().next.getClass().getName()); - - p = checkStructure("/{var:[^\\/]*}",1); - assertEquals(CaptureVariablePathElement.class.getName(),p.getHeadSection().next.getClass().getName()); + assertEquals(CaptureVariablePathElement.class.getName(), p.getHeadSection().next.getClass().getName()); + assertFalse(p.matches("/aaa")); + + p = checkStructure("/{var:a{1,2}}", 1); + assertEquals(CaptureVariablePathElement.class.getName(), p.getHeadSection().next.getClass().getName()); + + p = checkStructure("/{var:[^\\/]*}", 1); + assertEquals(CaptureVariablePathElement.class.getName(), p.getHeadSection().next.getClass().getName()); Map result = p.matchAndExtract("/foo"); - assertEquals("foo",result.get("var")); + assertEquals("foo", result.get("var")); - p = checkStructure("/{var:\\[*}",1); - assertEquals(CaptureVariablePathElement.class.getName(),p.getHeadSection().next.getClass().getName()); + p = checkStructure("/{var:\\[*}", 1); + assertEquals(CaptureVariablePathElement.class.getName(), p.getHeadSection().next.getClass().getName()); result = p.matchAndExtract("/[[["); - assertEquals("[[[",result.get("var")); + assertEquals("[[[", result.get("var")); - p = checkStructure("/{var:[\\{]*}",1); - assertEquals(CaptureVariablePathElement.class.getName(),p.getHeadSection().next.getClass().getName()); + p = checkStructure("/{var:[\\{]*}", 1); + assertEquals(CaptureVariablePathElement.class.getName(), p.getHeadSection().next.getClass().getName()); result = p.matchAndExtract("/{{{"); - assertEquals("{{{",result.get("var")); + assertEquals("{{{", result.get("var")); - p = checkStructure("/{var:[\\}]*}",1); - assertEquals(CaptureVariablePathElement.class.getName(),p.getHeadSection().next.getClass().getName()); + p = checkStructure("/{var:[\\}]*}", 1); + assertEquals(CaptureVariablePathElement.class.getName(), p.getHeadSection().next.getClass().getName()); result = p.matchAndExtract("/}}}"); - assertEquals("}}}",result.get("var")); + assertEquals("}}}", result.get("var")); p = checkStructure("*"); - assertEquals(WildcardPathElement.class.getName(),p.getHeadSection().getClass().getName()); + assertEquals(WildcardPathElement.class.getName(), p.getHeadSection().getClass().getName()); checkStructure("/*"); checkStructure("/*/"); checkStructure("*/"); checkStructure("/*/"); p = checkStructure("/*a*/"); - assertEquals(RegexPathElement.class.getName(),p.getHeadSection().next.getClass().getName()); + assertEquals(RegexPathElement.class.getName(), p.getHeadSection().next.getClass().getName()); p = checkStructure("*/"); - assertEquals(WildcardPathElement.class.getName(),p.getHeadSection().getClass().getName()); + assertEquals(WildcardPathElement.class.getName(), p.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()); - - + assertEquals(RegexPathElement.class.getName(), p.getHeadSection().getClass().getName()); + } - + @Test public void completeCapturingPatterns() { p = checkStructure("{foo}"); - assertEquals(CaptureVariablePathElement.class.getName(),p.getHeadSection().getClass().getName()); + assertEquals(CaptureVariablePathElement.class.getName(), p.getHeadSection().getClass().getName()); checkStructure("/{foo}"); checkStructure("//{f}/"); checkStructure("/{foo}/{bar}/{wibble}"); } - + @Test public void completeCaptureWithConstraints() { p = checkStructure("{foo:...}"); assertPathElements(p, CaptureVariablePathElement.class); p = checkStructure("{foo:[0-9]*}"); assertPathElements(p, CaptureVariablePathElement.class); - checkError("{foo:}",5,PatternMessage.MISSING_REGEX_CONSTRAINT); + checkError("{foo:}", 5, PatternMessage.MISSING_REGEX_CONSTRAINT); } @Test public void partialCapturingPatterns() { p = checkStructure("{foo}abc"); - assertEquals(RegexPathElement.class.getName(),p.getHeadSection().getClass().getName()); + assertEquals(RegexPathElement.class.getName(), p.getHeadSection().getClass().getName()); checkStructure("abc{foo}"); checkStructure("/abc{foo}"); checkStructure("{foo}def/"); @@ -203,177 +203,179 @@ public class PathPatternParserTests { @Test public void illegalCapturePatterns() { - checkError("{abc/",4,PatternMessage.MISSING_CLOSE_CAPTURE); - checkError("{abc:}/",5,PatternMessage.MISSING_REGEX_CONSTRAINT); - checkError("{",1,PatternMessage.MISSING_CLOSE_CAPTURE); - checkError("{abc",4,PatternMessage.MISSING_CLOSE_CAPTURE); - checkError("{/}",1,PatternMessage.MISSING_CLOSE_CAPTURE); - checkError("//{",3,PatternMessage.MISSING_CLOSE_CAPTURE); - checkError("}",0,PatternMessage.MISSING_OPEN_CAPTURE); - checkError("/}",1,PatternMessage.MISSING_OPEN_CAPTURE); - checkError("def}",3,PatternMessage.MISSING_OPEN_CAPTURE); - checkError("//{/}",3,PatternMessage.MISSING_CLOSE_CAPTURE); - checkError("//{{/}",3,PatternMessage.ILLEGAL_NESTED_CAPTURE); - checkError("//{abc{/}",6,PatternMessage.ILLEGAL_NESTED_CAPTURE); - checkError("/{0abc}/abc",2,PatternMessage.ILLEGAL_CHARACTER_AT_START_OF_CAPTURE_DESCRIPTOR); - checkError("/{a?bc}/abc",3,PatternMessage.ILLEGAL_CHARACTER_IN_CAPTURE_DESCRIPTOR); - checkError("/{abc}_{abc}",1,PatternMessage.ILLEGAL_DOUBLE_CAPTURE); - checkError("/foobar/{abc}_{abc}",8,PatternMessage.ILLEGAL_DOUBLE_CAPTURE); - checkError("/foobar/{abc:..}_{abc:..}",8,PatternMessage.ILLEGAL_DOUBLE_CAPTURE); + checkError("{abc/", 4, PatternMessage.MISSING_CLOSE_CAPTURE); + checkError("{abc:}/", 5, PatternMessage.MISSING_REGEX_CONSTRAINT); + checkError("{", 1, PatternMessage.MISSING_CLOSE_CAPTURE); + checkError("{abc", 4, PatternMessage.MISSING_CLOSE_CAPTURE); + checkError("{/}", 1, PatternMessage.MISSING_CLOSE_CAPTURE); + checkError("//{", 3, PatternMessage.MISSING_CLOSE_CAPTURE); + checkError("}", 0, PatternMessage.MISSING_OPEN_CAPTURE); + checkError("/}", 1, PatternMessage.MISSING_OPEN_CAPTURE); + checkError("def}", 3, PatternMessage.MISSING_OPEN_CAPTURE); + checkError("//{/}", 3, PatternMessage.MISSING_CLOSE_CAPTURE); + checkError("//{{/}", 3, PatternMessage.ILLEGAL_NESTED_CAPTURE); + checkError("//{abc{/}", 6, PatternMessage.ILLEGAL_NESTED_CAPTURE); + checkError("/{0abc}/abc", 2, PatternMessage.ILLEGAL_CHARACTER_AT_START_OF_CAPTURE_DESCRIPTOR); + checkError("/{a?bc}/abc", 3, PatternMessage.ILLEGAL_CHARACTER_IN_CAPTURE_DESCRIPTOR); + checkError("/{abc}_{abc}", 1, PatternMessage.ILLEGAL_DOUBLE_CAPTURE); + checkError("/foobar/{abc}_{abc}", 8, PatternMessage.ILLEGAL_DOUBLE_CAPTURE); + checkError("/foobar/{abc:..}_{abc:..}", 8, PatternMessage.ILLEGAL_DOUBLE_CAPTURE); PathPattern pp = parse("/{abc:foo(bar)}"); try { pp.matchAndExtract("/foo"); fail("Should have raised exception"); - } catch (IllegalArgumentException iae) { - assertEquals("No capture groups allowed in the constraint regex: foo(bar)",iae.getMessage()); + } + catch (IllegalArgumentException iae) { + assertEquals("No capture groups allowed in the constraint regex: foo(bar)", iae.getMessage()); } try { pp.matchAndExtract("/foobar"); fail("Should have raised exception"); - } catch (IllegalArgumentException iae) { - assertEquals("No capture groups allowed in the constraint regex: foo(bar)",iae.getMessage()); + } + catch (IllegalArgumentException iae) { + assertEquals("No capture groups allowed in the constraint regex: foo(bar)", iae.getMessage()); } } @Test public void badPatterns() { // checkError("/{foo}{bar}/",6,PatternMessage.CANNOT_HAVE_ADJACENT_CAPTURES); - checkError("/{?}/",2,PatternMessage.ILLEGAL_CHARACTER_AT_START_OF_CAPTURE_DESCRIPTOR,"?"); - checkError("/{a?b}/",3,PatternMessage.ILLEGAL_CHARACTER_IN_CAPTURE_DESCRIPTOR,"?"); - checkError("/{%%$}",2,PatternMessage.ILLEGAL_CHARACTER_AT_START_OF_CAPTURE_DESCRIPTOR,"%"); - checkError("/{ }",2,PatternMessage.ILLEGAL_CHARACTER_AT_START_OF_CAPTURE_DESCRIPTOR," "); - checkError("/{%:[0-9]*}",2,PatternMessage.ILLEGAL_CHARACTER_AT_START_OF_CAPTURE_DESCRIPTOR,"%"); + checkError("/{?}/", 2, PatternMessage.ILLEGAL_CHARACTER_AT_START_OF_CAPTURE_DESCRIPTOR, "?"); + checkError("/{a?b}/", 3, PatternMessage.ILLEGAL_CHARACTER_IN_CAPTURE_DESCRIPTOR, "?"); + checkError("/{%%$}", 2, PatternMessage.ILLEGAL_CHARACTER_AT_START_OF_CAPTURE_DESCRIPTOR, "%"); + checkError("/{ }", 2, PatternMessage.ILLEGAL_CHARACTER_AT_START_OF_CAPTURE_DESCRIPTOR, " "); + checkError("/{%:[0-9]*}", 2, PatternMessage.ILLEGAL_CHARACTER_AT_START_OF_CAPTURE_DESCRIPTOR, "%"); } - + @Test public void patternPropertyGetCaptureCountTests() { // Test all basic section types - assertEquals(1,parse("{foo}").getCapturedVariableCount()); - assertEquals(0,parse("foo").getCapturedVariableCount()); - assertEquals(1,parse("{*foobar}").getCapturedVariableCount()); - assertEquals(1,parse("/{*foobar}").getCapturedVariableCount()); - assertEquals(0,parse("/**").getCapturedVariableCount()); - assertEquals(1,parse("{abc}asdf").getCapturedVariableCount()); - assertEquals(1,parse("{abc}_*").getCapturedVariableCount()); - assertEquals(2,parse("{abc}_{def}").getCapturedVariableCount()); - assertEquals(0,parse("/").getCapturedVariableCount()); - assertEquals(0,parse("a?b").getCapturedVariableCount()); - assertEquals(0,parse("*").getCapturedVariableCount()); + assertEquals(1, parse("{foo}").getCapturedVariableCount()); + assertEquals(0, parse("foo").getCapturedVariableCount()); + assertEquals(1, parse("{*foobar}").getCapturedVariableCount()); + assertEquals(1, parse("/{*foobar}").getCapturedVariableCount()); + assertEquals(0, parse("/**").getCapturedVariableCount()); + assertEquals(1, parse("{abc}asdf").getCapturedVariableCount()); + assertEquals(1, parse("{abc}_*").getCapturedVariableCount()); + assertEquals(2, parse("{abc}_{def}").getCapturedVariableCount()); + assertEquals(0, parse("/").getCapturedVariableCount()); + assertEquals(0, parse("a?b").getCapturedVariableCount()); + assertEquals(0, parse("*").getCapturedVariableCount()); // Test on full templates - assertEquals(0,parse("/foo/bar").getCapturedVariableCount()); - assertEquals(1,parse("/{foo}").getCapturedVariableCount()); - assertEquals(2,parse("/{foo}/{bar}").getCapturedVariableCount()); - assertEquals(4,parse("/{foo}/{bar}_{goo}_{wibble}/abc/bar").getCapturedVariableCount()); + assertEquals(0, parse("/foo/bar").getCapturedVariableCount()); + assertEquals(1, parse("/{foo}").getCapturedVariableCount()); + assertEquals(2, parse("/{foo}/{bar}").getCapturedVariableCount()); + assertEquals(4, parse("/{foo}/{bar}_{goo}_{wibble}/abc/bar").getCapturedVariableCount()); } - + @Test public void patternPropertyGetWildcardCountTests() { // Test all basic section types - assertEquals(computeScore(1,0),parse("{foo}").getScore()); - assertEquals(computeScore(0,0),parse("foo").getScore()); - assertEquals(computeScore(0,0),parse("{*foobar}").getScore()); + assertEquals(computeScore(1, 0), parse("{foo}").getScore()); + assertEquals(computeScore(0, 0), parse("foo").getScore()); + assertEquals(computeScore(0, 0), parse("{*foobar}").getScore()); // assertEquals(1,parse("/**").getScore()); - assertEquals(computeScore(1,0),parse("{abc}asdf").getScore()); - assertEquals(computeScore(1,1),parse("{abc}_*").getScore()); - assertEquals(computeScore(2,0),parse("{abc}_{def}").getScore()); - assertEquals(computeScore(0,0),parse("/").getScore()); - assertEquals(computeScore(0,0),parse("a?b").getScore()); // currently deliberate - assertEquals(computeScore(0,1),parse("*").getScore()); + assertEquals(computeScore(1, 0), parse("{abc}asdf").getScore()); + assertEquals(computeScore(1, 1), parse("{abc}_*").getScore()); + assertEquals(computeScore(2, 0), parse("{abc}_{def}").getScore()); + assertEquals(computeScore(0, 0), parse("/").getScore()); + assertEquals(computeScore(0, 0), parse("a?b").getScore()); // currently deliberate + assertEquals(computeScore(0, 1), parse("*").getScore()); // Test on full templates - assertEquals(computeScore(0,0),parse("/foo/bar").getScore()); - assertEquals(computeScore(1,0),parse("/{foo}").getScore()); - assertEquals(computeScore(2,0),parse("/{foo}/{bar}").getScore()); - assertEquals(computeScore(4,0),parse("/{foo}/{bar}_{goo}_{wibble}/abc/bar").getScore()); - assertEquals(computeScore(4,3),parse("/{foo}/*/*_*/{bar}_{goo}_{wibble}/abc/bar").getScore()); + assertEquals(computeScore(0, 0), parse("/foo/bar").getScore()); + assertEquals(computeScore(1, 0), parse("/{foo}").getScore()); + assertEquals(computeScore(2, 0), parse("/{foo}/{bar}").getScore()); + assertEquals(computeScore(4, 0), parse("/{foo}/{bar}_{goo}_{wibble}/abc/bar").getScore()); + assertEquals(computeScore(4, 3), parse("/{foo}/*/*_*/{bar}_{goo}_{wibble}/abc/bar").getScore()); } - + @Test public void multipleSeparatorPatterns() { p = checkStructure("///aaa"); - assertEquals(4,p.getNormalizedLength()); - assertPathElements(p,SeparatorPathElement.class,LiteralPathElement.class); + assertEquals(4, p.getNormalizedLength()); + assertPathElements(p, SeparatorPathElement.class, LiteralPathElement.class); p = checkStructure("///aaa////aaa/b"); - assertEquals(10,p.getNormalizedLength()); - assertPathElements(p,SeparatorPathElement.class, LiteralPathElement.class, + assertEquals(10, p.getNormalizedLength()); + assertPathElements(p, SeparatorPathElement.class, LiteralPathElement.class, SeparatorPathElement.class, LiteralPathElement.class, SeparatorPathElement.class, LiteralPathElement.class); p = checkStructure("/////**"); - assertEquals(1,p.getNormalizedLength()); - assertPathElements(p,WildcardTheRestPathElement.class); + assertEquals(1, p.getNormalizedLength()); + assertPathElements(p, WildcardTheRestPathElement.class); } - + @Test public void patternPropertyGetLengthTests() { // Test all basic section types - assertEquals(1,parse("{foo}").getNormalizedLength()); - assertEquals(3,parse("foo").getNormalizedLength()); - assertEquals(1,parse("{*foobar}").getNormalizedLength()); - assertEquals(1,parse("/{*foobar}").getNormalizedLength()); - assertEquals(1,parse("/**").getNormalizedLength()); - assertEquals(5,parse("{abc}asdf").getNormalizedLength()); - assertEquals(3,parse("{abc}_*").getNormalizedLength()); - assertEquals(3,parse("{abc}_{def}").getNormalizedLength()); - assertEquals(1,parse("/").getNormalizedLength()); - assertEquals(3,parse("a?b").getNormalizedLength()); - assertEquals(1,parse("*").getNormalizedLength()); + assertEquals(1, parse("{foo}").getNormalizedLength()); + assertEquals(3, parse("foo").getNormalizedLength()); + assertEquals(1, parse("{*foobar}").getNormalizedLength()); + assertEquals(1, parse("/{*foobar}").getNormalizedLength()); + assertEquals(1, parse("/**").getNormalizedLength()); + assertEquals(5, parse("{abc}asdf").getNormalizedLength()); + assertEquals(3, parse("{abc}_*").getNormalizedLength()); + assertEquals(3, parse("{abc}_{def}").getNormalizedLength()); + assertEquals(1, parse("/").getNormalizedLength()); + assertEquals(3, parse("a?b").getNormalizedLength()); + assertEquals(1, parse("*").getNormalizedLength()); // Test on full templates - assertEquals(8,parse("/foo/bar").getNormalizedLength()); - assertEquals(2,parse("/{foo}").getNormalizedLength()); - assertEquals(4,parse("/{foo}/{bar}").getNormalizedLength()); - assertEquals(16,parse("/{foo}/{bar}_{goo}_{wibble}/abc/bar").getNormalizedLength()); + assertEquals(8, parse("/foo/bar").getNormalizedLength()); + assertEquals(2, parse("/{foo}").getNormalizedLength()); + assertEquals(4, parse("/{foo}/{bar}").getNormalizedLength()); + assertEquals(16, parse("/{foo}/{bar}_{goo}_{wibble}/abc/bar").getNormalizedLength()); } - + @Test public void compareTests() { - PathPattern p1,p2,p3; - + PathPattern p1, p2, p3; + // Based purely on number of captures p1 = parse("{a}"); p2 = parse("{a}/{b}"); p3 = parse("{a}/{b}/{c}"); - assertEquals(-1,p1.compareTo(p2)); // Based on number of captures + assertEquals(-1, p1.compareTo(p2)); // Based on number of captures List patterns = new ArrayList<>(); patterns.add(p2); patterns.add(p3); patterns.add(p1); - Collections.sort(patterns,new PathPatternComparator()); - assertEquals(p1,patterns.get(0)); - + Collections.sort(patterns, new PathPatternComparator()); + assertEquals(p1, patterns.get(0)); + // Based purely on length p1 = parse("/a/b/c"); p2 = parse("/a/boo/c/doo"); p3 = parse("/asdjflaksjdfjasdf"); - assertEquals(1,p1.compareTo(p2)); + assertEquals(1, p1.compareTo(p2)); patterns = new ArrayList<>(); patterns.add(p2); patterns.add(p3); patterns.add(p1); - Collections.sort(patterns,new PathPatternComparator()); - assertEquals(p3,patterns.get(0)); - + Collections.sort(patterns, new PathPatternComparator()); + assertEquals(p3, patterns.get(0)); + // Based purely on 'wildness' p1 = parse("/*"); p2 = parse("/*/*"); p3 = parse("/*/*/*_*"); - assertEquals(-1,p1.compareTo(p2)); + assertEquals(-1, p1.compareTo(p2)); patterns = new ArrayList<>(); patterns.add(p2); patterns.add(p3); patterns.add(p1); - Collections.sort(patterns,new PathPatternComparator()); - assertEquals(p1,patterns.get(0)); + Collections.sort(patterns, new PathPatternComparator()); + assertEquals(p1, patterns.get(0)); // Based purely on catchAll p1 = parse("{*foobar}"); p2 = parse("{*goo}"); - assertEquals(0,p1.compareTo(p2)); - + assertEquals(0, p1.compareTo(p2)); + p1 = parse("/{*foobar}"); p2 = parse("/abc/{*ww}"); - assertEquals(+1,p1.compareTo(p2)); - assertEquals(-1,p2.compareTo(p1)); + assertEquals(+1, p1.compareTo(p2)); + assertEquals(-1, p2.compareTo(p1)); p3 = parse("/this/that/theother"); assertTrue(p1.isCatchAll()); @@ -383,20 +385,20 @@ public class PathPatternParserTests { patterns.add(p2); patterns.add(p3); patterns.add(p1); - Collections.sort(patterns,new PathPatternComparator()); - assertEquals(p3,patterns.get(0)); - assertEquals(p2,patterns.get(1)); - + Collections.sort(patterns, new PathPatternComparator()); + 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()); + Collections.sort(patterns, new PathPatternComparator()); assertNull(patterns.get(2)); } // --- - + private PathPattern parse(String pattern) { PathPatternParser patternParser = new PathPatternParser(); return patternParser.parse(pattern); @@ -408,22 +410,22 @@ public class PathPatternParserTests { */ private PathPattern checkStructure(String pattern) { int count = 0; - for (int i=0;i... sectionClasses) { PathElement head = p.getHeadSection(); - for (int i=0;i