Polished UriComponents implementation

(cherry picked from commit c3b624d)
This commit is contained in:
Juergen Hoeller 2013-05-03 13:22:11 +02:00
parent e3fa49063e
commit b2bd319d47
4 changed files with 88 additions and 169 deletions

View File

@ -177,11 +177,9 @@ final class HierarchicalUriComponents extends UriComponents {
@Override @Override
public HierarchicalUriComponents encode(String encoding) throws UnsupportedEncodingException { public HierarchicalUriComponents encode(String encoding) throws UnsupportedEncodingException {
Assert.hasLength(encoding, "'encoding' must not be empty"); Assert.hasLength(encoding, "'encoding' must not be empty");
if (this.encoded) { if (this.encoded) {
return this; return this;
} }
String encodedScheme = encodeUriComponent(this.getScheme(), encoding, Type.SCHEME); String encodedScheme = encodeUriComponent(this.getScheme(), encoding, Type.SCHEME);
String encodedUserInfo = encodeUriComponent(this.userInfo, encoding, Type.USER_INFO); String encodedUserInfo = encodeUriComponent(this.userInfo, encoding, Type.USER_INFO);
String encodedHost = encodeUriComponent(this.host, encoding, Type.HOST); String encodedHost = encodeUriComponent(this.host, encoding, Type.HOST);
@ -198,7 +196,6 @@ final class HierarchicalUriComponents extends UriComponents {
encodedQueryParams.put(encodedName, encodedValues); encodedQueryParams.put(encodedName, encodedValues);
} }
String encodedFragment = encodeUriComponent(this.getFragment(), encoding, Type.FRAGMENT); String encodedFragment = encodeUriComponent(this.getFragment(), encoding, Type.FRAGMENT);
return new HierarchicalUriComponents(encodedScheme, encodedUserInfo, encodedHost, this.port, encodedPath, return new HierarchicalUriComponents(encodedScheme, encodedUserInfo, encodedHost, this.port, encodedPath,
encodedQueryParams, encodedFragment, true, false); encodedQueryParams, encodedFragment, true, false);
} }
@ -212,15 +209,11 @@ final class HierarchicalUriComponents extends UriComponents {
* @return the encoded URI * @return the encoded URI
* @throws IllegalArgumentException when the given uri parameter is not a valid URI * @throws IllegalArgumentException when the given uri parameter is not a valid URI
*/ */
static String encodeUriComponent(String source, String encoding, Type type) static String encodeUriComponent(String source, String encoding, Type type) throws UnsupportedEncodingException {
throws UnsupportedEncodingException {
if (source == null) { if (source == null) {
return null; return null;
} }
Assert.hasLength(encoding, "'encoding' must not be empty"); Assert.hasLength(encoding, "'encoding' must not be empty");
byte[] bytes = encodeBytes(source.getBytes(encoding), type); byte[] bytes = encodeBytes(source.getBytes(encoding), type);
return new String(bytes, "US-ASCII"); return new String(bytes, "US-ASCII");
} }
@ -229,8 +222,7 @@ final class HierarchicalUriComponents extends UriComponents {
Assert.notNull(source, "'source' must not be null"); Assert.notNull(source, "'source' must not be null");
Assert.notNull(type, "'type' must not be null"); Assert.notNull(type, "'type' must not be null");
ByteArrayOutputStream bos = new ByteArrayOutputStream(source.length); ByteArrayOutputStream bos = new ByteArrayOutputStream(source.length);
for (int i = 0; i < source.length; i++) { for (byte b : source) {
int b = source[i];
if (b < 0) { if (b < 0) {
b += 256; b += 256;
} }
@ -277,9 +269,7 @@ final class HierarchicalUriComponents extends UriComponents {
if (source == null) { if (source == null) {
return; return;
} }
int length = source.length(); int length = source.length();
for (int i=0; i < length; i++) { for (int i=0; i < length; i++) {
char ch = source.charAt(i); char ch = source.charAt(i);
if (ch == '%') { if (ch == '%') {
@ -309,8 +299,7 @@ final class HierarchicalUriComponents extends UriComponents {
@Override @Override
protected HierarchicalUriComponents expandInternal(UriTemplateVariables uriVariables) { protected HierarchicalUriComponents expandInternal(UriTemplateVariables uriVariables) {
Assert.state(!encoded, "Cannot expand an already encoded UriComponents object"); Assert.state(!this.encoded, "Cannot expand an already encoded UriComponents object");
String expandedScheme = expandUriComponent(this.getScheme(), uriVariables); String expandedScheme = expandUriComponent(this.getScheme(), uriVariables);
String expandedUserInfo = expandUriComponent(this.userInfo, uriVariables); String expandedUserInfo = expandUriComponent(this.userInfo, uriVariables);
String expandedHost = expandUriComponent(this.host, uriVariables); String expandedHost = expandUriComponent(this.host, uriVariables);
@ -327,7 +316,6 @@ final class HierarchicalUriComponents extends UriComponents {
expandedQueryParams.put(expandedName, expandedValues); expandedQueryParams.put(expandedName, expandedValues);
} }
String expandedFragment = expandUriComponent(this.getFragment(), uriVariables); String expandedFragment = expandUriComponent(this.getFragment(), uriVariables);
return new HierarchicalUriComponents(expandedScheme, expandedUserInfo, expandedHost, this.port, expandedPath, return new HierarchicalUriComponents(expandedScheme, expandedUserInfo, expandedHost, this.port, expandedPath,
expandedQueryParams, expandedFragment, false, false); expandedQueryParams, expandedFragment, false, false);
} }
@ -353,12 +341,10 @@ final class HierarchicalUriComponents extends UriComponents {
@Override @Override
public String toUriString() { public String toUriString() {
StringBuilder uriBuilder = new StringBuilder(); StringBuilder uriBuilder = new StringBuilder();
if (getScheme() != null) { if (getScheme() != null) {
uriBuilder.append(getScheme()); uriBuilder.append(getScheme());
uriBuilder.append(':'); uriBuilder.append(':');
} }
if (this.userInfo != null || this.host != null) { if (this.userInfo != null || this.host != null) {
uriBuilder.append("//"); uriBuilder.append("//");
if (this.userInfo != null) { if (this.userInfo != null) {
@ -373,7 +359,6 @@ final class HierarchicalUriComponents extends UriComponents {
uriBuilder.append(port); uriBuilder.append(port);
} }
} }
String path = getPath(); String path = getPath();
if (StringUtils.hasLength(path)) { if (StringUtils.hasLength(path)) {
if (uriBuilder.length() != 0 && path.charAt(0) != PATH_DELIMITER) { if (uriBuilder.length() != 0 && path.charAt(0) != PATH_DELIMITER) {
@ -381,18 +366,15 @@ final class HierarchicalUriComponents extends UriComponents {
} }
uriBuilder.append(path); uriBuilder.append(path);
} }
String query = getQuery(); String query = getQuery();
if (query != null) { if (query != null) {
uriBuilder.append('?'); uriBuilder.append('?');
uriBuilder.append(query); uriBuilder.append(query);
} }
if (getFragment() != null) { if (getFragment() != null) {
uriBuilder.append('#'); uriBuilder.append('#');
uriBuilder.append(getFragment()); uriBuilder.append(getFragment());
} }
return uriBuilder.toString(); return uriBuilder.toString();
} }
@ -431,15 +413,13 @@ final class HierarchicalUriComponents extends UriComponents {
return false; return false;
} }
HierarchicalUriComponents other = (HierarchicalUriComponents) obj; HierarchicalUriComponents other = (HierarchicalUriComponents) obj;
boolean rtn = true; return ObjectUtils.nullSafeEquals(getScheme(), other.getScheme()) &&
rtn &= ObjectUtils.nullSafeEquals(getScheme(), other.getScheme()); ObjectUtils.nullSafeEquals(getUserInfo(), other.getUserInfo()) &&
rtn &= ObjectUtils.nullSafeEquals(getUserInfo(), other.getUserInfo()); ObjectUtils.nullSafeEquals(getHost(), other.getHost()) &&
rtn &= ObjectUtils.nullSafeEquals(getHost(), other.getHost()); getPort() == other.getPort() &&
rtn &= getPort() == other.getPort(); this.path.equals(other.path) &&
rtn &= this.path.equals(other.path); this.queryParams.equals(other.queryParams) &&
rtn &= this.queryParams.equals(other.queryParams); ObjectUtils.nullSafeEquals(getFragment(), other.getFragment());
rtn &= ObjectUtils.nullSafeEquals(getFragment(), other.getFragment());
return rtn;
} }
@Override @Override
@ -532,15 +512,12 @@ final class HierarchicalUriComponents extends UriComponents {
/** /**
* Indicates whether the given character is allowed in this URI component. * 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 * @return {@code true} if the character is allowed; {@code false} otherwise
*/ */
public abstract boolean isAllowed(int c); public abstract boolean isAllowed(int c);
/** /**
* Indicates whether the given character is in the {@code ALPHA} set. * 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> * @see <a href="http://www.ietf.org/rfc/rfc3986.txt">RFC 3986, appendix A</a>
*/ */
protected boolean isAlpha(int c) { protected boolean isAlpha(int c) {
@ -549,7 +526,6 @@ final class HierarchicalUriComponents extends UriComponents {
/** /**
* Indicates whether the given character is in the {@code DIGIT} set. * 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> * @see <a href="http://www.ietf.org/rfc/rfc3986.txt">RFC 3986, appendix A</a>
*/ */
protected boolean isDigit(int c) { protected boolean isDigit(int c) {
@ -558,7 +534,6 @@ final class HierarchicalUriComponents extends UriComponents {
/** /**
* Indicates whether the given character is in the {@code gen-delims} set. * 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> * @see <a href="http://www.ietf.org/rfc/rfc3986.txt">RFC 3986, appendix A</a>
*/ */
protected boolean isGenericDelimiter(int c) { protected boolean isGenericDelimiter(int c) {
@ -567,7 +542,6 @@ final class HierarchicalUriComponents extends UriComponents {
/** /**
* Indicates whether the given character is in the {@code sub-delims} set. * 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> * @see <a href="http://www.ietf.org/rfc/rfc3986.txt">RFC 3986, appendix A</a>
*/ */
protected boolean isSubDelimiter(int c) { protected boolean isSubDelimiter(int c) {
@ -577,7 +551,6 @@ final class HierarchicalUriComponents extends UriComponents {
/** /**
* Indicates whether the given character is in the {@code reserved} set. * 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> * @see <a href="http://www.ietf.org/rfc/rfc3986.txt">RFC 3986, appendix A</a>
*/ */
protected boolean isReserved(char c) { protected boolean isReserved(char c) {
@ -586,7 +559,6 @@ final class HierarchicalUriComponents extends UriComponents {
/** /**
* Indicates whether the given character is in the {@code unreserved} set. * 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> * @see <a href="http://www.ietf.org/rfc/rfc3986.txt">RFC 3986, appendix A</a>
*/ */
protected boolean isUnreserved(int c) { protected boolean isUnreserved(int c) {
@ -595,7 +567,6 @@ final class HierarchicalUriComponents extends UriComponents {
/** /**
* Indicates whether the given character is in the {@code pchar} set. * 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> * @see <a href="http://www.ietf.org/rfc/rfc3986.txt">RFC 3986, appendix A</a>
*/ */
protected boolean isPchar(int c) { protected boolean isPchar(int c) {
@ -624,11 +595,11 @@ final class HierarchicalUriComponents extends UriComponents {
/** /**
* Represents a path backed by a string. * Represents a path backed by a string.
*/ */
final static class FullPathComponent implements PathComponent { static final class FullPathComponent implements PathComponent {
private final String path; private final String path;
FullPathComponent(String path) { public FullPathComponent(String path) {
this.path = path; this.path = path;
} }
@ -668,14 +639,15 @@ final class HierarchicalUriComponents extends UriComponents {
} }
} }
/** /**
* Represents a path backed by a string list (i.e. path segments). * Represents a path backed by a string list (i.e. path segments).
*/ */
final static class PathSegmentComponent implements PathComponent { static final class PathSegmentComponent implements PathComponent {
private final List<String> pathSegments; private final List<String> pathSegments;
PathSegmentComponent(List<String> pathSegments) { public PathSegmentComponent(List<String> pathSegments) {
this.pathSegments = Collections.unmodifiableList(pathSegments); this.pathSegments = Collections.unmodifiableList(pathSegments);
} }
@ -732,17 +704,17 @@ final class HierarchicalUriComponents extends UriComponents {
public int hashCode() { public int hashCode() {
return getPathSegments().hashCode(); return getPathSegments().hashCode();
} }
} }
/** /**
* Represents a collection of PathComponents. * Represents a collection of PathComponents.
*/ */
final static class PathComponentComposite implements PathComponent { static final class PathComponentComposite implements PathComponent {
private final List<PathComponent> pathComponents; private final List<PathComponent> pathComponents;
PathComponentComposite(List<PathComponent> pathComponents) { public PathComponentComposite(List<PathComponent> pathComponents) {
this.pathComponents = pathComponents; this.pathComponents = pathComponents;
} }
@ -763,15 +735,15 @@ final class HierarchicalUriComponents extends UriComponents {
} }
public PathComponent encode(String encoding) throws UnsupportedEncodingException { public PathComponent encode(String encoding) throws UnsupportedEncodingException {
List<PathComponent> encodedComponents = new ArrayList<PathComponent>(pathComponents.size()); List<PathComponent> encodedComponents = new ArrayList<PathComponent>(this.pathComponents.size());
for (PathComponent pathComponent : pathComponents) { for (PathComponent pathComponent : this.pathComponents) {
encodedComponents.add(pathComponent.encode(encoding)); encodedComponents.add(pathComponent.encode(encoding));
} }
return new PathComponentComposite(encodedComponents); return new PathComponentComposite(encodedComponents);
} }
public void verify() { public void verify() {
for (PathComponent pathComponent : pathComponents) { for (PathComponent pathComponent : this.pathComponents) {
pathComponent.verify(); pathComponent.verify();
} }
} }
@ -786,41 +758,32 @@ final class HierarchicalUriComponents extends UriComponents {
} }
/** /**
* Represents an empty path. * Represents an empty path.
*/ */
final static PathComponent NULL_PATH_COMPONENT = new PathComponent() { static final PathComponent NULL_PATH_COMPONENT = new PathComponent() {
public String getPath() { public String getPath() {
return null; return null;
} }
public List<String> getPathSegments() { public List<String> getPathSegments() {
return Collections.emptyList(); return Collections.emptyList();
} }
public PathComponent encode(String encoding) throws UnsupportedEncodingException { public PathComponent encode(String encoding) throws UnsupportedEncodingException {
return this; return this;
} }
public void verify() { public void verify() {
} }
public PathComponent expand(UriTemplateVariables uriVariables) { public PathComponent expand(UriTemplateVariables uriVariables) {
return this; return this;
} }
@Override @Override
public boolean equals(Object obj) { public boolean equals(Object obj) {
return (this == obj); return (this == obj);
} }
@Override @Override
public int hashCode() { public int hashCode() {
return 42; return 42;
} }
}; };
} }

View File

@ -98,10 +98,14 @@ final class OpaqueUriComponents extends UriComponents {
String expandedScheme = expandUriComponent(this.getScheme(), uriVariables); String expandedScheme = expandUriComponent(this.getScheme(), uriVariables);
String expandedSSp = expandUriComponent(this.ssp, uriVariables); String expandedSSp = expandUriComponent(this.ssp, uriVariables);
String expandedFragment = expandUriComponent(this.getFragment(), uriVariables); String expandedFragment = expandUriComponent(this.getFragment(), uriVariables);
return new OpaqueUriComponents(expandedScheme, expandedSSp, expandedFragment); return new OpaqueUriComponents(expandedScheme, expandedSSp, expandedFragment);
} }
@Override
public UriComponents normalize() {
return this;
}
@Override @Override
public String toUriString() { public String toUriString() {
StringBuilder uriBuilder = new StringBuilder(); StringBuilder uriBuilder = new StringBuilder();
@ -131,10 +135,6 @@ final class OpaqueUriComponents extends UriComponents {
} }
} }
@Override
public UriComponents normalize() {
return this;
}
@Override @Override
public boolean equals(Object obj) { public boolean equals(Object obj) {
@ -146,11 +146,10 @@ final class OpaqueUriComponents extends UriComponents {
} }
OpaqueUriComponents other = (OpaqueUriComponents) obj; OpaqueUriComponents other = (OpaqueUriComponents) obj;
boolean rtn = true; return ObjectUtils.nullSafeEquals(getScheme(), other.getScheme()) &&
rtn &= ObjectUtils.nullSafeEquals(getScheme(), other.getScheme()); ObjectUtils.nullSafeEquals(this.ssp, other.ssp) &&
rtn &= ObjectUtils.nullSafeEquals(this.ssp, other.ssp); ObjectUtils.nullSafeEquals(getFragment(), other.getFragment());
rtn &= ObjectUtils.nullSafeEquals(getFragment(), other.getFragment());
return rtn;
} }
@Override @Override

View File

@ -64,7 +64,7 @@ public abstract class UriComponents implements Serializable {
* Returns the scheme. Can be {@code null}. * Returns the scheme. Can be {@code null}.
*/ */
public final String getScheme() { public final String getScheme() {
return scheme; return this.scheme;
} }
/** /**
@ -115,11 +115,9 @@ public abstract class UriComponents implements Serializable {
} }
// encoding
/** /**
* Encode all URI components using their specific encoding rules, and returns the result * Encode all URI components using their specific encoding rules, and returns the
* as a new {@code UriComponents} instance. This method uses UTF-8 to encode. * result as a new {@code UriComponents} instance. This method uses UTF-8 to encode.
* @return the encoded uri components * @return the encoded uri components
*/ */
public final UriComponents encode() { public final UriComponents encode() {
@ -140,9 +138,6 @@ public abstract class UriComponents implements Serializable {
*/ */
public abstract UriComponents encode(String encoding) throws UnsupportedEncodingException; public abstract UriComponents encode(String encoding) throws UnsupportedEncodingException;
// expanding
/** /**
* Replaces all URI template variables with the values from a given map. The map keys * 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 * represent variable names; the values variable values. The order of variables is not
@ -174,6 +169,30 @@ public abstract class UriComponents implements Serializable {
*/ */
abstract UriComponents expandInternal(UriTemplateVariables uriVariables); abstract UriComponents expandInternal(UriTemplateVariables uriVariables);
/**
* Normalize the path removing sequences like "path/..".
* @see org.springframework.util.StringUtils#cleanPath(String)
*/
public abstract UriComponents normalize();
/**
* Returns a URI string from this {@code UriComponents} instance.
*/
public abstract String toUriString();
/**
* Returns a {@code URI} from this {@code UriComponents} instance.
*/
public abstract URI toUri();
@Override
public final String toString() {
return toUriString();
}
// static expansion helpers
static String expandUriComponent(String source, UriTemplateVariables uriVariables) { static String expandUriComponent(String source, UriTemplateVariables uriVariables) {
if (source == null) { if (source == null) {
return null; return null;
@ -197,34 +216,13 @@ public abstract class UriComponents implements Serializable {
private static String getVariableName(String match) { private static String getVariableName(String match) {
int colonIdx = match.indexOf(':'); int colonIdx = match.indexOf(':');
return colonIdx == -1 ? match : match.substring(0, colonIdx); return (colonIdx != -1 ? match.substring(0, colonIdx) : match);
} }
private static String getVariableValueAsString(Object variableValue) { private static String getVariableValueAsString(Object variableValue) {
return variableValue != null ? variableValue.toString() : ""; return (variableValue != null ? variableValue.toString() : "");
} }
/**
* Returns a URI string from this {@code UriComponents} instance.
*/
public abstract String toUriString();
/**
* Returns a {@code URI} from this {@code UriComponents} instance.
*/
public abstract URI toUri();
@Override
public final String toString() {
return toUriString();
}
/**
* Normalize the path removing sequences like "path/..".
* @see org.springframework.util.StringUtils#cleanPath(String)
*/
public abstract UriComponents normalize();
/** /**
* Defines the contract for URI Template variables * Defines the contract for URI Template variables
@ -233,9 +231,9 @@ public abstract class UriComponents implements Serializable {
interface UriTemplateVariables { interface UriTemplateVariables {
Object getValue(String name); Object getValue(String name);
} }
/** /**
* URI template variables backed by a map. * URI template variables backed by a map.
*/ */
@ -268,11 +266,11 @@ public abstract class UriComponents implements Serializable {
} }
public Object getValue(String name) { public Object getValue(String name) {
if (!valueIterator.hasNext()) { if (!this.valueIterator.hasNext()) {
throw new IllegalArgumentException( throw new IllegalArgumentException(
"Not enough variable values available to expand '" + name + "'"); "Not enough variable values available to expand '" + name + "'");
} }
return valueIterator.next(); return this.valueIterator.next();
} }
} }

View File

@ -100,9 +100,9 @@ public class UriComponentsBuilder {
private String fragment; private String fragment;
/** /**
* Default constructor. Protected to prevent direct instantiation. * Default constructor. Protected to prevent direct instantiation.
*
* @see #newInstance() * @see #newInstance()
* @see #fromPath(String) * @see #fromPath(String)
* @see #fromUri(URI) * @see #fromUri(URI)
@ -110,11 +110,11 @@ public class UriComponentsBuilder {
protected UriComponentsBuilder() { protected UriComponentsBuilder() {
} }
// Factory methods // Factory methods
/** /**
* Returns a new, empty builder. * Returns a new, empty builder.
*
* @return the new {@code UriComponentsBuilder} * @return the new {@code UriComponentsBuilder}
*/ */
public static UriComponentsBuilder newInstance() { public static UriComponentsBuilder newInstance() {
@ -123,7 +123,6 @@ public class UriComponentsBuilder {
/** /**
* Returns a builder that is initialized with the given path. * Returns a builder that is initialized with the given path.
*
* @param path the path to initialize with * @param path the path to initialize with
* @return the new {@code UriComponentsBuilder} * @return the new {@code UriComponentsBuilder}
*/ */
@ -135,7 +134,6 @@ public class UriComponentsBuilder {
/** /**
* Returns a builder that is initialized with the given {@code URI}. * Returns a builder that is initialized with the given {@code URI}.
*
* @param uri the URI to initialize with * @param uri the URI to initialize with
* @return the new {@code UriComponentsBuilder} * @return the new {@code UriComponentsBuilder}
*/ */
@ -147,20 +145,16 @@ public class UriComponentsBuilder {
/** /**
* Returns a builder that is initialized with the given URI string. * Returns a builder that is initialized with the given URI string.
*
* <p><strong>Note:</strong> The presence of reserved characters can prevent * <p><strong>Note:</strong> The presence of reserved characters can prevent
* correct parsing of the URI string. For example if a query parameter * correct parsing of the URI string. For example if a query parameter
* contains {@code '='} or {@code '&'} characters, the query string cannot * contains {@code '='} or {@code '&'} characters, the query string cannot
* be parsed unambiguously. Such values should be substituted for URI * be parsed unambiguously. Such values should be substituted for URI
* variables to enable correct parsing: * variables to enable correct parsing:
*
* <pre> * <pre>
* String uriString = &quot;/hotels/42?filter={value}&quot;; * String uriString = &quot;/hotels/42?filter={value}&quot;;
* UriComponentsBuilder.fromUriString(uriString).buildAndExpand(&quot;hot&amp;cold&quot;); * UriComponentsBuilder.fromUriString(uriString).buildAndExpand(&quot;hot&amp;cold&quot;);
* </pre> * </pre>
* * @param uri the URI string to initialize with
* @param uri
* the URI string to initialize with
* @return the new {@code UriComponentsBuilder} * @return the new {@code UriComponentsBuilder}
*/ */
public static UriComponentsBuilder fromUriString(String uri) { public static UriComponentsBuilder fromUriString(String uri) {
@ -168,7 +162,6 @@ public class UriComponentsBuilder {
Matcher m = URI_PATTERN.matcher(uri); Matcher m = URI_PATTERN.matcher(uri);
if (m.matches()) { if (m.matches()) {
UriComponentsBuilder builder = new UriComponentsBuilder(); UriComponentsBuilder builder = new UriComponentsBuilder();
String scheme = m.group(2); String scheme = m.group(2);
String userInfo = m.group(5); String userInfo = m.group(5);
String host = m.group(6); String host = m.group(6);
@ -176,19 +169,14 @@ public class UriComponentsBuilder {
String path = m.group(9); String path = m.group(9);
String query = m.group(11); String query = m.group(11);
String fragment = m.group(13); String fragment = m.group(13);
boolean opaque = false; boolean opaque = false;
if (StringUtils.hasLength(scheme)) { if (StringUtils.hasLength(scheme)) {
String s = uri.substring(scheme.length()); String s = uri.substring(scheme.length());
if (!s.startsWith(":/")) { if (!s.startsWith(":/")) {
opaque = true; opaque = true;
} }
} }
builder.scheme(scheme); builder.scheme(scheme);
if (opaque) { if (opaque) {
String ssp = uri.substring(scheme.length()).substring(1); String ssp = uri.substring(scheme.length()).substring(1);
if (StringUtils.hasLength(fragment)) { if (StringUtils.hasLength(fragment)) {
@ -205,11 +193,9 @@ public class UriComponentsBuilder {
builder.path(path); builder.path(path);
builder.query(query); builder.query(query);
} }
if (StringUtils.hasText(fragment)) { if (StringUtils.hasText(fragment)) {
builder.fragment(fragment); builder.fragment(fragment);
} }
return builder; return builder;
} }
else { else {
@ -219,18 +205,15 @@ public class UriComponentsBuilder {
/** /**
* Creates a new {@code UriComponents} object from the string HTTP URL. * Creates a new {@code UriComponents} object from the string HTTP URL.
*
* <p><strong>Note:</strong> The presence of reserved characters can prevent * <p><strong>Note:</strong> The presence of reserved characters can prevent
* correct parsing of the URI string. For example if a query parameter * correct parsing of the URI string. For example if a query parameter
* contains {@code '='} or {@code '&'} characters, the query string cannot * contains {@code '='} or {@code '&'} characters, the query string cannot
* be parsed unambiguously. Such values should be substituted for URI * be parsed unambiguously. Such values should be substituted for URI
* variables to enable correct parsing: * variables to enable correct parsing:
*
* <pre> * <pre>
* String uriString = &quot;/hotels/42?filter={value}&quot;; * String uriString = &quot;/hotels/42?filter={value}&quot;;
* UriComponentsBuilder.fromUriString(uriString).buildAndExpand(&quot;hot&amp;cold&quot;); * UriComponentsBuilder.fromUriString(uriString).buildAndExpand(&quot;hot&amp;cold&quot;);
* </pre> * </pre>
*
* @param httpUrl the source URI * @param httpUrl the source URI
* @return the URI components of the URI * @return the URI components of the URI
*/ */
@ -258,12 +241,10 @@ public class UriComponentsBuilder {
} }
// build methods // build methods
/** /**
* 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.
*
* @return the URI components * @return the URI components
*/ */
public UriComponents build() { public UriComponents build() {
@ -273,18 +254,17 @@ public class UriComponentsBuilder {
/** /**
* Builds a {@code UriComponents} instance from the various components * Builds a {@code UriComponents} instance from the various components
* contained in this builder. * contained in this builder.
*
* @param encoded whether all the components set in this builder are * @param encoded whether all the components set in this builder are
* encoded ({@code true}) or not ({@code false}). * encoded ({@code true}) or not ({@code false}).
* @return the URI components * @return the URI components
*/ */
public UriComponents build(boolean encoded) { public UriComponents build(boolean encoded) {
if (ssp != null) { if (this.ssp != null) {
return new OpaqueUriComponents(scheme, ssp, fragment); return new OpaqueUriComponents(this.scheme, this.ssp, this.fragment);
} }
else { else {
return new HierarchicalUriComponents( return new HierarchicalUriComponents(this.scheme, this.userInfo, this.host, this.port,
scheme, userInfo, host, port, pathBuilder.build(), queryParams, fragment, encoded, true); this.pathBuilder.build(), this.queryParams, this.fragment, encoded, true);
} }
} }
@ -292,7 +272,6 @@ public class UriComponentsBuilder {
* Builds a {@code UriComponents} instance and replaces URI template variables * Builds a {@code UriComponents} instance and replaces URI template variables
* with the values from a map. This is a shortcut method, which combines * with the values from a map. This is a shortcut method, which combines
* calls to {@link #build()} and then {@link UriComponents#expand(Map)}. * calls to {@link #build()} and then {@link UriComponents#expand(Map)}.
*
* @param uriVariables the map of URI variables * @param uriVariables the map of URI variables
* @return the URI components with expanded values * @return the URI components with expanded values
*/ */
@ -304,7 +283,6 @@ public class UriComponentsBuilder {
* Builds a {@code UriComponents} instance and replaces URI template variables * Builds a {@code UriComponents} instance and replaces URI template variables
* with the values from an array. This is a shortcut method, which combines * with the values from an array. This is a shortcut method, which combines
* calls to {@link #build()} and then {@link UriComponents#expand(Object...)}. * calls to {@link #build()} and then {@link UriComponents#expand(Object...)}.
*
* @param uriVariableValues URI variable values * @param uriVariableValues URI variable values
* @return the URI components with expanded values * @return the URI components with expanded values
*/ */
@ -312,19 +290,17 @@ public class UriComponentsBuilder {
return build(false).expand(uriVariableValues); 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. * Initializes all components of this URI builder with the components of the given URI.
*
* @param uri the URI * @param uri the URI
* @return this UriComponentsBuilder * @return this UriComponentsBuilder
*/ */
public UriComponentsBuilder uri(URI uri) { public UriComponentsBuilder uri(URI uri) {
Assert.notNull(uri, "'uri' must not be null"); Assert.notNull(uri, "'uri' must not be null");
this.scheme = uri.getScheme(); this.scheme = uri.getScheme();
if (uri.isOpaque()) { if (uri.isOpaque()) {
this.ssp = uri.getRawSchemeSpecificPart(); this.ssp = uri.getRawSchemeSpecificPart();
resetHierarchicalComponents(); resetHierarchicalComponents();
@ -369,9 +345,7 @@ public class UriComponentsBuilder {
/** /**
* Sets the URI scheme. The given scheme may contain URI template variables, * 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. * 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 * @return this UriComponentsBuilder
*/ */
public UriComponentsBuilder scheme(String scheme) { public UriComponentsBuilder scheme(String scheme) {
@ -384,7 +358,6 @@ public class UriComponentsBuilder {
* {@linkplain #userInfo(String) user-info}, {@linkplain #host(String) host}, * {@linkplain #userInfo(String) user-info}, {@linkplain #host(String) host},
* {@linkplain #port(int) port}, {@linkplain #path(String) path}, and * {@linkplain #port(int) port}, {@linkplain #path(String) path}, and
* {@link #query(String) query}. * {@link #query(String) query}.
*
* @param ssp the URI scheme-specific-part, may contain URI template parameters * @param ssp the URI scheme-specific-part, may contain URI template parameters
* @return this UriComponentsBuilder * @return this UriComponentsBuilder
*/ */
@ -398,7 +371,6 @@ public class UriComponentsBuilder {
* Sets the URI user info. The given user info may contain URI template * 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 * variables, and may also be {@code null} to clear the user info of this
* builder. * builder.
*
* @param userInfo the URI user info * @param userInfo the URI user info
* @return this UriComponentsBuilder * @return this UriComponentsBuilder
*/ */
@ -411,7 +383,6 @@ public class UriComponentsBuilder {
/** /**
* Sets the URI host. The given host may contain URI template variables, and * 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. * may also be {@code null} to clear the host of this builder.
*
* @param host the URI host * @param host the URI host
* @return this UriComponentsBuilder * @return this UriComponentsBuilder
*/ */
@ -423,7 +394,6 @@ public class UriComponentsBuilder {
/** /**
* Sets the URI port. Passing {@code -1} will clear the port of this builder. * Sets the URI port. Passing {@code -1} will clear the port of this builder.
*
* @param port the URI port * @param port the URI port
* @return this UriComponentsBuilder * @return this UriComponentsBuilder
*/ */
@ -437,7 +407,6 @@ public class UriComponentsBuilder {
/** /**
* Appends the given path to the existing path of this builder. The given * Appends the given path to the existing path of this builder. The given
* path may contain URI template variables. * path may contain URI template variables.
*
* @param path the URI path * @param path the URI path
* @return this UriComponentsBuilder * @return this UriComponentsBuilder
*/ */
@ -449,7 +418,6 @@ public class UriComponentsBuilder {
/** /**
* Sets the path of this builder overriding all existing path and path segment values. * Sets the path of this builder overriding all existing path and path segment values.
*
* @param path the URI path; a {@code null} value results in an empty path. * @param path the URI path; a {@code null} value results in an empty path.
* @return this UriComponentsBuilder * @return this UriComponentsBuilder
*/ */
@ -462,7 +430,6 @@ public class UriComponentsBuilder {
/** /**
* Appends the given path segments to the existing path of this builder. Each given * Appends the given path segments to the existing path of this builder. Each given
* path segments may contain URI template variables. * path segments may contain URI template variables.
*
* @param pathSegments the URI path segments * @param pathSegments the URI path segments
* @return this UriComponentsBuilder * @return this UriComponentsBuilder
*/ */
@ -476,18 +443,15 @@ public class UriComponentsBuilder {
/** /**
* Appends the given query to the existing query of this builder. * Appends the given query to the existing query of this builder.
* The given query may contain URI template variables. * The given query may contain URI template variables.
*
* <p><strong>Note:</strong> The presence of reserved characters can prevent * <p><strong>Note:</strong> The presence of reserved characters can prevent
* correct parsing of the URI string. For example if a query parameter * correct parsing of the URI string. For example if a query parameter
* contains {@code '='} or {@code '&'} characters, the query string cannot * contains {@code '='} or {@code '&'} characters, the query string cannot
* be parsed unambiguously. Such values should be substituted for URI * be parsed unambiguously. Such values should be substituted for URI
* variables to enable correct parsing: * variables to enable correct parsing:
*
* <pre> * <pre>
* String uriString = &quot;/hotels/42?filter={value}&quot;; * String uriString = &quot;/hotels/42?filter={value}&quot;;
* UriComponentsBuilder.fromUriString(uriString).buildAndExpand(&quot;hot&amp;cold&quot;); * UriComponentsBuilder.fromUriString(uriString).buildAndExpand(&quot;hot&amp;cold&quot;);
* </pre> * </pre>
*
* @param query the query string * @param query the query string
* @return this UriComponentsBuilder * @return this UriComponentsBuilder
*/ */
@ -511,7 +475,6 @@ public class UriComponentsBuilder {
/** /**
* Sets the query of this builder overriding all existing query parameters. * Sets the query of this builder overriding all existing query parameters.
*
* @param query the query string; a {@code null} value removes all query parameters. * @param query the query string; a {@code null} value removes all query parameters.
* @return this UriComponentsBuilder * @return this UriComponentsBuilder
*/ */
@ -527,11 +490,8 @@ public class UriComponentsBuilder {
* given name or any of the values may contain URI template variables. If no * 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 * values are given, the resulting URI will contain the query parameter name
* only (i.e. {@code ?foo} instead of {@code ?foo=bar}. * only (i.e. {@code ?foo} instead of {@code ?foo=bar}.
* * @param name the query parameter name
* @param name * @param values the query parameter values
* the query parameter name
* @param values
* the query parameter values
* @return this UriComponentsBuilder * @return this UriComponentsBuilder
*/ */
public UriComponentsBuilder queryParam(String name, Object... values) { public UriComponentsBuilder queryParam(String name, Object... values) {
@ -553,11 +513,8 @@ public class UriComponentsBuilder {
* Sets the query parameter values overriding all existing query values for * Sets the query parameter values overriding all existing query values for
* the same parameter. If no values are given, the query parameter is * the same parameter. If no values are given, the query parameter is
* removed. * removed.
* * @param name the query parameter name
* @param name * @param values the query parameter values
* the query parameter name
* @param values
* the query parameter values
* @return this UriComponentsBuilder * @return this UriComponentsBuilder
*/ */
public UriComponentsBuilder replaceQueryParam(String name, Object... values) { public UriComponentsBuilder replaceQueryParam(String name, Object... values) {
@ -574,9 +531,7 @@ public class UriComponentsBuilder {
* Sets the URI fragment. The given fragment may contain URI template * Sets the URI fragment. The given fragment may contain URI template
* variables, and may also be {@code null} to clear the fragment of this * variables, and may also be {@code null} to clear the fragment of this
* builder. * builder.
* * @param fragment the URI fragment
* @param fragment
* the URI fragment
* @return this UriComponentsBuilder * @return this UriComponentsBuilder
*/ */
public UriComponentsBuilder fragment(String fragment) { public UriComponentsBuilder fragment(String fragment) {
@ -592,12 +547,14 @@ public class UriComponentsBuilder {
private interface PathComponentBuilder { private interface PathComponentBuilder {
PathComponent build(); PathComponent build();
} }
private static class CompositePathComponentBuilder implements PathComponentBuilder { private static class CompositePathComponentBuilder implements PathComponentBuilder {
private LinkedList<PathComponentBuilder> componentBuilders = new LinkedList<PathComponentBuilder>(); private final LinkedList<PathComponentBuilder> componentBuilders = new LinkedList<PathComponentBuilder>();
public CompositePathComponentBuilder() { public CompositePathComponentBuilder() {
} }
@ -650,8 +607,8 @@ public class UriComponentsBuilder {
public PathComponent build() { public PathComponent build() {
int size = this.componentBuilders.size(); int size = this.componentBuilders.size();
List<PathComponent> components = new ArrayList<PathComponent>(size); List<PathComponent> components = new ArrayList<PathComponent>(size);
for (int i = 0; i < size; i++) { for (PathComponentBuilder componentBuilder : this.componentBuilders) {
PathComponent pathComponent = this.componentBuilders.get(i).build(); PathComponent pathComponent = componentBuilder.build();
if (pathComponent != null) { if (pathComponent != null) {
components.add(pathComponent); components.add(pathComponent);
} }
@ -666,9 +623,10 @@ public class UriComponentsBuilder {
} }
} }
private static class FullPathComponentBuilder implements PathComponentBuilder { private static class FullPathComponentBuilder implements PathComponentBuilder {
private StringBuilder path = new StringBuilder(); private final StringBuilder path = new StringBuilder();
public void append(String path) { public void append(String path) {
this.path.append(path); this.path.append(path);
@ -690,9 +648,10 @@ public class UriComponentsBuilder {
} }
} }
private static class PathSegmentComponentBuilder implements PathComponentBuilder { private static class PathSegmentComponentBuilder implements PathComponentBuilder {
private List<String> pathSegments = new LinkedList<String>(); private final List<String> pathSegments = new LinkedList<String>();
public void append(String... pathSegments) { public void append(String... pathSegments) {
for (String pathSegment : pathSegments) { for (String pathSegment : pathSegments) {
@ -703,8 +662,8 @@ public class UriComponentsBuilder {
} }
public PathComponent build() { public PathComponent build() {
return this.pathSegments.isEmpty() ? return (this.pathSegments.isEmpty() ? null :
null : new HierarchicalUriComponents.PathSegmentComponent(this.pathSegments); new HierarchicalUriComponents.PathSegmentComponent(this.pathSegments));
} }
} }