diff --git a/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/support/ServletUriBuilder.java b/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/support/ServletUriComponentsBuilder.java
similarity index 83%
rename from org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/support/ServletUriBuilder.java
rename to org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/support/ServletUriComponentsBuilder.java
index 536c23efbf7..4c5b0413e60 100644
--- a/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/support/ServletUriBuilder.java
+++ b/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/support/ServletUriComponentsBuilder.java
@@ -22,13 +22,13 @@ import org.springframework.util.Assert;
import org.springframework.web.context.request.RequestAttributes;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
-import org.springframework.web.util.UriBuilder;
+import org.springframework.web.util.UriComponentsBuilder;
/** @author Arjen Poutsma */
-public class ServletUriBuilder extends UriBuilder {
+public class ServletUriComponentsBuilder extends UriComponentsBuilder {
- public static ServletUriBuilder fromCurrentServletRequest() {
+ public static ServletUriComponentsBuilder fromCurrentServletRequest() {
RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes();
Assert.state(requestAttributes != null, "Could not find current RequestAttributes in RequestContextHolder");
Assert.isInstanceOf(ServletRequestAttributes.class, requestAttributes);
@@ -38,10 +38,10 @@ public class ServletUriBuilder extends UriBuilder {
return fromServletRequest(servletRequest);
}
- public static ServletUriBuilder fromServletRequest(HttpServletRequest request) {
+ public static ServletUriComponentsBuilder fromServletRequest(HttpServletRequest request) {
Assert.notNull(request, "'request' must not be null");
- ServletUriBuilder builder = new ServletUriBuilder();
+ ServletUriComponentsBuilder builder = new ServletUriComponentsBuilder();
builder.scheme(request.getScheme());
builder.host(request.getServerName());
builder.port(request.getServerPort());
diff --git a/org.springframework.web/src/main/java/org/springframework/web/util/UriBuilder.java b/org.springframework.web/src/main/java/org/springframework/web/util/UriBuilder.java
deleted file mode 100644
index 205d9b7d458..00000000000
--- a/org.springframework.web/src/main/java/org/springframework/web/util/UriBuilder.java
+++ /dev/null
@@ -1,515 +0,0 @@
-/*
- * 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.net.URI;
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.EnumSet;
-import java.util.List;
-import java.util.Map;
-
-import org.springframework.util.Assert;
-import org.springframework.util.CollectionUtils;
-import org.springframework.util.ObjectUtils;
-import org.springframework.util.StringUtils;
-
-/**
- * Builder for {@link URI} objects.
- *
- *
Typical usage involves:
- *
- * - Create a {@code UriBuilder} with one of the static factory methods (such as {@link #fromPath(String)} or
- * {@link #fromUri(URI)})
- * - Set the various URI components through the respective methods ({@link #scheme(String)},
- * {@link #userInfo(String)}, {@link #host(String)}, {@link #port(int)}, {@link #path(String)},
- * {@link #pathSegment(String...)}, {@link #queryParam(String, Object...)}, and {@link #fragment(String)}.
- * - Build the URI with one of the {@link #build} method variants.
- *
- *
- * Most of the URI component methods accept URI template variables (i.e. {@code "{foo}"}), which are expanded by
- * calling {@code build}.
- *
- *
Inspired by {@link javax.ws.rs.core.UriBuilder}.
- *
- * @author Arjen Poutsma
- * @since 3.1
- * @see #newInstance()
- * @see #fromPath(String)
- * @see #fromUri(URI)
- */
-public class UriBuilder {
-
- private String scheme;
-
- private String userInfo;
-
- private String host;
-
- private int port = -1;
-
- private final List pathSegments = new ArrayList();
-
- private final StringBuilder queryBuilder = new StringBuilder();
-
- private String fragment;
-
- /**
- * Default constructor. Protected to prevent direct instantiation.
- *
- * @see #newInstance()
- * @see #fromPath(String)
- * @see #fromUri(URI)
- */
- protected UriBuilder() {
- }
-
- // Factory methods
-
- /**
- * Returns a new, empty URI builder.
- *
- * @return the new {@code UriBuilder}
- */
- public static UriBuilder newInstance() {
- return new UriBuilder();
- }
-
- /**
- * Returns a URI builder that is initialized with the given path.
- *
- * @param path the path to initialize with
- * @return the new {@code UriBuilder}
- */
- public static UriBuilder fromPath(String path) {
- UriBuilder builder = new UriBuilder();
- builder.path(path);
- return builder;
- }
-
- /**
- * Returns a URI builder that is initialized with the given {@code URI}.
- *
- * @param uri the URI to initialize with
- * @return the new {@code UriBuilder}
- */
- public static UriBuilder fromUri(URI uri) {
- UriBuilder builder = new UriBuilder();
- builder.uri(uri);
- return builder;
- }
-
- // build methods
-
- /**
- * Builds a URI with no URI template variables. Any template variable definitions found will be encoded (i.e.
- * {@code "/{foo}"} will result in {@code "/%7Bfoo%7D"}.
- * @return the resulting URI
- */
- public URI build() {
- String port = portAsString();
- String path = null;
- if (!pathSegments.isEmpty()) {
- StringBuilder pathBuilder = new StringBuilder();
- for (String pathSegment : pathSegments) {
- boolean startsWithSlash = pathSegment.charAt(0) == '/';
- boolean endsWithSlash = pathBuilder.length() > 0 && pathBuilder.charAt(pathBuilder.length() - 1) == '/';
-
- if (!endsWithSlash && !startsWithSlash) {
- pathBuilder.append('/');
- }
- else if (endsWithSlash && startsWithSlash) {
- pathSegment = pathSegment.substring(1);
- }
- pathBuilder.append(pathSegment);
- }
- path = pathBuilder.toString();
- }
- String query = queryAsString();
-
- String uri = UriUtils.buildUri(scheme, null, userInfo, host, port, path, query, fragment);
-
- uri = StringUtils.replace(uri, "{", "%7B");
- uri = StringUtils.replace(uri, "}", "%7D");
-
- return URI.create(uri);
- }
-
- /**
- * Builds a URI with the given URI template variables. Any template variable definitions found will be expanded with
- * the given variables map. All variable values will be encoded in accordance with the encoding rules for the URI
- * component they occur in.
- *
- * @param uriVariables the map of URI variables
- * @return the resulting URI
- */
- public URI build(Map uriVariables) {
- return buildFromMap(uriVariables, true);
- }
-
- /**
- * Builds a URI with the given URI template variables. Any template variable definitions found will be expanded with the
- * given variables map. All variable values will not be encoded.
- *
- * @param uriVariables the map of URI variables
- * @return the resulting URI
- */
- public URI buildFromEncoded(Map uriVariables) {
- return buildFromMap(uriVariables, false);
- }
-
- private URI buildFromMap(Map uriVariables, boolean encodeUriVariableValues) {
- if (CollectionUtils.isEmpty(uriVariables)) {
- return build();
- }
- String scheme = expand(this.scheme, UriComponents.Type.SCHEME, uriVariables, encodeUriVariableValues);
- String userInfo = expand(this.userInfo, UriComponents.Type.USER_INFO, uriVariables, encodeUriVariableValues);
- String host = expand(this.host, UriComponents.Type.HOST, uriVariables, encodeUriVariableValues);
- String port = expand(this.portAsString(), UriComponents.Type.PORT, uriVariables, encodeUriVariableValues);
- String path = null;
- if (!this.pathSegments.isEmpty()) {
- StringBuilder pathBuilder = new StringBuilder();
- for (String pathSegment : this.pathSegments) {
- boolean startsWithSlash = pathSegment.charAt(0) == '/';
- boolean endsWithSlash = pathBuilder.length() > 0 && pathBuilder.charAt(pathBuilder.length() - 1) == '/';
-
- if (!endsWithSlash && !startsWithSlash) {
- pathBuilder.append('/');
- }
- else if (endsWithSlash && startsWithSlash) {
- pathSegment = pathSegment.substring(1);
- }
- pathSegment = expand(pathSegment, UriComponents.Type.PATH_SEGMENT, uriVariables, encodeUriVariableValues);
- pathBuilder.append(pathSegment);
- }
- path = pathBuilder.toString();
- }
- String query = expand(this.queryAsString(), UriComponents.Type.QUERY, uriVariables, encodeUriVariableValues);
- String fragment = expand(this.fragment, UriComponents.Type.FRAGMENT, uriVariables, encodeUriVariableValues);
-
- String uri = UriUtils.buildUri(scheme, null, userInfo, host, port, path, query, fragment);
- return URI.create(uri);
- }
-
- private String expand(String source,
- UriComponents.Type uriComponent,
- Map uriVariables,
- boolean encodeUriVariableValues) {
- if (source == null) {
- return null;
- }
- if (source.indexOf('{') == -1) {
- return source;
- }
- UriTemplate template = new UriComponentTemplate(source, uriComponent, encodeUriVariableValues);
- return template.expandAsString(uriVariables);
- }
-
- /**
- * Builds a URI with the given URI template variable values. Any template variable definitions found will be expanded
- * with the given variables. All variable values will be encoded in accordance with the encoding rules for the URI
- * component they occur in.
- *
- * @param uriVariableValues the array of URI variables
- * @return the resulting URI
- */
- public URI build(Object... uriVariableValues) {
- return buildFromVarArg(true, uriVariableValues);
- }
-
- /**
- * Builds a URI with the given URI template variable values. Any template variable definitions found will be expanded
- * with the given variables. All variable values will not be encoded.
- *
- * @param uriVariableValues the array of URI variables
- * @return the resulting URI
- */
- public URI buildFromEncoded(Object... uriVariableValues) {
- return buildFromVarArg(false, uriVariableValues);
- }
-
- private URI buildFromVarArg(boolean encodeUriVariableValues, Object... uriVariableValues) {
- if (ObjectUtils.isEmpty(uriVariableValues)) {
- return build();
- }
-
- StringBuilder uriBuilder = new StringBuilder();
-
- UriTemplate template;
-
- if (scheme != null) {
- template = new UriComponentTemplate(scheme, UriComponents.Type.SCHEME, encodeUriVariableValues);
- uriBuilder.append(template.expandAsString(uriVariableValues));
- uriBuilder.append(':');
- }
-
- if (userInfo != null || host != null || port != -1) {
- uriBuilder.append("//");
-
- if (StringUtils.hasLength(userInfo)) {
- template = new UriComponentTemplate(userInfo, UriComponents.Type.USER_INFO, encodeUriVariableValues);
- uriBuilder.append(template.expandAsString(uriVariableValues));
- uriBuilder.append('@');
- }
-
- if (host != null) {
- template = new UriComponentTemplate(host, UriComponents.Type.HOST, encodeUriVariableValues);
- uriBuilder.append(template.expandAsString(uriVariableValues));
- }
-
- if (port != -1) {
- uriBuilder.append(':');
- uriBuilder.append(port);
- }
- }
-
- if (!pathSegments.isEmpty()) {
- for (String pathSegment : pathSegments) {
- boolean startsWithSlash = pathSegment.charAt(0) == '/';
- boolean endsWithSlash = uriBuilder.length() > 0 && uriBuilder.charAt(uriBuilder.length() - 1) == '/';
-
- if (!endsWithSlash && !startsWithSlash) {
- uriBuilder.append('/');
- }
- else if (endsWithSlash && startsWithSlash) {
- pathSegment = pathSegment.substring(1);
- }
- template = new UriComponentTemplate(pathSegment, UriComponents.Type.PATH_SEGMENT, encodeUriVariableValues);
- uriBuilder.append(template.expandAsString(uriVariableValues));
- }
- }
-
- if (queryBuilder.length() > 0) {
- uriBuilder.append('?');
- template = new UriComponentTemplate(queryBuilder.toString(), UriComponents.Type.QUERY, encodeUriVariableValues);
- uriBuilder.append(template.expandAsString(uriVariableValues));
- }
-
- if (StringUtils.hasLength(fragment)) {
- uriBuilder.append('#');
- template = new UriComponentTemplate(fragment, UriComponents.Type.FRAGMENT, encodeUriVariableValues);
- uriBuilder.append(template.expandAsString(uriVariableValues));
- }
-
- return URI.create(uriBuilder.toString());
- }
-
- // URI components methods
-
- /**
- * Initializes all components of this URI builder with the components of the given URI.
- *
- * @param uri the URI
- * @return this UriBuilder
- */
- public UriBuilder uri(URI uri) {
- Assert.notNull(uri, "'uri' must not be null");
- Assert.isTrue(!uri.isOpaque(), "Opaque URI [" + uri + "] not supported");
-
- this.scheme = uri.getScheme();
-
- if (uri.getRawUserInfo() != null) {
- this.userInfo = uri.getRawUserInfo();
- }
- if (uri.getHost() != null) {
- this.host = uri.getHost();
- }
- if (uri.getPort() != -1) {
- this.port = uri.getPort();
- }
- if (StringUtils.hasLength(uri.getRawPath())) {
- String[] pathSegments = StringUtils.tokenizeToStringArray(uri.getRawPath(), "/");
-
- this.pathSegments.clear();
- Collections.addAll(this.pathSegments, pathSegments);
- }
- if (StringUtils.hasLength(uri.getRawQuery())) {
- this.queryBuilder.setLength(0);
- this.queryBuilder.append(uri.getRawQuery());
- }
- if (uri.getRawFragment() != null) {
- this.fragment = uri.getRawFragment();
- }
- return this;
- }
-
- /**
- * Sets the URI scheme. The given scheme may contain URI template variables, and may also be {@code null} to clear the
- * scheme of this builder.
- *
- * @param scheme the URI scheme
- * @return this UriBuilder
- */
- public UriBuilder scheme(String scheme) {
- if (scheme != null) {
- Assert.hasLength(scheme, "'scheme' must not be empty");
- this.scheme = encodeUriComponent(scheme, UriComponents.Type.SCHEME);
- }
- else {
- this.scheme = null;
- }
- return this;
- }
-
- /**
- * Sets the URI user info. The given user info may contain URI template variables, and may also be {@code null} to
- * clear the user info of this builder.
- *
- * @param userInfo the URI user info
- * @return this UriBuilder
- */
- public UriBuilder userInfo(String userInfo) {
- if (userInfo != null) {
- Assert.hasLength(userInfo, "'userInfo' must not be empty");
- this.userInfo = encodeUriComponent(userInfo, UriComponents.Type.USER_INFO);
- }
- else {
- this.userInfo = null;
- }
- return this;
- }
-
- /**
- * Sets the URI host. The given host may contain URI template variables, and may also be {@code null} to clear the host
- * of this builder.
- *
- * @param host the URI host
- * @return this UriBuilder
- */
- public UriBuilder host(String host) {
- if (host != null) {
- Assert.hasLength(host, "'host' must not be empty");
- this.host = encodeUriComponent(host, UriComponents.Type.HOST);
- }
- else {
- this.host = null;
- }
- return this;
- }
-
- /**
- * Sets the URI port. Passing {@code -1} will clear the port of this builder.
- *
- * @param port the URI port
- * @return this UriBuilder
- */
- public UriBuilder port(int port) {
- Assert.isTrue(port >= -1, "'port' must not be < -1");
- this.port = port;
- return this;
- }
-
- private String portAsString() {
- return this.port != -1 ? Integer.toString(this.port) : null;
- }
-
- /**
- * Appends the given path to the existing path of this builder. The given path may contain URI template variables.
- *
- * @param path the URI path
- * @return this UriBuilder
- */
- public UriBuilder path(String path) {
- Assert.notNull(path, "path must not be null");
-
- String[] pathSegments = StringUtils.tokenizeToStringArray(path, "/");
- return pathSegment(pathSegments);
- }
-
- /**
- * Appends the given path segments to the existing path of this builder. Each given path segments may contain URI
- * template variables.
- *
- * @param segments the URI path segments
- * @return this UriBuilder
- */
- public UriBuilder pathSegment(String... segments) throws IllegalArgumentException {
- Assert.notNull(segments, "'segments' must not be null");
- for (String segment : segments) {
- this.pathSegments.add(encodeUriComponent(segment, UriComponents.Type.PATH_SEGMENT));
- }
-
- return this;
- }
-
- /**
- * Appends the given query parameter to the existing query parameters. The given name or any of the values may contain
- * URI template variables. If no values are given, the resulting URI will contain the query parameter name only (i.e.
- * {@code ?foo} instead of {@code ?foo=bar}.
- *
- * @param name the query parameter name
- * @param values the query parameter values
- * @return this UriBuilder
- */
- public UriBuilder queryParam(String name, Object... values) {
- Assert.notNull(name, "'name' must not be null");
-
- String encodedName = encodeUriComponent(name, UriComponents.Type.QUERY_PARAM);
-
- if (ObjectUtils.isEmpty(values)) {
- if (queryBuilder.length() != 0) {
- queryBuilder.append('&');
- }
- queryBuilder.append(encodedName);
- }
- else {
- for (Object value : values) {
- if (queryBuilder.length() != 0) {
- queryBuilder.append('&');
- }
- queryBuilder.append(encodedName);
-
- String valueAsString = value != null ? value.toString() : "";
- if (valueAsString.length() != 0) {
- queryBuilder.append('=');
- queryBuilder.append(encodeUriComponent(valueAsString, UriComponents.Type.QUERY_PARAM));
- }
-
- }
- }
- return this;
- }
-
- private String queryAsString() {
- return queryBuilder.length() != 0 ? queryBuilder.toString() : null;
- }
-
- /**
- * Sets the URI fragment. The given fragment may contain URI template variables, and may also be {@code null} to clear
- * the fragment of this builder.
- *
- * @param fragment the URI fragment
- * @return this UriBuilder
- */
- public UriBuilder fragment(String fragment) {
- if (fragment != null) {
- Assert.hasLength(fragment, "'fragment' must not be empty");
- this.fragment = encodeUriComponent(fragment, UriComponents.Type.FRAGMENT);
- }
- else {
- this.fragment = null;
- }
- return this;
- }
-
-
- private String encodeUriComponent(String source, UriComponents.Type uriComponent) {
- return UriUtils.encodeUriComponent(source, uriComponent, EnumSet.of(UriUtils.EncodingOption.ALLOW_TEMPLATE_VARS));
- }
-
-
-}
diff --git a/org.springframework.web/src/main/java/org/springframework/web/util/UriComponentTemplate.java b/org.springframework.web/src/main/java/org/springframework/web/util/UriComponentTemplate.java
deleted file mode 100644
index c2d73bbce7b..00000000000
--- a/org.springframework.web/src/main/java/org/springframework/web/util/UriComponentTemplate.java
+++ /dev/null
@@ -1,46 +0,0 @@
-/*
- * 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 org.springframework.util.Assert;
-
-/**
- * Subclass of {@link UriTemplate} that operates on URI components, rather than full URIs.
- *
- * @author Arjen Poutsma
- * @since 3.1
- */
-class UriComponentTemplate extends UriTemplate {
-
- private final UriComponents.Type uriComponent;
-
- private boolean encodeUriVariableValues;
-
- UriComponentTemplate(String uriTemplate, UriComponents.Type uriComponent, boolean encodeUriVariableValues) {
- super(uriTemplate);
- Assert.notNull(uriComponent, "'uriComponent' must not be null");
- this.uriComponent = uriComponent;
- this.encodeUriVariableValues = encodeUriVariableValues;
- }
-
- @Override
- protected String getVariableValueAsString(Object variableValue) {
- String variableValueString = super.getVariableValueAsString(variableValue);
- return encodeUriVariableValues ? UriUtils.encodeUriComponent(variableValueString, uriComponent) :
- variableValueString;
- }
-}
diff --git a/org.springframework.web/src/main/java/org/springframework/web/util/UriComponents.java b/org.springframework.web/src/main/java/org/springframework/web/util/UriComponents.java
index 92ae9094598..1552d44c797 100644
--- a/org.springframework.web/src/main/java/org/springframework/web/util/UriComponents.java
+++ b/org.springframework.web/src/main/java/org/springframework/web/util/UriComponents.java
@@ -16,11 +16,14 @@
package org.springframework.web.util;
+import java.io.ByteArrayOutputStream;
+import java.io.UnsupportedEncodingException;
+import java.net.URI;
+import java.net.URISyntaxException;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.EnumMap;
-import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
@@ -28,526 +31,748 @@ import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.springframework.util.Assert;
-import org.springframework.util.CollectionUtils;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
import org.springframework.util.StringUtils;
/**
- * Represents the components that make up a URI, mapping component type to string values. Contains convenience getters
- * and setters for all components, as well as the regular {@link Map} implementation.
- *
- * This mapping does not contain mappings for {@link org.springframework.web.util.UriComponents.Type#PATH_SEGMENT} or nor {@link
- * org.springframework.web.util.UriComponents.Type#QUERY_PARAM}, since those components can occur multiple times in the URI. Instead, one can use {@link
- * #getPathSegments()} or {@link #getQueryParams()} respectively.
+ * Represents an immutable collection of URI components, mapping component type to string values. Contains convenience
+ * getters for all components, as well as the regular {@link Map} implementation. Effectively similar to {@link URI},
+ * but with more powerful encoding options.
+ *
+ * Note that this {@code Map} does not contain entries for {@link Type#PATH_SEGMENT}
+ * nor {@link Type#QUERY_PARAM}, since those components can occur multiple
+ * times in the URI. Instead, one can use {@link #getPathSegments()} or {@link #getQueryParams()} respectively.
*
* @author Arjen Poutsma
* @since 3.1
+ * @see UriComponentsBuilder
*/
-public class UriComponents implements Map {
+public final class UriComponents implements Map {
- private static final String PATH_DELIMITER = "/";
+ /**
+ * The default encoding used for various encode methods.
+ */
+ public static final String DEFAULT_ENCODING = "UTF-8";
- private static final Pattern QUERY_PARAM_PATTERN = Pattern.compile("([^&=]+)=?([^&=]+)?");
+ private static final String SCHEME_PATTERN = "([^:/?#]+):";
- private final Map uriComponents;
+ private static final String HTTP_PATTERN = "(http|https):";
- /** Constructs a new, empty instance of the {@code UriComponents} object. */
- public UriComponents() {
- this.uriComponents = new EnumMap(Type.class);
- }
+ private static final String USERINFO_PATTERN = "([^@/]*)";
- /**
- * Creates an instance of the {@code UriComponents} object that contains the given component.
- *
- * @param uriComponents the component to initialize with
- */
- public UriComponents(Map uriComponents) {
- Assert.notNull(uriComponents, "'uriComponents' must not be null");
- this.uriComponents = new EnumMap(uriComponents);
- }
+ private static final String HOST_PATTERN = "([^/?#:]*)";
- // convenience properties
+ private static final String PORT_PATTERN = "(\\d*)";
- /**
- * Returns the scheme.
- *
- * @return the scheme. Can be {@code null}.
- */
- public String getScheme() {
- return get(Type.SCHEME);
- }
+ private static final String PATH_PATTERN = "([^?#]*)";
- /**
- * Sets the scheme.
- *
- * @param scheme the scheme. Can be {@code null}.
- */
- public void setScheme(String scheme) {
- put(Type.SCHEME, scheme);
- }
+ private static final String QUERY_PATTERN = "([^#]*)";
- /**
- * Returns the authority.
- *
- * @return the authority. Can be {@code null}.
- */
- public String getAuthority() {
- return get(Type.AUTHORITY);
- }
+ private static final String LAST_PATTERN = "(.*)";
- /**
- * Sets the authority.
- *
- * @param authority the authority. Can be {@code null}.
- */
- public void setAuthority(String authority) {
- put(Type.AUTHORITY, authority);
- }
+ // Regex patterns that matches URIs. See RFC 3986, appendix B
+ private static final Pattern URI_PATTERN = Pattern.compile(
+ "^(" + SCHEME_PATTERN + ")?" + "(//(" + USERINFO_PATTERN + "@)?" + HOST_PATTERN + "(:" + PORT_PATTERN +
+ ")?" + ")?" + PATH_PATTERN + "(\\?" + QUERY_PATTERN + ")?" + "(#" + LAST_PATTERN + ")?");
- /**
- * Returns the user info.
- *
- * @return the user info. Can be {@code null}.
- */
- public String getUserInfo() {
- return get(Type.USER_INFO);
- }
+ private static final Pattern HTTP_URL_PATTERN = Pattern.compile(
+ "^" + HTTP_PATTERN + "(//(" + USERINFO_PATTERN + "@)?" + HOST_PATTERN + "(:" + PORT_PATTERN + ")?" + ")?" +
+ PATH_PATTERN + "(\\?" + LAST_PATTERN + ")?");
- /**
- * Sets the user info
- *
- * @param userInfo the user info. Can be {@code null}
- */
- public void setUserInfo(String userInfo) {
- put(Type.USER_INFO, userInfo);
- }
- /**
- * Returns the host.
- *
- * @return the host. Can be {@code null}.
- */
- public String getHost() {
- return get(Type.HOST);
- }
+ private static final String PATH_DELIMITER = "/";
- /**
- * Sets the host.
- *
- * @param host the host. Can be {@code null}.
- */
- public void setHost(String host) {
- put(Type.HOST, host);
- }
+ private static final Pattern QUERY_PARAM_PATTERN = Pattern.compile("([^&=]+)=?([^&=]+)?");
- /**
- * Returns the port as string.
- *
- * @return the port as string. Can be {@code null}.
- */
- public String getPort() {
- return get(Type.PORT);
- }
+ private final Map uriComponents;
- /**
- * Sets the port as string.
- *
- * @param port the port as string. Can be {@code null}.
- */
- public void setPort(String port) {
- put(Type.PORT, port);
- }
+ private final boolean encoded;
- /**
- * Returns the port as integer. Returns {@code -1} if no port has been set.
- *
- * @return the port the port as integer
- */
- public int getPortAsInteger() {
- String port = getPort();
- return port != null ? Integer.parseInt(port) : -1;
- }
+ private UriComponents(Map uriComponents, boolean encoded) {
+ Assert.notEmpty(uriComponents, "'uriComponents' must not be empty");
+ this.uriComponents = Collections.unmodifiableMap(uriComponents);
+ this.encoded = encoded;
+ }
- /**
- * Sets the port as integer. A value < 0 resets the port to an empty value.
- *
- * @param port the port as integer
- */
- public void setPortAsInteger(int port) {
- String portString = port > -1 ? Integer.toString(port) : null;
- put(Type.PORT, portString);
- }
+ /**
+ * Creates a new {@code UriComponents} object from the given string URI.
+ *
+ * @param uri the source URI
+ * @return the URI components of the URI
+ */
+ public static UriComponents fromUriString(String uri) {
+ Assert.notNull(uri, "'uri' must not be null");
+ Matcher m = URI_PATTERN.matcher(uri);
+ if (m.matches()) {
+ Map result = new EnumMap(UriComponents.Type.class);
- /**
- * Returns the path.
- *
- * @return the path. Can be {@code null}.
- */
- public String getPath() {
- return get(Type.PATH);
- }
+ result.put(UriComponents.Type.SCHEME, m.group(2));
+ result.put(UriComponents.Type.AUTHORITY, m.group(3));
+ result.put(UriComponents.Type.USER_INFO, m.group(5));
+ result.put(UriComponents.Type.HOST, m.group(6));
+ result.put(UriComponents.Type.PORT, m.group(8));
+ result.put(UriComponents.Type.PATH, m.group(9));
+ result.put(UriComponents.Type.QUERY, m.group(11));
+ result.put(UriComponents.Type.FRAGMENT, m.group(13));
- /**
- * Sets the path.
- *
- * @param path the path. Can be {@code null}.
- */
- public void setPath(String path) {
- put(Type.PATH, path);
- }
+ return new UriComponents(result, false);
+ }
+ else {
+ throw new IllegalArgumentException("[" + uri + "] is not a valid URI");
+ }
+ }
- /**
- * Returns the list of path segments.
- *
- * @return the path segments. Empty if no path has been set.
- */
- public List getPathSegments() {
- String path = getPath();
- if (path != null) {
- return Arrays.asList(StringUtils.tokenizeToStringArray(path, PATH_DELIMITER));
- }
- else {
- return Collections.emptyList();
- }
- }
+ /**
+ * Creates a new {@code UriComponents} object from the string HTTP URL.
+ *
+ * @param httpUrl the source URI
+ * @return the URI components of the URI
+ */
+ public static UriComponents fromHttpUrl(String httpUrl) {
+ Assert.notNull(httpUrl, "'httpUrl' must not be null");
+ Matcher m = HTTP_URL_PATTERN.matcher(httpUrl);
+ if (m.matches()) {
+ Map result = new EnumMap(UriComponents.Type.class);
- /**
- * Sets the path segments. An empty or {@code null} value resets the path to an empty value.
- *
- * @param pathSegments the path segments
- */
- public void setPathSegments(List pathSegments) {
- if (!CollectionUtils.isEmpty(pathSegments)) {
- StringBuilder pathBuilder = new StringBuilder("/");
- for (Iterator iterator = pathSegments.iterator(); iterator.hasNext(); ) {
- String pathSegment = iterator.next();
- pathBuilder.append(pathSegment);
- if (iterator.hasNext()) {
- pathBuilder.append('/');
- }
- }
- setPath(pathBuilder.toString());
- }
- else {
- setPath(null);
- }
- }
+ result.put(UriComponents.Type.SCHEME, m.group(1));
+ result.put(UriComponents.Type.AUTHORITY, m.group(2));
+ result.put(UriComponents.Type.USER_INFO, m.group(4));
+ result.put(UriComponents.Type.HOST, m.group(5));
+ result.put(UriComponents.Type.PORT, m.group(7));
+ result.put(UriComponents.Type.PATH, m.group(8));
+ result.put(UriComponents.Type.QUERY, m.group(10));
- /**
- * Returns the query.
- *
- * @return the query. Can be {@code null}.
- */
- public String getQuery() {
- return get(Type.QUERY);
- }
+ return new UriComponents(result, false);
+ }
+ else {
+ throw new IllegalArgumentException("[" + httpUrl + "] is not a valid HTTP URL");
+ }
+ }
- /**
- * Sets the query.
- *
- * @param query the query. Can be {@code null}.
- */
- public void setQuery(String query) {
- put(Type.QUERY, query);
- }
+ /**
+ * Creates a new {@code UriComponents} object from the given {@code URI}.
+ *
+ * @param uri the URI
+ * @return the URI components of the URI
+ */
+ public static UriComponents fromUri(URI uri) {
+ Assert.notNull(uri, "'uri' must not be null");
- /**
- * Returns the map of query parameters.
- *
- * @return the query parameters. Empty if no query has been set.
- */
- public MultiValueMap getQueryParams() {
- MultiValueMap result = new LinkedMultiValueMap();
- String query = getQuery();
- if (query != null) {
- Matcher m = QUERY_PARAM_PATTERN.matcher(query);
- while (m.find()) {
- String name = m.group(1);
- String value = m.group(2);
- result.add(name, value);
- }
- }
- return result;
- }
+ Map uriComponents = new EnumMap(Type.class);
+ if (uri.getScheme() != null) {
+ uriComponents.put(Type.SCHEME, uri.getScheme());
+ }
+ if (uri.getRawAuthority() != null) {
+ uriComponents.put(Type.AUTHORITY, uri.getRawAuthority());
+ }
+ if (uri.getRawUserInfo() != null) {
+ uriComponents.put(Type.USER_INFO, uri.getRawUserInfo());
+ }
+ if (uri.getHost() != null) {
+ uriComponents.put(Type.HOST, uri.getHost());
+ }
+ if (uri.getPort() != -1) {
+ uriComponents.put(Type.PORT, Integer.toString(uri.getPort()));
+ }
+ if (uri.getRawPath() != null) {
+ uriComponents.put(Type.PATH, uri.getRawPath());
+ }
+ if (uri.getRawQuery() != null) {
+ uriComponents.put(Type.QUERY, uri.getRawQuery());
+ }
+ if (uri.getRawFragment() != null) {
+ uriComponents.put(Type.FRAGMENT, uri.getRawFragment());
+ }
+ return new UriComponents(uriComponents, true);
+ }
- /**
- * Sets the query parameters. An empty or {@code null} value resets the query to an empty value.
- *
- * @param queryParams the query parameters
- */
- public void setQueryParams(MultiValueMap queryParams) {
- if (!CollectionUtils.isEmpty(queryParams)) {
- StringBuilder queryBuilder = new StringBuilder();
- for (Iterator>> entryIterator = queryParams.entrySet().iterator();
- entryIterator.hasNext(); ) {
- Entry> entry = entryIterator.next();
- String name = entry.getKey();
- List values = entry.getValue();
- if (CollectionUtils.isEmpty(values) || (values.size() == 1 && values.get(0) == null)) {
- queryBuilder.append(name);
- }
- else {
- for (Iterator valueIterator = values.iterator(); valueIterator.hasNext(); ) {
- String value = valueIterator.next();
- queryBuilder.append(name);
- queryBuilder.append('=');
- queryBuilder.append(value);
- if (valueIterator.hasNext()) {
- queryBuilder.append('&');
- }
- }
- }
- if (entryIterator.hasNext()) {
- queryBuilder.append('&');
- }
- }
- setQuery(queryBuilder.toString());
- }
- else {
- setQuery(null);
- }
- }
- /**
- * Returns the fragment.
- *
- * @return the fragment. Can be {@code null}.
- */
- public String getFragment() {
- return get(Type.FRAGMENT);
- }
+ /**
+ * Creates an instance of the {@code UriComponents} object from the given components. All the given arguments
+ * can be {@code null} and are considered to be unencoded.
+ */
+ public static UriComponents fromUriComponents(String scheme,
+ String authority,
+ String userInfo,
+ String host,
+ String port,
+ String path,
+ String query,
+ String fragment) {
+ return fromUriComponents(scheme, authority, userInfo, host, port, path, query, fragment, false);
+ }
- /**
- * Sets the fragment.
- *
- * @param fragment the fragment. Can be {@code null}.
- */
- public void setFragment(String fragment) {
- put(Type.FRAGMENT, fragment);
- }
+ /**
+ * Creates an instance of the {@code UriComponents} object from the given components. All the given arguments
+ * can be {@code null}.
+ *
+ * @param encoded {@code true} if the arguments are encoded; {@code false} otherwise
+ */
+ public static UriComponents fromUriComponents(String scheme,
+ String authority,
+ String userInfo,
+ String host,
+ String port,
+ String path,
+ String query,
+ String fragment,
+ boolean encoded) {
+ Map uriComponents = new EnumMap(Type.class);
+ if (scheme != null) {
+ uriComponents.put(Type.SCHEME, scheme);
+ }
+ if (authority != null) {
+ uriComponents.put(Type.AUTHORITY, authority);
+ }
+ if (userInfo != null) {
+ uriComponents.put(Type.USER_INFO, userInfo);
+ }
+ if (host != null) {
+ uriComponents.put(Type.HOST, host);
+ }
+ if (port != null) {
+ uriComponents.put(Type.PORT, port);
+ }
+ if (path != null) {
+ uriComponents.put(Type.PATH, path);
+ }
+ if (query != null) {
+ uriComponents.put(Type.QUERY, query);
+ }
+ if (fragment != null) {
+ uriComponents.put(Type.FRAGMENT, fragment);
+ }
+ return new UriComponents(uriComponents, encoded);
+ }
- // Map implementation
+ /**
+ * Creates an instance of the {@code UriComponents} object that contains the given components map.
+ *
+ * @param uriComponents the component to initialize with
+ */
+ public static UriComponents fromUriComponentMap(Map uriComponents) {
+ boolean encoded;
+ if (uriComponents instanceof UriComponents) {
+ encoded = ((UriComponents) uriComponents).encoded;
+ }
+ else {
+ encoded = false;
+ }
+ return new UriComponents(uriComponents, encoded);
+ }
- public int size() {
- return this.uriComponents.size();
- }
+ /**
+ * Creates an instance of the {@code UriComponents} object that contains the given components map.
+ *
+ * @param uriComponents the component to initialize with
+ * @param encoded whether the components are encpded
+ */
+ public static UriComponents fromUriComponentMap(Map uriComponents, boolean encoded) {
+ return new UriComponents(uriComponents, encoded);
+ }
- public boolean isEmpty() {
- return this.uriComponents.isEmpty();
- }
+ // component getters
- public boolean containsKey(Object key) {
- return this.uriComponents.containsKey(key);
- }
+ /**
+ * Returns the scheme.
+ *
+ * @return the scheme. Can be {@code null}.
+ */
+ public String getScheme() {
+ return get(Type.SCHEME);
+ }
- public boolean containsValue(Object value) {
- return this.uriComponents.containsValue(value);
- }
+ /**
+ * Returns the authority.
+ *
+ * @return the authority. Can be {@code null}.
+ */
+ public String getAuthority() {
+ return get(Type.AUTHORITY);
+ }
- public String get(Object key) {
- return this.uriComponents.get(key);
- }
+ /**
+ * Returns the user info.
+ *
+ * @return the user info. Can be {@code null}.
+ */
+ public String getUserInfo() {
+ return get(Type.USER_INFO);
+ }
- public String put(Type key, String value) {
- return this.uriComponents.put(key, value);
- }
+ /**
+ * Returns the host.
+ *
+ * @return the host. Can be {@code null}.
+ */
+ public String getHost() {
+ return get(Type.HOST);
+ }
- public String remove(Object key) {
- return this.uriComponents.remove(key);
- }
+ /**
+ * Returns the port as string.
+ *
+ * @return the port as string. Can be {@code null}.
+ */
+ public String getPort() {
+ return get(Type.PORT);
+ }
- public void putAll(Map extends Type, ? extends String> m) {
- this.uriComponents.putAll(m);
- }
+ /**
+ * Returns the port as integer. Returns {@code -1} if no port has been set.
+ *
+ * @return the port the port as integer
+ */
+ public int getPortAsInteger() {
+ String port = getPort();
+ return port != null ? Integer.parseInt(port) : -1;
+ }
- public void clear() {
- this.uriComponents.clear();
- }
+ /**
+ * Returns the path.
+ *
+ * @return the path. Can be {@code null}.
+ */
+ public String getPath() {
+ return get(Type.PATH);
+ }
- public Set keySet() {
- return this.uriComponents.keySet();
- }
+ /**
+ * Returns the list of path segments.
+ *
+ * @return the path segments. Empty if no path has been set.
+ */
+ public List getPathSegments() {
+ String path = getPath();
+ if (path != null) {
+ return Arrays.asList(StringUtils.tokenizeToStringArray(path, PATH_DELIMITER));
+ }
+ else {
+ return Collections.emptyList();
+ }
+ }
- public Collection values() {
- return this.uriComponents.values();
- }
+ /**
+ * Returns the query.
+ *
+ * @return the query. Can be {@code null}.
+ */
+ public String getQuery() {
+ return get(Type.QUERY);
+ }
- public Set> entrySet() {
- return this.uriComponents.entrySet();
- }
+ /**
+ * Returns the map of query parameters.
+ *
+ * @return the query parameters. Empty if no query has been set.
+ */
+ public MultiValueMap getQueryParams() {
+ MultiValueMap result = new LinkedMultiValueMap();
+ String query = getQuery();
+ if (query != null) {
+ Matcher m = QUERY_PARAM_PATTERN.matcher(query);
+ while (m.find()) {
+ String name = m.group(1);
+ String value = m.group(2);
+ result.add(name, value);
+ }
+ }
+ return result;
+ }
- @Override
- public boolean equals(Object o) {
- if (this == o) {
- return true;
- }
- if (o instanceof UriComponents) {
- UriComponents other = (UriComponents) o;
- return this.uriComponents.equals(other.uriComponents);
- }
- return false;
- }
+ /**
+ * Returns the fragment.
+ *
+ * @return the fragment. Can be {@code null}.
+ */
+ public String getFragment() {
+ return get(Type.FRAGMENT);
+ }
- @Override
- public int hashCode() {
- return this.uriComponents.hashCode();
- }
+ // other functionality
- @Override
- public String toString() {
- return this.uriComponents.toString();
- }
+ public UriComponents encode() {
+ try {
+ return encode(DEFAULT_ENCODING);
+ }
+ catch (UnsupportedEncodingException e) {
+ throw new InternalError("\"" + DEFAULT_ENCODING + "\" not supported");
+ }
+ }
- // inner types
+ /**
+ * Encodes all URI components using their specific encoding rules, and returns the result as a new
+ * {@code UriComponents} instance.
+ *
+ * @param encoding the encoding of the values contained in this map
+ * @return the encoded uri components
+ * @throws UnsupportedEncodingException if the given encoding is not supported
+ */
+ public UriComponents encode(String encoding) throws UnsupportedEncodingException {
+ Assert.hasLength(encoding, "'encoding' must not be empty");
- /**
- * Enumeration used to identify the parts of a URI.
- *
- * Contains methods to indicate whether a given character is valid in a specific URI component.
- *
- * @author Arjen Poutsma
- * @see RFC 3986
- */
- public static enum Type {
+ if (encoded) {
+ return this;
+ }
- SCHEME {
- @Override
- public boolean isAllowed(int c) {
- return isAlpha(c) || isDigit(c) || '+' == c || '-' == c || '.' == c;
- }
- },
- AUTHORITY {
- @Override
- public boolean isAllowed(int c) {
- return isUnreserved(c) || isSubDelimiter(c) || ':' == c || '@' == c;
- }
- },
- USER_INFO {
- @Override
- public boolean isAllowed(int c) {
- return isUnreserved(c) || isSubDelimiter(c) || ':' == c;
- }
- },
- HOST {
- @Override
- public boolean isAllowed(int c) {
- return isUnreserved(c) || isSubDelimiter(c);
- }
- },
- PORT {
- @Override
- public boolean isAllowed(int c) {
- return isDigit(c);
- }
- },
- PATH {
- @Override
- public boolean isAllowed(int c) {
- return isPchar(c) || '/' == c;
- }
- },
- PATH_SEGMENT {
- @Override
- public boolean isAllowed(int c) {
- return isPchar(c);
- }
- },
- QUERY {
- @Override
- public boolean isAllowed(int c) {
- return isPchar(c) || '/' == c || '?' == c;
- }
- },
- QUERY_PARAM {
- @Override
- public boolean isAllowed(int c) {
- if ('=' == c || '+' == c || '&' == c) {
- return false;
- }
- else {
- return isPchar(c) || '/' == c || '?' == c;
- }
- }
- },
- FRAGMENT {
- @Override
- public boolean isAllowed(int c) {
- return isPchar(c) || '/' == c || '?' == c;
- }
- };
+ final Map encoded = new EnumMap(Type.class);
+ for (Entry entry : uriComponents.entrySet()) {
+ Type key = entry.getKey();
+ String value = entry.getValue();
+ if (value != null) {
+ value = encodeUriComponent(value, encoding, key);
+ }
+ encoded.put(key, value);
+ }
+ return new UriComponents(encoded, true);
+ }
- /**
- * Indicates whether the given character is allowed in this URI component.
- *
- * @param c the character
- * @return {@code true} if the character is allowed; {@code false} otherwise
- */
- public abstract boolean isAllowed(int c);
+ /**
+ * Encodes the given source into an encoded String using the rules specified by the given component and with the
+ * given options.
+ *
+ * @param source the source string
+ * @param encoding the encoding of the source string
+ * @param uriComponent the URI component for the source
+ * @param encodingOptions the options used when encoding. May be {@code null}.
+ * @return the encoded URI
+ * @throws IllegalArgumentException when the given uri parameter is not a valid URI
+ * @see EncodingOption
+ */
+ static String encodeUriComponent(String source,
+ String encoding,
+ UriComponents.Type uriComponent) throws UnsupportedEncodingException {
+ Assert.hasLength(encoding, "'encoding' must not be empty");
- /**
- * Indicates whether the given character is in the {@code ALPHA} set.
- *
- * @see RFC 3986, appendix A
- */
- protected boolean isAlpha(int c) {
- return c >= 'a' && c <= 'z' || c >= 'A' && c <= 'Z';
- }
+ byte[] bytes = encodeInternal(source.getBytes(encoding), uriComponent);
+ return new String(bytes, "US-ASCII");
+ }
- /**
- * Indicates whether the given character is in the {@code DIGIT} set.
- *
- * @see RFC 3986, appendix A
- */
- protected boolean isDigit(int c) {
- return c >= '0' && c <= '9';
- }
+ private static byte[] encodeInternal(byte[] source,
+ UriComponents.Type uriComponent) {
+ Assert.notNull(source, "'source' must not be null");
+ Assert.notNull(uriComponent, "'uriComponent' must not be null");
- /**
- * Indicates whether the given character is in the {@code gen-delims} set.
- *
- * @see RFC 3986, appendix A
- */
- protected boolean isGenericDelimiter(int c) {
- return ':' == c || '/' == c || '?' == c || '#' == c || '[' == c || ']' == c || '@' == c;
- }
+ ByteArrayOutputStream bos = new ByteArrayOutputStream(source.length);
+ for (int i = 0; i < source.length; i++) {
+ int b = source[i];
+ if (b < 0) {
+ b += 256;
+ }
+ if (uriComponent.isAllowed(b)) {
+ bos.write(b);
+ }
+ else {
+ bos.write('%');
- /**
- * Indicates whether the given character is in the {@code sub-delims} set.
- *
- * @see RFC 3986, appendix A
- */
- protected boolean isSubDelimiter(int c) {
- return '!' == c || '$' == c || '&' == c || '\'' == c || '(' == c || ')' == c || '*' == c || '+' == c ||
- ',' == c || ';' == c || '=' == c;
- }
+ char hex1 = Character.toUpperCase(Character.forDigit((b >> 4) & 0xF, 16));
+ char hex2 = Character.toUpperCase(Character.forDigit(b & 0xF, 16));
- /**
- * Indicates whether the given character is in the {@code reserved} set.
- *
- * @see RFC 3986, appendix A
- */
- protected boolean isReserved(char c) {
- return isGenericDelimiter(c) || isReserved(c);
- }
+ bos.write(hex1);
+ bos.write(hex2);
+ }
+ }
+ return bos.toByteArray();
+ }
- /**
- * Indicates whether the given character is in the {@code unreserved} set.
- *
- * @see RFC 3986, appendix A
- */
- protected boolean isUnreserved(int c) {
- return isAlpha(c) || isDigit(c) || '-' == c || '.' == c || '_' == c || '~' == c;
- }
- /**
- * Indicates whether the given character is in the {@code pchar} set.
- *
- * @see RFC 3986, appendix A
- */
- protected boolean isPchar(int c) {
- return isUnreserved(c) || isSubDelimiter(c) || ':' == c || '@' == c;
- }
+ /**
+ * Returns a URI string from this {@code UriComponents} instance.
+ *
+ * @return the URI created from the given components
+ */
+ public String toUriString() {
+ StringBuilder uriBuilder = new StringBuilder();
+
+ if (getScheme() != null) {
+ uriBuilder.append(getScheme());
+ uriBuilder.append(':');
+ }
+
+ if (getUserInfo() != null || getHost() != null || getPort() != null) {
+ uriBuilder.append("//");
+ if (getUserInfo() != null) {
+ uriBuilder.append(getUserInfo());
+ uriBuilder.append('@');
+ }
+ if (getHost() != null) {
+ uriBuilder.append(getHost());
+ }
+ if (getPort() != null) {
+ uriBuilder.append(':');
+ uriBuilder.append(getPort());
+ }
+ }
+ else if (getAuthority() != null) {
+ uriBuilder.append("//");
+ uriBuilder.append(getAuthority());
+ }
+
+ if (getPath() != null) {
+ uriBuilder.append(getPath());
+ }
+
+ if (getQuery() != null) {
+ uriBuilder.append('?');
+ uriBuilder.append(getQuery());
+ }
+
+ if (getFragment() != null) {
+ uriBuilder.append('#');
+ uriBuilder.append(getFragment());
+ }
+
+ return uriBuilder.toString();
+ }
+
+ /**
+ * Returns a {@code URI} from this {@code UriComponents} instance.
+ *
+ * @return the URI created from the given components
+ */
+ public URI toUri() {
+ try {
+ if (encoded) {
+ return new URI(toUriString());
+ }
+ else {
+ if (getUserInfo() != null || getHost() != null || getPort() != null) {
+ return new URI(getScheme(), getUserInfo(), getHost(), getPortAsInteger(), getPath(), getQuery(),
+ getFragment());
+ }
+ else {
+ return new URI(getScheme(), getAuthority(), getPath(), getQuery(), getFragment());
+ }
+ }
+ }
+ catch (URISyntaxException ex) {
+ throw new IllegalStateException("Could not create URI object: " + ex.getMessage(), ex);
+ }
+ }
+
+ // Map implementation
+
+ public int size() {
+ return this.uriComponents.size();
+ }
+
+ public boolean isEmpty() {
+ return this.uriComponents.isEmpty();
+ }
+
+ public boolean containsKey(Object key) {
+ return this.uriComponents.containsKey(key);
+ }
+
+ public boolean containsValue(Object value) {
+ return this.uriComponents.containsValue(value);
+ }
+
+ public String get(Object key) {
+ return this.uriComponents.get(key);
+ }
+
+ public String put(Type key, String value) {
+ return this.uriComponents.put(key, value);
+ }
+
+ public String remove(Object key) {
+ return this.uriComponents.remove(key);
+ }
+
+ public void putAll(Map extends Type, ? extends String> m) {
+ this.uriComponents.putAll(m);
+ }
+
+ public void clear() {
+ this.uriComponents.clear();
+ }
+
+ public Set keySet() {
+ return this.uriComponents.keySet();
+ }
+
+ public Collection values() {
+ return this.uriComponents.values();
+ }
+
+ public Set> entrySet() {
+ return this.uriComponents.entrySet();
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (o instanceof UriComponents) {
+ UriComponents other = (UriComponents) o;
+ return this.uriComponents.equals(other.uriComponents);
+ }
+ return false;
+ }
+
+ @Override
+ public int hashCode() {
+ return this.uriComponents.hashCode();
+ }
+
+ @Override
+ public String toString() {
+ return this.uriComponents.toString();
+ }
+
+ // inner types
+
+ /**
+ * Enumeration used to identify the parts of a URI.
+ *
+ * Contains methods to indicate whether a given character is valid in a specific URI component.
+ *
+ * @author Arjen Poutsma
+ * @see RFC 3986
+ */
+ public static enum Type {
+
+ SCHEME {
+ @Override
+ public boolean isAllowed(int c) {
+ return isAlpha(c) || isDigit(c) || '+' == c || '-' == c || '.' == c;
+ }
+ },
+ AUTHORITY {
+ @Override
+ public boolean isAllowed(int c) {
+ return isUnreserved(c) || isSubDelimiter(c) || ':' == c || '@' == c;
+ }
+ },
+ USER_INFO {
+ @Override
+ public boolean isAllowed(int c) {
+ return isUnreserved(c) || isSubDelimiter(c) || ':' == c;
+ }
+ },
+ HOST {
+ @Override
+ public boolean isAllowed(int c) {
+ return isUnreserved(c) || isSubDelimiter(c);
+ }
+ },
+ PORT {
+ @Override
+ public boolean isAllowed(int c) {
+ return isDigit(c);
+ }
+ },
+ PATH {
+ @Override
+ public boolean isAllowed(int c) {
+ return isPchar(c) || '/' == c;
+ }
+ },
+ PATH_SEGMENT {
+ @Override
+ public boolean isAllowed(int c) {
+ return isPchar(c);
+ }
+ },
+ QUERY {
+ @Override
+ public boolean isAllowed(int c) {
+ return isPchar(c) || '/' == c || '?' == c;
+ }
+ },
+ QUERY_PARAM {
+ @Override
+ public boolean isAllowed(int c) {
+ if ('=' == c || '+' == c || '&' == c) {
+ return false;
+ }
+ else {
+ return isPchar(c) || '/' == c || '?' == c;
+ }
+ }
+ },
+ FRAGMENT {
+ @Override
+ public boolean isAllowed(int c) {
+ return isPchar(c) || '/' == c || '?' == c;
+ }
+ };
+
+ /**
+ * Indicates whether the given character is allowed in this URI component.
+ *
+ * @param c the character
+ * @return {@code true} if the character is allowed; {@code false} otherwise
+ */
+ public abstract boolean isAllowed(int c);
+
+ /**
+ * Indicates whether the given character is in the {@code ALPHA} set.
+ *
+ * @see RFC 3986, appendix A
+ */
+ protected boolean isAlpha(int c) {
+ return c >= 'a' && c <= 'z' || c >= 'A' && c <= 'Z';
+ }
+
+ /**
+ * Indicates whether the given character is in the {@code DIGIT} set.
+ *
+ * @see RFC 3986, appendix A
+ */
+ protected boolean isDigit(int c) {
+ return c >= '0' && c <= '9';
+ }
+
+ /**
+ * Indicates whether the given character is in the {@code gen-delims} set.
+ *
+ * @see RFC 3986, appendix A
+ */
+ protected boolean isGenericDelimiter(int c) {
+ return ':' == c || '/' == c || '?' == c || '#' == c || '[' == c || ']' == c || '@' == c;
+ }
+
+ /**
+ * Indicates whether the given character is in the {@code sub-delims} set.
+ *
+ * @see RFC 3986, appendix A
+ */
+ protected boolean isSubDelimiter(int c) {
+ return '!' == c || '$' == c || '&' == c || '\'' == c || '(' == c || ')' == c || '*' == c || '+' == c ||
+ ',' == c || ';' == c || '=' == c;
+ }
+
+ /**
+ * Indicates whether the given character is in the {@code reserved} set.
+ *
+ * @see RFC 3986, appendix A
+ */
+ protected boolean isReserved(char c) {
+ return isGenericDelimiter(c) || isReserved(c);
+ }
+
+ /**
+ * Indicates whether the given character is in the {@code unreserved} set.
+ *
+ * @see RFC 3986, appendix A
+ */
+ protected boolean isUnreserved(int c) {
+ return isAlpha(c) || isDigit(c) || '-' == c || '.' == c || '_' == c || '~' == c;
+ }
+
+ /**
+ * Indicates whether the given character is in the {@code pchar} set.
+ *
+ * @see RFC 3986, appendix A
+ */
+ protected boolean isPchar(int c) {
+ return isUnreserved(c) || isSubDelimiter(c) || ':' == c || '@' == c;
+ }
+
+ }
- }
}
diff --git a/org.springframework.web/src/main/java/org/springframework/web/util/UriComponentsBuilder.java b/org.springframework.web/src/main/java/org/springframework/web/util/UriComponentsBuilder.java
new file mode 100644
index 00000000000..b94de723cb9
--- /dev/null
+++ b/org.springframework.web/src/main/java/org/springframework/web/util/UriComponentsBuilder.java
@@ -0,0 +1,323 @@
+/*
+ * 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.net.URI;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+import org.springframework.util.Assert;
+import org.springframework.util.ObjectUtils;
+import org.springframework.util.StringUtils;
+
+/**
+ * Builder for {@link UriComponents}.
+ *
+ * Typical usage involves:
+ *
+ * - Create a {@code UriComponentsBuilder} with one of the static factory methods (such as
+ * {@link #fromPath(String)} or {@link #fromUri(URI)})
+ * - Set the various URI components through the respective methods ({@link #scheme(String)},
+ * {@link #userInfo(String)}, {@link #host(String)}, {@link #port(int)}, {@link #path(String)},
+ * {@link #pathSegment(String...)}, {@link #queryParam(String, Object...)}, and
+ * {@link #fragment(String)}.
+ * - Build the {@link UriComponents} instance with the {@link #build()} method.
+ *
+ *
+ * @author Arjen Poutsma
+ * @see #newInstance()
+ * @see #fromPath(String)
+ * @see #fromUri(URI)
+ * @since 3.1
+ */
+public class UriComponentsBuilder {
+
+ private static final char PATH_DELIMITER = '/';
+
+ private String scheme;
+
+ private String userInfo;
+
+ private String host;
+
+ private int port = -1;
+
+ private final List pathSegments = new ArrayList();
+
+ private final StringBuilder queryBuilder = new StringBuilder();
+
+ private String fragment;
+
+ /**
+ * Default constructor. Protected to prevent direct instantiation.
+ *
+ * @see #newInstance()
+ * @see #fromPath(String)
+ * @see #fromUri(URI)
+ */
+ protected UriComponentsBuilder() {
+ }
+
+ // Factory methods
+
+ /**
+ * Returns a new, empty URI builder.
+ *
+ * @return the new {@code UriComponentsBuilder}
+ */
+ public static UriComponentsBuilder newInstance() {
+ return new UriComponentsBuilder();
+ }
+
+ /**
+ * Returns a URI builder that is initialized with the given path.
+ *
+ * @param path the path to initialize with
+ * @return the new {@code UriComponentsBuilder}
+ */
+ public static UriComponentsBuilder fromPath(String path) {
+ UriComponentsBuilder builder = new UriComponentsBuilder();
+ builder.path(path);
+ return builder;
+ }
+
+ /**
+ * Returns a URI builder that is initialized with the given {@code URI}.
+ *
+ * @param uri the URI to initialize with
+ * @return the new {@code UriComponentsBuilder}
+ */
+ public static UriComponentsBuilder fromUri(URI uri) {
+ UriComponentsBuilder builder = new UriComponentsBuilder();
+ builder.uri(uri);
+ return builder;
+ }
+
+ // build methods
+
+ /**
+ * Builds a {@code UriComponents} instance from the various components contained in this builder.
+ *
+ * @return the URI components
+ */
+ public UriComponents build() {
+ String port = portAsString();
+ String path = pathAsString();
+ String query = queryAsString();
+ return UriComponents.fromUriComponents(scheme, null, userInfo, host, port, path, query, fragment, false);
+ }
+
+ // URI components methods
+
+ /**
+ * Initializes all components of this URI builder with the components of the given URI.
+ *
+ * @param uri the URI
+ * @return this UriComponentsBuilder
+ */
+ public UriComponentsBuilder uri(URI uri) {
+ Assert.notNull(uri, "'uri' must not be null");
+ Assert.isTrue(!uri.isOpaque(), "Opaque URI [" + uri + "] not supported");
+
+ this.scheme = uri.getScheme();
+
+ if (uri.getUserInfo() != null) {
+ this.userInfo = uri.getUserInfo();
+ }
+ if (uri.getHost() != null) {
+ this.host = uri.getHost();
+ }
+ if (uri.getPort() != -1) {
+ this.port = uri.getPort();
+ }
+ if (StringUtils.hasLength(uri.getPath())) {
+ String[] pathSegments = StringUtils.tokenizeToStringArray(uri.getPath(), "/");
+
+ this.pathSegments.clear();
+ Collections.addAll(this.pathSegments, pathSegments);
+ }
+ if (StringUtils.hasLength(uri.getQuery())) {
+ this.queryBuilder.setLength(0);
+ this.queryBuilder.append(uri.getQuery());
+ }
+ if (uri.getFragment() != null) {
+ this.fragment = uri.getFragment();
+ }
+ return this;
+ }
+
+ /**
+ * Sets the URI scheme. The given scheme may contain URI template variables, and may also be {@code null} to clear the
+ * scheme of this builder.
+ *
+ * @param scheme the URI scheme
+ * @return this UriComponentsBuilder
+ */
+ public UriComponentsBuilder scheme(String scheme) {
+ this.scheme = scheme;
+ return this;
+ }
+
+ /**
+ * Sets the URI user info. The given user info may contain URI template variables, and may also be {@code null} to
+ * clear the user info of this builder.
+ *
+ * @param userInfo the URI user info
+ * @return this UriComponentsBuilder
+ */
+ public UriComponentsBuilder userInfo(String userInfo) {
+ this.userInfo = userInfo;
+ return this;
+ }
+
+ /**
+ * Sets the URI host. The given host may contain URI template variables, and may also be {@code null} to clear the host
+ * of this builder.
+ *
+ * @param host the URI host
+ * @return this UriComponentsBuilder
+ */
+ public UriComponentsBuilder host(String host) {
+ this.host = host;
+ return this;
+ }
+
+ /**
+ * Sets the URI port. Passing {@code -1} will clear the port of this builder.
+ *
+ * @param port the URI port
+ * @return this UriComponentsBuilder
+ */
+ public UriComponentsBuilder port(int port) {
+ Assert.isTrue(port >= -1, "'port' must not be < -1");
+ this.port = port;
+ return this;
+ }
+
+ private String portAsString() {
+ return this.port != -1 ? Integer.toString(this.port) : null;
+ }
+
+ /**
+ * Appends the given path to the existing path of this builder. The given path may contain URI template variables.
+ *
+ * @param path the URI path
+ * @return this UriComponentsBuilder
+ */
+ public UriComponentsBuilder path(String path) {
+ Assert.notNull(path, "path must not be null");
+
+ String[] pathSegments = StringUtils.tokenizeToStringArray(path, "/");
+ return pathSegment(pathSegments);
+ }
+
+ private String pathAsString() {
+ if (!pathSegments.isEmpty()) {
+ StringBuilder pathBuilder = new StringBuilder();
+ for (String pathSegment : pathSegments) {
+ boolean startsWithSlash = pathSegment.charAt(0) == PATH_DELIMITER;
+ boolean endsWithSlash =
+ pathBuilder.length() > 0 && pathBuilder.charAt(pathBuilder.length() - 1) == PATH_DELIMITER;
+
+ if (!endsWithSlash && !startsWithSlash) {
+ pathBuilder.append('/');
+ }
+ else if (endsWithSlash && startsWithSlash) {
+ pathSegment = pathSegment.substring(1);
+ }
+ pathBuilder.append(pathSegment);
+ }
+ return pathBuilder.toString();
+ }
+ else {
+ return null;
+ }
+ }
+
+
+ /**
+ * Appends the given path segments to the existing path of this builder. Each given path segments may contain URI
+ * template variables.
+ *
+ * @param segments the URI path segments
+ * @return this UriComponentsBuilder
+ */
+ public UriComponentsBuilder pathSegment(String... segments) throws IllegalArgumentException {
+ Assert.notNull(segments, "'segments' must not be null");
+ Collections.addAll(this.pathSegments, segments);
+
+ return this;
+ }
+
+ /**
+ * Appends the given query parameter to the existing query parameters. The given name or any of the values may contain
+ * URI template variables. If no values are given, the resulting URI will contain the query parameter name only (i.e.
+ * {@code ?foo} instead of {@code ?foo=bar}.
+ *
+ * @param name the query parameter name
+ * @param values the query parameter values
+ * @return this UriComponentsBuilder
+ */
+ public UriComponentsBuilder queryParam(String name, Object... values) {
+ Assert.notNull(name, "'name' must not be null");
+
+ if (ObjectUtils.isEmpty(values)) {
+ if (queryBuilder.length() != 0) {
+ queryBuilder.append('&');
+ }
+ queryBuilder.append(name);
+ }
+ else {
+ for (Object value : values) {
+ if (queryBuilder.length() != 0) {
+ queryBuilder.append('&');
+ }
+ queryBuilder.append(name);
+
+ if (value != null) {
+ queryBuilder.append('=');
+ queryBuilder.append(value.toString());
+ }
+ }
+ }
+ return this;
+ }
+
+ private String queryAsString() {
+ return queryBuilder.length() != 0 ? queryBuilder.toString() : null;
+ }
+
+ /**
+ * Sets the URI fragment. The given fragment may contain URI template variables, and may also be {@code null} to clear
+ * the fragment of this builder.
+ *
+ * @param fragment the URI fragment
+ * @return this UriComponentsBuilder
+ */
+ public UriComponentsBuilder fragment(String fragment) {
+ if (fragment != null) {
+ Assert.hasLength(fragment, "'fragment' must not be empty");
+ this.fragment = fragment;
+ }
+ else {
+ this.fragment = null;
+ }
+ return this;
+ }
+
+}
diff --git a/org.springframework.web/src/main/java/org/springframework/web/util/UriTemplate.java b/org.springframework.web/src/main/java/org/springframework/web/util/UriTemplate.java
index 6026020b976..832c45dfd78 100644
--- a/org.springframework.web/src/main/java/org/springframework/web/util/UriTemplate.java
+++ b/org.springframework.web/src/main/java/org/springframework/web/util/UriTemplate.java
@@ -21,10 +21,13 @@ import java.io.UnsupportedEncodingException;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.Collections;
+import java.util.EnumMap;
+import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
+import java.util.Set;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
@@ -49,16 +52,17 @@ public class UriTemplate implements Serializable {
/** Replaces template variables in the URI template. */
private static final String DEFAULT_VARIABLE_PATTERN = "(.*)";
-
private final List variableNames;
private final Pattern matchPattern;
private final String uriTemplate;
+ private final UriComponents uriComponents;
+
/**
- * Construct a new {@link UriTemplate} with the given URI String.
+ * Construct a new {@code UriTemplate} with the given URI String.
* @param uriTemplate the URI template string
*/
public UriTemplate(String uriTemplate) {
@@ -66,8 +70,18 @@ public class UriTemplate implements Serializable {
this.uriTemplate = uriTemplate;
this.variableNames = parser.getVariableNames();
this.matchPattern = parser.getMatchPattern();
+ this.uriComponents = UriComponents.fromUriString(uriTemplate);
}
+ public UriTemplate(Map uriComponents) {
+ this.uriComponents = UriComponents.fromUriComponentMap(uriComponents);
+ String uriTemplate = this.uriComponents.toUriString();
+ Parser parser = new Parser(uriTemplate);
+ this.uriTemplate = uriTemplate;
+ this.variableNames = parser.getVariableNames();
+ this.matchPattern = parser.getMatchPattern();
+ }
+
/**
* Return the names of the variables in the template, in order.
* @return the template variable names
@@ -76,6 +90,7 @@ public class UriTemplate implements Serializable {
return this.variableNames;
}
+ // expanding
/**
* Given the Map of variables, expands this template into a URI. The Map keys represent variable names,
@@ -95,7 +110,8 @@ public class UriTemplate implements Serializable {
* or if it does not contain values for all the variable names
*/
public URI expand(Map uriVariables) {
- return encodeUri(expandAsString(uriVariables));
+ UriComponents expandedComponents = expandAsUriComponents(uriVariables, true);
+ return expandedComponents.toUri();
}
/**
@@ -116,20 +132,72 @@ public class UriTemplate implements Serializable {
* @throws IllegalArgumentException if uriVariables is null;
* or if it does not contain values for all the variable names
*/
- public String expandAsString(Map 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(values);
+ public String expandAsString(final Map uriVariables, boolean encode) {
+ UriComponents expandedComponents = expandAsUriComponents(uriVariables, encode);
+ return expandedComponents.toUriString();
}
- /**
+ public UriComponents expandAsUriComponents(final Map uriVariables, boolean encode) {
+ Assert.notNull(uriVariables, "'uriVariables' must not be null");
+ Set variablesSet = new HashSet(this.variableNames);
+ variablesSet.removeAll(uriVariables.keySet());
+ Assert.isTrue(variablesSet.isEmpty(),
+ "'uriVariables' does not contain keys for all variables: " + variablesSet);
+
+ Map expandedComponents = new EnumMap(UriComponents.Type.class);
+
+ for (Map.Entry entry : this.uriComponents.entrySet()) {
+ UriComponents.Type key = entry.getKey();
+ String value = entry.getValue();
+ String expandedValue = expandUriComponent(key, value, uriVariables);
+ expandedComponents.put(key, expandedValue);
+ }
+ UriComponents result = UriComponents.fromUriComponentMap(expandedComponents);
+ if (encode) {
+ result = result.encode();
+ }
+ return result;
+ }
+
+ private String expandUriComponent(UriComponents.Type componentType, String value, Map uriVariables) {
+ if (value == null) {
+ return null;
+ }
+ if (value.indexOf('{') == -1) {
+ return value;
+ }
+ Matcher matcher = NAMES_PATTERN.matcher(value);
+ StringBuffer sb = new StringBuffer();
+ while (matcher.find()) {
+ String match = matcher.group(1);
+ String variableName = getVariableName(match);
+ Object variableValue = uriVariables.get(variableName);
+ String uriVariableValueString = getVariableValueAsString(variableValue);
+ String replacement = Matcher.quoteReplacement(uriVariableValueString);
+ matcher.appendReplacement(sb, replacement);
+ }
+ matcher.appendTail(sb);
+ return sb.toString();
+ }
+
+ private String getVariableName(String match) {
+ int colonIdx = match.indexOf(':');
+ return colonIdx == -1 ? match : match.substring(0, colonIdx);
+ }
+
+ /**
+ * Template method that returns the string representation of the given URI template value.
+ *
+ * Defaults implementation simply calls {@link Object#toString()}, or returns an empty string for {@code null}.
+ *
+ * @param variableValue the URI template variable value
+ * @return the variable value as string
+ */
+ protected String getVariableValueAsString(Object variableValue) {
+ return variableValue != null ? variableValue.toString() : "";
+ }
+
+ /**
* Given an array of variables, expand this template into a full URI. The array represent variable values.
* The order of variables is significant.
*
Example:
@@ -144,7 +212,8 @@ public class UriTemplate implements Serializable {
* or if it does not contain sufficient variables
*/
public URI expand(Object... uriVariableValues) {
- return encodeUri(expandAsString(uriVariableValues));
+ UriComponents expandedComponents = expandAsUriComponents(uriVariableValues, true);
+ return expandedComponents.toUri();
}
/**
@@ -157,42 +226,37 @@ public class UriTemplate implements Serializable {
*
* will print:
http://example.com/hotels/1/bookings/42
*
- * @param uriVariableValues the array of URI variables
- * @return the expanded URI
+ *
+ * @param uriVariableValues the array of URI variables
+ * @return the expanded URI
* @throws IllegalArgumentException if uriVariables is null
* or if it does not contain sufficient variables
*/
- public String expandAsString(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 uriVariableValue = uriVariableValues[i++];
- String uriVariableValueString = getVariableValueAsString(uriVariableValue);
- String replacement = Matcher.quoteReplacement(uriVariableValueString);
- matcher.appendReplacement(uriBuffer, replacement);
- }
- matcher.appendTail(uriBuffer);
- return uriBuffer.toString();
+ public String expandAsString(boolean encode, Object[] uriVariableValues) {
+ UriComponents expandedComponents = expandAsUriComponents(uriVariableValues, encode);
+ return expandedComponents.toUriString();
}
- /**
- * Template method that returns the string representation of the given URI template value.
- *
- * Defaults implementation simply calls {@link Object#toString()}, or returns an empty string for {@code null}.
- *
- * @param variableValue the URI template variable value
- * @return the variable value as string
- */
- protected String getVariableValueAsString(Object variableValue) {
- return variableValue != null ? variableValue.toString() : "";
- }
+ public UriComponents expandAsUriComponents(Object[] uriVariableValues, boolean encode) {
+ 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);
+ }
+ Map uriVariables = new LinkedHashMap(this.variableNames.size());
+
+ for (int i = 0, size = variableNames.size(); i < size; i++) {
+ String variableName = variableNames.get(i);
+ Object variableValue = uriVariableValues[i];
+ uriVariables.put(variableName, variableValue);
+ }
+
+ return expandAsUriComponents(uriVariables, encode);
+ }
+
+
+ // matching
/**
* Indicate whether the given URI matches this template.
@@ -316,4 +380,5 @@ public class UriTemplate implements Serializable {
}
}
+
}
diff --git a/org.springframework.web/src/main/java/org/springframework/web/util/UriUtils.java b/org.springframework.web/src/main/java/org/springframework/web/util/UriUtils.java
index 5f4bb72d6dd..05e45e9b13a 100644
--- a/org.springframework.web/src/main/java/org/springframework/web/util/UriUtils.java
+++ b/org.springframework.web/src/main/java/org/springframework/web/util/UriUtils.java
@@ -18,12 +18,6 @@ package org.springframework.web.util;
import java.io.ByteArrayOutputStream;
import java.io.UnsupportedEncodingException;
-import java.util.Collections;
-import java.util.EnumMap;
-import java.util.Map;
-import java.util.Set;
-import java.util.regex.Matcher;
-import java.util.regex.Pattern;
import org.springframework.util.Assert;
@@ -44,181 +38,6 @@ import org.springframework.util.Assert;
*/
public abstract class UriUtils {
- private static final String DEFAULT_ENCODING = "UTF-8";
-
- private static final String SCHEME_PATTERN = "([^:/?#]+):";
-
- private static final String HTTP_PATTERN = "(http|https):";
-
- private static final String USERINFO_PATTERN = "([^@/]*)";
-
- private static final String HOST_PATTERN = "([^/?#:]*)";
-
- private static final String PORT_PATTERN = "(\\d*)";
-
- private static final String PATH_PATTERN = "([^?#]*)";
-
- private static final String QUERY_PATTERN = "([^#]*)";
-
- private static final String LAST_PATTERN = "(.*)";
-
- // Regex patterns that matches URIs. See RFC 3986, appendix B
- private static final Pattern URI_PATTERN = Pattern.compile(
- "^(" + SCHEME_PATTERN + ")?" + "(//(" + USERINFO_PATTERN + "@)?" + HOST_PATTERN + "(:" + PORT_PATTERN +
- ")?" + ")?" + PATH_PATTERN + "(\\?" + QUERY_PATTERN + ")?" + "(#" + LAST_PATTERN + ")?");
-
- private static final Pattern HTTP_URL_PATTERN = Pattern.compile(
- "^" + HTTP_PATTERN + "(//(" + USERINFO_PATTERN + "@)?" + HOST_PATTERN + "(:" + PORT_PATTERN + ")?" + ")?" +
- PATH_PATTERN + "(\\?" + LAST_PATTERN + ")?");
-
-
- // Parsing
-
- /**
- * Parses the given source URI into a mapping of URI components to string values.
- *
- * @param uri the source URI
- * @return the URI components of the URI
- */
- public static UriComponents parseUriComponents(String uri) {
- Assert.notNull(uri, "'uri' must not be null");
- Matcher m = URI_PATTERN.matcher(uri);
- if (m.matches()) {
- Map result = new EnumMap(UriComponents.Type.class);
-
- result.put(UriComponents.Type.SCHEME, m.group(2));
- result.put(UriComponents.Type.AUTHORITY, m.group(3));
- result.put(UriComponents.Type.USER_INFO, m.group(5));
- result.put(UriComponents.Type.HOST, m.group(6));
- result.put(UriComponents.Type.PORT, m.group(8));
- result.put(UriComponents.Type.PATH, m.group(9));
- result.put(UriComponents.Type.QUERY, m.group(11));
- result.put(UriComponents.Type.FRAGMENT, m.group(13));
-
- return new UriComponents(result);
- }
- else {
- throw new IllegalArgumentException("[" + uri + "] is not a valid URI");
- }
- }
-
- /**
- * Parses the given source HTTP URL into a mapping of URI components to string values.
-
- * Note that the returned map will contain a mapping for
- * {@link org.springframework.web.util.UriComponents.Type#FRAGMENT}, as fragments are not supposed to be sent to the
- * server, but retained by the client.
- *
- * @param httpUrl the source URI
- * @return the URI components of the URI
- */
- public static UriComponents parseHttpUrlComponents(String httpUrl) {
- Assert.notNull(httpUrl, "'httpUrl' must not be null");
- Matcher m = HTTP_URL_PATTERN.matcher(httpUrl);
- if (m.matches()) {
- Map result = new EnumMap(UriComponents.Type.class);
-
- result.put(UriComponents.Type.SCHEME, m.group(1));
- result.put(UriComponents.Type.AUTHORITY, m.group(2));
- result.put(UriComponents.Type.USER_INFO, m.group(4));
- result.put(UriComponents.Type.HOST, m.group(5));
- result.put(UriComponents.Type.PORT, m.group(7));
- result.put(UriComponents.Type.PATH, m.group(8));
- result.put(UriComponents.Type.QUERY, m.group(10));
-
- return new UriComponents(result);
- }
- else {
- throw new IllegalArgumentException("[" + httpUrl + "] is not a valid HTTP URL");
- }
- }
-
- // building
-
- /**
- * Builds a URI from the given URI components. The given map should contain at least one entry.
- *
- * Note that {@link org.springframework.web.util.UriComponents.Type#PATH_SEGMENT} and {@link org.springframework.web.util.UriComponents.Type#QUERY_PARAM} keys (if any)
- * will not be used to build the URI, in favor of {@link org.springframework.web.util.UriComponents.Type#PATH} and {@link org.springframework.web.util.UriComponents.Type#QUERY}
- * respectively.
- *
- * @param uriComponents the components to build the URI out of
- * @return the URI created from the given components
- */
- public static String buildUri(Map uriComponents) {
- Assert.notEmpty(uriComponents, "'uriComponents' must not be empty");
-
- return buildUri(uriComponents.get(UriComponents.Type.SCHEME), uriComponents.get(
- UriComponents.Type.AUTHORITY),
- uriComponents.get(UriComponents.Type.USER_INFO), uriComponents.get(UriComponents.Type.HOST),
- uriComponents.get(UriComponents.Type.PORT), uriComponents.get(UriComponents.Type.PATH),
- uriComponents.get(UriComponents.Type.QUERY), uriComponents.get(UriComponents.Type.FRAGMENT));
- }
-
- /**
- * Builds a URI from the given URI component parameters. All parameters can be {@code null}.
- *
- * @param scheme the scheme
- * @param authority the authority
- * @param userinfo the user info
- * @param host the host
- * @param port the port
- * @param path the path
- * @param query the query
- * @param fragment the fragment
- * @return the URI created from the given components
- */
- public static String buildUri(String scheme,
- String authority,
- String userinfo,
- String host,
- String port,
- String path,
- String query,
- String fragment) {
- StringBuilder uriBuilder = new StringBuilder();
-
- if (scheme != null) {
- uriBuilder.append(scheme);
- uriBuilder.append(':');
- }
-
- if (userinfo != null || host != null || port != null) {
- uriBuilder.append("//");
- if (userinfo != null) {
- uriBuilder.append(userinfo);
- uriBuilder.append('@');
- }
- if (host != null) {
- uriBuilder.append(host);
- }
- if (port != null) {
- uriBuilder.append(':');
- uriBuilder.append(port);
- }
- }
- else if (authority != null) {
- uriBuilder.append("//");
- uriBuilder.append(authority);
- }
-
- if (path != null) {
- uriBuilder.append(path);
- }
-
- if (query != null) {
- uriBuilder.append('?');
- uriBuilder.append(query);
- }
-
- if (fragment != null) {
- uriBuilder.append('#');
- uriBuilder.append(fragment);
- }
-
- return uriBuilder.toString();
- }
-
// encoding
/**
@@ -232,9 +51,10 @@ public abstract class UriUtils {
* @throws UnsupportedEncodingException when the given encoding parameter is not supported
*/
public static String encodeUri(String uri, String encoding) throws UnsupportedEncodingException {
- Map uriComponents = parseUriComponents(uri);
- return encodeUriComponents(uriComponents, encoding);
- }
+ UriComponents uriComponents = UriComponents.fromUriString(uri);
+ UriComponents encoded = uriComponents.encode(encoding);
+ return encoded.toUriString();
+ }
/**
* Encodes the given HTTP URI into an encoded String. All various URI components are encoded according to their
@@ -248,33 +68,9 @@ public abstract class UriUtils {
* @throws UnsupportedEncodingException when the given encoding parameter is not supported
*/
public static String encodeHttpUrl(String httpUrl, String encoding) throws UnsupportedEncodingException {
- Map uriComponents = parseHttpUrlComponents(httpUrl);
- return encodeUriComponents(uriComponents, encoding);
- }
-
- /**
- * Encodes the given source URI components into an encoded String. All various URI components are optional, but encoded
- * according to their respective valid character sets.
- *
- * @param uriComponents the URI components
- * @param encoding the character encoding to encode to
- * @return the encoded URI
- * @throws IllegalArgumentException when the given uri parameter is not a valid URI
- * @throws UnsupportedEncodingException when the given encoding parameter is not supported
- */
- public static String encodeUriComponents(Map uriComponents,
- String encoding) throws UnsupportedEncodingException {
- Assert.notEmpty(uriComponents, "'uriComponents' must not be empty");
- Assert.hasLength(encoding, "'encoding' must not be empty");
-
- Map encodedUriComponents = new EnumMap(UriComponents.Type.class);
- for (Map.Entry entry : uriComponents.entrySet()) {
- if (entry.getValue() != null) {
- String encodedValue = encodeUriComponent(entry.getValue(), encoding, entry.getKey(), null);
- encodedUriComponents.put(entry.getKey(), encodedValue);
- }
- }
- return buildUri(encodedUriComponents);
+ UriComponents uriComponents = UriComponents.fromHttpUrl(httpUrl);
+ UriComponents encoded = uriComponents.encode(encoding);
+ return encoded.toUriString();
}
/**
@@ -303,141 +99,10 @@ public abstract class UriUtils {
String query,
String fragment,
String encoding) throws UnsupportedEncodingException {
- Assert.hasLength(encoding, "'encoding' must not be empty");
+ UriComponents uriComponents = UriComponents.fromUriComponents(scheme, authority, userInfo, host, port, path, query, fragment);
+ UriComponents encoded = uriComponents.encode(encoding);
- if (scheme != null) {
- scheme = encodeScheme(scheme, encoding);
- }
- if (authority != null) {
- authority = encodeAuthority(authority, encoding);
- }
- if (userInfo != null) {
- userInfo = encodeUserInfo(userInfo, encoding);
- }
- if (host != null) {
- host = encodeHost(host, encoding);
- }
- if (port != null) {
- port = encodePort(port, encoding);
- }
- if (path != null) {
- path = encodePath(path, encoding);
- }
- if (query != null) {
- query = encodeQuery(query, encoding);
- }
- if (fragment != null) {
- fragment = encodeFragment(fragment, encoding);
- }
- return buildUri(scheme, authority, userInfo, host, port, path, query, fragment);
- }
-
- /**
- * Encodes the given source into an encoded String using the rules specified by the given component.
- *
- * @param source the source string
- * @param uriComponent the URI component for the source
- * @return the encoded URI
- * @throws IllegalArgumentException when the given uri parameter is not a valid URI
- */
- public static String encodeUriComponent(String source, UriComponents.Type uriComponent) {
- return encodeUriComponent(source, uriComponent, null);
- }
-
- /**
- * Encodes the given source into an encoded String using the rules specified by the given component and with the
- * given options.
- *
- * @param source the source string
- * @param encoding the encoding of the source string
- * @param uriComponent the URI component for the source
- * @param encodingOptions the options used when encoding. May be {@code null}.
- * @return the encoded URI
- * @throws IllegalArgumentException when the given uri parameter is not a valid URI
- * @see EncodingOption
- */
- public static String encodeUriComponent(String source,
- UriComponents.Type uriComponent,
- Set encodingOptions) {
- try {
- return encodeUriComponent(source, DEFAULT_ENCODING, uriComponent, encodingOptions);
- }
- catch (UnsupportedEncodingException ex) {
- throw new InternalError("\"" + DEFAULT_ENCODING + "\" not supported");
- }
- }
-
-
- /**
- * Encodes the given source into an encoded String using the rules specified by the given component.
- *
- * @param source the source string
- * @param encoding the encoding of the source string
- * @param uriComponent the URI component for the source
- * @return the encoded URI
- * @throws IllegalArgumentException when the given uri parameter is not a valid URI
- */
- public static String encodeUriComponent(String source,
- String encoding,
- UriComponents.Type uriComponent) throws UnsupportedEncodingException {
- return encodeUriComponent(source, encoding, uriComponent, null);
- }
-
- /**
- * Encodes the given source into an encoded String using the rules specified by the given component and with the
- * given options.
- *
- * @param source the source string
- * @param encoding the encoding of the source string
- * @param uriComponent the URI component for the source
- * @param encodingOptions the options used when encoding. May be {@code null}.
- * @return the encoded URI
- * @throws IllegalArgumentException when the given uri parameter is not a valid URI
- * @see EncodingOption
- */
- public static String encodeUriComponent(String source,
- String encoding,
- UriComponents.Type uriComponent,
- Set encodingOptions) throws UnsupportedEncodingException {
- Assert.hasLength(encoding, "'encoding' must not be empty");
-
- byte[] bytes = encodeInternal(source.getBytes(encoding), uriComponent, encodingOptions);
- return new String(bytes, "US-ASCII");
- }
-
- private static byte[] encodeInternal(byte[] source,
- UriComponents.Type uriComponent,
- Set encodingOptions) {
- Assert.notNull(source, "'source' must not be null");
- Assert.notNull(uriComponent, "'uriComponent' must not be null");
-
- if (encodingOptions == null) {
- encodingOptions = Collections.emptySet();
- }
-
- ByteArrayOutputStream bos = new ByteArrayOutputStream(source.length);
- for (int i = 0; i < source.length; i++) {
- int b = source[i];
- if (b < 0) {
- b += 256;
- }
- if (uriComponent.isAllowed(b)) {
- bos.write(b);
- }
- else if (encodingOptions.contains(EncodingOption.ALLOW_TEMPLATE_VARS) && (b == '{' || b == '}')) {
- bos.write(b);
- }
- else {
- bos.write('%');
-
- char hex1 = Character.toUpperCase(Character.forDigit((b >> 4) & 0xF, 16));
- char hex2 = Character.toUpperCase(Character.forDigit(b & 0xF, 16));
-
- bos.write(hex1);
- bos.write(hex2);
- }
- }
- return bos.toByteArray();
+ return encoded.toUriString();
}
// encoding convenience methods
@@ -451,7 +116,7 @@ public abstract class UriUtils {
* @throws UnsupportedEncodingException when the given encoding parameter is not supported
*/
public static String encodeScheme(String scheme, String encoding) throws UnsupportedEncodingException {
- return encodeUriComponent(scheme, encoding, UriComponents.Type.SCHEME, null);
+ return UriComponents.encodeUriComponent(scheme, encoding, UriComponents.Type.SCHEME);
}
/**
@@ -463,7 +128,7 @@ public abstract class UriUtils {
* @throws UnsupportedEncodingException when the given encoding parameter is not supported
*/
public static String encodeAuthority(String authority, String encoding) throws UnsupportedEncodingException {
- return encodeUriComponent(authority, encoding, UriComponents.Type.AUTHORITY, null);
+ return UriComponents.encodeUriComponent(authority, encoding, UriComponents.Type.AUTHORITY);
}
/**
@@ -475,7 +140,7 @@ public abstract class UriUtils {
* @throws UnsupportedEncodingException when the given encoding parameter is not supported
*/
public static String encodeUserInfo(String userInfo, String encoding) throws UnsupportedEncodingException {
- return encodeUriComponent(userInfo, encoding, UriComponents.Type.USER_INFO, null);
+ return UriComponents.encodeUriComponent(userInfo, encoding, UriComponents.Type.USER_INFO);
}
/**
@@ -487,7 +152,7 @@ public abstract class UriUtils {
* @throws UnsupportedEncodingException when the given encoding parameter is not supported
*/
public static String encodeHost(String host, String encoding) throws UnsupportedEncodingException {
- return encodeUriComponent(host, encoding, UriComponents.Type.HOST, null);
+ return UriComponents.encodeUriComponent(host, encoding, UriComponents.Type.HOST);
}
/**
@@ -499,7 +164,7 @@ public abstract class UriUtils {
* @throws UnsupportedEncodingException when the given encoding parameter is not supported
*/
public static String encodePort(String port, String encoding) throws UnsupportedEncodingException {
- return encodeUriComponent(port, encoding, UriComponents.Type.PORT, null);
+ return UriComponents.encodeUriComponent(port, encoding, UriComponents.Type.PORT);
}
/**
@@ -511,7 +176,7 @@ public abstract class UriUtils {
* @throws UnsupportedEncodingException when the given encoding parameter is not supported
*/
public static String encodePath(String path, String encoding) throws UnsupportedEncodingException {
- return encodeUriComponent(path, encoding, UriComponents.Type.PATH, null);
+ return UriComponents.encodeUriComponent(path, encoding, UriComponents.Type.PATH);
}
/**
@@ -523,7 +188,7 @@ public abstract class UriUtils {
* @throws UnsupportedEncodingException when the given encoding parameter is not supported
*/
public static String encodePathSegment(String segment, String encoding) throws UnsupportedEncodingException {
- return encodeUriComponent(segment, encoding, UriComponents.Type.PATH_SEGMENT, null);
+ return UriComponents.encodeUriComponent(segment, encoding, UriComponents.Type.PATH_SEGMENT);
}
/**
@@ -535,7 +200,7 @@ public abstract class UriUtils {
* @throws UnsupportedEncodingException when the given encoding parameter is not supported
*/
public static String encodeQuery(String query, String encoding) throws UnsupportedEncodingException {
- return encodeUriComponent(query, encoding, UriComponents.Type.QUERY, null);
+ return UriComponents.encodeUriComponent(query, encoding, UriComponents.Type.QUERY);
}
/**
@@ -547,7 +212,7 @@ public abstract class UriUtils {
* @throws UnsupportedEncodingException when the given encoding parameter is not supported
*/
public static String encodeQueryParam(String queryParam, String encoding) throws UnsupportedEncodingException {
- return encodeUriComponent(queryParam, encoding, UriComponents.Type.QUERY_PARAM, null);
+ return UriComponents.encodeUriComponent(queryParam, encoding, UriComponents.Type.QUERY_PARAM);
}
/**
@@ -559,7 +224,7 @@ public abstract class UriUtils {
* @throws UnsupportedEncodingException when the given encoding parameter is not supported
*/
public static String encodeFragment(String fragment, String encoding) throws UnsupportedEncodingException {
- return encodeUriComponent(fragment, encoding, UriComponents.Type.FRAGMENT, null);
+ return UriComponents.encodeUriComponent(fragment, encoding, UriComponents.Type.FRAGMENT);
}
@@ -609,16 +274,4 @@ public abstract class UriUtils {
return changed ? new String(bos.toByteArray(), encoding) : source;
}
- /**
- * Enumeration used to control how URIs are encoded.
- */
- public enum EncodingOption {
-
- /**
- * Allow for URI template variables to occur in the URI component (i.e. '{foo}')
- */
- ALLOW_TEMPLATE_VARS
-
- }
-
}
diff --git a/org.springframework.web/src/test/java/org/springframework/web/util/UriBuilderTests.java b/org.springframework.web/src/test/java/org/springframework/web/util/UriBuilderTests.java
deleted file mode 100644
index 15e31d7fa0b..00000000000
--- a/org.springframework.web/src/test/java/org/springframework/web/util/UriBuilderTests.java
+++ /dev/null
@@ -1,131 +0,0 @@
-/*
- * 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.UnsupportedEncodingException;
-import java.net.URI;
-import java.net.URISyntaxException;
-import java.util.HashMap;
-import java.util.Map;
-
-import org.junit.Ignore;
-import org.junit.Test;
-
-import static org.junit.Assert.*;
-
-/** @author Arjen Poutsma */
-public class UriBuilderTests {
-
- @Test
- public void plain() throws URISyntaxException {
- UriBuilder builder = UriBuilder.newInstance();
- URI result = builder.scheme("http").host("example.com").path("foo").queryParam("bar").fragment("baz").build();
-
- URI expected = new URI("http://example.com/foo?bar#baz");
- assertEquals("Invalid result URI", expected, result);
- }
-
- @Test
- public void fromPath() throws URISyntaxException {
- URI result = UriBuilder.fromPath("foo").queryParam("bar").fragment("baz").build();
-
- URI expected = new URI("/foo?bar#baz");
- assertEquals("Invalid result URI", expected, result);
- }
-
- @Test
- public void fromUri() throws URISyntaxException {
- URI uri = new URI("http://example.com/foo?bar#baz");
-
- URI result = UriBuilder.fromUri(uri).build();
- assertEquals("Invalid result URI", uri, result);
- }
-
- @Test
- @Ignore("Working on it")
- public void templateVarsVarArgs() throws URISyntaxException {
- URI result = UriBuilder.fromPath("/{foo}/{bar}").build("baz", "qux");
-
- URI expected = new URI("http://example.com/baz/qux");
- assertEquals("Invalid result URI", expected, result);
- }
-
- @Test
- public void templateVarsEncoded() throws URISyntaxException, UnsupportedEncodingException {
- URI result = UriBuilder.fromPath("{foo}").build("bar baz");
-
- URI expected = new URI("/bar%20baz");
- assertEquals("Invalid result URI", expected, result);
- }
-
- @Test
- public void templateVarsNotEncoded() throws URISyntaxException {
- UriBuilder builder = UriBuilder.newInstance();
- URI result = builder.scheme("http").host("example.com").path("{foo}").buildFromEncoded("bar%20baz");
-
- URI expected = new URI("http://example.com/bar%20baz");
- assertEquals("Invalid result URI", expected, result);
- }
-
- @Test
- public void templateVarsMap() throws URISyntaxException {
- Map vars = new HashMap(2);
- vars.put("bar", "qux");
- vars.put("foo", "baz");
- URI result = UriBuilder.fromPath("/{foo}/{bar}").build(vars);
- URI expected = new URI("/baz/qux");
- assertEquals("Invalid result URI", expected, result);
- }
-
- @Test
- public void unusedTemplateVars() throws URISyntaxException {
- UriBuilder builder = UriBuilder.newInstance();
- URI result = builder.scheme("http").host("example.com").path("{foo}").build();
-
- URI expected = new URI("http://example.com/%7Bfoo%7D");
- assertEquals("Invalid result URI", expected, result);
- }
-
- @Test
- public void pathSegments() throws URISyntaxException {
- UriBuilder builder = UriBuilder.newInstance();
- URI result = builder.pathSegment("foo").pathSegment("bar").build();
-
- URI expected = new URI("/foo/bar");
- assertEquals("Invalid result URI", expected, result);
- }
-
- @Test
- public void queryParam() throws URISyntaxException {
- UriBuilder builder = UriBuilder.newInstance();
- URI result = builder.queryParam("baz", "qux", 42).build();
-
- URI expected = new URI("?baz=qux&baz=42");
- assertEquals("Invalid result URI", expected, result);
- }
-
- @Test
- public void emptyQueryParam() throws URISyntaxException {
- UriBuilder builder = UriBuilder.newInstance();
- URI result = builder.queryParam("baz").build();
-
- URI expected = new URI("?baz");
- assertEquals("Invalid result URI", expected, result);
- }
-
-
-}
diff --git a/org.springframework.web/src/test/java/org/springframework/web/util/UriComponentsBuilderTests.java b/org.springframework.web/src/test/java/org/springframework/web/util/UriComponentsBuilderTests.java
new file mode 100644
index 00000000000..75efa2bb417
--- /dev/null
+++ b/org.springframework.web/src/test/java/org/springframework/web/util/UriComponentsBuilderTests.java
@@ -0,0 +1,110 @@
+/*
+ * 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.net.URI;
+import java.net.URISyntaxException;
+
+import org.junit.Test;
+
+import static org.junit.Assert.assertEquals;
+
+/** @author Arjen Poutsma */
+public class UriComponentsBuilderTests {
+
+ @Test
+ public void plain() throws URISyntaxException {
+ UriComponentsBuilder builder = UriComponentsBuilder.newInstance();
+ UriComponents result = builder.scheme("http").host("example.com").path("foo").queryParam("bar").fragment("baz").build();
+ assertEquals("http", result.getScheme());
+ assertEquals("example.com", result.getHost());
+ assertEquals("/foo", result.getPath());
+ assertEquals("bar", result.getQuery());
+ assertEquals("baz", result.getFragment());
+
+ URI expected = new URI("http://example.com/foo?bar#baz");
+ assertEquals("Invalid result URI", expected, result.toUri());
+ }
+
+ @Test
+ public void fromPath() throws URISyntaxException {
+ UriComponents result = UriComponentsBuilder.fromPath("foo").queryParam("bar").fragment("baz").build();
+ assertEquals("/foo", result.getPath());
+ assertEquals("bar", result.getQuery());
+ assertEquals("baz", result.getFragment());
+
+ URI expected = new URI("/foo?bar#baz");
+ assertEquals("Invalid result URI", expected, result.toUri());
+
+ result = UriComponentsBuilder.fromPath("/foo").build();
+ assertEquals("/foo", result.getPath());
+
+ expected = new URI("/foo");
+ assertEquals("Invalid result URI", expected, result.toUri());
+ }
+
+ @Test
+ public void fromUri() throws URISyntaxException {
+ URI uri = new URI("http://example.com/foo?bar#baz");
+ UriComponents result = UriComponentsBuilder.fromUri(uri).build();
+ assertEquals("http", result.getScheme());
+ assertEquals("example.com", result.getHost());
+ assertEquals("/foo", result.getPath());
+ assertEquals("bar", result.getQuery());
+ assertEquals("baz", result.getFragment());
+
+ assertEquals("Invalid result URI", uri, result.toUri());
+ }
+
+ @Test
+ public void pathSegments() throws URISyntaxException {
+ UriComponentsBuilder builder = UriComponentsBuilder.newInstance();
+ URI result = builder.pathSegment("foo").pathSegment("bar").build().toUri();
+
+ URI expected = new URI("/foo/bar");
+ assertEquals("Invalid result URI", expected, result);
+ }
+
+ @Test
+ public void queryParam() throws URISyntaxException {
+ UriComponentsBuilder builder = UriComponentsBuilder.newInstance();
+ URI result = builder.queryParam("baz", "qux", 42).build().toUri();
+
+ URI expected = new URI("?baz=qux&baz=42");
+ assertEquals("Invalid result URI", expected, result);
+ }
+
+ @Test
+ public void emptyQueryParam() throws URISyntaxException {
+ UriComponentsBuilder builder = UriComponentsBuilder.newInstance();
+ URI result = builder.queryParam("baz").build().toUri();
+
+ URI expected = new URI("?baz");
+ assertEquals("Invalid result URI", expected, result);
+ }
+
+ @Test
+ public void combineWithUriTemplate() throws URISyntaxException {
+ UriComponentsBuilder builder = UriComponentsBuilder.fromPath("/{foo}");
+ UriComponents components = builder.build();
+ UriTemplate template = new UriTemplate(components);
+ URI uri = template.expand("bar baz");
+ assertEquals(new URI("/bar%20baz"), uri);
+ }
+
+
+}
diff --git a/org.springframework.web/src/test/java/org/springframework/web/util/UriComponentsTests.java b/org.springframework.web/src/test/java/org/springframework/web/util/UriComponentsTests.java
index df8f51b8072..93fe3fa35ae 100644
--- a/org.springframework.web/src/test/java/org/springframework/web/util/UriComponentsTests.java
+++ b/org.springframework.web/src/test/java/org/springframework/web/util/UriComponentsTests.java
@@ -16,58 +16,105 @@
package org.springframework.web.util;
+import java.net.URI;
+import java.net.URISyntaxException;
import java.util.Arrays;
+import java.util.Collections;
import java.util.List;
-
-import org.junit.Before;
-import org.junit.Test;
+import java.util.Map;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
-import static org.junit.Assert.*;
+import org.junit.Test;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNull;
/** @author Arjen Poutsma */
public class UriComponentsTests {
- private UriComponents components;
+ @Test
+ public void fromUri() {
+ Map result = UriComponents.fromUriString("http://www.ietf.org/rfc/rfc3986.txt");
+ assertEquals("http", result.get(UriComponents.Type.SCHEME));
+ assertNull(result.get(UriComponents.Type.USER_INFO));
+ assertEquals("www.ietf.org", result.get(UriComponents.Type.HOST));
+ assertNull(result.get(UriComponents.Type.PORT));
+ assertEquals("/rfc/rfc3986.txt", result.get(UriComponents.Type.PATH));
+ assertNull(result.get(UriComponents.Type.QUERY));
+ assertNull(result.get(UriComponents.Type.FRAGMENT));
+
+ result = UriComponents.fromUriString(
+ "http://arjen:foobar@java.sun.com:80/javase/6/docs/api/java/util/BitSet.html?foo=bar#and(java.util.BitSet)");
+ assertEquals("http", result.get(UriComponents.Type.SCHEME));
+ assertEquals("arjen:foobar", result.get(UriComponents.Type.USER_INFO));
+ assertEquals("java.sun.com", result.get(UriComponents.Type.HOST));
+ assertEquals("80", result.get(UriComponents.Type.PORT));
+ assertEquals("/javase/6/docs/api/java/util/BitSet.html", result.get(UriComponents.Type.PATH));
+ assertEquals("foo=bar", result.get(UriComponents.Type.QUERY));
+ assertEquals("and(java.util.BitSet)", result.get(UriComponents.Type.FRAGMENT));
+
+ result = UriComponents.fromUriString("mailto:java-net@java.sun.com");
+ assertEquals("mailto", result.get(UriComponents.Type.SCHEME));
+ assertNull(result.get(UriComponents.Type.USER_INFO));
+ assertNull(result.get(UriComponents.Type.HOST));
+ assertNull(result.get(UriComponents.Type.PORT));
+ assertEquals("java-net@java.sun.com", result.get(UriComponents.Type.PATH));
+ assertNull(result.get(UriComponents.Type.QUERY));
+ assertNull(result.get(UriComponents.Type.FRAGMENT));
+
+ result = UriComponents.fromUriString("docs/guide/collections/designfaq.html#28");
+ assertNull(result.get(UriComponents.Type.SCHEME));
+ assertNull(result.get(UriComponents.Type.USER_INFO));
+ assertNull(result.get(UriComponents.Type.HOST));
+ assertNull(result.get(UriComponents.Type.PORT));
+ assertEquals("docs/guide/collections/designfaq.html", result.get(UriComponents.Type.PATH));
+ assertNull(result.get(UriComponents.Type.QUERY));
+ assertEquals("28", result.get(UriComponents.Type.FRAGMENT));
+ }
- @Before
- public void createComponents() {
- components = new UriComponents();
- }
-
@Test
public void pathSegments() {
- String path = "/foo/bar";
- components.setPath(path);
+ String path = "/foo/bar";
+ UriComponents components = UriComponents.fromUriComponentMap(Collections.singletonMap(UriComponents.Type.PATH, path));
List expected = Arrays.asList("foo", "bar");
List pathSegments = components.getPathSegments();
assertEquals(expected, pathSegments);
-
- components.setPath(null);
-
- components.setPathSegments(expected);
- assertEquals(path, components.getPath());
}
@Test
public void queryParams() {
String query = "foo=bar&foo=baz&qux";
- components.setQuery(query);
+ UriComponents components = UriComponents.fromUriComponentMap(
+ Collections.singletonMap(UriComponents.Type.QUERY, query));
MultiValueMap expected = new LinkedMultiValueMap(1);
expected.put("foo", Arrays.asList("bar", "baz"));
expected.set("qux", null);
MultiValueMap result = components.getQueryParams();
assertEquals(expected, result);
-
- components.setQuery(null);
-
- components.setQueryParams(expected);
- assertEquals(query, components.getQuery());
-
}
+
+ @Test
+ public void encode() {
+ UriComponents uriComponents = UriComponents.fromUriString("http://example.com/hotel list");
+ UriComponents encoded = uriComponents.encode();
+ assertEquals("/hotel%20list", encoded.getPath());
+ }
+
+ @Test
+ public void toUriEncoded() throws URISyntaxException {
+ UriComponents uriComponents = UriComponents.fromUriString("http://example.com/hotel list/Z\u00fcrich");
+ UriComponents encoded = uriComponents.encode();
+ assertEquals(new URI("http://example.com/hotel%20list/Z%C3%BCrich"), encoded.toUri());
+ }
+
+ @Test
+ public void toUriNotEncoded() throws URISyntaxException {
+ UriComponents uriComponents = UriComponents.fromUriString("http://example.com/hotel list/Z\u00fcrich");
+ assertEquals(new URI("http://example.com/hotel%20list/Z\u00fcrich"), uriComponents.toUri());
+ }
}
diff --git a/org.springframework.web/src/test/java/org/springframework/web/util/UriTemplateTests.java b/org.springframework.web/src/test/java/org/springframework/web/util/UriTemplateTests.java
index 7323cdcf39b..d16ba69bed4 100644
--- a/org.springframework.web/src/test/java/org/springframework/web/util/UriTemplateTests.java
+++ b/org.springframework.web/src/test/java/org/springframework/web/util/UriTemplateTests.java
@@ -53,6 +53,16 @@ public class UriTemplateTests {
template.expand("1");
}
+ @Test
+ public void expandMap() throws Exception {
+ Map uriVariables = new HashMap(2);
+ uriVariables.put("booking", "42");
+ uriVariables.put("hotel", "1");
+ UriTemplate template = new UriTemplate("http://example.com/hotels/{hotel}/bookings/{booking}");
+ URI result = template.expand(uriVariables);
+ assertEquals("Invalid expanded template", new URI("http://example.com/hotels/1/bookings/42"), result);
+ }
+
@Test
public void expandMapDuplicateVariables() throws Exception {
UriTemplate template = new UriTemplate("/order/{c}/{c}/{c}");
@@ -61,16 +71,6 @@ public class UriTemplateTests {
assertEquals("Invalid expanded template", new URI("/order/cheeseburger/cheeseburger/cheeseburger"), result);
}
- @Test
- public void expandMap() throws Exception {
- Map uriVariables = new HashMap(2);
- uriVariables.put("booking", "42");
- uriVariables.put("hotel", "1");
- UriTemplate template = new UriTemplate("http://example.com/hotels/{hotel}/bookings/{booking}");
- URI result = template.expand(uriVariables);
- assertEquals("Invalid expanded template", new URI("http://example.com/hotels/1/bookings/42"), result);
- }
-
@Test
public void expandMapNonString() throws Exception {
Map uriVariables = new HashMap(2);
@@ -80,6 +80,15 @@ public class UriTemplateTests {
URI result = template.expand(uriVariables);
assertEquals("Invalid expanded template", new URI("http://example.com/hotels/1/bookings/42"), result);
}
+
+ @Test
+ public void expandMapEncoded() throws Exception {
+ Map uriVariables = Collections.singletonMap("hotel", "Z\u00fcrich");
+ UriTemplate template = new UriTemplate("http://example.com/hotel list/{hotel}");
+ URI result = template.expand(uriVariables);
+ assertEquals("Invalid expanded template", new URI("http://example.com/hotel%20list/Z%C3%BCrich"), result);
+ }
+
@Test(expected = IllegalArgumentException.class)
public void expandMapInvalidAmountVariables() throws Exception {
diff --git a/org.springframework.web/src/test/java/org/springframework/web/util/UriUtilsTests.java b/org.springframework.web/src/test/java/org/springframework/web/util/UriUtilsTests.java
index 27f5e15a83d..53df99702ac 100644
--- a/org.springframework.web/src/test/java/org/springframework/web/util/UriUtilsTests.java
+++ b/org.springframework.web/src/test/java/org/springframework/web/util/UriUtilsTests.java
@@ -17,11 +17,10 @@
package org.springframework.web.util;
import java.io.UnsupportedEncodingException;
-import java.util.Map;
import org.junit.Test;
-import static org.junit.Assert.*;
+import static org.junit.Assert.assertEquals;
/**
* @author Arjen Poutsma
@@ -31,45 +30,6 @@ public class UriUtilsTests {
private static final String ENC = "UTF-8";
- @Test
- public void parseUriComponents() {
- Map result = UriUtils.parseUriComponents("http://www.ietf.org/rfc/rfc3986.txt");
- assertEquals("http", result.get(UriComponents.Type.SCHEME));
- assertNull(result.get(UriComponents.Type.USER_INFO));
- assertEquals("www.ietf.org", result.get(UriComponents.Type.HOST));
- assertNull(result.get(UriComponents.Type.PORT));
- assertEquals("/rfc/rfc3986.txt", result.get(UriComponents.Type.PATH));
- assertNull(result.get(UriComponents.Type.QUERY));
- assertNull(result.get(UriComponents.Type.FRAGMENT));
-
- result = UriUtils.parseUriComponents(
- "http://arjen:foobar@java.sun.com:80/javase/6/docs/api/java/util/BitSet.html?foo=bar#and(java.util.BitSet)");
- assertEquals("http", result.get(UriComponents.Type.SCHEME));
- assertEquals("arjen:foobar", result.get(UriComponents.Type.USER_INFO));
- assertEquals("java.sun.com", result.get(UriComponents.Type.HOST));
- assertEquals("80", result.get(UriComponents.Type.PORT));
- assertEquals("/javase/6/docs/api/java/util/BitSet.html", result.get(UriComponents.Type.PATH));
- assertEquals("foo=bar", result.get(UriComponents.Type.QUERY));
- assertEquals("and(java.util.BitSet)", result.get(UriComponents.Type.FRAGMENT));
-
- result = UriUtils.parseUriComponents("mailto:java-net@java.sun.com");
- assertEquals("mailto", result.get(UriComponents.Type.SCHEME));
- assertNull(result.get(UriComponents.Type.USER_INFO));
- assertNull(result.get(UriComponents.Type.HOST));
- assertNull(result.get(UriComponents.Type.PORT));
- assertEquals("java-net@java.sun.com", result.get(UriComponents.Type.PATH));
- assertNull(result.get(UriComponents.Type.QUERY));
- assertNull(result.get(UriComponents.Type.FRAGMENT));
-
- result = UriUtils.parseUriComponents("docs/guide/collections/designfaq.html#28");
- assertNull(result.get(UriComponents.Type.SCHEME));
- assertNull(result.get(UriComponents.Type.USER_INFO));
- assertNull(result.get(UriComponents.Type.HOST));
- assertNull(result.get(UriComponents.Type.PORT));
- assertEquals("docs/guide/collections/designfaq.html", result.get(UriComponents.Type.PATH));
- assertNull(result.get(UriComponents.Type.QUERY));
- assertEquals("28", result.get(UriComponents.Type.FRAGMENT));
- }
@Test
public void encodeScheme() throws UnsupportedEncodingException {