org.springframework.web.util.UriTemplate Maven / Gradle / Ivy
/*
* Copyright 2002-2014 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.Collections;
import java.util.LinkedHashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.springframework.util.Assert;
/**
* 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.
*
* See {@link #expand(Map)}, {@link #expand(Object[])}, and {@link #match(String)} for example usages.
*
* @author Arjen Poutsma
* @author Juergen Hoeller
* @since 3.0
* @see URI Templates
*/
@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 variableNames;
private final Pattern matchPattern;
private final String uriTemplate;
/**
* Construct a new {@code UriTemplate} with the given URI String.
* @param uriTemplate the URI template string
*/
public UriTemplate(String uriTemplate) {
Parser parser = new Parser(uriTemplate);
this.uriTemplate = uriTemplate;
this.variableNames = parser.getVariableNames();
this.matchPattern = parser.getMatchPattern();
this.uriComponents = UriComponentsBuilder.fromUriString(uriTemplate).build();
}
/**
* Return the names of the variables in the template, in order.
* @return the template variable names
*/
public List 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: {@code http://example.com/hotels/Rest%20%26%20Relax/bookings/42}
* @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 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: {@code http://example.com/hotels/Rest%20%26%20Relax/bookings/42}
* @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.
* @param uri the URI to match to
* @return {@code true} if it matches; {@code false} otherwise
*/
public boolean matches(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: {@code {hotel=1, booking=42}}
* @param uri the URI to match to
* @return a map of variable values
*/
public Map match(String uri) {
Assert.notNull(uri, "'uri' must not be null");
Map 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;
}
/**
* Static inner class to parse URI template strings into a matching regular expression.
*/
private static class Parser {
private final List variableNames = new LinkedList();
private final StringBuilder patternBuilder = new StringBuilder();
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 + "\"");
}
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);
}
}
private String quote(String fullPath, int start, int end) {
if (start == end) {
return "";
}
return Pattern.quote(fullPath.substring(start, end));
}
private List getVariableNames() {
return Collections.unmodifiableList(this.variableNames);
}
private Pattern getMatchPattern() {
return Pattern.compile(this.patternBuilder.toString());
}
}
}