Make PathPatternParser multi-threaded

With this change the original PathPatternParser is renamed
InternalPathPatternParser and a new PathPatternParser class is added.
This new PathPatternParser class is a very simple thread-safe wrapper for
the InternalPathPatternParser. It achieves this by creating a new
InternalPathPatternParser for each new parse request.  This follows the
model used for SpEL parsing.
This commit is contained in:
Andy Clement 2017-02-19 11:32:47 -08:00 committed by Brian Clozel
parent 8450c69497
commit a0505bf1e7
2 changed files with 426 additions and 367 deletions

View File

@ -0,0 +1,408 @@
/*
* 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.ArrayList;
import java.util.List;
import java.util.regex.PatternSyntaxException;
/**
* 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.
*
* @author Andy Clement
* @since 5.0
*/
public class InternalPathPatternParser {
// The expected path separator to split path elements during parsing
char separator = PathPatternParser.DEFAULT_SEPARATOR;
// Is the parser producing case sensitive PathPattern matchers
boolean caseSensitive = true;
// The input data for parsing
private char[] pathPatternData;
// The length of the input data
private int pathPatternLength;
// Current parsing position
int pos;
// How many ? characters in a particular path element
private int singleCharWildcardCount;
// Is the path pattern using * characters in a particular path element
private boolean wildcard = false;
// Is the construct {*...} being used in a particular path element
private boolean isCaptureTheRestVariable = false;
// Has the parser entered a {...} variable capture block in a particular
// path element
private boolean insideVariableCapture = false;
// How many variable captures are occurring in a particular path element
private int variableCaptureCount = 0;
// Start of the most recent path element in a particular path element
int pathElementStart;
// Start of the most recent variable capture in a particular path element
int variableCaptureStart;
// Variables captures in this path pattern
List<String> capturedVariableNames;
// The head of the path element chain currently being built
PathElement headPE;
// 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.
*/
public InternalPathPatternParser(char separator, boolean caseSensitive) {
this.separator = separator;
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.
*
* @param pathPattern the input path pattern, e.g. /foo/{bar}
* @return a PathPattern for quickly matching paths against the specified path pattern
*/
public PathPattern parse(String pathPattern) {
if (pathPattern == null) {
pathPattern = "";
}
// int starstar = pathPattern.indexOf("**");
// if (starstar!=-1 && starstar!=pathPattern.length()-2) {
// throw new IllegalStateException("Not allowed ** unless at end of pattern: "+pathPattern);
// }
pathPatternData = pathPattern.toCharArray();
pathPatternLength = pathPatternData.length;
headPE = null;
currentPE = null;
capturedVariableNames = null;
pathElementStart = -1;
pos = 0;
resetPathElementState();
while (pos < pathPatternLength) {
char ch = pathPatternData[pos];
if (ch == separator) {
if (pathElementStart != -1) {
pushPathElement(createPathElement());
}
// Skip over multiple separators
while ((pos + 1) < pathPatternLength && pathPatternData[pos + 1] == separator) {
pos++;
}
if (peekDoubleWildcard()) {
pushPathElement(new WildcardTheRestPathElement(pos, separator));
pos += 2;
}
else {
pushPathElement(new SeparatorPathElement(pos, separator));
}
}
else {
if (pathElementStart == -1) {
pathElementStart = pos;
}
if (ch == '?') {
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);
}
insideVariableCapture = true;
variableCaptureStart = pos;
}
else if (ch == '}') {
if (!insideVariableCapture) {
throw new PatternParseException(pos, pathPatternData, PatternMessage.MISSING_OPEN_CAPTURE);
}
insideVariableCapture = false;
if (isCaptureTheRestVariable && (pos + 1) < pathPatternLength) {
throw new PatternParseException(pos + 1, pathPatternData,
PatternMessage.NO_MORE_DATA_EXPECTED_AFTER_CAPTURE_THE_REST);
}
variableCaptureCount++;
}
else if (ch == ':') {
if (insideVariableCapture) {
skipCaptureRegex();
insideVariableCapture = false;
variableCaptureCount++;
}
}
else if (ch == '*') {
if (insideVariableCapture) {
if (variableCaptureStart == pos - 1) {
isCaptureTheRestVariable = true;
}
}
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,
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));
}
}
}
pos++;
}
if (pathElementStart != -1) {
pushPathElement(createPathElement());
}
return new PathPattern(pathPattern, headPE, separator, caseSensitive);
}
/**
* Just hit a ':' and want to jump over the regex specification for this
* variable. pos will be pointing at the ':', we want to skip until the }.
* <p>
* Nested {...} pairs don't have to be escaped: <tt>/abc/{var:x{1,2}}/def</tt>
* <p>An escaped } will not be treated as the end of the regex: <tt>/abc/{var:x\\{y:}/def</tt>
* <p>A separator that should not indicate the end of the regex can be escaped:
*/
private void skipCaptureRegex() {
pos++;
int regexStart = pos;
int curlyBracketDepth = 0; // how deep in nested {...} pairs
boolean previousBackslash = false;
while (pos < pathPatternLength) {
char ch = pathPatternData[pos];
if (ch == '\\' && !previousBackslash) {
pos++;
previousBackslash = true;
continue;
}
if (ch == '{' && !previousBackslash) {
curlyBracketDepth++;
}
else if (ch == '}' && !previousBackslash) {
if (curlyBracketDepth == 0) {
if (regexStart == pos) {
throw new PatternParseException(regexStart, pathPatternData,
PatternMessage.MISSING_REGEX_CONSTRAINT);
}
return;
}
curlyBracketDepth--;
}
if (ch == separator && !previousBackslash) {
throw new PatternParseException(pos, pathPatternData, PatternMessage.MISSING_CLOSE_CAPTURE);
}
pos++;
previousBackslash = false;
}
throw new PatternParseException(pos - 1, pathPatternData, PatternMessage.MISSING_CLOSE_CAPTURE);
}
/**
* After processing a separator, a quick peek whether it is followed by **
* (and only ** before the end of the pattern or the next separator)
*/
private boolean peekDoubleWildcard() {
if ((pos + 2) >= pathPatternLength) {
return false;
}
if (pathPatternData[pos + 1] != '*' || pathPatternData[pos + 2] != '*') {
return false;
}
return (pos + 3 == pathPatternLength);
}
/**
* @param newPathElement the new path element to add to the chain being built
*/
private void pushPathElement(PathElement newPathElement) {
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;
}
else if (currentPE instanceof SeparatorPathElement) {
PathElement peBeforeSeparator = currentPE.prev;
if (peBeforeSeparator == null) {
// /{*foobar} is at the start
headPE = newPathElement;
newPathElement.prev = peBeforeSeparator;
}
else {
peBeforeSeparator.next = newPathElement;
newPathElement.prev = peBeforeSeparator;
}
currentPE = newPathElement;
}
else {
throw new IllegalStateException("Expected SeparatorPathElement but was " + currentPE);
}
}
else {
if (headPE == null) {
headPE = newPathElement;
currentPE = newPathElement;
}
else {
currentPE.next = newPathElement;
newPathElement.prev = currentPE;
currentPE = newPathElement;
}
}
resetPathElementState();
}
/**
* Used the knowledge built up whilst processing since the last path element to determine what kind of path
* element to create.
* @return the new path element
*/
private PathElement createPathElement() {
if (insideVariableCapture) {
throw new PatternParseException(pos, pathPatternData, PatternMessage.MISSING_CLOSE_CAPTURE);
}
char[] pathElementText = new char[pos - pathElementStart];
System.arraycopy(pathPatternData, pathElementStart, pathElementText, 0, pos - pathElementStart);
PathElement newPE = null;
if (variableCaptureCount > 0) {
if (variableCaptureCount == 1
&& pathElementStart == variableCaptureStart && pathPatternData[pos - 1] == '}') {
if (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);
}
catch (PatternSyntaxException pse) {
throw new PatternParseException(pse, findRegexStart(pathPatternData, pathElementStart)
+ pse.getIndex(), pathPatternData, PatternMessage.JDK_PATTERN_SYNTAX_EXCEPTION);
}
recordCapturedVariable(pathElementStart, ((CaptureVariablePathElement) newPE).getVariableName());
}
}
else {
if (isCaptureTheRestVariable) {
throw new PatternParseException(pathElementStart, pathPatternData,
PatternMessage.CAPTURE_ALL_IS_STANDALONE_CONSTRUCT);
}
RegexPathElement newRegexSection = new RegexPathElement(pathElementStart, pathElementText,
caseSensitive, pathPatternData);
for (String variableName : newRegexSection.getVariableNames()) {
recordCapturedVariable(pathElementStart, variableName);
}
newPE = newRegexSection;
}
}
else {
if (wildcard) {
if (pos - 1 == pathElementStart) {
newPE = new WildcardPathElement(pathElementStart);
}
else {
newPE = new RegexPathElement(pathElementStart, pathElementText, caseSensitive, pathPatternData);
}
}
else if (singleCharWildcardCount != 0) {
newPE = new SingleCharWildcardedPathElement(pathElementStart, pathElementText,
singleCharWildcardCount, caseSensitive);
}
else {
newPE = new LiteralPathElement(pathElementStart, pathElementText, caseSensitive);
}
}
return newPE;
}
/**
* For a path element representing a captured variable, locate the constraint pattern.
* 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
*/
private int findRegexStart(char[] data, int offset) {
int pos = offset;
while (pos < data.length) {
if (data[pos] == ':') {
return pos + 1;
}
pos++;
}
return -1;
}
/**
* 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;
}
/**
* 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 (capturedVariableNames.contains(variableName)) {
throw new PatternParseException(pos, this.pathPatternData,
PatternMessage.ILLEGAL_DOUBLE_CAPTURE, variableName);
}
capturedVariableNames.add(variableName);
}
}

View File

@ -16,14 +16,10 @@
package org.springframework.web.util.patterns;
import java.util.ArrayList;
import java.util.List;
import java.util.regex.PatternSyntaxException;
/**
/**
* 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
*/
@ -31,64 +27,23 @@ public class PathPatternParser {
public final static char DEFAULT_SEPARATOR = '/';
// The expected path separator to split path elements during parsing
char separator = DEFAULT_SEPARATOR;
// Is the parser producing case sensitive PathPattern matchers, default true
private boolean caseSensitive = true;
// Is the parser producing case sensitive PathPattern matchers
boolean caseSensitive = true;
// The input data for parsing
private char[] pathPatternData;
// The length of the input data
private int pathPatternLength;
// Current parsing position
int pos;
// How many ? characters in a particular path element
private int singleCharWildcardCount;
// Is the path pattern using * characters in a particular path element
private boolean wildcard = false;
// Is the construct {*...} being used in a particular path element
private boolean isCaptureTheRestVariable = false;
// Has the parser entered a {...} variable capture block in a particular
// path element
private boolean insideVariableCapture = false;
// How many variable captures are occurring in a particular path element
private int variableCaptureCount = 0;
// Start of the most recent path element in a particular path element
int pathElementStart;
// Start of the most recent variable capture in a particular path element
int variableCaptureStart;
// Variables captures in this path pattern
List<String> capturedVariableNames;
// The head of the path element chain currently being built
PathElement headPE;
// The most recently constructed path element in the chain
PathElement currentPE;
// The expected path separator to split path elements during parsing, default '/'
private char separator = DEFAULT_SEPARATOR;
/**
* Default constructor, will use the default path separator to identify
* the elements of the path pattern.
* Create a path pattern parser that will use the default separator '/' when
* parsing patterns.
*/
public PathPatternParser() {
}
/**
* Create a PatternParser that will use the specified separator instead of
* the default.
*
* @param separator the path separator to look for when parsing.
* 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
*/
public PathPatternParser(char separator) {
this.separator = separator;
@ -102,319 +57,15 @@ public class PathPatternParser {
* 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.
* 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
*/
public PathPattern parse(String pathPattern) {
if (pathPattern == null) {
pathPattern = "";
}
// int starstar = pathPattern.indexOf("**");
// if (starstar!=-1 && starstar!=pathPattern.length()-2) {
// throw new IllegalStateException("Not allowed ** unless at end of pattern: "+pathPattern);
// }
pathPatternData = pathPattern.toCharArray();
pathPatternLength = pathPatternData.length;
headPE = null;
currentPE = null;
capturedVariableNames = null;
pathElementStart = -1;
pos = 0;
resetPathElementState();
while (pos < pathPatternLength) {
char ch = pathPatternData[pos];
if (ch == separator) {
if (pathElementStart != -1) {
pushPathElement(createPathElement());
}
// Skip over multiple separators
while ((pos + 1) < pathPatternLength && pathPatternData[pos + 1] == separator) {
pos++;
}
if (peekDoubleWildcard()) {
pushPathElement(new WildcardTheRestPathElement(pos, separator));
pos += 2;
}
else {
pushPathElement(new SeparatorPathElement(pos, separator));
}
}
else {
if (pathElementStart == -1) {
pathElementStart = pos;
}
if (ch == '?') {
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);
}
insideVariableCapture = true;
variableCaptureStart = pos;
}
else if (ch == '}') {
if (!insideVariableCapture) {
throw new PatternParseException(pos, pathPatternData, PatternMessage.MISSING_OPEN_CAPTURE);
}
insideVariableCapture = false;
if (isCaptureTheRestVariable && (pos + 1) < pathPatternLength) {
throw new PatternParseException(pos + 1, pathPatternData,
PatternMessage.NO_MORE_DATA_EXPECTED_AFTER_CAPTURE_THE_REST);
}
variableCaptureCount++;
}
else if (ch == ':') {
if (insideVariableCapture) {
skipCaptureRegex();
insideVariableCapture = false;
variableCaptureCount++;
}
}
else if (ch == '*') {
if (insideVariableCapture) {
if (variableCaptureStart == pos - 1) {
isCaptureTheRestVariable = true;
}
}
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,
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));
}
}
}
pos++;
}
if (pathElementStart != -1) {
pushPathElement(createPathElement());
}
return new PathPattern(pathPattern, headPE, separator, caseSensitive);
public PathPattern parse(String pattern) {
InternalPathPatternParser ippp = new InternalPathPatternParser(separator, caseSensitive);
return ippp.parse(pattern);
}
/**
* Just hit a ':' and want to jump over the regex specification for this
* variable. pos will be pointing at the ':', we want to skip until the }.
* <p>
* Nested {...} pairs don't have to be escaped: <tt>/abc/{var:x{1,2}}/def</tt>
* <p>An escaped } will not be treated as the end of the regex: <tt>/abc/{var:x\\{y:}/def</tt>
* <p>A separator that should not indicate the end of the regex can be escaped:
*/
private void skipCaptureRegex() {
pos++;
int regexStart = pos;
int curlyBracketDepth = 0; // how deep in nested {...} pairs
boolean previousBackslash = false;
while (pos < pathPatternLength) {
char ch = pathPatternData[pos];
if (ch == '\\' && !previousBackslash) {
pos++;
previousBackslash = true;
continue;
}
if (ch == '{' && !previousBackslash) {
curlyBracketDepth++;
}
else if (ch == '}' && !previousBackslash) {
if (curlyBracketDepth == 0) {
if (regexStart == pos) {
throw new PatternParseException(regexStart, pathPatternData,
PatternMessage.MISSING_REGEX_CONSTRAINT);
}
return;
}
curlyBracketDepth--;
}
if (ch == separator && !previousBackslash) {
throw new PatternParseException(pos, pathPatternData, PatternMessage.MISSING_CLOSE_CAPTURE);
}
pos++;
previousBackslash = false;
}
throw new PatternParseException(pos - 1, pathPatternData, PatternMessage.MISSING_CLOSE_CAPTURE);
}
/**
* After processing a separator, a quick peek whether it is followed by **
* (and only ** before the end of the pattern or the next separator)
*/
private boolean peekDoubleWildcard() {
if ((pos + 2) >= pathPatternLength) {
return false;
}
if (pathPatternData[pos + 1] != '*' || pathPatternData[pos + 2] != '*') {
return false;
}
return (pos + 3 == pathPatternLength);
}
/**
* @param newPathElement the new path element to add to the chain being built
*/
private void pushPathElement(PathElement newPathElement) {
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;
}
else if (currentPE instanceof SeparatorPathElement) {
PathElement peBeforeSeparator = currentPE.prev;
if (peBeforeSeparator == null) {
// /{*foobar} is at the start
headPE = newPathElement;
newPathElement.prev = peBeforeSeparator;
}
else {
peBeforeSeparator.next = newPathElement;
newPathElement.prev = peBeforeSeparator;
}
currentPE = newPathElement;
}
else {
throw new IllegalStateException("Expected SeparatorPathElement but was " + currentPE);
}
}
else {
if (headPE == null) {
headPE = newPathElement;
currentPE = newPathElement;
}
else {
currentPE.next = newPathElement;
newPathElement.prev = currentPE;
currentPE = newPathElement;
}
}
resetPathElementState();
}
/**
* Used the knowledge built up whilst processing since the last path element to determine what kind of path
* element to create.
* @return the new path element
*/
private PathElement createPathElement() {
if (insideVariableCapture) {
throw new PatternParseException(pos, pathPatternData, PatternMessage.MISSING_CLOSE_CAPTURE);
}
char[] pathElementText = new char[pos - pathElementStart];
System.arraycopy(pathPatternData, pathElementStart, pathElementText, 0, pos - pathElementStart);
PathElement newPE = null;
if (variableCaptureCount > 0) {
if (variableCaptureCount == 1
&& pathElementStart == variableCaptureStart && pathPatternData[pos - 1] == '}') {
if (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);
}
catch (PatternSyntaxException pse) {
throw new PatternParseException(pse, findRegexStart(pathPatternData, pathElementStart)
+ pse.getIndex(), pathPatternData, PatternMessage.JDK_PATTERN_SYNTAX_EXCEPTION);
}
recordCapturedVariable(pathElementStart, ((CaptureVariablePathElement) newPE).getVariableName());
}
}
else {
if (isCaptureTheRestVariable) {
throw new PatternParseException(pathElementStart, pathPatternData,
PatternMessage.CAPTURE_ALL_IS_STANDALONE_CONSTRUCT);
}
RegexPathElement newRegexSection = new RegexPathElement(pathElementStart, pathElementText,
caseSensitive, pathPatternData);
for (String variableName : newRegexSection.getVariableNames()) {
recordCapturedVariable(pathElementStart, variableName);
}
newPE = newRegexSection;
}
}
else {
if (wildcard) {
if (pos - 1 == pathElementStart) {
newPE = new WildcardPathElement(pathElementStart);
}
else {
newPE = new RegexPathElement(pathElementStart, pathElementText, caseSensitive, pathPatternData);
}
}
else if (singleCharWildcardCount != 0) {
newPE = new SingleCharWildcardedPathElement(pathElementStart, pathElementText,
singleCharWildcardCount, caseSensitive);
}
else {
newPE = new LiteralPathElement(pathElementStart, pathElementText, caseSensitive);
}
}
return newPE;
}
/**
* For a path element representing a captured variable, locate the constraint pattern.
* 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
*/
private int findRegexStart(char[] data, int offset) {
int pos = offset;
while (pos < data.length) {
if (data[pos] == ':') {
return pos + 1;
}
pos++;
}
return -1;
}
/**
* 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;
}
/**
* 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 (capturedVariableNames.contains(variableName)) {
throw new PatternParseException(pos, this.pathPatternData,
PatternMessage.ILLEGAL_DOUBLE_CAPTURE, variableName);
}
capturedVariableNames.add(variableName);
}
}
}