Replace NAMES_PATTERN in UriTemplate
The URI template is now manually parsed vs using a regex to extract URI variable names and to create a pattern for matching to actual URLs. This provides more control to deal with nested curly braces. Issue: SPR-13627
This commit is contained in:
@@ -18,9 +18,9 @@ package org.springframework.web.util;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.net.URI;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.regex.Matcher;
|
||||
@@ -37,19 +37,13 @@ import org.springframework.util.Assert;
|
||||
*
|
||||
* @author Arjen Poutsma
|
||||
* @author Juergen Hoeller
|
||||
* @author Rossen Stoyanchev
|
||||
* @since 3.0
|
||||
* @see <a href="http://bitworking.org/projects/URI-Templates/">URI Templates</a>
|
||||
*/
|
||||
@SuppressWarnings("serial")
|
||||
public class UriTemplate implements Serializable {
|
||||
|
||||
/** Captures URI template variable names. */
|
||||
private static final Pattern NAMES_PATTERN = Pattern.compile("\\{([^/]+?)\\}");
|
||||
|
||||
/** Replaces template variables in the URI template. */
|
||||
private static final String DEFAULT_VARIABLE_PATTERN = "(.*)";
|
||||
|
||||
|
||||
private final UriComponents uriComponents;
|
||||
|
||||
private final List<String> variableNames;
|
||||
@@ -64,11 +58,13 @@ public class UriTemplate implements Serializable {
|
||||
* @param uriTemplate the URI template string
|
||||
*/
|
||||
public UriTemplate(String uriTemplate) {
|
||||
Parser parser = new Parser(uriTemplate);
|
||||
Assert.hasText(uriTemplate, "'uriTemplate' must not be null");
|
||||
this.uriTemplate = uriTemplate;
|
||||
this.variableNames = parser.getVariableNames();
|
||||
this.matchPattern = parser.getMatchPattern();
|
||||
this.uriComponents = UriComponentsBuilder.fromUriString(uriTemplate).build();
|
||||
|
||||
TemplateInfo info = TemplateInfo.parse(uriTemplate);
|
||||
this.variableNames = Collections.unmodifiableList(info.getVariableNames());
|
||||
this.matchPattern = info.getMatchPattern();
|
||||
}
|
||||
|
||||
|
||||
@@ -169,60 +165,81 @@ public class UriTemplate implements Serializable {
|
||||
|
||||
|
||||
/**
|
||||
* Static inner class to parse URI template strings into a matching regular expression.
|
||||
* Helper to extract variable names and regex for matching to actual URLs.
|
||||
*/
|
||||
private static class Parser {
|
||||
private static class TemplateInfo {
|
||||
|
||||
private final List<String> variableNames = new LinkedList<String>();
|
||||
private final List<String> variableNames;
|
||||
|
||||
private final StringBuilder patternBuilder = new StringBuilder();
|
||||
private final Pattern pattern;
|
||||
|
||||
private Parser(String uriTemplate) {
|
||||
Assert.hasText(uriTemplate, "'uriTemplate' must not be null");
|
||||
Matcher matcher = NAMES_PATTERN.matcher(uriTemplate);
|
||||
int end = 0;
|
||||
while (matcher.find()) {
|
||||
this.patternBuilder.append(quote(uriTemplate, end, matcher.start()));
|
||||
String match = matcher.group(1);
|
||||
int colonIdx = match.indexOf(':');
|
||||
if (colonIdx == -1) {
|
||||
this.patternBuilder.append(DEFAULT_VARIABLE_PATTERN);
|
||||
this.variableNames.add(match);
|
||||
}
|
||||
else {
|
||||
if (colonIdx + 1 == match.length()) {
|
||||
throw new IllegalArgumentException(
|
||||
"No custom regular expression specified after ':' in \"" + match + "\"");
|
||||
|
||||
private TemplateInfo(List<String> vars, Pattern pattern) {
|
||||
this.variableNames = vars;
|
||||
this.pattern = pattern;
|
||||
}
|
||||
|
||||
public List<String> getVariableNames() {
|
||||
return this.variableNames;
|
||||
}
|
||||
|
||||
public Pattern getMatchPattern() {
|
||||
return this.pattern;
|
||||
}
|
||||
|
||||
private static TemplateInfo parse(String uriTemplate) {
|
||||
int level = 0;
|
||||
List<String> variableNames = new ArrayList<String>();
|
||||
StringBuilder pattern = new StringBuilder();
|
||||
StringBuilder builder = new StringBuilder();
|
||||
for (int i = 0 ; i < uriTemplate.length(); i++) {
|
||||
char c = uriTemplate.charAt(i);
|
||||
if (c == '{') {
|
||||
level++;
|
||||
if (level == 1) {
|
||||
pattern.append(quote(builder));
|
||||
builder = new StringBuilder();
|
||||
continue;
|
||||
}
|
||||
String variablePattern = match.substring(colonIdx + 1, match.length());
|
||||
this.patternBuilder.append('(');
|
||||
this.patternBuilder.append(variablePattern);
|
||||
this.patternBuilder.append(')');
|
||||
String variableName = match.substring(0, colonIdx);
|
||||
this.variableNames.add(variableName);
|
||||
}
|
||||
end = matcher.end();
|
||||
}
|
||||
this.patternBuilder.append(quote(uriTemplate, end, uriTemplate.length()));
|
||||
int lastIdx = this.patternBuilder.length() - 1;
|
||||
if (lastIdx >= 0 && this.patternBuilder.charAt(lastIdx) == '/') {
|
||||
this.patternBuilder.deleteCharAt(lastIdx);
|
||||
else if (c == '}') {
|
||||
level--;
|
||||
if (level == 0) {
|
||||
String variable = builder.toString();
|
||||
int idx = variable.indexOf(':');
|
||||
if (idx == -1) {
|
||||
pattern.append("(.*)");
|
||||
variableNames.add(variable);
|
||||
}
|
||||
else {
|
||||
if (idx + 1 == variable.length()) {
|
||||
throw new IllegalArgumentException(
|
||||
"No custom regular expression specified after ':' " +
|
||||
"in \"" + variable + "\"");
|
||||
}
|
||||
String regex = variable.substring(idx + 1, variable.length());
|
||||
pattern.append('(');
|
||||
pattern.append(regex);
|
||||
pattern.append(')');
|
||||
variableNames.add(variable.substring(0, idx));
|
||||
}
|
||||
builder = new StringBuilder();
|
||||
continue;
|
||||
}
|
||||
}
|
||||
if (i + 1 == uriTemplate.length()) {
|
||||
if (c != '/') {
|
||||
builder.append(c);
|
||||
}
|
||||
pattern.append(quote(builder));
|
||||
}
|
||||
builder.append(c);
|
||||
}
|
||||
return new TemplateInfo(variableNames, Pattern.compile(pattern.toString()));
|
||||
}
|
||||
|
||||
private String quote(String fullPath, int start, int end) {
|
||||
if (start == end) {
|
||||
return "";
|
||||
}
|
||||
return Pattern.quote(fullPath.substring(start, end));
|
||||
}
|
||||
|
||||
private List<String> getVariableNames() {
|
||||
return Collections.unmodifiableList(this.variableNames);
|
||||
}
|
||||
|
||||
private Pattern getMatchPattern() {
|
||||
return Pattern.compile(this.patternBuilder.toString());
|
||||
private static String quote(StringBuilder builder) {
|
||||
return builder.length() != 0 ? Pattern.quote(builder.toString()) : "";
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user