Browse Source
Before this commit, UriComponentsBuilder did not handle opaque URIs at all. After this commit it does. Support is introduced by making UriComponents an abstract base class, and having two concrete subclasses: HierarchicalUriComponents and OpaqueUriComponents. The former is more or less the same as the old UriComponents class. Issue: SPR-9798pull/274/head
6 changed files with 1308 additions and 895 deletions
@ -0,0 +1,858 @@
@@ -0,0 +1,858 @@
|
||||
/* |
||||
* 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.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; |
||||
|
||||
import org.springframework.util.Assert; |
||||
import org.springframework.util.CollectionUtils; |
||||
import org.springframework.util.LinkedMultiValueMap; |
||||
import org.springframework.util.MultiValueMap; |
||||
import org.springframework.util.ObjectUtils; |
||||
import org.springframework.util.StringUtils; |
||||
|
||||
/** |
||||
* Extension of {@link UriComponents} for hierarchical URIs. |
||||
* |
||||
* @author Arjen Poutsma |
||||
* @since 3.2 |
||||
* @see <a href="http://tools.ietf.org/html/rfc3986#section-1.2.3">Hierarchical URIs</a> |
||||
*/ |
||||
final class HierarchicalUriComponents extends UriComponents { |
||||
|
||||
private static final char PATH_DELIMITER = '/'; |
||||
|
||||
private final String userInfo; |
||||
|
||||
private final String host; |
||||
|
||||
private final int port; |
||||
|
||||
private final PathComponent path; |
||||
|
||||
private final MultiValueMap<String, String> queryParams; |
||||
|
||||
private final boolean encoded; |
||||
|
||||
/** |
||||
* Package-private constructor. All arguments 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 |
||||
* @param queryParams the query parameters |
||||
* @param fragment the fragment |
||||
* @param encoded whether the components are already encoded |
||||
* @param verify whether the components need to be checked for illegal characters |
||||
*/ |
||||
HierarchicalUriComponents(String scheme, String userInfo, String host, int port, |
||||
PathComponent path, MultiValueMap<String, String> queryParams, |
||||
String fragment, boolean encoded, boolean verify) { |
||||
|
||||
super(scheme, fragment); |
||||
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<String, String>(0)); |
||||
this.encoded = encoded; |
||||
if (verify) { |
||||
verify(); |
||||
} |
||||
} |
||||
|
||||
// component getters
|
||||
|
||||
@Override |
||||
public String getSchemeSpecificPart() { |
||||
return null; |
||||
} |
||||
|
||||
@Override |
||||
public String getUserInfo() { |
||||
return this.userInfo; |
||||
} |
||||
|
||||
@Override |
||||
public String getHost() { |
||||
return this.host; |
||||
} |
||||
|
||||
@Override |
||||
public int getPort() { |
||||
return this.port; |
||||
} |
||||
|
||||
@Override |
||||
public String getPath() { |
||||
return this.path.getPath(); |
||||
} |
||||
|
||||
@Override |
||||
public List<String> getPathSegments() { |
||||
return this.path.getPathSegments(); |
||||
} |
||||
|
||||
@Override |
||||
public String getQuery() { |
||||
if (!this.queryParams.isEmpty()) { |
||||
StringBuilder queryBuilder = new StringBuilder(); |
||||
for (Map.Entry<String, List<String>> entry : this.queryParams.entrySet()) { |
||||
String name = entry.getKey(); |
||||
List<String> 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); |
||||
|
||||
if (value != null) { |
||||
queryBuilder.append('='); |
||||
queryBuilder.append(value.toString()); |
||||
} |
||||
} |
||||
} |
||||
} |
||||
return queryBuilder.toString(); |
||||
} |
||||
else { |
||||
return null; |
||||
} |
||||
} |
||||
|
||||
/** |
||||
* Returns the map of query parameters. |
||||
* |
||||
* @return the query parameters. Empty if no query has been set. |
||||
*/ |
||||
@Override |
||||
public MultiValueMap<String, String> getQueryParams() { |
||||
return this.queryParams; |
||||
} |
||||
|
||||
// encoding
|
||||
|
||||
/** |
||||
* 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 |
||||
*/ |
||||
@Override |
||||
public HierarchicalUriComponents encode(String encoding) throws UnsupportedEncodingException { |
||||
Assert.hasLength(encoding, "'encoding' must not be empty"); |
||||
|
||||
if (this.encoded) { |
||||
return this; |
||||
} |
||||
|
||||
String encodedScheme = encodeUriComponent(this.getScheme(), encoding, Type.SCHEME); |
||||
String encodedUserInfo = encodeUriComponent(this.userInfo, encoding, Type.USER_INFO); |
||||
String encodedHost = encodeUriComponent(this.host, encoding, Type.HOST); |
||||
PathComponent encodedPath = this.path.encode(encoding); |
||||
MultiValueMap<String, String> encodedQueryParams = |
||||
new LinkedMultiValueMap<String, String>(this.queryParams.size()); |
||||
for (Map.Entry<String, List<String>> entry : this.queryParams.entrySet()) { |
||||
String encodedName = encodeUriComponent(entry.getKey(), encoding, Type.QUERY_PARAM); |
||||
List<String> encodedValues = new ArrayList<String>(entry.getValue().size()); |
||||
for (String value : entry.getValue()) { |
||||
String encodedValue = encodeUriComponent(value, encoding, Type.QUERY_PARAM); |
||||
encodedValues.add(encodedValue); |
||||
} |
||||
encodedQueryParams.put(encodedName, encodedValues); |
||||
} |
||||
String encodedFragment = encodeUriComponent(this.getFragment(), encoding, Type.FRAGMENT); |
||||
|
||||
return new HierarchicalUriComponents(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; |
||||
} |
||||
|
||||
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 component has illegal characters |
||||
*/ |
||||
private void verify() { |
||||
if (!this.encoded) { |
||||
return; |
||||
} |
||||
verifyUriComponent(getScheme(), Type.SCHEME); |
||||
verifyUriComponent(userInfo, Type.USER_INFO); |
||||
verifyUriComponent(host, Type.HOST); |
||||
this.path.verify(); |
||||
for (Map.Entry<String, List<String>> entry : queryParams.entrySet()) { |
||||
verifyUriComponent(entry.getKey(), Type.QUERY_PARAM); |
||||
for (String value : entry.getValue()) { |
||||
verifyUriComponent(value, Type.QUERY_PARAM); |
||||
} |
||||
} |
||||
verifyUriComponent(getFragment(), Type.FRAGMENT); |
||||
} |
||||
|
||||
|
||||
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
|
||||
|
||||
@Override |
||||
protected HierarchicalUriComponents expandInternal(UriTemplateVariables uriVariables) { |
||||
Assert.state(!encoded, "Cannot expand an already encoded UriComponents object"); |
||||
|
||||
String expandedScheme = expandUriComponent(this.getScheme(), uriVariables); |
||||
String expandedUserInfo = expandUriComponent(this.userInfo, uriVariables); |
||||
String expandedHost = expandUriComponent(this.host, uriVariables); |
||||
PathComponent expandedPath = this.path.expand(uriVariables); |
||||
MultiValueMap<String, String> expandedQueryParams = |
||||
new LinkedMultiValueMap<String, String>(this.queryParams.size()); |
||||
for (Map.Entry<String, List<String>> entry : this.queryParams.entrySet()) { |
||||
String expandedName = expandUriComponent(entry.getKey(), uriVariables); |
||||
List<String> expandedValues = new ArrayList<String>(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. |
||||
* |
||||
* @return the URI string |
||||
*/ |
||||
@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. |
||||
* |
||||
* @return the URI |
||||
*/ |
||||
@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 o) { |
||||
if (this == o) { |
||||
return true; |
||||
} |
||||
if (!(o instanceof OpaqueUriComponents)) { |
||||
return false; |
||||
} |
||||
|
||||
HierarchicalUriComponents other = (HierarchicalUriComponents) o; |
||||
|
||||
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. |
||||
* <p/> |
||||
* Contains methods to indicate whether a given character is valid in a specific URI component. |
||||
* |
||||
* @author Arjen Poutsma |
||||
* @see <a href="http://www.ietf.org/rfc/rfc3986.txt">RFC 3986</a> |
||||
*/ |
||||
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 <a href="http://www.ietf.org/rfc/rfc3986.txt">RFC 3986, appendix A</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 <a href="http://www.ietf.org/rfc/rfc3986.txt">RFC 3986, appendix A</a> |
||||
*/ |
||||
protected boolean isDigit(int c) { |
||||
return c >= '0' && c <= '9'; |
||||
} |
||||
|
||||
/** |
||||
* Indicates whether the given character is in the {@code gen-delims} set. |
||||
* |
||||
* @see <a href="http://www.ietf.org/rfc/rfc3986.txt">RFC 3986, appendix A</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 <a href="http://www.ietf.org/rfc/rfc3986.txt">RFC 3986, appendix A</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 <a href="http://www.ietf.org/rfc/rfc3986.txt">RFC 3986, appendix A</a> |
||||
*/ |
||||
protected boolean isReserved(char c) { |
||||
return isGenericDelimiter(c) || isReserved(c); |
||||
} |
||||
|
||||
/** |
||||
* Indicates whether the given character is in the {@code unreserved} set. |
||||
* |
||||
* @see <a href="http://www.ietf.org/rfc/rfc3986.txt">RFC 3986, appendix A</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 <a href="http://www.ietf.org/rfc/rfc3986.txt">RFC 3986, appendix A</a> |
||||
*/ |
||||
protected boolean isPchar(int c) { |
||||
return isUnreserved(c) || isSubDelimiter(c) || ':' == c || '@' == c; |
||||
} |
||||
|
||||
} |
||||
|
||||
/** |
||||
* Defines the contract for path (segments). |
||||
*/ |
||||
interface PathComponent { |
||||
|
||||
String getPath(); |
||||
|
||||
List<String> 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<String> 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 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<String> pathSegments; |
||||
|
||||
PathSegmentComponent(List<String> pathSegments) { |
||||
this.pathSegments = Collections.unmodifiableList(pathSegments); |
||||
} |
||||
|
||||
public String getPath() { |
||||
StringBuilder pathBuilder = new StringBuilder(); |
||||
pathBuilder.append(PATH_DELIMITER); |
||||
for (Iterator<String> 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<String> getPathSegments() { |
||||
return this.pathSegments; |
||||
} |
||||
|
||||
public PathComponent encode(String encoding) throws UnsupportedEncodingException { |
||||
List<String> pathSegments = getPathSegments(); |
||||
List<String> encodedPathSegments = new ArrayList<String>(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<String> pathSegments = getPathSegments(); |
||||
List<String> expandedPathSegments = new ArrayList<String>(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<PathComponent> pathComponents; |
||||
|
||||
PathComponentComposite(List<PathComponent> 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<String> getPathSegments() { |
||||
List<String> result = new ArrayList<String>(); |
||||
for (PathComponent pathComponent : this.pathComponents) { |
||||
result.addAll(pathComponent.getPathSegments()); |
||||
} |
||||
return result; |
||||
} |
||||
|
||||
public PathComponent encode(String encoding) throws UnsupportedEncodingException { |
||||
List<PathComponent> encodedComponents = new ArrayList<PathComponent>(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<PathComponent> expandedComponents = new ArrayList<PathComponent>(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<String> 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; |
||||
} |
||||
|
||||
}; |
||||
|
||||
} |
||||
@ -0,0 +1,168 @@
@@ -0,0 +1,168 @@
|
||||
/* |
||||
* 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 <a href="http://tools.ietf.org/html/rfc3986#section-1.2.3">Hierarchical vs Opaque URIs</a> |
||||
*/ |
||||
final class OpaqueUriComponents extends UriComponents { |
||||
|
||||
private static final MultiValueMap<String, String> QUERY_PARAMS_NONE = new LinkedMultiValueMap<String, String>(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<String> getPathSegments() { |
||||
return Collections.emptyList(); |
||||
} |
||||
|
||||
@Override |
||||
public String getQuery() { |
||||
return null; |
||||
} |
||||
|
||||
@Override |
||||
public MultiValueMap<String, String> 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 o) { |
||||
if (this == o) { |
||||
return true; |
||||
} |
||||
if (!(o instanceof OpaqueUriComponents)) { |
||||
return false; |
||||
} |
||||
|
||||
OpaqueUriComponents other = (OpaqueUriComponents) o; |
||||
|
||||
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; |
||||
} |
||||
|
||||
} |
||||
File diff suppressed because it is too large
Load Diff
Loading…
Reference in new issue