Browse Source
git-svn-id: https://src.springframework.org/svn/spring-framework/trunk@669 50f2f4bb-b051-0410-bef5-90022cba6387pull/1/head
2 changed files with 356 additions and 0 deletions
@ -0,0 +1,242 @@ |
|||||||
|
/* |
||||||
|
* Copyright 2002-2009 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.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. An URI template is a URI-like string that contained variables marked of in braces |
||||||
|
* (<code>{</code>, <code>}</code>), which can be expanded to produce a URI. |
||||||
|
* <p/> |
||||||
|
* See {@link #expand(Map)}, {@link #expand(String[])}, and {@link #match(String)} for example usages. |
||||||
|
* |
||||||
|
* @author Arjen Poutsma |
||||||
|
* @see <a href="http://bitworking.org/projects/URI-Templates/">URI Templates</a> |
||||||
|
*/ |
||||||
|
public final class UriTemplate { |
||||||
|
|
||||||
|
/** |
||||||
|
* Captures URI template variable names. |
||||||
|
*/ |
||||||
|
private static final Pattern NAMES_PATTERN = Pattern.compile("\\{([^/]+?)\\}"); |
||||||
|
|
||||||
|
/** |
||||||
|
* Replaces template variables in the URI template. |
||||||
|
*/ |
||||||
|
private static final String VALUE_REGEX = "(.*)"; |
||||||
|
|
||||||
|
private final List<String> variableNames; |
||||||
|
|
||||||
|
private final Pattern matchPattern; |
||||||
|
|
||||||
|
private final String uriTemplate; |
||||||
|
|
||||||
|
/** |
||||||
|
* Constructs a new {@link UriTemplate} with the given 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(); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Returns the names of the variables in the template, in order. |
||||||
|
* |
||||||
|
* @return the template variable names |
||||||
|
*/ |
||||||
|
public List<String> getVariableNames() { |
||||||
|
return variableNames; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Given the map of variables, expands this template into a URI string. The map keys represent variable names, the |
||||||
|
* map values variable values. The order of variables is not significant. |
||||||
|
* <p/> |
||||||
|
* Example: |
||||||
|
* <pre> |
||||||
|
* 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, String> uriVariables) { |
||||||
|
Assert.notNull(uriVariables, "'uriVariables' must not be null"); |
||||||
|
String[] values = new String[variableNames.size()]; |
||||||
|
for (int i = 0; i < variableNames.size(); i++) { |
||||||
|
String name = variableNames.get(i); |
||||||
|
Assert.isTrue(uriVariables.containsKey(name), "'uriVariables' has no value for [" + name + "]"); |
||||||
|
values[i] = uriVariables.get(name); |
||||||
|
} |
||||||
|
return expand(values); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Given an array of variables, expands this template into a URI string. The array represent variable values. The |
||||||
|
* order of variables is significant. |
||||||
|
* <p/> |
||||||
|
* Example: |
||||||
|
* <pre> |
||||||
|
* 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(String... uriVariableValues) { |
||||||
|
Assert.notNull(uriVariableValues, "'uriVariableValues' must not be null"); |
||||||
|
if (uriVariableValues.length != variableNames.size()) { |
||||||
|
throw new IllegalArgumentException( |
||||||
|
"Invalid amount of variables values in [" + uriTemplate + "]: expected " + variableNames.size() + |
||||||
|
"; got " + uriVariableValues.length); |
||||||
|
} |
||||||
|
Matcher m = NAMES_PATTERN.matcher(uriTemplate); |
||||||
|
StringBuffer buffer = new StringBuffer(); |
||||||
|
int i = 0; |
||||||
|
while (m.find()) { |
||||||
|
String uriVariable = uriVariableValues[i++]; |
||||||
|
m.appendReplacement(buffer, uriVariable); |
||||||
|
} |
||||||
|
m.appendTail(buffer); |
||||||
|
return URI.create(buffer.toString()); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Indicates 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 m = matchPattern.matcher(uri); |
||||||
|
return m.matches(); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Matches 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> |
||||||
|
* 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>(variableNames.size()); |
||||||
|
Matcher m = matchPattern.matcher(uri); |
||||||
|
if (m.find()) { |
||||||
|
for (int i = 1; i <= m.groupCount(); i++) { |
||||||
|
String name = variableNames.get(i - 1); |
||||||
|
String value = m.group(i); |
||||||
|
result.put(name, value); |
||||||
|
} |
||||||
|
} |
||||||
|
return result; |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public String toString() { |
||||||
|
return uriTemplate; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Static inner class to parse uri template strings into a matching regular expression. |
||||||
|
*/ |
||||||
|
private static class Parser { |
||||||
|
|
||||||
|
private List<String> variableNames = new LinkedList<String>(); |
||||||
|
|
||||||
|
private 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()) { |
||||||
|
patternBuilder.append(encodeAndQuote(uriTemplate, end, m.start())); |
||||||
|
patternBuilder.append(VALUE_REGEX); |
||||||
|
variableNames.add(m.group(1)); |
||||||
|
end = m.end(); |
||||||
|
} |
||||||
|
patternBuilder.append(encodeAndQuote(uriTemplate, end, uriTemplate.length())); |
||||||
|
|
||||||
|
int lastIdx = patternBuilder.length() - 1; |
||||||
|
if (lastIdx >= 0 && patternBuilder.charAt(lastIdx) == '/') { |
||||||
|
patternBuilder.deleteCharAt(lastIdx); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
private String encodeAndQuote(String fullPath, int start, int end) { |
||||||
|
if (start == end) { |
||||||
|
return ""; |
||||||
|
} |
||||||
|
String result = fullPath.substring(start, end); |
||||||
|
try { |
||||||
|
URI uri = new URI(null, null, result, null); |
||||||
|
result = uri.toASCIIString(); |
||||||
|
} |
||||||
|
catch (URISyntaxException e) { |
||||||
|
throw new IllegalArgumentException("Could not create URI from [" + fullPath + "]"); |
||||||
|
} |
||||||
|
return Pattern.quote(result); |
||||||
|
} |
||||||
|
|
||||||
|
private List<String> getVariableNames() { |
||||||
|
return Collections.unmodifiableList(variableNames); |
||||||
|
} |
||||||
|
|
||||||
|
private Pattern getMatchPattern() { |
||||||
|
return Pattern.compile(patternBuilder.toString()); |
||||||
|
} |
||||||
|
|
||||||
|
} |
||||||
|
|
||||||
|
} |
||||||
@ -0,0 +1,114 @@ |
|||||||
|
/* |
||||||
|
* Copyright 2002-2009 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.net.URI; |
||||||
|
import java.util.Arrays; |
||||||
|
import java.util.Collections; |
||||||
|
import java.util.HashMap; |
||||||
|
import java.util.List; |
||||||
|
import java.util.Map; |
||||||
|
|
||||||
|
import static org.junit.Assert.*; |
||||||
|
import org.junit.Before; |
||||||
|
import org.junit.Test; |
||||||
|
|
||||||
|
/** |
||||||
|
* @author Arjen Poutsma |
||||||
|
*/ |
||||||
|
public class UriTemplateTests { |
||||||
|
|
||||||
|
private UriTemplate template; |
||||||
|
|
||||||
|
@Before |
||||||
|
public void create() { |
||||||
|
template = new UriTemplate("http://example.com/hotels/{hotel}/bookings/{booking}"); |
||||||
|
} |
||||||
|
|
||||||
|
@Test |
||||||
|
public void getVariableNames() throws Exception { |
||||||
|
List<String> variableNames = template.getVariableNames(); |
||||||
|
assertEquals("Invalid variable names", Arrays.asList("hotel", "booking"), variableNames); |
||||||
|
} |
||||||
|
|
||||||
|
@Test |
||||||
|
public void expandVarArgs() throws Exception { |
||||||
|
URI result = template.expand("1", "42"); |
||||||
|
assertEquals("Invalid expanded template", new URI("http://example.com/hotels/1/bookings/42"), result); |
||||||
|
} |
||||||
|
|
||||||
|
@Test(expected = IllegalArgumentException.class) |
||||||
|
public void expandVarArgsInvalidAmountVariables() throws Exception { |
||||||
|
template.expand("1", "42", "100"); |
||||||
|
} |
||||||
|
|
||||||
|
@Test |
||||||
|
public void expandMapDuplicateVariables() throws Exception { |
||||||
|
template = new UriTemplate("/order/{c}/{c}/{c}"); |
||||||
|
assertEquals("Invalid variable names", Arrays.asList("c", "c", "c"), template.getVariableNames()); |
||||||
|
URI result = template.expand(Collections.singletonMap("c", "cheeseburger")); |
||||||
|
assertEquals("Invalid expanded template", new URI("/order/cheeseburger/cheeseburger/cheeseburger"), result); |
||||||
|
} |
||||||
|
|
||||||
|
@Test |
||||||
|
public void expandMap() throws Exception { |
||||||
|
Map<String, String> uriVariables = new HashMap<String, String>(2); |
||||||
|
uriVariables.put("booking", "42"); |
||||||
|
uriVariables.put("hotel", "1"); |
||||||
|
URI result = template.expand(uriVariables); |
||||||
|
assertEquals("Invalid expanded template", new URI("http://example.com/hotels/1/bookings/42"), result); |
||||||
|
} |
||||||
|
|
||||||
|
@Test(expected = IllegalArgumentException.class) |
||||||
|
public void expandMapInvalidAmountVariables() throws Exception { |
||||||
|
template.expand(Collections.singletonMap("hotel", "1")); |
||||||
|
} |
||||||
|
|
||||||
|
@Test(expected = IllegalArgumentException.class) |
||||||
|
public void expandMapUnboundVariables() throws Exception { |
||||||
|
Map<String, String> uriVariables = new HashMap<String, String>(2); |
||||||
|
uriVariables.put("booking", "42"); |
||||||
|
uriVariables.put("bar", "1"); |
||||||
|
template.expand(uriVariables); |
||||||
|
} |
||||||
|
|
||||||
|
@Test |
||||||
|
public void matches() throws Exception { |
||||||
|
assertTrue("UriTemplate does not match", template.matches("http://example.com/hotels/1/bookings/42")); |
||||||
|
assertFalse("UriTemplate matches", template.matches("http://example.com/hotels/bookings")); |
||||||
|
assertFalse("UriTemplate matches", template.matches("")); |
||||||
|
assertFalse("UriTemplate matches", template.matches(null)); |
||||||
|
} |
||||||
|
|
||||||
|
@Test |
||||||
|
public void match() throws Exception { |
||||||
|
Map<String, String> expected = new HashMap<String, String>(2); |
||||||
|
expected.put("booking", "42"); |
||||||
|
expected.put("hotel", "1"); |
||||||
|
|
||||||
|
Map<String, String> result = template.match("http://example.com/hotels/1/bookings/42"); |
||||||
|
assertEquals("Invalid match", expected, result); |
||||||
|
} |
||||||
|
|
||||||
|
@Test |
||||||
|
public void matchDuplicate() throws Exception { |
||||||
|
template = new UriTemplate("/order/{c}/{c}/{c}"); |
||||||
|
Map<String, String> result = template.match("/order/cheeseburger/cheeseburger/cheeseburger"); |
||||||
|
Map<String, String> expected = Collections.singletonMap("c", "cheeseburger"); |
||||||
|
assertEquals("Invalid match", expected, result); |
||||||
|
} |
||||||
|
} |
||||||
Loading…
Reference in new issue