/org.springframework.web/src/main/java/org/springframework/web/util/UriTemplate.java
Java | 325 lines | 161 code | 33 blank | 131 comment | 23 complexity | 2cb92d00b43a999269a9b19365a3f0ab MD5 | raw file
- /*
- * Copyright 2002-2011 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.io.UnsupportedEncodingException;
- import java.net.URI;
- import java.net.URISyntaxException;
- 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>{</code>, <code>}</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.
- *
- * @author Arjen Poutsma
- * @author Juergen Hoeller
- * @since 3.0
- * @see <a href="http://bitworking.org/projects/URI-Templates/">URI Templates</a>
- */
- 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 List<String> variableNames;
- private final Pattern matchPattern;
- private final String uriTemplate;
- private final UriUtils.UriComponent uriComponent;
- /**
- * Construct a new {@link 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.uriComponent = null;
- }
- /**
- * Construct a new {@link UriTemplate} with the given URI String.
- * @param uriTemplate the URI template string
- */
- public UriTemplate(String uriTemplate, UriUtils.UriComponent uriComponent) {
- Parser parser = new Parser(uriTemplate);
- this.uriTemplate = uriTemplate;
- this.variableNames = parser.getVariableNames();
- this.matchPattern = parser.getMatchPattern();
- this.uriComponent = uriComponent;
- }
- /**
- * 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.
- * <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", "1");
- * System.out.println(template.expand(uriVariables));
- * </pre>
- * will print: <blockquote><code>http://example.com/hotels/1/bookings/42</code></blockquote>
- * @param uriVariables the map of URI variables
- * @return the expanded URI
- * @throws IllegalArgumentException if <code>uriVariables</code> is <code>null</code>;
- * or if it does not contain values for all the variable names
- */
- public URI expand(Map<String, ?> uriVariables) {
- return encodeUri(expandAsString(true, uriVariables));
- }
- /**
- * 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", "1");
- * System.out.println(template.expand(uriVariables));
- * </pre>
- * will print: <blockquote><code>http://example.com/hotels/1/bookings/42</code></blockquote>
- * @param encodeUriVariableValues indicates whether uri template variables should be encoded or not
- * @param uriVariables the map of URI variables
- * @return the expanded URI
- * @throws IllegalArgumentException if <code>uriVariables</code> is <code>null</code>;
- * or if it does not contain values for all the variable names
- */
- public String expandAsString(boolean encodeUriVariableValues, Map<String, ?> uriVariables) {
- Assert.notNull(uriVariables, "'uriVariables' must not be null");
- Object[] values = new Object[this.variableNames.size()];
- for (int i = 0; i < this.variableNames.size(); i++) {
- String name = this.variableNames.get(i);
- if (!uriVariables.containsKey(name)) {
- throw new IllegalArgumentException("'uriVariables' Map has no value for '" + name + "'");
- }
- values[i] = uriVariables.get(name);
- }
- return expandAsString(encodeUriVariableValues, values);
- }
- /**
- * 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("1", "42));
- * </pre>
- * will print: <blockquote><code>http://example.com/hotels/1/bookings/42</code></blockquote>
- * @param uriVariableValues the array of URI variables
- * @return the expanded URI
- * @throws IllegalArgumentException if <code>uriVariables</code> is <code>null</code>
- * or if it does not contain sufficient variables
- */
- public URI expand(Object... uriVariableValues) {
- return encodeUri(expandAsString(true, uriVariableValues));
- }
- /**
- * Given an array of variables, expand this template into a full URI String. 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("1", "42));
- * </pre>
- * will print: <blockquote><code>http://example.com/hotels/1/bookings/42</code></blockquote>
- * @param encodeVariableValues indicates whether uri template variables should be encoded or not
- * @param uriVariableValues the array of URI variables
- * @return the expanded URI
- * @throws IllegalArgumentException if <code>uriVariables</code> is <code>null</code>
- * or if it does not contain sufficient variables
- */
- public String expandAsString(boolean encodeVariableValues, Object... uriVariableValues) {
- Assert.notNull(uriVariableValues, "'uriVariableValues' must not be null");
- if (uriVariableValues.length < this.variableNames.size()) {
- throw new IllegalArgumentException(
- "Not enough of variables values in [" + this.uriTemplate + "]: expected at least " +
- this.variableNames.size() + "; got " + uriVariableValues.length);
- }
- Matcher matcher = NAMES_PATTERN.matcher(this.uriTemplate);
- StringBuffer uriBuffer = new StringBuffer();
- int i = 0;
- while (matcher.find()) {
- Object uriVariable = uriVariableValues[i++];
- String uriVariableString = uriVariable != null ? uriVariable.toString() : "";
- if (encodeVariableValues && uriComponent != null) {
- uriVariableString = UriUtils.encode(uriVariableString, uriComponent, false);
- }
- String replacement = Matcher.quoteReplacement(uriVariableString);
- matcher.appendReplacement(uriBuffer, replacement);
- }
- matcher.appendTail(uriBuffer);
- return uriBuffer.toString();
- }
- /**
- * Indicate whether the given URI matches this template.
- * @param uri the URI to match to
- * @return <code>true</code> if it matches; <code>false</code> 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.
- * <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}</code></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<String, String>(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;
- }
- /**
- * Encodes the given String as URL.
- * <p>Defaults to {@link UriUtils#encodeUri(String, String)}.
- * @param uri the URI to encode
- * @return the encoded URI
- */
- protected URI encodeUri(String uri) {
- try {
- String encoded = UriUtils.encodeUri(uri, "UTF-8");
- return new URI(encoded);
- }
- catch (UnsupportedEncodingException ex) {
- // should not happen, UTF-8 is always supported
- throw new IllegalStateException(ex);
- }
- catch (URISyntaxException ex) {
- throw new IllegalArgumentException("Could not create URI from [" + uri + "]: " + ex, ex);
- }
- }
- @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<String> variableNames = new LinkedList<String>();
- private final StringBuilder patternBuilder = new StringBuilder();
- private Parser(String uriTemplate) {
- Assert.hasText(uriTemplate, "'uriTemplate' must not be null");
- Matcher m = NAMES_PATTERN.matcher(uriTemplate);
- int end = 0;
- while (m.find()) {
- this.patternBuilder.append(quote(uriTemplate, end, m.start()));
- String match = m.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 = m.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<String> getVariableNames() {
- return Collections.unmodifiableList(this.variableNames);
- }
- private Pattern getMatchPattern() {
- return Pattern.compile(this.patternBuilder.toString());
- }
- }
- }