PageRenderTime 58ms CodeModel.GetById 30ms RepoModel.GetById 0ms app.codeStats 0ms

/org.springframework.web/src/main/java/org/springframework/web/util/UriTemplate.java

https://github.com/gkamal/spring-framework
Java | 325 lines | 161 code | 33 blank | 131 comment | 23 complexity | 2cb92d00b43a999269a9b19365a3f0ab MD5 | raw file
  1. /*
  2. * Copyright 2002-2011 the original author or authors.
  3. *
  4. * Licensed under the Apache License, Version 2.0 (the "License");
  5. * you may not use this file except in compliance with the License.
  6. * You may obtain a copy of the License at
  7. *
  8. * http://www.apache.org/licenses/LICENSE-2.0
  9. *
  10. * Unless required by applicable law or agreed to in writing, software
  11. * distributed under the License is distributed on an "AS IS" BASIS,
  12. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13. * See the License for the specific language governing permissions and
  14. * limitations under the License.
  15. */
  16. package org.springframework.web.util;
  17. import java.io.Serializable;
  18. import java.io.UnsupportedEncodingException;
  19. import java.net.URI;
  20. import java.net.URISyntaxException;
  21. import java.util.Collections;
  22. import java.util.LinkedHashMap;
  23. import java.util.LinkedList;
  24. import java.util.List;
  25. import java.util.Map;
  26. import java.util.regex.Matcher;
  27. import java.util.regex.Pattern;
  28. import org.springframework.util.Assert;
  29. /**
  30. * Represents a URI template. A URI template is a URI-like String that contains variables enclosed
  31. * by braces (<code>{</code>, <code>}</code>), which can be expanded to produce an actual URI.
  32. *
  33. * <p>See {@link #expand(Map)}, {@link #expand(Object[])}, and {@link #match(String)} for example usages.
  34. *
  35. * @author Arjen Poutsma
  36. * @author Juergen Hoeller
  37. * @since 3.0
  38. * @see <a href="http://bitworking.org/projects/URI-Templates/">URI Templates</a>
  39. */
  40. public class UriTemplate implements Serializable {
  41. /** Captures URI template variable names. */
  42. private static final Pattern NAMES_PATTERN = Pattern.compile("\\{([^/]+?)\\}");
  43. /** Replaces template variables in the URI template. */
  44. private static final String DEFAULT_VARIABLE_PATTERN = "(.*)";
  45. private final List<String> variableNames;
  46. private final Pattern matchPattern;
  47. private final String uriTemplate;
  48. private final UriUtils.UriComponent uriComponent;
  49. /**
  50. * Construct a new {@link UriTemplate} with the given URI String.
  51. * @param uriTemplate the URI template string
  52. */
  53. public UriTemplate(String uriTemplate) {
  54. Parser parser = new Parser(uriTemplate);
  55. this.uriTemplate = uriTemplate;
  56. this.variableNames = parser.getVariableNames();
  57. this.matchPattern = parser.getMatchPattern();
  58. this.uriComponent = null;
  59. }
  60. /**
  61. * Construct a new {@link UriTemplate} with the given URI String.
  62. * @param uriTemplate the URI template string
  63. */
  64. public UriTemplate(String uriTemplate, UriUtils.UriComponent uriComponent) {
  65. Parser parser = new Parser(uriTemplate);
  66. this.uriTemplate = uriTemplate;
  67. this.variableNames = parser.getVariableNames();
  68. this.matchPattern = parser.getMatchPattern();
  69. this.uriComponent = uriComponent;
  70. }
  71. /**
  72. * Return the names of the variables in the template, in order.
  73. * @return the template variable names
  74. */
  75. public List<String> getVariableNames() {
  76. return this.variableNames;
  77. }
  78. /**
  79. * Given the Map of variables, expands this template into a URI. The Map keys represent variable names,
  80. * the Map values variable values. The order of variables is not significant.
  81. * <p>Example:
  82. * <pre class="code">
  83. * UriTemplate template = new UriTemplate("http://example.com/hotels/{hotel}/bookings/{booking}");
  84. * Map&lt;String, String&gt; uriVariables = new HashMap&lt;String, String&gt;();
  85. * uriVariables.put("booking", "42");
  86. * uriVariables.put("hotel", "1");
  87. * System.out.println(template.expand(uriVariables));
  88. * </pre>
  89. * will print: <blockquote><code>http://example.com/hotels/1/bookings/42</code></blockquote>
  90. * @param uriVariables the map of URI variables
  91. * @return the expanded URI
  92. * @throws IllegalArgumentException if <code>uriVariables</code> is <code>null</code>;
  93. * or if it does not contain values for all the variable names
  94. */
  95. public URI expand(Map<String, ?> uriVariables) {
  96. return encodeUri(expandAsString(true, uriVariables));
  97. }
  98. /**
  99. * Given the Map of variables, expands this template into a URI. The Map keys represent variable names,
  100. * the Map values variable values. The order of variables is not significant.
  101. * <p>Example:
  102. * <pre class="code">
  103. * UriTemplate template = new UriTemplate("http://example.com/hotels/{hotel}/bookings/{booking}");
  104. * Map&lt;String, String&gt; uriVariables = new HashMap&lt;String, String&gt;();
  105. * uriVariables.put("booking", "42");
  106. * uriVariables.put("hotel", "1");
  107. * System.out.println(template.expand(uriVariables));
  108. * </pre>
  109. * will print: <blockquote><code>http://example.com/hotels/1/bookings/42</code></blockquote>
  110. * @param encodeUriVariableValues indicates whether uri template variables should be encoded or not
  111. * @param uriVariables the map of URI variables
  112. * @return the expanded URI
  113. * @throws IllegalArgumentException if <code>uriVariables</code> is <code>null</code>;
  114. * or if it does not contain values for all the variable names
  115. */
  116. public String expandAsString(boolean encodeUriVariableValues, Map<String, ?> uriVariables) {
  117. Assert.notNull(uriVariables, "'uriVariables' must not be null");
  118. Object[] values = new Object[this.variableNames.size()];
  119. for (int i = 0; i < this.variableNames.size(); i++) {
  120. String name = this.variableNames.get(i);
  121. if (!uriVariables.containsKey(name)) {
  122. throw new IllegalArgumentException("'uriVariables' Map has no value for '" + name + "'");
  123. }
  124. values[i] = uriVariables.get(name);
  125. }
  126. return expandAsString(encodeUriVariableValues, values);
  127. }
  128. /**
  129. * Given an array of variables, expand this template into a full URI. The array represent variable values.
  130. * The order of variables is significant.
  131. * <p>Example:
  132. * <pre class="code">
  133. * UriTemplate template = new UriTemplate("http://example.com/hotels/{hotel}/bookings/{booking}");
  134. * System.out.println(template.expand("1", "42));
  135. * </pre>
  136. * will print: <blockquote><code>http://example.com/hotels/1/bookings/42</code></blockquote>
  137. * @param uriVariableValues the array of URI variables
  138. * @return the expanded URI
  139. * @throws IllegalArgumentException if <code>uriVariables</code> is <code>null</code>
  140. * or if it does not contain sufficient variables
  141. */
  142. public URI expand(Object... uriVariableValues) {
  143. return encodeUri(expandAsString(true, uriVariableValues));
  144. }
  145. /**
  146. * Given an array of variables, expand this template into a full URI String. The array represent variable values.
  147. * The order of variables is significant.
  148. * <p>Example:
  149. * <pre class="code">
  150. * UriTemplate template = new UriTemplate("http://example.com/hotels/{hotel}/bookings/{booking}");
  151. * System.out.println(template.expand("1", "42));
  152. * </pre>
  153. * will print: <blockquote><code>http://example.com/hotels/1/bookings/42</code></blockquote>
  154. * @param encodeVariableValues indicates whether uri template variables should be encoded or not
  155. * @param uriVariableValues the array of URI variables
  156. * @return the expanded URI
  157. * @throws IllegalArgumentException if <code>uriVariables</code> is <code>null</code>
  158. * or if it does not contain sufficient variables
  159. */
  160. public String expandAsString(boolean encodeVariableValues, Object... uriVariableValues) {
  161. Assert.notNull(uriVariableValues, "'uriVariableValues' must not be null");
  162. if (uriVariableValues.length < this.variableNames.size()) {
  163. throw new IllegalArgumentException(
  164. "Not enough of variables values in [" + this.uriTemplate + "]: expected at least " +
  165. this.variableNames.size() + "; got " + uriVariableValues.length);
  166. }
  167. Matcher matcher = NAMES_PATTERN.matcher(this.uriTemplate);
  168. StringBuffer uriBuffer = new StringBuffer();
  169. int i = 0;
  170. while (matcher.find()) {
  171. Object uriVariable = uriVariableValues[i++];
  172. String uriVariableString = uriVariable != null ? uriVariable.toString() : "";
  173. if (encodeVariableValues && uriComponent != null) {
  174. uriVariableString = UriUtils.encode(uriVariableString, uriComponent, false);
  175. }
  176. String replacement = Matcher.quoteReplacement(uriVariableString);
  177. matcher.appendReplacement(uriBuffer, replacement);
  178. }
  179. matcher.appendTail(uriBuffer);
  180. return uriBuffer.toString();
  181. }
  182. /**
  183. * Indicate whether the given URI matches this template.
  184. * @param uri the URI to match to
  185. * @return <code>true</code> if it matches; <code>false</code> otherwise
  186. */
  187. public boolean matches(String uri) {
  188. if (uri == null) {
  189. return false;
  190. }
  191. Matcher matcher = this.matchPattern.matcher(uri);
  192. return matcher.matches();
  193. }
  194. /**
  195. * Match the given URI to a map of variable values. Keys in the returned map are variable names,
  196. * values are variable values, as occurred in the given URI.
  197. * <p>Example:
  198. * <pre class="code">
  199. * UriTemplate template = new UriTemplate("http://example.com/hotels/{hotel}/bookings/{booking}");
  200. * System.out.println(template.match("http://example.com/hotels/1/bookings/42"));
  201. * </pre>
  202. * will print: <blockquote><code>{hotel=1, booking=42}</code></blockquote>
  203. * @param uri the URI to match to
  204. * @return a map of variable values
  205. */
  206. public Map<String, String> match(String uri) {
  207. Assert.notNull(uri, "'uri' must not be null");
  208. Map<String, String> result = new LinkedHashMap<String, String>(this.variableNames.size());
  209. Matcher matcher = this.matchPattern.matcher(uri);
  210. if (matcher.find()) {
  211. for (int i = 1; i <= matcher.groupCount(); i++) {
  212. String name = this.variableNames.get(i - 1);
  213. String value = matcher.group(i);
  214. result.put(name, value);
  215. }
  216. }
  217. return result;
  218. }
  219. /**
  220. * Encodes the given String as URL.
  221. * <p>Defaults to {@link UriUtils#encodeUri(String, String)}.
  222. * @param uri the URI to encode
  223. * @return the encoded URI
  224. */
  225. protected URI encodeUri(String uri) {
  226. try {
  227. String encoded = UriUtils.encodeUri(uri, "UTF-8");
  228. return new URI(encoded);
  229. }
  230. catch (UnsupportedEncodingException ex) {
  231. // should not happen, UTF-8 is always supported
  232. throw new IllegalStateException(ex);
  233. }
  234. catch (URISyntaxException ex) {
  235. throw new IllegalArgumentException("Could not create URI from [" + uri + "]: " + ex, ex);
  236. }
  237. }
  238. @Override
  239. public String toString() {
  240. return this.uriTemplate;
  241. }
  242. /**
  243. * Static inner class to parse URI template strings into a matching regular expression.
  244. */
  245. private static class Parser {
  246. private final List<String> variableNames = new LinkedList<String>();
  247. private final StringBuilder patternBuilder = new StringBuilder();
  248. private Parser(String uriTemplate) {
  249. Assert.hasText(uriTemplate, "'uriTemplate' must not be null");
  250. Matcher m = NAMES_PATTERN.matcher(uriTemplate);
  251. int end = 0;
  252. while (m.find()) {
  253. this.patternBuilder.append(quote(uriTemplate, end, m.start()));
  254. String match = m.group(1);
  255. int colonIdx = match.indexOf(':');
  256. if (colonIdx == -1) {
  257. this.patternBuilder.append(DEFAULT_VARIABLE_PATTERN);
  258. this.variableNames.add(match);
  259. }
  260. else {
  261. if (colonIdx + 1 == match.length()) {
  262. throw new IllegalArgumentException("No custom regular expression specified after ':' in \"" + match + "\"");
  263. }
  264. String variablePattern = match.substring(colonIdx + 1, match.length());
  265. this.patternBuilder.append('(');
  266. this.patternBuilder.append(variablePattern);
  267. this.patternBuilder.append(')');
  268. String variableName = match.substring(0, colonIdx);
  269. this.variableNames.add(variableName);
  270. }
  271. end = m.end();
  272. }
  273. this.patternBuilder.append(quote(uriTemplate, end, uriTemplate.length()));
  274. int lastIdx = this.patternBuilder.length() - 1;
  275. if (lastIdx >= 0 && this.patternBuilder.charAt(lastIdx) == '/') {
  276. this.patternBuilder.deleteCharAt(lastIdx);
  277. }
  278. }
  279. private String quote(String fullPath, int start, int end) {
  280. if (start == end) {
  281. return "";
  282. }
  283. return Pattern.quote(fullPath.substring(start, end));
  284. }
  285. private List<String> getVariableNames() {
  286. return Collections.unmodifiableList(this.variableNames);
  287. }
  288. private Pattern getMatchPattern() {
  289. return Pattern.compile(this.patternBuilder.toString());
  290. }
  291. }
  292. }