/*
* Copyright 2002-2018 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;
import java.io.Serializable;
import java.net.URI;
import java.util.ArrayList;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
Represents a URI template. A URI template is a URI-like String that contains variables enclosed by braces ({}
) which can be expanded to produce an actual URI. See expand(Map<String,?>)
, expand(Object[])
, and match(String)
for example usages.
This class is designed to be thread-safe and reusable, allowing for any number
of expand or match calls.
Author: Arjen Poutsma, Juergen Hoeller, Rossen Stoyanchev Since: 3.0
/**
* Represents a URI template. A URI template is a URI-like String that contains variables
* enclosed by braces ({@code {}}) which can be expanded to produce an actual URI.
*
* <p>See {@link #expand(Map)}, {@link #expand(Object[])}, and {@link #match(String)}
* for example usages.
*
* <p>This class is designed to be thread-safe and reusable, allowing for any number
* of expand or match calls.
*
* @author Arjen Poutsma
* @author Juergen Hoeller
* @author Rossen Stoyanchev
* @since 3.0
*/
@SuppressWarnings("serial")
public class UriTemplate implements Serializable {
private final String uriTemplate;
private final UriComponents uriComponents;
private final List<String> variableNames;
private final Pattern matchPattern;
Construct a new UriTemplate
with the given URI String. Params: - uriTemplate – the URI template string
/**
* Construct a new {@code UriTemplate} with the given URI String.
* @param uriTemplate the URI template string
*/
public UriTemplate(String uriTemplate) {
Assert.hasText(uriTemplate, "'uriTemplate' must not be null");
this.uriTemplate = uriTemplate;
this.uriComponents = UriComponentsBuilder.fromUriString(uriTemplate).build();
TemplateInfo info = TemplateInfo.parse(uriTemplate);
this.variableNames = Collections.unmodifiableList(info.getVariableNames());
this.matchPattern = info.getMatchPattern();
}
Return the names of the variables in the template, in order.
Returns: the template variable names
/**
* Return the names of the variables in the template, in order.
* @return the template variable names
*/
public List<String> getVariableNames() {
return this.variableNames;
}
Given the Map of variables, expands this template into a URI. The Map keys represent variable names,
the Map values variable values. The order of variables is not significant.
Example:
UriTemplate template = new UriTemplate("http://example.com/hotels/{hotel}/bookings/{booking}");
Map<String, String> uriVariables = new HashMap<String, String>();
uriVariables.put("booking", "42");
uriVariables.put("hotel", "Rest & Relax");
System.out.println(template.expand(uriVariables));
will print: http://example.com/hotels/Rest%20%26%20Relax/bookings/42
Params: - uriVariables – the map of URI variables
Throws: - IllegalArgumentException – if
uriVariables
is null
; or if it does not contain values for all the variable names
Returns: the expanded URI
/**
* Given the Map of variables, expands this template into a URI. The Map keys represent variable names,
* the Map values variable values. The order of variables is not significant.
* <p>Example:
* <pre class="code">
* UriTemplate template = new UriTemplate("http://example.com/hotels/{hotel}/bookings/{booking}");
* Map<String, String> uriVariables = new HashMap<String, String>();
* uriVariables.put("booking", "42");
* uriVariables.put("hotel", "Rest & Relax");
* System.out.println(template.expand(uriVariables));
* </pre>
* will print: <blockquote>{@code http://example.com/hotels/Rest%20%26%20Relax/bookings/42}</blockquote>
* @param uriVariables the map of URI variables
* @return the expanded URI
* @throws IllegalArgumentException if {@code uriVariables} is {@code null};
* or if it does not contain values for all the variable names
*/
public URI expand(Map<String, ?> uriVariables) {
UriComponents expandedComponents = this.uriComponents.expand(uriVariables);
UriComponents encodedComponents = expandedComponents.encode();
return encodedComponents.toUri();
}
Given an array of variables, expand this template into a full URI. The array represent variable values.
The order of variables is significant.
Example:
UriTemplate template = new UriTemplate("http://example.com/hotels/{hotel}/bookings/{booking}");
System.out.println(template.expand("Rest & Relax", 42));
will print: http://example.com/hotels/Rest%20%26%20Relax/bookings/42
Params: - uriVariableValues – the array of URI variables
Throws: - IllegalArgumentException – if
uriVariables
is null
or if it does not contain sufficient variables
Returns: the expanded URI
/**
* Given an array of variables, expand this template into a full URI. The array represent variable values.
* The order of variables is significant.
* <p>Example:
* <pre class="code">
* UriTemplate template = new UriTemplate("http://example.com/hotels/{hotel}/bookings/{booking}");
* System.out.println(template.expand("Rest & Relax", 42));
* </pre>
* will print: <blockquote>{@code http://example.com/hotels/Rest%20%26%20Relax/bookings/42}</blockquote>
* @param uriVariableValues the array of URI variables
* @return the expanded URI
* @throws IllegalArgumentException if {@code uriVariables} is {@code null}
* or if it does not contain sufficient variables
*/
public URI expand(Object... uriVariableValues) {
UriComponents expandedComponents = this.uriComponents.expand(uriVariableValues);
UriComponents encodedComponents = expandedComponents.encode();
return encodedComponents.toUri();
}
Indicate whether the given URI matches this template.
Params: - uri – the URI to match to
Returns: true
if it matches; false
otherwise
/**
* Indicate whether the given URI matches this template.
* @param uri the URI to match to
* @return {@code true} if it matches; {@code false} otherwise
*/
public boolean matches(@Nullable String uri) {
if (uri == null) {
return false;
}
Matcher matcher = this.matchPattern.matcher(uri);
return matcher.matches();
}
Match the given URI to a map of variable values. Keys in the returned map are variable names,
values are variable values, as occurred in the given URI.
Example:
UriTemplate template = new UriTemplate("http://example.com/hotels/{hotel}/bookings/{booking}");
System.out.println(template.match("http://example.com/hotels/1/bookings/42"));
will print: {hotel=1, booking=42}
Params: - uri – the URI to match to
Returns: a map of variable values
/**
* Match the given URI to a map of variable values. Keys in the returned map are variable names,
* values are variable values, as occurred in the given URI.
* <p>Example:
* <pre class="code">
* UriTemplate template = new UriTemplate("http://example.com/hotels/{hotel}/bookings/{booking}");
* System.out.println(template.match("http://example.com/hotels/1/bookings/42"));
* </pre>
* will print: <blockquote>{@code {hotel=1, booking=42}}</blockquote>
* @param uri the URI to match to
* @return a map of variable values
*/
public Map<String, String> match(String uri) {
Assert.notNull(uri, "'uri' must not be null");
Map<String, String> result = new LinkedHashMap<>(this.variableNames.size());
Matcher matcher = this.matchPattern.matcher(uri);
if (matcher.find()) {
for (int i = 1; i <= matcher.groupCount(); i++) {
String name = this.variableNames.get(i - 1);
String value = matcher.group(i);
result.put(name, value);
}
}
return result;
}
@Override
public String toString() {
return this.uriTemplate;
}
Helper to extract variable names and regex for matching to actual URLs.
/**
* Helper to extract variable names and regex for matching to actual URLs.
*/
private static final class TemplateInfo {
private final List<String> variableNames;
private final Pattern pattern;
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;
}
public static TemplateInfo parse(String uriTemplate) {
int level = 0;
List<String> variableNames = new ArrayList<>();
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) {
// start of URI variable
pattern.append(quote(builder));
builder = new StringBuilder();
continue;
}
}
else if (c == '}') {
level--;
if (level == 0) {
// end of URI variable
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;
}
}
builder.append(c);
}
if (builder.length() > 0) {
pattern.append(quote(builder));
}
return new TemplateInfo(variableNames, Pattern.compile(pattern.toString()));
}
private static String quote(StringBuilder builder) {
return (builder.length() > 0 ? Pattern.quote(builder.toString()) : "");
}
}
}