(entry.getValue().size());
+ for (String value : entry.getValue()) {
+ String expandedValue = expandUriComponent(value, uriVariables);
+ expandedValues.add(expandedValue);
+ }
+ expandedQueryParams.put(expandedName, expandedValues);
+ }
+ String expandedFragment = expandUriComponent(this.getFragment(), uriVariables);
+
+ return new HierarchicalUriComponents(expandedScheme, expandedUserInfo, expandedHost, this.port, expandedPath,
+ expandedQueryParams, expandedFragment, false, false);
+ }
+
+ /**
+ * Normalize the path removing sequences like "path/..".
+ * @see StringUtils#cleanPath(String)
+ */
+ @Override
+ public UriComponents normalize() {
+ String normalizedPath = StringUtils.cleanPath(getPath());
+ return new HierarchicalUriComponents(getScheme(), this.userInfo, this.host,
+ this.port, new FullPathComponent(normalizedPath), this.queryParams,
+ getFragment(), this.encoded, false);
+ }
+
+
+ // other functionality
+
+ /**
+ * Returns a URI string from this {@code UriComponents} instance.
+ */
+ @Override
+ public String toUriString() {
+ StringBuilder uriBuilder = new StringBuilder();
+
+ if (getScheme() != null) {
+ uriBuilder.append(getScheme());
+ uriBuilder.append(':');
+ }
+
+ if (this.userInfo != null || this.host != null) {
+ uriBuilder.append("//");
+ if (this.userInfo != null) {
+ uriBuilder.append(this.userInfo);
+ uriBuilder.append('@');
+ }
+ if (this.host != null) {
+ uriBuilder.append(host);
+ }
+ if (this.port != -1) {
+ uriBuilder.append(':');
+ uriBuilder.append(port);
+ }
+ }
+
+ String path = getPath();
+ if (StringUtils.hasLength(path)) {
+ if (uriBuilder.length() != 0 && path.charAt(0) != PATH_DELIMITER) {
+ uriBuilder.append(PATH_DELIMITER);
+ }
+ uriBuilder.append(path);
+ }
+
+ String query = getQuery();
+ if (query != null) {
+ uriBuilder.append('?');
+ uriBuilder.append(query);
+ }
+
+ if (getFragment() != null) {
+ uriBuilder.append('#');
+ uriBuilder.append(getFragment());
+ }
+
+ return uriBuilder.toString();
+ }
+
+ /**
+ * Returns a {@code URI} from this {@code UriComponents} instance.
+ */
+ @Override
+ public URI toUri() {
+ try {
+ if (this.encoded) {
+ return new URI(toString());
+ }
+ else {
+ String path = getPath();
+ if (StringUtils.hasLength(path) && path.charAt(0) != PATH_DELIMITER) {
+ path = PATH_DELIMITER + path;
+ }
+ return new URI(getScheme(), getUserInfo(), getHost(), getPort(), path, getQuery(),
+ getFragment());
+ }
+ }
+ catch (URISyntaxException ex) {
+ throw new IllegalStateException("Could not create URI object: " + ex.getMessage(), ex);
+ }
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj) {
+ return true;
+ }
+ if (!(obj instanceof OpaqueUriComponents)) {
+ return false;
+ }
+
+ HierarchicalUriComponents other = (HierarchicalUriComponents) obj;
+
+ if (ObjectUtils.nullSafeEquals(getScheme(), other.getScheme())) {
+ return false;
+ }
+ if (ObjectUtils.nullSafeEquals(getUserInfo(), other.getUserInfo())) {
+ return false;
+ }
+ if (ObjectUtils.nullSafeEquals(getHost(), other.getHost())) {
+ return false;
+ }
+ if (this.port != other.port) {
+ return false;
+ }
+ if (!this.path.equals(other.path)) {
+ return false;
+ }
+ if (!this.queryParams.equals(other.queryParams)) {
+ return false;
+ }
+ if (ObjectUtils.nullSafeEquals(getFragment(), other.getFragment())) {
+ return false;
+ }
+
+ return true;
+ }
+
+ @Override
+ public int hashCode() {
+ int result = ObjectUtils.nullSafeHashCode(getScheme());
+ result = 31 * result + ObjectUtils.nullSafeHashCode(this.userInfo);
+ result = 31 * result + ObjectUtils.nullSafeHashCode(this.host);
+ result = 31 * result + this.port;
+ result = 31 * result + this.path.hashCode();
+ result = 31 * result + this.queryParams.hashCode();
+ result = 31 * result + ObjectUtils.nullSafeHashCode(getFragment());
+ return result;
+ }
+
+
+ // 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.
+ * @see RFC 3986
+ */
+ 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;
+ }
+ }
+
+
+ /**
+ * Defines the contract for path (segments).
+ */
+ interface PathComponent {
+
+ String getPath();
+
+ List getPathSegments();
+
+ PathComponent encode(String encoding) throws UnsupportedEncodingException;
+
+ void verify();
+
+ PathComponent expand(UriTemplateVariables uriVariables);
+ }
+
+
+ /**
+ * Represents a path backed by a string.
+ */
+ final static class FullPathComponent implements PathComponent {
+
+ private final String path;
+
+ FullPathComponent(String path) {
+ this.path = path;
+ }
+
+ public String getPath() {
+ return path;
+ }
+
+ public List getPathSegments() {
+ String delimiter = new String(new char[]{PATH_DELIMITER});
+ String[] pathSegments = StringUtils.tokenizeToStringArray(path, delimiter);
+ return Collections.unmodifiableList(Arrays.asList(pathSegments));
+ }
+
+ public PathComponent encode(String encoding) throws UnsupportedEncodingException {
+ String encodedPath = encodeUriComponent(getPath(),encoding, Type.PATH);
+ return new FullPathComponent(encodedPath);
+ }
+
+ public void verify() {
+ verifyUriComponent(this.path, Type.PATH);
+ }
+
+ public PathComponent expand(UriTemplateVariables uriVariables) {
+ String expandedPath = expandUriComponent(getPath(), uriVariables);
+ return new FullPathComponent(expandedPath);
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ return (this == obj || (obj instanceof FullPathComponent &&
+ getPath().equals(((FullPathComponent) obj).getPath())));
+ }
+
+ @Override
+ public int hashCode() {
+ return getPath().hashCode();
+ }
+ }
+
+ /**
+ * Represents a path backed by a string list (i.e. path segments).
+ */
+ final static class PathSegmentComponent implements PathComponent {
+
+ private final List pathSegments;
+
+ PathSegmentComponent(List pathSegments) {
+ this.pathSegments = Collections.unmodifiableList(pathSegments);
+ }
+
+ public String getPath() {
+ StringBuilder pathBuilder = new StringBuilder();
+ pathBuilder.append(PATH_DELIMITER);
+ for (Iterator iterator = this.pathSegments.iterator(); iterator.hasNext(); ) {
+ String pathSegment = iterator.next();
+ pathBuilder.append(pathSegment);
+ if (iterator.hasNext()) {
+ pathBuilder.append(PATH_DELIMITER);
+ }
+ }
+ return pathBuilder.toString();
+ }
+
+ public List getPathSegments() {
+ return this.pathSegments;
+ }
+
+ public PathComponent encode(String encoding) throws UnsupportedEncodingException {
+ List pathSegments = getPathSegments();
+ List encodedPathSegments = new ArrayList(pathSegments.size());
+ for (String pathSegment : pathSegments) {
+ String encodedPathSegment = encodeUriComponent(pathSegment, encoding, Type.PATH_SEGMENT);
+ encodedPathSegments.add(encodedPathSegment);
+ }
+ return new PathSegmentComponent(encodedPathSegments);
+ }
+
+ public void verify() {
+ for (String pathSegment : getPathSegments()) {
+ verifyUriComponent(pathSegment, Type.PATH_SEGMENT);
+ }
+ }
+
+ public PathComponent expand(UriTemplateVariables uriVariables) {
+ List pathSegments = getPathSegments();
+ List expandedPathSegments = new ArrayList(pathSegments.size());
+ for (String pathSegment : pathSegments) {
+ String expandedPathSegment = expandUriComponent(pathSegment, uriVariables);
+ expandedPathSegments.add(expandedPathSegment);
+ }
+ return new PathSegmentComponent(expandedPathSegments);
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ return (this == obj || (obj instanceof PathSegmentComponent &&
+ getPathSegments().equals(((PathSegmentComponent) obj).getPathSegments())));
+ }
+
+ @Override
+ public int hashCode() {
+ return getPathSegments().hashCode();
+ }
+
+ }
+
+ /**
+ * Represents a collection of PathComponents.
+ */
+ final static class PathComponentComposite implements PathComponent {
+
+ private final List pathComponents;
+
+ PathComponentComposite(List pathComponents) {
+ this.pathComponents = pathComponents;
+ }
+
+ public String getPath() {
+ StringBuilder pathBuilder = new StringBuilder();
+ for (PathComponent pathComponent : this.pathComponents) {
+ pathBuilder.append(pathComponent.getPath());
+ }
+ return pathBuilder.toString();
+ }
+
+ public List getPathSegments() {
+ List result = new ArrayList();
+ for (PathComponent pathComponent : this.pathComponents) {
+ result.addAll(pathComponent.getPathSegments());
+ }
+ return result;
+ }
+
+ public PathComponent encode(String encoding) throws UnsupportedEncodingException {
+ List encodedComponents = new ArrayList(pathComponents.size());
+ for (PathComponent pathComponent : pathComponents) {
+ encodedComponents.add(pathComponent.encode(encoding));
+ }
+ return new PathComponentComposite(encodedComponents);
+ }
+
+ public void verify() {
+ for (PathComponent pathComponent : pathComponents) {
+ pathComponent.verify();
+ }
+ }
+
+ public PathComponent expand(UriTemplateVariables uriVariables) {
+ List expandedComponents = new ArrayList(this.pathComponents.size());
+ for (PathComponent pathComponent : this.pathComponents) {
+ expandedComponents.add(pathComponent.expand(uriVariables));
+ }
+ return new PathComponentComposite(expandedComponents);
+ }
+ }
+
+
+
+ /**
+ * Represents an empty path.
+ */
+ final static PathComponent NULL_PATH_COMPONENT = new PathComponent() {
+
+ public String getPath() {
+ return null;
+ }
+
+ public List getPathSegments() {
+ return Collections.emptyList();
+ }
+
+ public PathComponent encode(String encoding) throws UnsupportedEncodingException {
+ return this;
+ }
+
+ public void verify() {
+ }
+
+ public PathComponent expand(UriTemplateVariables uriVariables) {
+ return this;
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ return (this == obj);
+ }
+
+ @Override
+ public int hashCode() {
+ return 42;
+ }
+
+ };
+
+}
diff --git a/org.springframework.web/src/main/java/org/springframework/web/util/OpaqueUriComponents.java b/org.springframework.web/src/main/java/org/springframework/web/util/OpaqueUriComponents.java
new file mode 100644
index 0000000000..d896518b29
--- /dev/null
+++ b/org.springframework.web/src/main/java/org/springframework/web/util/OpaqueUriComponents.java
@@ -0,0 +1,169 @@
+/*
+ * Copyright 2002-2012 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.Collections;
+import java.util.List;
+
+import org.springframework.util.LinkedMultiValueMap;
+import org.springframework.util.MultiValueMap;
+import org.springframework.util.ObjectUtils;
+
+/**
+ * Extension of {@link UriComponents} for opaque URIs.
+ *
+ * @author Arjen Poutsma
+ * @since 3.2
+ * @see Hierarchical vs Opaque URIs
+ */
+final class OpaqueUriComponents extends UriComponents {
+
+ private static final MultiValueMap QUERY_PARAMS_NONE = new LinkedMultiValueMap(0);
+
+ private final String ssp;
+
+
+ OpaqueUriComponents(String scheme, String schemeSpecificPart, String fragment) {
+ super(scheme, fragment);
+ this.ssp = schemeSpecificPart;
+ }
+
+
+ @Override
+ public String getSchemeSpecificPart() {
+ return this.ssp;
+ }
+
+ @Override
+ public String getUserInfo() {
+ return null;
+ }
+
+ @Override
+ public String getHost() {
+ return null;
+ }
+
+ @Override
+ public int getPort() {
+ return -1;
+ }
+
+ @Override
+ public String getPath() {
+ return null;
+ }
+
+ @Override
+ public List getPathSegments() {
+ return Collections.emptyList();
+ }
+
+ @Override
+ public String getQuery() {
+ return null;
+ }
+
+ @Override
+ public MultiValueMap getQueryParams() {
+ return QUERY_PARAMS_NONE;
+ }
+
+ @Override
+ public UriComponents encode(String encoding) throws UnsupportedEncodingException {
+ return this;
+ }
+
+ @Override
+ protected UriComponents expandInternal(UriTemplateVariables uriVariables) {
+ String expandedScheme = expandUriComponent(this.getScheme(), uriVariables);
+ String expandedSSp = expandUriComponent(this.ssp, uriVariables);
+ String expandedFragment = expandUriComponent(this.getFragment(), uriVariables);
+
+ return new OpaqueUriComponents(expandedScheme, expandedSSp, expandedFragment);
+ }
+
+ @Override
+ public String toUriString() {
+ StringBuilder uriBuilder = new StringBuilder();
+
+ if (getScheme() != null) {
+ uriBuilder.append(getScheme());
+ uriBuilder.append(':');
+ }
+ if (this.ssp != null) {
+ uriBuilder.append(this.ssp);
+ }
+ if (getFragment() != null) {
+ uriBuilder.append('#');
+ uriBuilder.append(getFragment());
+ }
+
+ return uriBuilder.toString();
+ }
+
+ @Override
+ public URI toUri() {
+ try {
+ return new URI(getScheme(), this.ssp, getFragment());
+ }
+ catch (URISyntaxException ex) {
+ throw new IllegalStateException("Could not create URI object: " + ex.getMessage(), ex);
+ }
+ }
+
+ @Override
+ public UriComponents normalize() {
+ return this;
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj) {
+ return true;
+ }
+ if (!(obj instanceof OpaqueUriComponents)) {
+ return false;
+ }
+
+ OpaqueUriComponents other = (OpaqueUriComponents) obj;
+
+ if (ObjectUtils.nullSafeEquals(getScheme(), other.getScheme())) {
+ return false;
+ }
+ if (ObjectUtils.nullSafeEquals(this.ssp, other.ssp)) {
+ return false;
+ }
+ if (ObjectUtils.nullSafeEquals(getFragment(), other.getFragment())) {
+ return false;
+ }
+
+ return true;
+ }
+
+ @Override
+ public int hashCode() {
+ int result = ObjectUtils.nullSafeHashCode(getScheme());
+ result = 31 * result + ObjectUtils.nullSafeHashCode(this.ssp);
+ result = 31 * result + ObjectUtils.nullSafeHashCode(getFragment());
+ return result;
+ }
+
+}
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 54cbcef437..f04c5f695b 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,13 +16,9 @@
package org.springframework.web.util;
-import java.io.ByteArrayOutputStream;
import java.io.UnsupportedEncodingException;
import java.net.URI;
-import java.net.URISyntaxException;
-import java.util.ArrayList;
import java.util.Arrays;
-import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
@@ -30,405 +26,154 @@ 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 an immutable collection of URI components, mapping component type to string values. Contains convenience
- * getters for all components. Effectively similar to {@link URI}, but with more powerful encoding options and support
- * for URI template variables.
+ * Represents an immutable collection of URI components, mapping component type to string
+ * values. Contains convenience getters for all components. Effectively similar to {@link
+ * java.net.URI}, but with more powerful encoding options and support for URI template
+ * variables.
*
* @author Arjen Poutsma
* @since 3.1
* @see UriComponentsBuilder
*/
-public final class UriComponents {
+public abstract class UriComponents {
- private static final String DEFAULT_ENCODING = "UTF-8";
-
- private static final char PATH_DELIMITER = '/';
+ private static final String DEFAULT_ENCODING = "UTF-8";
/** Captures URI template variable names. */
private static final Pattern NAMES_PATTERN = Pattern.compile("\\{([^/]+?)\\}");
+
private final String scheme;
- private final String userInfo;
-
- private final String host;
-
- private final int port;
-
- private final PathComponent path;
-
- private final MultiValueMap queryParams;
-
private final String fragment;
- private final boolean encoded;
- /**
- * Package-friendly constructor that creates a new {@code UriComponents} instance from the given parameters. All
- * parameters are optional, and can be {@code null}.
- *
- * @param scheme the scheme
- * @param userInfo the user info
- * @param host the host
- * @param port the port
- * @param path the path component
- * @param queryParams the query parameters
- * @param fragment the fragment
- * @param encoded whether the components are encoded
- * @param verify whether the components need to be verified to determine whether they contain illegal characters
- */
- UriComponents(String scheme,
- String userInfo,
- String host,
- int port,
- PathComponent path,
- MultiValueMap queryParams,
- String fragment,
- boolean encoded,
- boolean verify) {
+ protected UriComponents(String scheme, String fragment) {
this.scheme = scheme;
- this.userInfo = userInfo;
- this.host = host;
- this.port = port;
- this.path = path != null ? path : NULL_PATH_COMPONENT;
- this.queryParams = CollectionUtils.unmodifiableMultiValueMap(
- queryParams != null ? queryParams : new LinkedMultiValueMap(0));
this.fragment = fragment;
- this.encoded = encoded;
- if (verify) {
- verify();
- }
}
+
// component getters
- /**
- * Returns the scheme.
- *
- * @return the scheme. Can be {@code null}.
- */
- public String getScheme() {
+ /**
+ * Returns the scheme. Can be {@code null}.
+ */
+ public final String getScheme() {
return scheme;
- }
-
- /**
- * Returns the user info.
- *
- * @return the user info. Can be {@code null}.
- */
- public String getUserInfo() {
- return userInfo;
- }
-
- /**
- * Returns the host.
- *
- * @return the host. Can be {@code null}.
- */
- public String getHost() {
- return host;
- }
-
- /**
- * Returns the port. Returns {@code -1} if no port has been set.
- *
- * @return the port
- */
- public int getPort() {
- return port;
- }
+ }
/**
- * Returns the path.
- *
- * @return the path. Can be {@code null}.
+ * Returns the scheme specific part. Can be {@code null}.
*/
- public String getPath() {
- return path.getPath();
- }
-
- /**
- * Returns the list of path segments.
- *
- * @return the path segments. Empty if no path has been set.
- */
- public List getPathSegments() {
- return path.getPathSegments();
- }
+ public abstract String getSchemeSpecificPart();
/**
- * Returns the query.
- *
- * @return the query. Can be {@code null}.
+ * Returns the user info. Can be {@code null}.
*/
- public String getQuery() {
- if (!queryParams.isEmpty()) {
- StringBuilder queryBuilder = new StringBuilder();
- for (Map.Entry> entry : queryParams.entrySet()) {
- String name = entry.getKey();
- List values = entry.getValue();
- if (CollectionUtils.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);
+ public abstract String getUserInfo();
- if (value != null) {
- queryBuilder.append('=');
- queryBuilder.append(value.toString());
- }
- }
- }
- }
- return queryBuilder.toString();
- }
- else {
- return null;
- }
+ /**
+ * Returns the host. Can be {@code null}.
+ */
+ public abstract String getHost();
+
+ /**
+ * Returns the port. Returns {@code -1} if no port has been set.
+ */
+ public abstract int getPort();
+
+ /**
+ * Returns the path. Can be {@code null}.
+ */
+ public abstract String getPath();
+
+ /**
+ * Returns the list of path segments. Empty if no path has been set.
+ */
+ public abstract List getPathSegments();
+
+ /**
+ * Returns the query. Can be {@code null}.
+ */
+ public abstract String getQuery();
+
+ /**
+ * Returns the map of query parameters. Empty if no query has been set.
+ */
+ public abstract MultiValueMap getQueryParams();
+
+ /**
+ * Returns the fragment. Can be {@code null}.
+ */
+ public final String getFragment() {
+ return this.fragment;
}
- /**
- * Returns the map of query parameters.
- *
- * @return the query parameters. Empty if no query has been set.
- */
- public MultiValueMap getQueryParams() {
- return queryParams;
- }
- /**
- * Returns the fragment.
- *
- * @return the fragment. Can be {@code null}.
- */
- public String getFragment() {
- return fragment;
- }
-
- // encoding
+ // encoding
/**
- * Encodes all URI components using their specific encoding rules, and returns the result as a new
- * {@code UriComponents} instance. This method uses UTF-8 to encode.
- *
+ * Encode all URI components using their specific encoding rules, and returns the result
+ * as a new {@code UriComponents} instance. This method uses UTF-8 to encode.
* @return the encoded uri components
*/
- public UriComponents encode() {
- try {
- return encode(DEFAULT_ENCODING);
- }
- catch (UnsupportedEncodingException e) {
- throw new InternalError("\"" + DEFAULT_ENCODING + "\" not supported");
- }
- }
-
- /**
- * 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");
-
- if (encoded) {
- return this;
- }
-
- String encodedScheme = encodeUriComponent(this.scheme, encoding, Type.SCHEME);
- String encodedUserInfo = encodeUriComponent(this.userInfo, encoding, Type.USER_INFO);
- String encodedHost = encodeUriComponent(this.host, encoding, Type.HOST);
- PathComponent encodedPath = path.encode(encoding);
- MultiValueMap encodedQueryParams =
- new LinkedMultiValueMap(this.queryParams.size());
- for (Map.Entry> entry : this.queryParams.entrySet()) {
- String encodedName = encodeUriComponent(entry.getKey(), encoding, Type.QUERY_PARAM);
- List encodedValues = new ArrayList(entry.getValue().size());
- for (String value : entry.getValue()) {
- String encodedValue = encodeUriComponent(value, encoding, Type.QUERY_PARAM);
- encodedValues.add(encodedValue);
- }
- encodedQueryParams.put(encodedName, encodedValues);
+ public final UriComponents encode() {
+ try {
+ return encode(DEFAULT_ENCODING);
}
- String encodedFragment = encodeUriComponent(this.fragment, encoding, Type.FRAGMENT);
-
- return new UriComponents(encodedScheme, encodedUserInfo, encodedHost, this.port, encodedPath,
- encodedQueryParams, encodedFragment, true, false);
- }
-
- /**
- * 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 type the URI component for the source
- * @return the encoded URI
- * @throws IllegalArgumentException when the given uri parameter is not a valid URI
- */
- static String encodeUriComponent(String source, String encoding, Type type)
- throws UnsupportedEncodingException {
- if (source == null) {
- return null;
+ catch (UnsupportedEncodingException e) {
+ throw new InternalError("\"" + DEFAULT_ENCODING + "\" not supported");
}
-
- Assert.hasLength(encoding, "'encoding' must not be empty");
-
- byte[] bytes = encodeBytes(source.getBytes(encoding), type);
- return new String(bytes, "US-ASCII");
}
- private static byte[] encodeBytes(byte[] source, Type type) {
- Assert.notNull(source, "'source' must not be null");
- Assert.notNull(type, "'type' must not be null");
-
- ByteArrayOutputStream bos = new ByteArrayOutputStream(source.length);
- for (int i = 0; i < source.length; i++) {
- int b = source[i];
- if (b < 0) {
- b += 256;
- }
- if (type.isAllowed(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();
- }
-
- // verifying
-
/**
- * Verifies all URI components to determine whether they contain any illegal characters, throwing an
- * {@code IllegalArgumentException} if so.
- *
- * @throws IllegalArgumentException if any of the components contain illegal characters
+ * Encode 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
*/
- private void verify() {
- if (!encoded) {
- return;
- }
- verifyUriComponent(scheme, Type.SCHEME);
- verifyUriComponent(userInfo, Type.USER_INFO);
- verifyUriComponent(host, Type.HOST);
- path.verify();
- for (Map.Entry> entry : queryParams.entrySet()) {
- verifyUriComponent(entry.getKey(), Type.QUERY_PARAM);
- for (String value : entry.getValue()) {
- verifyUriComponent(value, Type.QUERY_PARAM);
- }
- }
- verifyUriComponent(fragment, Type.FRAGMENT);
- }
+ public abstract UriComponents encode(String encoding) throws UnsupportedEncodingException;
- private static void verifyUriComponent(String source, Type type) {
- if (source == null) {
- return;
- }
-
- int length = source.length();
-
- for (int i=0; i < length; i++) {
- char ch = source.charAt(i);
- if (ch == '%') {
- if ((i + 2) < length) {
- char hex1 = source.charAt(i + 1);
- char hex2 = source.charAt(i + 2);
- int u = Character.digit(hex1, 16);
- int l = Character.digit(hex2, 16);
- if (u == -1 || l == -1) {
- throw new IllegalArgumentException("Invalid encoded sequence \"" + source.substring(i) + "\"");
- }
- i += 2;
- }
- else {
- throw new IllegalArgumentException("Invalid encoded sequence \"" + source.substring(i) + "\"");
- }
- }
- else if (!type.isAllowed(ch)) {
- throw new IllegalArgumentException(
- "Invalid character '" + ch + "' for " + type.name() + " in \"" + source + "\"");
- }
- }
- }
-
// expanding
/**
- * Replaces all URI template variables with the values from a given map. The map keys represent
- * variable names; the values variable values. The order of variables is not significant.
-
+ * Replaces all URI template variables with the values from a given map. The map keys
+ * represent variable names; the values variable values. The order of variables is not
+ * significant.
* @param uriVariables the map of URI variables
* @return the expanded uri components
*/
- public UriComponents expand(Map uriVariables) {
+ public final UriComponents expand(Map uriVariables) {
Assert.notNull(uriVariables, "'uriVariables' must not be null");
-
return expandInternal(new MapTemplateVariables(uriVariables));
}
/**
- * Replaces all URI template variables with the values from a given array. The array represent variable values.
- * The order of variables is significant.
-
+ * Replaces all URI template variables with the values from a given array. The array
+ * represent variable values. The order of variables is significant.
* @param uriVariableValues URI variable values
* @return the expanded uri components
*/
- public UriComponents expand(Object... uriVariableValues) {
+ public final UriComponents expand(Object... uriVariableValues) {
Assert.notNull(uriVariableValues, "'uriVariableValues' must not be null");
-
return expandInternal(new VarArgsTemplateVariables(uriVariableValues));
}
- private UriComponents expandInternal(UriTemplateVariables uriVariables) {
- Assert.state(!encoded, "Cannot expand an already encoded UriComponents object");
+ /**
+ * Replaces all URI template variables with the values from the given {@link
+ * UriTemplateVariables}
+ * @param uriVariables URI template values
+ * @return the expanded uri components
+ */
+ abstract UriComponents expandInternal(UriTemplateVariables uriVariables);
- String expandedScheme = expandUriComponent(this.scheme, uriVariables);
- String expandedUserInfo = expandUriComponent(this.userInfo, uriVariables);
- String expandedHost = expandUriComponent(this.host, uriVariables);
- PathComponent expandedPath = path.expand(uriVariables);
- MultiValueMap expandedQueryParams =
- new LinkedMultiValueMap(this.queryParams.size());
- for (Map.Entry> entry : this.queryParams.entrySet()) {
- String expandedName = expandUriComponent(entry.getKey(), uriVariables);
- List expandedValues = new ArrayList(entry.getValue().size());
- for (String value : entry.getValue()) {
- String expandedValue = expandUriComponent(value, uriVariables);
- expandedValues.add(expandedValue);
- }
- expandedQueryParams.put(expandedName, expandedValues);
- }
- String expandedFragment = expandUriComponent(this.fragment, uriVariables);
-
- return new UriComponents(expandedScheme, expandedUserInfo, expandedHost, this.port, expandedPath,
- expandedQueryParams, expandedFragment, false, false);
- }
-
- private static String expandUriComponent(String source, UriTemplateVariables uriVariables) {
+ static String expandUriComponent(String source, UriTemplateVariables uriVariables) {
if (source == null) {
return null;
}
@@ -458,536 +203,36 @@ public final class UriComponents {
return variableValue != null ? variableValue.toString() : "";
}
- /**
- * Normalize the path removing sequences like "path/..".
- * @see StringUtils#cleanPath(String)
- */
- public UriComponents normalize() {
- String normalizedPath = StringUtils.cleanPath(getPath());
- return new UriComponents(scheme, userInfo, host, this.port, new FullPathComponent(normalizedPath),
- queryParams, fragment, encoded, false);
- }
+ /**
+ * Returns a URI string from this {@code UriComponents} instance.
+ */
+ public abstract String toUriString();
- // other functionality
-
- /**
- * Returns a URI string from this {@code UriComponents} instance.
- *
- * @return the URI string
- */
- public String toUriString() {
- StringBuilder uriBuilder = new StringBuilder();
-
- if (scheme != null) {
- uriBuilder.append(scheme);
- uriBuilder.append(':');
- }
-
- if (userInfo != null || host != null) {
- uriBuilder.append("//");
- if (userInfo != null) {
- uriBuilder.append(userInfo);
- uriBuilder.append('@');
- }
- if (host != null) {
- uriBuilder.append(host);
- }
- if (port != -1) {
- uriBuilder.append(':');
- uriBuilder.append(port);
- }
- }
-
- String path = getPath();
- if (StringUtils.hasLength(path)) {
- if (uriBuilder.length() != 0 && path.charAt(0) != PATH_DELIMITER) {
- uriBuilder.append(PATH_DELIMITER);
- }
- uriBuilder.append(path);
- }
-
- String query = getQuery();
- if (query != null) {
- uriBuilder.append('?');
- uriBuilder.append(query);
- }
-
- if (fragment != null) {
- uriBuilder.append('#');
- uriBuilder.append(fragment);
- }
-
- return uriBuilder.toString();
- }
-
- /**
- * Returns a {@code URI} from this {@code UriComponents} instance.
- *
- * @return the URI
- */
- public URI toUri() {
- try {
- if (encoded) {
- return new URI(toUriString());
- }
- else {
- String path = getPath();
- if (StringUtils.hasLength(path) && path.charAt(0) != PATH_DELIMITER) {
- path = PATH_DELIMITER + path;
- }
- return new URI(getScheme(), getUserInfo(), getHost(), getPort(), path, getQuery(),
- getFragment());
- }
- }
- catch (URISyntaxException ex) {
- throw new IllegalStateException("Could not create URI object: " + ex.getMessage(), ex);
- }
- }
+ /**
+ * Returns a {@code URI} from this {@code UriComponents} instance.
+ */
+ public abstract URI toUri();
@Override
- public boolean equals(Object o) {
- if (this == o) {
- return true;
- }
- if (o instanceof UriComponents) {
- UriComponents other = (UriComponents) o;
-
- if (scheme != null ? !scheme.equals(other.scheme) : other.scheme != null) {
- return false;
- }
- if (userInfo != null ? !userInfo.equals(other.userInfo) : other.userInfo != null) {
- return false;
- }
- if (host != null ? !host.equals(other.host) : other.host != null) {
- return false;
- }
- if (port != other.port) {
- return false;
- }
- if (!path.equals(other.path)) {
- return false;
- }
- if (!queryParams.equals(other.queryParams)) {
- return false;
- }
- if (fragment != null ? !fragment.equals(other.fragment) : other.fragment != null) {
- return false;
- }
- return true;
- }
- else {
- return false;
- }
- }
-
- @Override
- public int hashCode() {
- int result = scheme != null ? scheme.hashCode() : 0;
- result = 31 * result + (userInfo != null ? userInfo.hashCode() : 0);
- result = 31 * result + (host != null ? host.hashCode() : 0);
- result = 31 * result + port;
- result = 31 * result + path.hashCode();
- result = 31 * result + queryParams.hashCode();
- result = 31 * result + (fragment != null ? fragment.hashCode() : 0);
- return result;
- }
-
- @Override
- public String toString() {
+ public final String toString() {
return toUriString();
- }
-
- // 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
- */
- 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;
- }
-
- }
-
- /**
- * Defines the contract for path (segments).
- */
- interface PathComponent {
-
- String getPath();
-
- List getPathSegments();
-
- PathComponent encode(String encoding) throws UnsupportedEncodingException;
-
- void verify();
-
- PathComponent expand(UriTemplateVariables uriVariables);
-
}
/**
- * Represents a path backed by a string.
+ * Normalize the path removing sequences like "path/..".
+ * @see org.springframework.util.StringUtils#cleanPath(String)
*/
- final static class FullPathComponent implements PathComponent {
+ public abstract UriComponents normalize();
- private final String path;
-
- FullPathComponent(String path) {
- this.path = path;
- }
-
- public String getPath() {
- return path;
- }
-
- public List getPathSegments() {
- String delimiter = new String(new char[]{PATH_DELIMITER});
- String[] pathSegments = StringUtils.tokenizeToStringArray(path, delimiter);
- return Collections.unmodifiableList(Arrays.asList(pathSegments));
- }
-
- public PathComponent encode(String encoding) throws UnsupportedEncodingException {
- String encodedPath = encodeUriComponent(getPath(),encoding, Type.PATH);
- return new FullPathComponent(encodedPath);
- }
-
- public void verify() {
- verifyUriComponent(path, Type.PATH);
- }
-
- public PathComponent expand(UriTemplateVariables uriVariables) {
- String expandedPath = expandUriComponent(getPath(), uriVariables);
- return new FullPathComponent(expandedPath);
- }
-
- @Override
- public boolean equals(Object o) {
- if (this == o) {
- return true;
- } else if (o instanceof FullPathComponent) {
- FullPathComponent other = (FullPathComponent) o;
- return this.getPath().equals(other.getPath());
- }
- return false;
- }
-
- @Override
- public int hashCode() {
- return getPath().hashCode();
- }
- }
-
- /**
- * Represents a path backed by a string list (i.e. path segments).
- */
- final static class PathSegmentComponent implements PathComponent {
-
- private final List pathSegments;
-
- PathSegmentComponent(List pathSegments) {
- this.pathSegments = Collections.unmodifiableList(pathSegments);
- }
-
- public String getPath() {
- StringBuilder pathBuilder = new StringBuilder();
- pathBuilder.append(PATH_DELIMITER);
- for (Iterator iterator = pathSegments.iterator(); iterator.hasNext(); ) {
- String pathSegment = iterator.next();
- pathBuilder.append(pathSegment);
- if (iterator.hasNext()) {
- pathBuilder.append(PATH_DELIMITER);
- }
- }
- return pathBuilder.toString();
- }
-
- public List getPathSegments() {
- return pathSegments;
- }
-
- public PathComponent encode(String encoding) throws UnsupportedEncodingException {
- List pathSegments = getPathSegments();
- List encodedPathSegments = new ArrayList(pathSegments.size());
- for (String pathSegment : pathSegments) {
- String encodedPathSegment = encodeUriComponent(pathSegment, encoding, Type.PATH_SEGMENT);
- encodedPathSegments.add(encodedPathSegment);
- }
- return new PathSegmentComponent(encodedPathSegments);
- }
-
- public void verify() {
- for (String pathSegment : getPathSegments()) {
- verifyUriComponent(pathSegment, Type.PATH_SEGMENT);
- }
- }
-
- public PathComponent expand(UriTemplateVariables uriVariables) {
- List pathSegments = getPathSegments();
- List expandedPathSegments = new ArrayList(pathSegments.size());
- for (String pathSegment : pathSegments) {
- String expandedPathSegment = expandUriComponent(pathSegment, uriVariables);
- expandedPathSegments.add(expandedPathSegment);
- }
- return new PathSegmentComponent(expandedPathSegments);
- }
-
- @Override
- public boolean equals(Object o) {
- if (this == o) {
- return true;
- } else if (o instanceof PathSegmentComponent) {
- PathSegmentComponent other = (PathSegmentComponent) o;
- return this.getPathSegments().equals(other.getPathSegments());
- }
- return false;
- }
-
- @Override
- public int hashCode() {
- return getPathSegments().hashCode();
- }
-
- }
-
- /**
- * Represents a collection of PathComponents.
- */
- final static class PathComponentComposite implements PathComponent {
-
- private final List pathComponents;
-
- PathComponentComposite(List pathComponents) {
- this.pathComponents = pathComponents;
- }
-
- public String getPath() {
- StringBuilder pathBuilder = new StringBuilder();
- for (PathComponent pathComponent : pathComponents) {
- pathBuilder.append(pathComponent.getPath());
- }
- return pathBuilder.toString();
- }
-
- public List getPathSegments() {
- List result = new ArrayList();
- for (PathComponent pathComponent : pathComponents) {
- result.addAll(pathComponent.getPathSegments());
- }
- return result;
- }
-
- public PathComponent encode(String encoding) throws UnsupportedEncodingException {
- List encodedComponents = new ArrayList(pathComponents.size());
- for (PathComponent pathComponent : pathComponents) {
- encodedComponents.add(pathComponent.encode(encoding));
- }
- return new PathComponentComposite(encodedComponents);
- }
-
- public void verify() {
- for (PathComponent pathComponent : pathComponents) {
- pathComponent.verify();
- }
- }
-
- public PathComponent expand(UriTemplateVariables uriVariables) {
- List expandedComponents = new ArrayList(pathComponents.size());
- for (PathComponent pathComponent : pathComponents) {
- expandedComponents.add(pathComponent.expand(uriVariables));
- }
- return new PathComponentComposite(expandedComponents);
- }
- }
-
-
-
- /**
- * Represents an empty path.
- */
- final static PathComponent NULL_PATH_COMPONENT = new PathComponent() {
-
- public String getPath() {
- return null;
- }
-
- public List getPathSegments() {
- return Collections.emptyList();
- }
-
- public PathComponent encode(String encoding) throws UnsupportedEncodingException {
- return this;
- }
-
- public void verify() {
- }
-
- public PathComponent expand(UriTemplateVariables uriVariables) {
- return this;
- }
-
- @Override
- public boolean equals(Object o) {
- return this == o;
- }
-
- @Override
- public int hashCode() {
- return 42;
- }
-
- };
/**
* Defines the contract for URI Template variables
- *
- * @see UriComponents#expand
+ * @see HierarchicalUriComponents#expand
*/
- private interface UriTemplateVariables {
+ interface UriTemplateVariables {
Object getValue(String name);
-
}
/**
@@ -1009,6 +254,7 @@ public final class UriComponents {
}
}
+
/**
* URI template variables backed by a variable argument array.
*/
@@ -1022,7 +268,8 @@ public final class UriComponents {
public Object getValue(String name) {
if (!valueIterator.hasNext()) {
- throw new IllegalArgumentException("Not enough variable values available to expand '" + name + "'");
+ throw new IllegalArgumentException(
+ "Not enough variable values available to expand '" + name + "'");
}
return valueIterator.next();
}
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
index d309acb954..b6709200bc 100644
--- 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
@@ -32,8 +32,8 @@ import org.springframework.util.StringUtils;
/**
* Builder for {@link UriComponents}.
- *
- * Typical usage involves:
+ *
+ * Typical usage involves:
*
* - Create a {@code UriComponentsBuilder} with one of the static factory methods (such as
* {@link #fromPath(String)} or {@link #fromUri(URI)})
@@ -46,10 +46,10 @@ import org.springframework.util.StringUtils;
*
* @author Arjen Poutsma
* @author Rossen Stoyanchev
+ * @since 3.1
* @see #newInstance()
* @see #fromPath(String)
* @see #fromUri(URI)
- * @since 3.1
*/
public class UriComponentsBuilder {
@@ -83,6 +83,8 @@ public class UriComponentsBuilder {
private String scheme;
+ private String ssp;
+
private String userInfo;
private String host;
@@ -105,7 +107,7 @@ public class UriComponentsBuilder {
protected UriComponentsBuilder() {
}
- // Factory methods
+ // Factory methods
/**
* Returns a new, empty builder.
@@ -143,7 +145,19 @@ public class UriComponentsBuilder {
/**
* Returns a builder that is initialized with the given URI string.
*
- * @param uri the URI string to initialize with
+ * Note: The presence of reserved characters can prevent
+ * correct parsing of the URI string. For example if a query parameter
+ * contains {@code '='} or {@code '&'} characters, the query string cannot
+ * be parsed unambiguously. Such values should be substituted for URI
+ * variables to enable correct parsing:
+ *
+ *
+ * String uriString = "/hotels/42?filter={value}";
+ * UriComponentsBuilder.fromUriString(uriString).buildAndExpand("hot&cold");
+ *
+ *
+ * @param uri
+ * the URI string to initialize with
* @return the new {@code UriComponentsBuilder}
*/
public static UriComponentsBuilder fromUriString(String uri) {
@@ -152,16 +166,43 @@ public class UriComponentsBuilder {
if (m.matches()) {
UriComponentsBuilder builder = new UriComponentsBuilder();
- builder.scheme(m.group(2));
- builder.userInfo(m.group(5));
- builder.host(m.group(6));
+ String scheme = m.group(2);
+ String userInfo = m.group(5);
+ String host = m.group(6);
String port = m.group(8);
- if (StringUtils.hasLength(port)) {
- builder.port(Integer.parseInt(port));
+ String path = m.group(9);
+ String query = m.group(11);
+ String fragment = m.group(13);
+
+ boolean opaque = false;
+
+ if (StringUtils.hasLength(scheme)) {
+ String s = uri.substring(scheme.length());
+ if (!s.startsWith(":/")) {
+ opaque = true;
+ }
}
- builder.path(m.group(9));
- builder.query(m.group(11));
- builder.fragment(m.group(13));
+
+ builder.scheme(scheme);
+
+
+ if (opaque) {
+ String ssp = uri.substring(scheme.length()).substring(1);
+ if (StringUtils.hasLength(fragment)) {
+ ssp = ssp.substring(0, ssp.length() - (fragment.length() + 1));
+ }
+ builder.schemeSpecificPart(ssp);
+ }
+ else {
+ builder.userInfo(userInfo);
+ builder.host(host);
+ if (StringUtils.hasLength(port)) {
+ builder.port(Integer.parseInt(port));
+ }
+ builder.path(path);
+ builder.query(query);
+ }
+ builder.fragment(fragment);
return builder;
}
@@ -173,6 +214,17 @@ public class UriComponentsBuilder {
/**
* Creates a new {@code UriComponents} object from the string HTTP URL.
*
+ * Note: The presence of reserved characters can prevent
+ * correct parsing of the URI string. For example if a query parameter
+ * contains {@code '='} or {@code '&'} characters, the query string cannot
+ * be parsed unambiguously. Such values should be substituted for URI
+ * variables to enable correct parsing:
+ *
+ *
+ * String uriString = "/hotels/42?filter={value}";
+ * UriComponentsBuilder.fromUriString(uriString).buildAndExpand("hot&cold");
+ *
+ *
* @param httpUrl the source URI
* @return the URI components of the URI
*/
@@ -201,7 +253,7 @@ public class UriComponentsBuilder {
- // build methods
+ // build methods
/**
* Builds a {@code UriComponents} instance from the various components contained in this builder.
@@ -213,13 +265,21 @@ public class UriComponentsBuilder {
}
/**
- * Builds a {@code UriComponents} instance from the various components contained in this builder.
+ * Builds a {@code UriComponents} instance from the various components
+ * contained in this builder.
*
- * @param encoded whether all the components set in this builder are encoded ({@code true}) or not ({@code false}).
+ * @param encoded whether all the components set in this builder are
+ * encoded ({@code true}) or not ({@code false}).
* @return the URI components
*/
public UriComponents build(boolean encoded) {
- return new UriComponents(scheme, userInfo, host, port, pathBuilder.build(), queryParams, fragment, encoded, true);
+ if (ssp != null) {
+ return new OpaqueUriComponents(scheme, ssp, fragment);
+ }
+ else {
+ return new HierarchicalUriComponents(
+ scheme, userInfo, host, port, pathBuilder.build(), queryParams, fragment, encoded, true);
+ }
}
/**
@@ -246,7 +306,7 @@ public class UriComponentsBuilder {
return build(false).expand(uriVariableValues);
}
- // URI components methods
+ // URI components methods
/**
* Initializes all components of this URI builder with the components of the given URI.
@@ -256,25 +316,31 @@ public class 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.getRawUserInfo() != null) {
- this.userInfo = uri.getRawUserInfo();
+ if (uri.isOpaque()) {
+ this.ssp = uri.getRawSchemeSpecificPart();
+ resetHierarchicalComponents();
}
- if (uri.getHost() != null) {
- this.host = uri.getHost();
- }
- if (uri.getPort() != -1) {
- this.port = uri.getPort();
- }
- if (StringUtils.hasLength(uri.getRawPath())) {
- this.pathBuilder = new FullPathComponentBuilder(uri.getRawPath());
- }
- if (StringUtils.hasLength(uri.getRawQuery())) {
- this.queryParams.clear();
- query(uri.getRawQuery());
+ else {
+ 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())) {
+ this.pathBuilder = new FullPathComponentBuilder(uri.getRawPath());
+ }
+ if (StringUtils.hasLength(uri.getRawQuery())) {
+ this.queryParams.clear();
+ query(uri.getRawQuery());
+ }
+ resetSchemeSpecificPart();
}
if (uri.getRawFragment() != null) {
this.fragment = uri.getRawFragment();
@@ -282,11 +348,24 @@ public class UriComponentsBuilder {
return this;
}
+ private void resetHierarchicalComponents() {
+ this.userInfo = null;
+ this.host = null;
+ this.port = -1;
+ this.pathBuilder = NULL_PATH_COMPONENT_BUILDER;
+ this.queryParams.clear();
+ }
+
+ private void resetSchemeSpecificPart() {
+ this.ssp = null;
+ }
+
/**
- * 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.
+ * 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
+ * @param scheme
+ * the URI scheme
* @return this UriComponentsBuilder
*/
public UriComponentsBuilder scheme(String scheme) {
@@ -295,26 +374,44 @@ public class UriComponentsBuilder {
}
/**
- * 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.
+ * Set the URI scheme-specific-part. When invoked, this method overwrites
+ * {@linkplain #userInfo(String) user-info}, {@linkplain #host(String) host},
+ * {@linkplain #port(int) port}, {@linkplain #path(String) path}, and
+ * {@link #query(String) query}.
+ *
+ * @param ssp the URI scheme-specific-part, may contain URI template parameters
+ * @return this UriComponentsBuilder
+ */
+ public UriComponentsBuilder schemeSpecificPart(String ssp) {
+ this.ssp = ssp;
+ resetHierarchicalComponents();
+ 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;
+ resetSchemeSpecificPart();
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.
+ * 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;
+ resetSchemeSpecificPart();
return this;
}
@@ -327,11 +424,13 @@ public class UriComponentsBuilder {
public UriComponentsBuilder port(int port) {
Assert.isTrue(port >= -1, "'port' must not be < -1");
this.port = port;
+ resetSchemeSpecificPart();
return this;
}
/**
- * Appends the given path to the existing path of this builder. The given path may contain URI template variables.
+ * 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
@@ -343,6 +442,7 @@ public class UriComponentsBuilder {
else {
this.pathBuilder = NULL_PATH_COMPONENT_BUILDER;
}
+ resetSchemeSpecificPart();
return this;
}
@@ -355,6 +455,7 @@ public class UriComponentsBuilder {
public UriComponentsBuilder replacePath(String path) {
this.pathBuilder = NULL_PATH_COMPONENT_BUILDER;
path(path);
+ resetSchemeSpecificPart();
return this;
}
@@ -368,11 +469,24 @@ public class UriComponentsBuilder {
public UriComponentsBuilder pathSegment(String... pathSegments) throws IllegalArgumentException {
Assert.notNull(pathSegments, "'segments' must not be null");
this.pathBuilder = this.pathBuilder.appendPathSegments(pathSegments);
+ resetSchemeSpecificPart();
return this;
}
/**
- * Appends the given query to the existing query of this builder. The given query may contain URI template variables.
+ * Appends the given query to the existing query of this builder.
+ * The given query may contain URI template variables.
+ *
+ * Note: The presence of reserved characters can prevent
+ * correct parsing of the URI string. For example if a query parameter
+ * contains {@code '='} or {@code '&'} characters, the query string cannot
+ * be parsed unambiguously. Such values should be substituted for URI
+ * variables to enable correct parsing:
+ *
+ *
+ * String uriString = "/hotels/42?filter={value}";
+ * UriComponentsBuilder.fromUriString(uriString).buildAndExpand("hot&cold");
+ *
*
* @param query the query string
* @return this UriComponentsBuilder
@@ -389,6 +503,7 @@ public class UriComponentsBuilder {
else {
this.queryParams.clear();
}
+ resetSchemeSpecificPart();
return this;
}
@@ -401,16 +516,20 @@ public class UriComponentsBuilder {
public UriComponentsBuilder replaceQuery(String query) {
this.queryParams.clear();
query(query);
+ resetSchemeSpecificPart();
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}.
+ * 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
+ * @param name
+ * the query parameter name
+ * @param values
+ * the query parameter values
* @return this UriComponentsBuilder
*/
public UriComponentsBuilder queryParam(String name, Object... values) {
@@ -424,15 +543,19 @@ public class UriComponentsBuilder {
else {
this.queryParams.add(name, null);
}
+ resetSchemeSpecificPart();
return this;
}
/**
- * Sets the query parameter values overriding all existing query values for the same parameter.
- * If no values are given, the query parameter is removed.
+ * Sets the query parameter values overriding all existing query values for
+ * the same parameter. If no values are given, the query parameter is
+ * removed.
*
- * @param name the query parameter name
- * @param values the query parameter values
+ * @param name
+ * the query parameter name
+ * @param values
+ * the query parameter values
* @return this UriComponentsBuilder
*/
public UriComponentsBuilder replaceQueryParam(String name, Object... values) {
@@ -441,14 +564,17 @@ public class UriComponentsBuilder {
if (!ObjectUtils.isEmpty(values)) {
queryParam(name, values);
}
+ resetSchemeSpecificPart();
return this;
}
/**
- * 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.
+ * 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
+ * @param fragment
+ * the URI fragment
* @return this UriComponentsBuilder
*/
public UriComponentsBuilder fragment(String fragment) {
@@ -463,11 +589,11 @@ public class UriComponentsBuilder {
}
/**
- * Represents a builder for {@link org.springframework.web.util.UriComponents.PathComponent}
+ * Represents a builder for {@link HierarchicalUriComponents.PathComponent}
*/
private interface PathComponentBuilder {
- UriComponents.PathComponent build();
+ HierarchicalUriComponents.PathComponent build();
PathComponentBuilder appendPath(String path);
@@ -485,8 +611,8 @@ public class UriComponentsBuilder {
this.path = new StringBuilder(path);
}
- public UriComponents.PathComponent build() {
- return new UriComponents.FullPathComponent(path.toString());
+ public HierarchicalUriComponents.PathComponent build() {
+ return new HierarchicalUriComponents.FullPathComponent(path.toString());
}
public PathComponentBuilder appendPath(String path) {
@@ -522,8 +648,8 @@ public class UriComponentsBuilder {
return result;
}
- public UriComponents.PathComponent build() {
- return new UriComponents.PathSegmentComponent(pathSegments);
+ public HierarchicalUriComponents.PathComponent build() {
+ return new HierarchicalUriComponents.PathSegmentComponent(pathSegments);
}
public PathComponentBuilder appendPath(String path) {
@@ -549,14 +675,14 @@ public class UriComponentsBuilder {
pathComponentBuilders.add(builder);
}
- public UriComponents.PathComponent build() {
- List pathComponents =
- new ArrayList(pathComponentBuilders.size());
+ public HierarchicalUriComponents.PathComponent build() {
+ List pathComponents =
+ new ArrayList(pathComponentBuilders.size());
for (PathComponentBuilder pathComponentBuilder : pathComponentBuilders) {
pathComponents.add(pathComponentBuilder.build());
}
- return new UriComponents.PathComponentComposite(pathComponents);
+ return new HierarchicalUriComponents.PathComponentComposite(pathComponents);
}
public PathComponentBuilder appendPath(String path) {
@@ -576,8 +702,8 @@ public class UriComponentsBuilder {
*/
private static PathComponentBuilder NULL_PATH_COMPONENT_BUILDER = new PathComponentBuilder() {
- public UriComponents.PathComponent build() {
- return UriComponents.NULL_PATH_COMPONENT;
+ public HierarchicalUriComponents.PathComponent build() {
+ return HierarchicalUriComponents.NULL_PATH_COMPONENT;
}
public PathComponentBuilder appendPath(String path) {
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 4d6c2a4499..e976a43b54 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
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2011 the original author or authors.
+ * Copyright 2002-2012 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.
@@ -29,9 +29,9 @@ import org.springframework.util.Assert;
*
* All {@code encode*(String, String} methods in this class operate in a similar way:
*
- * - Valid characters for the specific URI component as defined in RFC 3986 stay the same.
- * - All other characters are converted into one or more bytes in the given encoding scheme. Each of the
- * resulting bytes is written as a hexadecimal string in the "
%xy" format.
+ * - Valid characters for the specific URI component as defined in RFC 3986 stay the same.
+ * - All other characters are converted into one or more bytes in the given encoding scheme. Each of the
+ * resulting bytes is written as a hexadecimal string in the "
%xy" format.
*
*
* @author Arjen Poutsma
@@ -64,6 +64,7 @@ public abstract class UriUtils {
private static final Pattern HTTP_URL_PATTERN = Pattern.compile(
"^" + HTTP_PATTERN + "(//(" + USERINFO_PATTERN + "@)?" + HOST_PATTERN + "(:" + PORT_PATTERN + ")?" + ")?" +
PATH_PATTERN + "(\\?" + LAST_PATTERN + ")?");
+
// encoding
/**
@@ -73,8 +74,8 @@ public abstract class UriUtils {
* characters in query parameter names and query parameter values because they cannot
* be parsed in a reliable way. Instead use:
*
- * UriComponents uriComponents = UriComponentsBuilder.fromUri("/path?name={value}").buildAndExpand("a=b");
- * String encodedUri = uriComponents.encode().toUriString();
+ * UriComponents uriComponents = UriComponentsBuilder.fromUri("/path?name={value}").buildAndExpand("a=b");
+ * String encodedUri = uriComponents.encode().toUriString();
*
* @param uri the URI to be encoded
* @param encoding the character encoding to encode to
@@ -83,6 +84,7 @@ public abstract class UriUtils {
* @throws UnsupportedEncodingException when the given encoding parameter is not supported
* @deprecated in favor of {@link UriComponentsBuilder}; see note about query param encoding
*/
+ @Deprecated
public static String encodeUri(String uri, String encoding) throws UnsupportedEncodingException {
Assert.notNull(uri, "'uri' must not be null");
Assert.hasLength(encoding, "'encoding' must not be empty");
@@ -113,8 +115,8 @@ public abstract class UriUtils {
* characters in query parameter names and query parameter values because they cannot
* be parsed in a reliable way. Instead use:
*
- * UriComponents uriComponents = UriComponentsBuilder.fromHttpUrl("/path?name={value}").buildAndExpand("a=b");
- * String encodedUri = uriComponents.encode().toUriString();
+ * UriComponents uriComponents = UriComponentsBuilder.fromHttpUrl("/path?name={value}").buildAndExpand("a=b");
+ * String encodedUri = uriComponents.encode().toUriString();
*
* @param httpUrl the HTTP URL to be encoded
* @param encoding the character encoding to encode to
@@ -123,6 +125,7 @@ public abstract class UriUtils {
* @throws UnsupportedEncodingException when the given encoding parameter is not supported
* @deprecated in favor of {@link UriComponentsBuilder}; see note about query param encoding
*/
+ @Deprecated
public static String encodeHttpUrl(String httpUrl, String encoding) throws UnsupportedEncodingException {
Assert.notNull(httpUrl, "'httpUrl' must not be null");
Assert.hasLength(encoding, "'encoding' must not be empty");
@@ -160,46 +163,47 @@ public abstract class UriUtils {
* @throws UnsupportedEncodingException when the given encoding parameter is not supported
* @deprecated in favor of {@link UriComponentsBuilder}
*/
+ @Deprecated
public static String encodeUriComponents(String scheme, String authority, String userInfo,
String host, String port, String path, String query, String fragment, String encoding)
throws UnsupportedEncodingException {
- Assert.hasLength(encoding, "'encoding' must not be empty");
- StringBuilder sb = new StringBuilder();
+ Assert.hasLength(encoding, "'encoding' must not be empty");
+ StringBuilder sb = new StringBuilder();
- if (scheme != null) {
- sb.append(encodeScheme(scheme, encoding));
- sb.append(':');
- }
+ if (scheme != null) {
+ sb.append(encodeScheme(scheme, encoding));
+ sb.append(':');
+ }
- if (authority != null) {
- sb.append("//");
- if (userInfo != null) {
- sb.append(encodeUserInfo(userInfo, encoding));
- sb.append('@');
- }
- if (host != null) {
- sb.append(encodeHost(host, encoding));
- }
- if (port != null) {
- sb.append(':');
- sb.append(encodePort(port, encoding));
- }
- }
+ if (authority != null) {
+ sb.append("//");
+ if (userInfo != null) {
+ sb.append(encodeUserInfo(userInfo, encoding));
+ sb.append('@');
+ }
+ if (host != null) {
+ sb.append(encodeHost(host, encoding));
+ }
+ if (port != null) {
+ sb.append(':');
+ sb.append(encodePort(port, encoding));
+ }
+ }
- sb.append(encodePath(path, encoding));
+ sb.append(encodePath(path, encoding));
- if (query != null) {
- sb.append('?');
- sb.append(encodeQuery(query, encoding));
- }
+ if (query != null) {
+ sb.append('?');
+ sb.append(encodeQuery(query, encoding));
+ }
- if (fragment != null) {
- sb.append('#');
- sb.append(encodeFragment(fragment, encoding));
- }
+ if (fragment != null) {
+ sb.append('#');
+ sb.append(encodeFragment(fragment, encoding));
+ }
- return sb.toString();
+ return sb.toString();
}
@@ -213,7 +217,8 @@ 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 UriComponents.encodeUriComponent(scheme, encoding, UriComponents.Type.SCHEME);
+ return HierarchicalUriComponents.encodeUriComponent(scheme, encoding,
+ HierarchicalUriComponents.Type.SCHEME);
}
/**
@@ -224,7 +229,8 @@ 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 UriComponents.encodeUriComponent(authority, encoding, UriComponents.Type.AUTHORITY);
+ return HierarchicalUriComponents.encodeUriComponent(authority, encoding,
+ HierarchicalUriComponents.Type.AUTHORITY);
}
/**
@@ -235,7 +241,8 @@ 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 UriComponents.encodeUriComponent(userInfo, encoding, UriComponents.Type.USER_INFO);
+ return HierarchicalUriComponents.encodeUriComponent(userInfo, encoding,
+ HierarchicalUriComponents.Type.USER_INFO);
}
/**
@@ -246,7 +253,8 @@ 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 UriComponents.encodeUriComponent(host, encoding, UriComponents.Type.HOST);
+ return HierarchicalUriComponents
+ .encodeUriComponent(host, encoding, HierarchicalUriComponents.Type.HOST);
}
/**
@@ -257,7 +265,8 @@ 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 UriComponents.encodeUriComponent(port, encoding, UriComponents.Type.PORT);
+ return HierarchicalUriComponents
+ .encodeUriComponent(port, encoding, HierarchicalUriComponents.Type.PORT);
}
/**
@@ -268,7 +277,8 @@ 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 UriComponents.encodeUriComponent(path, encoding, UriComponents.Type.PATH);
+ return HierarchicalUriComponents
+ .encodeUriComponent(path, encoding, HierarchicalUriComponents.Type.PATH);
}
/**
@@ -279,7 +289,8 @@ 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 UriComponents.encodeUriComponent(segment, encoding, UriComponents.Type.PATH_SEGMENT);
+ return HierarchicalUriComponents.encodeUriComponent(segment, encoding,
+ HierarchicalUriComponents.Type.PATH_SEGMENT);
}
/**
@@ -290,7 +301,8 @@ 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 UriComponents.encodeUriComponent(query, encoding, UriComponents.Type.QUERY);
+ return HierarchicalUriComponents
+ .encodeUriComponent(query, encoding, HierarchicalUriComponents.Type.QUERY);
}
/**
@@ -301,7 +313,8 @@ 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 UriComponents.encodeUriComponent(queryParam, encoding, UriComponents.Type.QUERY_PARAM);
+ return HierarchicalUriComponents.encodeUriComponent(queryParam, encoding,
+ HierarchicalUriComponents.Type.QUERY_PARAM);
}
/**
@@ -312,7 +325,8 @@ 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 UriComponents.encodeUriComponent(fragment, encoding, UriComponents.Type.FRAGMENT);
+ return HierarchicalUriComponents.encodeUriComponent(fragment, encoding,
+ HierarchicalUriComponents.Type.FRAGMENT);
}
@@ -321,11 +335,11 @@ public abstract class UriUtils {
/**
* Decodes the given encoded source String into an URI. Based on the following rules:
*
- * - Alphanumeric characters {@code "a"} through {@code "z"}, {@code "A"} through {@code "Z"}, and
- * {@code "0"} through {@code "9"} stay the same.
- * - Special characters {@code "-"}, {@code "_"}, {@code "."}, and {@code "*"} stay the same.
- * - A sequence "
%xy" is interpreted as a hexadecimal representation of the character.
- *
+ * - Alphanumeric characters {@code "a"} through {@code "z"}, {@code "A"} through {@code "Z"}, and
+ * {@code "0"} through {@code "9"} stay the same.
+ * - Special characters {@code "-"}, {@code "_"}, {@code "."}, and {@code "*"} stay the same.
+ * - A sequence "
%xy" is interpreted as a hexadecimal representation of the character.
+ *
* @param source the source string
* @param encoding the encoding
* @return the decoded URI
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
index 3fefe43ebc..be0eebbc9c 100644
--- 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
@@ -16,9 +16,6 @@
package org.springframework.web.util;
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertNull;
-
import java.net.URI;
import java.net.URISyntaxException;
import java.util.Arrays;
@@ -26,21 +23,26 @@ import java.util.HashMap;
import java.util.Map;
import org.junit.Test;
+
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
-/** @author Arjen Poutsma */
+import static org.junit.Assert.*;
+
+/**
+ * @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());
+ 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());
@@ -49,43 +51,54 @@ public class UriComponentsBuilderTests {
@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());
+ 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());
+ assertEquals("/foo", result.getPath());
- expected = new URI("/foo");
+ expected = new URI("/foo");
assertEquals("Invalid result URI", expected, result.toUri());
}
@Test
- public void fromUri() throws URISyntaxException {
+ public void fromHierarchicalUri() 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());
+ 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 fromOpaqueUri() throws URISyntaxException {
+ URI uri = new URI("mailto:foo@bar.com#baz");
+ UriComponents result = UriComponentsBuilder.fromUri(uri).build();
+ assertEquals("mailto", result.getScheme());
+ assertEquals("foo@bar.com", result.getSchemeSpecificPart());
+ assertEquals("baz", result.getFragment());
assertEquals("Invalid result URI", uri, result.toUri());
}
// SPR-9317
- @Test
- public void fromUriEncodedQuery() throws URISyntaxException {
- URI uri = new URI("http://www.example.org/?param=aGVsbG9Xb3JsZA%3D%3D");
- String fromUri = UriComponentsBuilder.fromUri(uri).build().getQueryParams().get("param").get(0);
- String fromUriString = UriComponentsBuilder.fromUriString(uri.toString()).build().getQueryParams().get("param").get(0);
+ @Test
+ public void fromUriEncodedQuery() throws URISyntaxException {
+ URI uri = new URI("http://www.example.org/?param=aGVsbG9Xb3JsZA%3D%3D");
+ String fromUri = UriComponentsBuilder.fromUri(uri).build().getQueryParams().get("param").get(0);
+ String fromUriString = UriComponentsBuilder.fromUriString(uri.toString()).build().getQueryParams().get("param").get(0);
- assertEquals(fromUri, fromUriString);
- }
+ assertEquals(fromUri, fromUriString);
+ }
@Test
public void fromUriString() {
@@ -102,34 +115,35 @@ public class UriComponentsBuilderTests {
result = UriComponentsBuilder.fromUriString(
"http://arjen:foobar@java.sun.com:80/javase/6/docs/api/java/util/BitSet.html?foo=bar#and(java.util.BitSet)")
.build();
- assertEquals("http", result.getScheme());
- assertEquals("arjen:foobar", result.getUserInfo());
- assertEquals("java.sun.com", result.getHost());
- assertEquals(80, result.getPort());
- assertEquals("/javase/6/docs/api/java/util/BitSet.html", result.getPath());
- assertEquals("foo=bar", result.getQuery());
+ assertEquals("http", result.getScheme());
+ assertEquals("arjen:foobar", result.getUserInfo());
+ assertEquals("java.sun.com", result.getHost());
+ assertEquals(80, result.getPort());
+ assertEquals("/javase/6/docs/api/java/util/BitSet.html", result.getPath());
+ assertEquals("foo=bar", result.getQuery());
MultiValueMap expectedQueryParams = new LinkedMultiValueMap(1);
expectedQueryParams.add("foo", "bar");
assertEquals(expectedQueryParams, result.getQueryParams());
- assertEquals("and(java.util.BitSet)", result.getFragment());
+ assertEquals("and(java.util.BitSet)", result.getFragment());
- result = UriComponentsBuilder.fromUriString("mailto:java-net@java.sun.com").build();
- assertEquals("mailto", result.getScheme());
- assertNull(result.getUserInfo());
- assertNull(result.getHost());
- assertEquals(-1, result.getPort());
- assertEquals("java-net@java.sun.com", result.getPathSegments().get(0));
- assertNull(result.getQuery());
- assertNull(result.getFragment());
+ result = UriComponentsBuilder.fromUriString("mailto:java-net@java.sun.com#baz").build();
+ assertEquals("mailto", result.getScheme());
+ assertNull(result.getUserInfo());
+ assertNull(result.getHost());
+ assertEquals(-1, result.getPort());
+ assertEquals("java-net@java.sun.com", result.getSchemeSpecificPart());
+ assertNull(result.getPath());
+ assertNull(result.getQuery());
+ assertEquals("baz", result.getFragment());
- result = UriComponentsBuilder.fromUriString("docs/guide/collections/designfaq.html#28").build();
- assertNull(result.getScheme());
- assertNull(result.getUserInfo());
- assertNull(result.getHost());
- assertEquals(-1, result.getPort());
- assertEquals("docs/guide/collections/designfaq.html", result.getPath());
- assertNull(result.getQuery());
- assertEquals("28", result.getFragment());
+ result = UriComponentsBuilder.fromUriString("docs/guide/collections/designfaq.html#28").build();
+ assertNull(result.getScheme());
+ assertNull(result.getUserInfo());
+ assertNull(result.getHost());
+ assertEquals(-1, result.getPort());
+ assertEquals("docs/guide/collections/designfaq.html", result.getPath());
+ assertNull(result.getQuery());
+ assertEquals("28", result.getFragment());
}
@@ -265,7 +279,7 @@ public class UriComponentsBuilderTests {
}
@Test
- public void buildAndExpand() {
+ public void buildAndExpandHierarchical() {
UriComponents result = UriComponentsBuilder.fromPath("/{foo}").buildAndExpand("fooValue");
assertEquals("/fooValue", result.toUriString());
@@ -275,4 +289,17 @@ public class UriComponentsBuilderTests {
result = UriComponentsBuilder.fromPath("/{foo}/{bar}").buildAndExpand(values);
assertEquals("/fooValue/barValue", result.toUriString());
}
+
+ @Test
+ public void buildAndExpandOpaque() {
+ UriComponents result = UriComponentsBuilder.fromUriString("mailto:{user}@{domain}").buildAndExpand("foo", "example.com");
+ assertEquals("mailto:foo@example.com", result.toUriString());
+
+ Map values = new HashMap();
+ values.put("user", "foo");
+ values.put("domain", "example.com");
+ UriComponentsBuilder.fromUriString("mailto:{user}@{domain}").buildAndExpand(values);
+ assertEquals("mailto:foo@example.com", result.toUriString());
+ }
+
}