From 663f0563292c4f9408cd2eec395e3f9eb30ef2ea Mon Sep 17 00:00:00 2001 From: Arjen Poutsma Date: Tue, 13 Sep 2011 14:12:54 +0000 Subject: [PATCH] SPR-5973: UriComponents no longer a Map, moved all static methods from UriComponents to builder, added expand method to UriComponents --- .../web/util/UriComponents.java | 775 +++++++++--------- .../web/util/UriComponentsBuilder.java | 222 +++-- .../springframework/web/util/UriTemplate.java | 149 +--- .../springframework/web/util/UriUtils.java | 13 +- .../web/util/UriComponentsBuilderTests.java | 96 ++- .../web/util/UriComponentsTests.java | 80 +- .../web/util/UriTemplateTests.java | 16 - .../web/util/UriUtilsTests.java | 30 +- 8 files changed, 634 insertions(+), 747 deletions(-) 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 1552d44c79..97f1b2d686 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 @@ -20,17 +20,17 @@ 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.Collection; import java.util.Collections; -import java.util.EnumMap; +import java.util.Iterator; import java.util.List; import java.util.Map; -import java.util.Set; 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; @@ -48,229 +48,54 @@ import org.springframework.util.StringUtils; * @since 3.1 * @see UriComponentsBuilder */ -public final class UriComponents implements Map { +public final class UriComponents { - /** - * The default encoding used for various encode methods. - */ - public static final String DEFAULT_ENCODING = "UTF-8"; - - private static final String SCHEME_PATTERN = "([^:/?#]+):"; - - private static final String HTTP_PATTERN = "(http|https):"; - - private static final String USERINFO_PATTERN = "([^@/]*)"; + private static final String DEFAULT_ENCODING = "UTF-8"; - private static final String HOST_PATTERN = "([^/?#:]*)"; + private static final char PATH_DELIMITER = '/'; - private static final String PORT_PATTERN = "(\\d*)"; + /** Captures URI template variable names. */ + private static final Pattern NAMES_PATTERN = Pattern.compile("\\{([^/]+?)\\}"); - private static final String PATH_PATTERN = "([^?#]*)"; + private final String scheme; - private static final String QUERY_PATTERN = "([^#]*)"; + private final String userInfo; - private static final String LAST_PATTERN = "(.*)"; + private final String host; - // Regex patterns that matches URIs. See RFC 3986, appendix B - private static final Pattern URI_PATTERN = Pattern.compile( - "^(" + SCHEME_PATTERN + ")?" + "(//(" + USERINFO_PATTERN + "@)?" + HOST_PATTERN + "(:" + PORT_PATTERN + - ")?" + ")?" + PATH_PATTERN + "(\\?" + QUERY_PATTERN + ")?" + "(#" + LAST_PATTERN + ")?"); + private final int port; - private static final Pattern HTTP_URL_PATTERN = Pattern.compile( - "^" + HTTP_PATTERN + "(//(" + USERINFO_PATTERN + "@)?" + HOST_PATTERN + "(:" + PORT_PATTERN + ")?" + ")?" + - PATH_PATTERN + "(\\?" + LAST_PATTERN + ")?"); + private final List pathSegments; + private final MultiValueMap queryParams; - private static final String PATH_DELIMITER = "/"; - - private static final Pattern QUERY_PARAM_PATTERN = Pattern.compile("([^&=]+)=?([^&=]+)?"); - - private final Map uriComponents; + private final String fragment; private final boolean encoded; - private UriComponents(Map uriComponents, boolean encoded) { - Assert.notEmpty(uriComponents, "'uriComponents' must not be empty"); - this.uriComponents = Collections.unmodifiableMap(uriComponents); - this.encoded = encoded; - } - - /** - * Creates a new {@code UriComponents} object from the given string URI. - * - * @param uri the source URI - * @return the URI components of the URI - */ - public static UriComponents fromUriString(String uri) { - Assert.notNull(uri, "'uri' must not be null"); - Matcher m = URI_PATTERN.matcher(uri); - if (m.matches()) { - Map result = new EnumMap(UriComponents.Type.class); - - result.put(UriComponents.Type.SCHEME, m.group(2)); - result.put(UriComponents.Type.AUTHORITY, m.group(3)); - result.put(UriComponents.Type.USER_INFO, m.group(5)); - result.put(UriComponents.Type.HOST, m.group(6)); - result.put(UriComponents.Type.PORT, m.group(8)); - result.put(UriComponents.Type.PATH, m.group(9)); - result.put(UriComponents.Type.QUERY, m.group(11)); - result.put(UriComponents.Type.FRAGMENT, m.group(13)); - - return new UriComponents(result, false); - } - else { - throw new IllegalArgumentException("[" + uri + "] is not a valid URI"); - } - } - - /** - * Creates a new {@code UriComponents} object from the string HTTP URL. - * - * @param httpUrl the source URI - * @return the URI components of the URI - */ - public static UriComponents fromHttpUrl(String httpUrl) { - Assert.notNull(httpUrl, "'httpUrl' must not be null"); - Matcher m = HTTP_URL_PATTERN.matcher(httpUrl); - if (m.matches()) { - Map result = new EnumMap(UriComponents.Type.class); - - result.put(UriComponents.Type.SCHEME, m.group(1)); - result.put(UriComponents.Type.AUTHORITY, m.group(2)); - result.put(UriComponents.Type.USER_INFO, m.group(4)); - result.put(UriComponents.Type.HOST, m.group(5)); - result.put(UriComponents.Type.PORT, m.group(7)); - result.put(UriComponents.Type.PATH, m.group(8)); - result.put(UriComponents.Type.QUERY, m.group(10)); - - return new UriComponents(result, false); - } - else { - throw new IllegalArgumentException("[" + httpUrl + "] is not a valid HTTP URL"); - } - } - - /** - * Creates a new {@code UriComponents} object from the given {@code URI}. - * - * @param uri the URI - * @return the URI components of the URI - */ - public static UriComponents fromUri(URI uri) { - Assert.notNull(uri, "'uri' must not be null"); - - Map uriComponents = new EnumMap(Type.class); - if (uri.getScheme() != null) { - uriComponents.put(Type.SCHEME, uri.getScheme()); - } - if (uri.getRawAuthority() != null) { - uriComponents.put(Type.AUTHORITY, uri.getRawAuthority()); - } - if (uri.getRawUserInfo() != null) { - uriComponents.put(Type.USER_INFO, uri.getRawUserInfo()); - } - if (uri.getHost() != null) { - uriComponents.put(Type.HOST, uri.getHost()); - } - if (uri.getPort() != -1) { - uriComponents.put(Type.PORT, Integer.toString(uri.getPort())); - } - if (uri.getRawPath() != null) { - uriComponents.put(Type.PATH, uri.getRawPath()); - } - if (uri.getRawQuery() != null) { - uriComponents.put(Type.QUERY, uri.getRawQuery()); - } - if (uri.getRawFragment() != null) { - uriComponents.put(Type.FRAGMENT, uri.getRawFragment()); - } - return new UriComponents(uriComponents, true); - } - - - /** - * Creates an instance of the {@code UriComponents} object from the given components. All the given arguments - * can be {@code null} and are considered to be unencoded. - */ - public static UriComponents fromUriComponents(String scheme, - String authority, - String userInfo, - String host, - String port, - String path, - String query, - String fragment) { - return fromUriComponents(scheme, authority, userInfo, host, port, path, query, fragment, false); - } - - /** - * Creates an instance of the {@code UriComponents} object from the given components. All the given arguments - * can be {@code null}. - * - * @param encoded {@code true} if the arguments are encoded; {@code false} otherwise - */ - public static UriComponents fromUriComponents(String scheme, - String authority, - String userInfo, - String host, - String port, - String path, - String query, - String fragment, - boolean encoded) { - Map uriComponents = new EnumMap(Type.class); - if (scheme != null) { - uriComponents.put(Type.SCHEME, scheme); - } - if (authority != null) { - uriComponents.put(Type.AUTHORITY, authority); - } - if (userInfo != null) { - uriComponents.put(Type.USER_INFO, userInfo); - } - if (host != null) { - uriComponents.put(Type.HOST, host); - } - if (port != null) { - uriComponents.put(Type.PORT, port); - } - if (path != null) { - uriComponents.put(Type.PATH, path); - } - if (query != null) { - uriComponents.put(Type.QUERY, query); - } - if (fragment != null) { - uriComponents.put(Type.FRAGMENT, fragment); - } - return new UriComponents(uriComponents, encoded); - } - - /** - * Creates an instance of the {@code UriComponents} object that contains the given components map. - * - * @param uriComponents the component to initialize with - */ - public static UriComponents fromUriComponentMap(Map uriComponents) { - boolean encoded; - if (uriComponents instanceof UriComponents) { - encoded = ((UriComponents) uriComponents).encoded; - } - else { - encoded = false; - } - return new UriComponents(uriComponents, encoded); - } - - /** - * Creates an instance of the {@code UriComponents} object that contains the given components map. - * - * @param uriComponents the component to initialize with - * @param encoded whether the components are encpded - */ - public static UriComponents fromUriComponentMap(Map uriComponents, boolean encoded) { - return new UriComponents(uriComponents, encoded); - } + public UriComponents(String scheme, + String userInfo, + String host, + int port, + List pathSegments, + MultiValueMap queryParams, + String fragment, + boolean encoded) { + this.scheme = scheme; + this.userInfo = userInfo; + this.host = host; + this.port = port; + if (pathSegments == null) { + pathSegments = Collections.emptyList(); + } + this.pathSegments = Collections.unmodifiableList(pathSegments); + if (queryParams == null) { + queryParams = new LinkedMultiValueMap(0); + } + this.queryParams = CollectionUtils.unmodifiableMultiValueMap(queryParams); + this.fragment = fragment; + this.encoded = encoded; + } // component getters @@ -280,16 +105,7 @@ public final class UriComponents implements Map { * @return the scheme. Can be {@code null}. */ public String getScheme() { - return get(Type.SCHEME); - } - - /** - * Returns the authority. - * - * @return the authority. Can be {@code null}. - */ - public String getAuthority() { - return get(Type.AUTHORITY); + return scheme; } /** @@ -298,7 +114,7 @@ public final class UriComponents implements Map { * @return the user info. Can be {@code null}. */ public String getUserInfo() { - return get(Type.USER_INFO); + return userInfo; } /** @@ -307,36 +123,47 @@ public final class UriComponents implements Map { * @return the host. Can be {@code null}. */ public String getHost() { - return get(Type.HOST); - } - - /** - * Returns the port as string. - * - * @return the port as string. Can be {@code null}. - */ - public String getPort() { - return get(Type.PORT); - } - - /** - * Returns the port as integer. Returns {@code -1} if no port has been set. - * - * @return the port the port as integer - */ - public int getPortAsInteger() { - String port = getPort(); - return port != null ? Integer.parseInt(port) : -1; + return host; } /** - * Returns the path. + * Returns the port. Returns {@code -1} if no port has been set. * - * @return the path. Can be {@code null}. + * @return the port */ - public String getPath() { - return get(Type.PATH); - } + public int getPort() { + return port; + } + + /** + * Returns the path. + * + * @return the path. Can be {@code null}. + */ + public String getPath() { + if (!pathSegments.isEmpty()) { + StringBuilder pathBuilder = new StringBuilder(); + for (String pathSegment : pathSegments) { + if (StringUtils.hasLength(pathSegment)) { + boolean startsWithSlash = pathSegment.charAt(0) == PATH_DELIMITER; + boolean endsWithSlash = + pathBuilder.length() > 0 && pathBuilder.charAt(pathBuilder.length() - 1) == PATH_DELIMITER; + + if (!endsWithSlash && !startsWithSlash) { + pathBuilder.append('/'); + } + else if (endsWithSlash && startsWithSlash) { + pathSegment = pathSegment.substring(1); + } + pathBuilder.append(pathSegment); + } + } + return pathBuilder.toString(); + } + else { + return null; + } + } /** * Returns the list of path segments. @@ -344,41 +171,54 @@ public final class UriComponents implements Map { * @return the path segments. Empty if no path has been set. */ public List getPathSegments() { - String path = getPath(); - if (path != null) { - return Arrays.asList(StringUtils.tokenizeToStringArray(path, PATH_DELIMITER)); - } - else { - return Collections.emptyList(); - } - } - - /** - * Returns the query. - * - * @return the query. Can be {@code null}. - */ - public String getQuery() { - return get(Type.QUERY); - } - - /** + return pathSegments; + } + + /** + * Returns the query. + * + * @return the query. 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); + + 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. */ public MultiValueMap getQueryParams() { - MultiValueMap result = new LinkedMultiValueMap(); - String query = getQuery(); - if (query != null) { - Matcher m = QUERY_PARAM_PATTERN.matcher(query); - while (m.find()) { - String name = m.group(1); - String value = m.group(2); - result.add(name, value); - } - } - return result; + return queryParams; } /** @@ -387,11 +227,17 @@ public final class UriComponents implements Map { * @return the fragment. Can be {@code null}. */ public String getFragment() { - return get(Type.FRAGMENT); + return fragment; } - // other functionality + // 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. + * + * @return the encoded uri components + */ public UriComponents encode() { try { return encode(DEFAULT_ENCODING); @@ -416,16 +262,29 @@ public final class UriComponents implements Map { return this; } - final Map encoded = new EnumMap(Type.class); - for (Entry entry : uriComponents.entrySet()) { - Type key = entry.getKey(); - String value = entry.getValue(); - if (value != null) { - value = encodeUriComponent(value, encoding, key); - } - encoded.put(key, value); - } - return new UriComponents(encoded, true); + 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); + List encodedPathSegments = new ArrayList(this.pathSegments.size()); + for (String pathSegment : this.pathSegments) { + String encodedPathSegment = encodeUriComponent(pathSegment, encoding, Type.PATH_SEGMENT); + encodedPathSegments.add(encodedPathSegment); + } + 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); + } + String encodedFragment = encodeUriComponent(this.fragment, encoding, Type.FRAGMENT); + + return new UriComponents(encodedScheme, encodedUserInfo, encodedHost, this.port, encodedPathSegments, + encodedQueryParams, encodedFragment, true); } /** @@ -434,25 +293,25 @@ public final class UriComponents implements Map { * * @param source the source string * @param encoding the encoding of the source string - * @param uriComponent the URI component for the source - * @param encodingOptions the options used when encoding. May be {@code null}. + * @param type the URI component for the source * @return the encoded URI * @throws IllegalArgumentException when the given uri parameter is not a valid URI - * @see EncodingOption */ - static String encodeUriComponent(String source, - String encoding, - UriComponents.Type uriComponent) throws UnsupportedEncodingException { - Assert.hasLength(encoding, "'encoding' must not be empty"); - - byte[] bytes = encodeInternal(source.getBytes(encoding), uriComponent); - return new String(bytes, "US-ASCII"); - } - - private static byte[] encodeInternal(byte[] source, - UriComponents.Type uriComponent) { - Assert.notNull(source, "'source' must not be null"); - Assert.notNull(uriComponent, "'uriComponent' must not be null"); + 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++) { @@ -460,7 +319,7 @@ public final class UriComponents implements Map { if (b < 0) { b += 256; } - if (uriComponent.isAllowed(b)) { + if (type.isAllowed(b)) { bos.write(b); } else { @@ -476,51 +335,181 @@ public final class UriComponents implements Map { return bos.toByteArray(); } + // 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. + + * @param uriVariables the map of URI variables + * @return the expanded uri components + */ + public UriComponents expand(Map uriVariables) { + Assert.notNull(uriVariables, "'uriVariables' must not be null"); + + String expandedScheme = expandUriComponent(this.scheme, uriVariables); + String expandedUserInfo = expandUriComponent(this.userInfo, uriVariables); + String expandedHost = expandUriComponent(this.host, uriVariables); + List expandedPathSegments = new ArrayList(this.pathSegments.size()); + for (String pathSegment : this.pathSegments) { + String expandedPathSegment = expandUriComponent(pathSegment, uriVariables); + expandedPathSegments.add(expandedPathSegment); + } + 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, expandedPathSegments, + expandedQueryParams, expandedFragment, false); + } + + private String expandUriComponent(String source, Map uriVariables) { + if (source == null) { + return null; + } + if (source.indexOf('{') == -1) { + return source; + } + Matcher matcher = NAMES_PATTERN.matcher(source); + StringBuffer sb = new StringBuffer(); + while (matcher.find()) { + String match = matcher.group(1); + String variableName = getVariableName(match); + Object variableValue = uriVariables.get(variableName); + String uriVariableValueString = getVariableValueAsString(variableValue); + String replacement = Matcher.quoteReplacement(uriVariableValueString); + matcher.appendReplacement(sb, replacement); + } + matcher.appendTail(sb); + return sb.toString(); + } + + /** + * 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) { + Assert.notNull(uriVariableValues, "'uriVariableValues' must not be null"); + + Iterator valueIterator = Arrays.asList(uriVariableValues).iterator(); + + String expandedScheme = expandUriComponent(this.scheme, valueIterator); + String expandedUserInfo = expandUriComponent(this.userInfo, valueIterator); + String expandedHost = expandUriComponent(this.host, valueIterator); + List expandedPathSegments = new ArrayList(this.pathSegments.size()); + for (String pathSegment : this.pathSegments) { + String expandedPathSegment = expandUriComponent(pathSegment, valueIterator); + expandedPathSegments.add(expandedPathSegment); + } + MultiValueMap expandedQueryParams = + new LinkedMultiValueMap(this.queryParams.size()); + for (Map.Entry> entry : this.queryParams.entrySet()) { + String expandedName = expandUriComponent(entry.getKey(), valueIterator); + List expandedValues = new ArrayList(entry.getValue().size()); + for (String value : entry.getValue()) { + String expandedValue = expandUriComponent(value, valueIterator); + expandedValues.add(expandedValue); + } + expandedQueryParams.put(expandedName, expandedValues); + } + String expandedFragment = expandUriComponent(this.fragment, valueIterator); + + return new UriComponents(expandedScheme, expandedUserInfo, expandedHost, this.port, expandedPathSegments, + expandedQueryParams, expandedFragment, false); + } + + private String expandUriComponent(String source, Iterator valueIterator) { + if (source == null) { + return null; + } + if (source.indexOf('{') == -1) { + return source; + } + Matcher matcher = NAMES_PATTERN.matcher(source); + StringBuffer sb = new StringBuffer(); + while (matcher.find()) { + if (!valueIterator.hasNext()) { + throw new IllegalArgumentException("Not enough variable values available to expand [" + source + "]"); + } + Object variableValue = valueIterator.next(); + String uriVariableValueString = getVariableValueAsString(variableValue); + String replacement = Matcher.quoteReplacement(uriVariableValueString); + matcher.appendReplacement(sb, replacement); + } + matcher.appendTail(sb); + return sb.toString(); + } + + + private String getVariableName(String match) { + int colonIdx = match.indexOf(':'); + return colonIdx == -1 ? match : match.substring(0, colonIdx); + } + + protected String getVariableValueAsString(Object variableValue) { + return variableValue != null ? variableValue.toString() : ""; + } + + + + + + // other functionality /** * Returns a URI string from this {@code UriComponents} instance. * - * @return the URI created from the given components + * @return the URI string */ public String toUriString() { StringBuilder uriBuilder = new StringBuilder(); - if (getScheme() != null) { - uriBuilder.append(getScheme()); + if (scheme != null) { + uriBuilder.append(scheme); uriBuilder.append(':'); } - if (getUserInfo() != null || getHost() != null || getPort() != null) { + if (userInfo != null || host != null) { uriBuilder.append("//"); - if (getUserInfo() != null) { - uriBuilder.append(getUserInfo()); + if (userInfo != null) { + uriBuilder.append(userInfo); uriBuilder.append('@'); } - if (getHost() != null) { - uriBuilder.append(getHost()); + if (host != null) { + uriBuilder.append(host); } - if (getPort() != null) { + if (port != -1) { uriBuilder.append(':'); - uriBuilder.append(getPort()); + uriBuilder.append(port); } } - else if (getAuthority() != null) { - uriBuilder.append("//"); - uriBuilder.append(getAuthority()); - } - if (getPath() != null) { - uriBuilder.append(getPath()); + String path = getPath(); + if (path != null) { + uriBuilder.append(path); } - if (getQuery() != null) { + String query = getQuery(); + if (query != null) { uriBuilder.append('?'); - uriBuilder.append(getQuery()); + uriBuilder.append(query); } - if (getFragment() != null) { + if (fragment != null) { uriBuilder.append('#'); - uriBuilder.append(getFragment()); + uriBuilder.append(fragment); } return uriBuilder.toString(); @@ -529,7 +518,7 @@ public final class UriComponents implements Map { /** * Returns a {@code URI} from this {@code UriComponents} instance. * - * @return the URI created from the given components + * @return the URI */ public URI toUri() { try { @@ -537,13 +526,8 @@ public final class UriComponents implements Map { return new URI(toUriString()); } else { - if (getUserInfo() != null || getHost() != null || getPort() != null) { - return new URI(getScheme(), getUserInfo(), getHost(), getPortAsInteger(), getPath(), getQuery(), - getFragment()); - } - else { - return new URI(getScheme(), getAuthority(), getPath(), getQuery(), getFragment()); - } + return new URI(getScheme(), getUserInfo(), getHost(), getPort(), getPath(), getQuery(), + getFragment()); } } catch (URISyntaxException ex) { @@ -551,76 +535,57 @@ public final class UriComponents implements Map { } } - // Map implementation - - public int size() { - return this.uriComponents.size(); - } - - public boolean isEmpty() { - return this.uriComponents.isEmpty(); - } - - public boolean containsKey(Object key) { - return this.uriComponents.containsKey(key); - } - - public boolean containsValue(Object value) { - return this.uriComponents.containsValue(value); - } - - public String get(Object key) { - return this.uriComponents.get(key); - } - - public String put(Type key, String value) { - return this.uriComponents.put(key, value); - } - - public String remove(Object key) { - return this.uriComponents.remove(key); - } - - public void putAll(Map m) { - this.uriComponents.putAll(m); - } - - public void clear() { - this.uriComponents.clear(); - } - - public Set keySet() { - return this.uriComponents.keySet(); - } - - public Collection values() { - return this.uriComponents.values(); - } - - public Set> entrySet() { - return this.uriComponents.entrySet(); - } - - @Override - public boolean equals(Object o) { - if (this == o) { - return true; - } - if (o instanceof UriComponents) { - UriComponents other = (UriComponents) o; - return this.uriComponents.equals(other.uriComponents); - } - return false; - } - - @Override - public int hashCode() { - return this.uriComponents.hashCode(); - } - - @Override + @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 (!pathSegments.equals(other.pathSegments)) { + 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 + pathSegments.hashCode(); + result = 31 * result + queryParams.hashCode(); + result = 31 * result + (fragment != null ? fragment.hashCode() : 0); + return result; + } + + @Override public String toString() { - return this.uriComponents.toString(); + return toUriString(); } // inner types 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 b94de723cb..be70cda8d2 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 @@ -20,8 +20,12 @@ import java.net.URI; import java.util.ArrayList; import java.util.Collections; import java.util.List; +import java.util.regex.Matcher; +import java.util.regex.Pattern; import org.springframework.util.Assert; +import org.springframework.util.LinkedMultiValueMap; +import org.springframework.util.MultiValueMap; import org.springframework.util.ObjectUtils; import org.springframework.util.StringUtils; @@ -49,6 +53,34 @@ public class UriComponentsBuilder { private static final char PATH_DELIMITER = '/'; + private static final Pattern QUERY_PARAM_PATTERN = Pattern.compile("([^&=]+)=?([^&=]+)?"); + + private static final String SCHEME_PATTERN = "([^:/?#]+):"; + + private static final String HTTP_PATTERN = "(http|https):"; + + private static final String USERINFO_PATTERN = "([^@/]*)"; + + private static final String HOST_PATTERN = "([^/?#:]*)"; + + private static final String PORT_PATTERN = "(\\d*)"; + + private static final String PATH_PATTERN = "([^?#]*)"; + + private static final String QUERY_PATTERN = "([^#]*)"; + + private static final String LAST_PATTERN = "(.*)"; + + // Regex patterns that matches URIs. See RFC 3986, appendix B + private static final Pattern URI_PATTERN = Pattern.compile( + "^(" + SCHEME_PATTERN + ")?" + "(//(" + USERINFO_PATTERN + "@)?" + HOST_PATTERN + "(:" + PORT_PATTERN + + ")?" + ")?" + PATH_PATTERN + "(\\?" + QUERY_PATTERN + ")?" + "(#" + LAST_PATTERN + ")?"); + + private static final Pattern HTTP_URL_PATTERN = Pattern.compile( + "^" + HTTP_PATTERN + "(//(" + USERINFO_PATTERN + "@)?" + HOST_PATTERN + "(:" + PORT_PATTERN + ")?" + ")?" + + PATH_PATTERN + "(\\?" + LAST_PATTERN + ")?"); + + private String scheme; private String userInfo; @@ -59,7 +91,7 @@ public class UriComponentsBuilder { private final List pathSegments = new ArrayList(); - private final StringBuilder queryBuilder = new StringBuilder(); + private final MultiValueMap queryParams = new LinkedMultiValueMap(); private String fragment; @@ -76,7 +108,7 @@ public class UriComponentsBuilder { // Factory methods /** - * Returns a new, empty URI builder. + * Returns a new, empty builder. * * @return the new {@code UriComponentsBuilder} */ @@ -85,7 +117,7 @@ public class UriComponentsBuilder { } /** - * Returns a URI 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 * @return the new {@code UriComponentsBuilder} @@ -97,7 +129,7 @@ public class UriComponentsBuilder { } /** - * Returns a URI 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 * @return the new {@code UriComponentsBuilder} @@ -108,6 +140,67 @@ public class UriComponentsBuilder { return builder; } + /** + * Returns a builder that is initialized with the given URI string. + * + * @param uri the URI string to initialize with + * @return the new {@code UriComponentsBuilder} + */ + public static UriComponentsBuilder fromUriString(String uri) { + Assert.hasLength(uri, "'uri' must not be empty"); + Matcher m = URI_PATTERN.matcher(uri); + if (m.matches()) { + UriComponentsBuilder builder = new UriComponentsBuilder(); + + builder.scheme(m.group(2)); + builder.userInfo(m.group(5)); + builder.host(m.group(6)); + String port = m.group(8); + if (StringUtils.hasLength(port)) { + builder.port(Integer.parseInt(port)); + } + builder.path(m.group(9)); + builder.query(m.group(11)); + builder.fragment(m.group(13)); + + return builder; + } + else { + throw new IllegalArgumentException("[" + uri + "] is not a valid URI"); + } + } + + /** + * Creates a new {@code UriComponents} object from the string HTTP URL. + * + * @param httpUrl the source URI + * @return the URI components of the URI + */ + public static UriComponentsBuilder fromHttpUrl(String httpUrl) { + Assert.notNull(httpUrl, "'httpUrl' must not be null"); + Matcher m = HTTP_URL_PATTERN.matcher(httpUrl); + if (m.matches()) { + UriComponentsBuilder builder = new UriComponentsBuilder(); + + builder.scheme(m.group(1)); + builder.userInfo(m.group(4)); + builder.host(m.group(5)); + String port = m.group(7); + if (StringUtils.hasLength(port)) { + builder.port(Integer.parseInt(port)); + } + builder.path(m.group(8)); + builder.query(m.group(10)); + + return builder; + } + else { + throw new IllegalArgumentException("[" + httpUrl + "] is not a valid HTTP URL"); + } + } + + + // build methods /** @@ -116,12 +209,19 @@ public class UriComponentsBuilder { * @return the URI components */ public UriComponents build() { - String port = portAsString(); - String path = pathAsString(); - String query = queryAsString(); - return UriComponents.fromUriComponents(scheme, null, userInfo, host, port, path, query, fragment, false); + return build(false); + } + + /** + * 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}). + * @return the URI components + */ + public UriComponents build(boolean encoded) { + return new UriComponents(scheme, userInfo, host, port, pathSegments, queryParams, fragment, encoded); } - + // URI components methods /** @@ -146,14 +246,12 @@ public class UriComponentsBuilder { this.port = uri.getPort(); } if (StringUtils.hasLength(uri.getPath())) { - String[] pathSegments = StringUtils.tokenizeToStringArray(uri.getPath(), "/"); - this.pathSegments.clear(); - Collections.addAll(this.pathSegments, pathSegments); + path(uri.getPath()); } if (StringUtils.hasLength(uri.getQuery())) { - this.queryBuilder.setLength(0); - this.queryBuilder.append(uri.getQuery()); + this.queryParams.clear(); + query(uri.getQuery()); } if (uri.getFragment() != null) { this.fragment = uri.getFragment(); @@ -220,36 +318,15 @@ public class UriComponentsBuilder { * @return this UriComponentsBuilder */ public UriComponentsBuilder path(String path) { - Assert.notNull(path, "path must not be null"); - - String[] pathSegments = StringUtils.tokenizeToStringArray(path, "/"); - return pathSegment(pathSegments); - } - - private String pathAsString() { - if (!pathSegments.isEmpty()) { - StringBuilder pathBuilder = new StringBuilder(); - for (String pathSegment : pathSegments) { - boolean startsWithSlash = pathSegment.charAt(0) == PATH_DELIMITER; - boolean endsWithSlash = - pathBuilder.length() > 0 && pathBuilder.charAt(pathBuilder.length() - 1) == PATH_DELIMITER; - - if (!endsWithSlash && !startsWithSlash) { - pathBuilder.append('/'); - } - else if (endsWithSlash && startsWithSlash) { - pathSegment = pathSegment.substring(1); - } - pathBuilder.append(pathSegment); - } - return pathBuilder.toString(); - } - else { - return null; - } + if (path != null) { + String[] pathSegments = StringUtils.tokenizeToStringArray(path, "/"); + pathSegment(pathSegments); + } else { + pathSegments.clear(); + } + return this; } - /** * Appends the given path segments to the existing path of this builder. Each given path segments may contain URI * template variables. @@ -264,6 +341,28 @@ public class UriComponentsBuilder { return this; } + /** + * Appends the given query to the existing query of this builder. The given query may contain URI template variables. + * + * @param query the URI path + * @return this UriComponentsBuilder + */ + public UriComponentsBuilder query(String query) { + if (query != null) { + Matcher m = QUERY_PARAM_PATTERN.matcher(query); + while (m.find()) { + String name = m.group(1); + String value = m.group(2); + queryParam(name, value); + } + } + else { + queryParams.clear(); + } + 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. @@ -274,33 +373,18 @@ public class UriComponentsBuilder { * @return this UriComponentsBuilder */ public UriComponentsBuilder queryParam(String name, Object... values) { - Assert.notNull(name, "'name' must not be null"); - - if (ObjectUtils.isEmpty(values)) { - if (queryBuilder.length() != 0) { - queryBuilder.append('&'); - } - queryBuilder.append(name); - } - else { - for (Object value : values) { - if (queryBuilder.length() != 0) { - queryBuilder.append('&'); - } - queryBuilder.append(name); - - if (value != null) { - queryBuilder.append('='); - queryBuilder.append(value.toString()); - } - } - } - return this; - } - - private String queryAsString() { - return queryBuilder.length() != 0 ? queryBuilder.toString() : null; - } + Assert.notNull(name, "'name' must not be null"); + if (!ObjectUtils.isEmpty(values)) { + for (Object value : values) { + String valueAsString = value != null ? value.toString() : null; + queryParams.add(name, valueAsString); + } + } + else { + queryParams.add(name, null); + } + return this; + } /** * Sets the URI fragment. The given fragment may contain URI template variables, and may also be {@code null} to clear diff --git a/org.springframework.web/src/main/java/org/springframework/web/util/UriTemplate.java b/org.springframework.web/src/main/java/org/springframework/web/util/UriTemplate.java index 832c45dfd7..80e8f4cd4c 100644 --- a/org.springframework.web/src/main/java/org/springframework/web/util/UriTemplate.java +++ b/org.springframework.web/src/main/java/org/springframework/web/util/UriTemplate.java @@ -21,13 +21,10 @@ import java.io.UnsupportedEncodingException; import java.net.URI; import java.net.URISyntaxException; import java.util.Collections; -import java.util.EnumMap; -import java.util.HashSet; import java.util.LinkedHashMap; import java.util.LinkedList; import java.util.List; import java.util.Map; -import java.util.Set; import java.util.regex.Matcher; import java.util.regex.Pattern; @@ -70,18 +67,9 @@ public class UriTemplate implements Serializable { this.uriTemplate = uriTemplate; this.variableNames = parser.getVariableNames(); this.matchPattern = parser.getMatchPattern(); - this.uriComponents = UriComponents.fromUriString(uriTemplate); + this.uriComponents = UriComponentsBuilder.fromUriString(uriTemplate).build(); } - public UriTemplate(Map uriComponents) { - this.uriComponents = UriComponents.fromUriComponentMap(uriComponents); - String uriTemplate = this.uriComponents.toUriString(); - Parser parser = new Parser(uriTemplate); - this.uriTemplate = uriTemplate; - this.variableNames = parser.getVariableNames(); - this.matchPattern = parser.getMatchPattern(); - } - /** * Return the names of the variables in the template, in order. * @return the template variable names @@ -110,93 +98,11 @@ public class UriTemplate implements Serializable { * or if it does not contain values for all the variable names */ public URI expand(Map uriVariables) { - UriComponents expandedComponents = expandAsUriComponents(uriVariables, true); - return expandedComponents.toUri(); - } - - /** - * Given the Map of variables, expands this template into a URI. The Map keys represent variable names, - * the Map values variable values. The order of variables is not significant. - *

Example: - *

-	 * UriTemplate template = new UriTemplate("http://example.com/hotels/{hotel}/bookings/{booking}");
-	 * Map<String, String> uriVariables = new HashMap<String, String>();
-	 * uriVariables.put("booking", "42");
-	 * uriVariables.put("hotel", "1");
-	 * System.out.println(template.expand(uriVariables));
-	 * 
- * will print:
http://example.com/hotels/1/bookings/42
- * - * @param uriVariables the map of URI variables - * @return the expanded URI - * @throws IllegalArgumentException if uriVariables is null; - * or if it does not contain values for all the variable names - */ - public String expandAsString(final Map uriVariables, boolean encode) { - UriComponents expandedComponents = expandAsUriComponents(uriVariables, encode); - return expandedComponents.toUriString(); + UriComponents expandedComponents = uriComponents.expand(uriVariables); + UriComponents encodedComponents = expandedComponents.encode(); + return encodedComponents.toUri(); } - public UriComponents expandAsUriComponents(final Map uriVariables, boolean encode) { - Assert.notNull(uriVariables, "'uriVariables' must not be null"); - Set variablesSet = new HashSet(this.variableNames); - variablesSet.removeAll(uriVariables.keySet()); - Assert.isTrue(variablesSet.isEmpty(), - "'uriVariables' does not contain keys for all variables: " + variablesSet); - - Map expandedComponents = new EnumMap(UriComponents.Type.class); - - for (Map.Entry entry : this.uriComponents.entrySet()) { - UriComponents.Type key = entry.getKey(); - String value = entry.getValue(); - String expandedValue = expandUriComponent(key, value, uriVariables); - expandedComponents.put(key, expandedValue); - } - UriComponents result = UriComponents.fromUriComponentMap(expandedComponents); - if (encode) { - result = result.encode(); - } - return result; - } - - private String expandUriComponent(UriComponents.Type componentType, String value, Map uriVariables) { - if (value == null) { - return null; - } - if (value.indexOf('{') == -1) { - return value; - } - Matcher matcher = NAMES_PATTERN.matcher(value); - StringBuffer sb = new StringBuffer(); - while (matcher.find()) { - String match = matcher.group(1); - String variableName = getVariableName(match); - Object variableValue = uriVariables.get(variableName); - String uriVariableValueString = getVariableValueAsString(variableValue); - String replacement = Matcher.quoteReplacement(uriVariableValueString); - matcher.appendReplacement(sb, replacement); - } - matcher.appendTail(sb); - return sb.toString(); - } - - private String getVariableName(String match) { - int colonIdx = match.indexOf(':'); - return colonIdx == -1 ? match : match.substring(0, colonIdx); - } - - /** - * Template method that returns the string representation of the given URI template value. - * - *

Defaults implementation simply calls {@link Object#toString()}, or returns an empty string for {@code null}. - * - * @param variableValue the URI template variable value - * @return the variable value as string - */ - protected String getVariableValueAsString(Object variableValue) { - return variableValue != null ? variableValue.toString() : ""; - } - /** * Given an array of variables, expand this template into a full URI. The array represent variable values. * The order of variables is significant. @@ -212,50 +118,11 @@ public class UriTemplate implements Serializable { * or if it does not contain sufficient variables */ public URI expand(Object... uriVariableValues) { - UriComponents expandedComponents = expandAsUriComponents(uriVariableValues, true); - return expandedComponents.toUri(); - } - - /** - * Given an array of variables, expand this template into a full URI String. The array represent variable values. - * The order of variables is significant. - *

Example: - *

-	 * UriTemplate template = new UriTemplate("http://example.com/hotels/{hotel}/bookings/{booking}");
-	 * System.out.println(template.expand("1", "42));
-	 * 
- * will print:
http://example.com/hotels/1/bookings/42
- * - * - * @param uriVariableValues the array of URI variables - * @return the expanded URI - * @throws IllegalArgumentException if uriVariables is null - * or if it does not contain sufficient variables - */ - public String expandAsString(boolean encode, Object[] uriVariableValues) { - UriComponents expandedComponents = expandAsUriComponents(uriVariableValues, encode); - return expandedComponents.toUriString(); + UriComponents expandedComponents = uriComponents.expand(uriVariableValues); + UriComponents encodedComponents = expandedComponents.encode(); + return encodedComponents.toUri(); } - public UriComponents expandAsUriComponents(Object[] uriVariableValues, boolean encode) { - Assert.notNull(uriVariableValues, "'uriVariableValues' must not be null"); - if (uriVariableValues.length < this.variableNames.size()) { - throw new IllegalArgumentException( - "Not enough of variables values in [" + this.uriTemplate + "]: expected at least " + - this.variableNames.size() + "; got " + uriVariableValues.length); - } - Map uriVariables = new LinkedHashMap(this.variableNames.size()); - - for (int i = 0, size = variableNames.size(); i < size; i++) { - String variableName = variableNames.get(i); - Object variableValue = uriVariableValues[i]; - uriVariables.put(variableName, variableValue); - } - - return expandAsUriComponents(uriVariables, encode); - } - - // matching /** @@ -302,7 +169,9 @@ public class UriTemplate implements Serializable { *

Defaults to {@link UriUtils#encodeUri(String, String)}. * @param uri the URI to encode * @return the encoded URI + * @deprecated No longer in use, with no direct replacement */ + @Deprecated protected URI encodeUri(String uri) { try { String encoded = UriUtils.encodeUri(uri, "UTF-8"); 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 05e45e9b13..cc818859e7 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 @@ -51,7 +51,7 @@ public abstract class UriUtils { * @throws UnsupportedEncodingException when the given encoding parameter is not supported */ public static String encodeUri(String uri, String encoding) throws UnsupportedEncodingException { - UriComponents uriComponents = UriComponents.fromUriString(uri); + UriComponents uriComponents = UriComponentsBuilder.fromUriString(uri).build(); UriComponents encoded = uriComponents.encode(encoding); return encoded.toUriString(); } @@ -68,7 +68,7 @@ public abstract class UriUtils { * @throws UnsupportedEncodingException when the given encoding parameter is not supported */ public static String encodeHttpUrl(String httpUrl, String encoding) throws UnsupportedEncodingException { - UriComponents uriComponents = UriComponents.fromHttpUrl(httpUrl); + UriComponents uriComponents = UriComponentsBuilder.fromHttpUrl(httpUrl).build(); UriComponents encoded = uriComponents.encode(encoding); return encoded.toUriString(); } @@ -99,8 +99,13 @@ public abstract class UriUtils { String query, String fragment, String encoding) throws UnsupportedEncodingException { - UriComponents uriComponents = UriComponents.fromUriComponents(scheme, authority, userInfo, host, port, path, query, fragment); - UriComponents encoded = uriComponents.encode(encoding); + int portAsInt = port != null ? Integer.parseInt(port) : -1; + + UriComponentsBuilder builder = UriComponentsBuilder.newInstance(); + builder.scheme(scheme).userInfo(userInfo).host(host).port(portAsInt); + builder.path(path).query(query).fragment(fragment); + + UriComponents encoded = builder.build().encode(encoding); return encoded.toUriString(); } 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 75efa2bb41..7caf2a9721 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 @@ -18,10 +18,14 @@ package org.springframework.web.util; import java.net.URI; import java.net.URISyntaxException; +import java.util.Arrays; import org.junit.Test; -import static org.junit.Assert.assertEquals; +import org.springframework.util.LinkedMultiValueMap; +import org.springframework.util.MultiValueMap; + +import static org.junit.Assert.*; /** @author Arjen Poutsma */ public class UriComponentsBuilderTests { @@ -70,41 +74,91 @@ public class UriComponentsBuilderTests { assertEquals("Invalid result URI", uri, result.toUri()); } + @Test + public void fromUriString() { + UriComponents result = UriComponentsBuilder.fromUriString("http://www.ietf.org/rfc/rfc3986.txt").build(); + assertEquals("http", result.getScheme()); + assertNull(result.getUserInfo()); + assertEquals("www.ietf.org", result.getHost()); + assertEquals(-1, result.getPort()); + assertEquals("/rfc/rfc3986.txt", result.getPath()); + assertEquals(Arrays.asList("rfc", "rfc3986.txt"), result.getPathSegments()); + assertNull(result.getQuery()); + assertNull(result.getFragment()); + + 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()); + MultiValueMap expectedQueryParams = new LinkedMultiValueMap(1); + expectedQueryParams.add("foo", "bar"); + assertEquals(expectedQueryParams, result.getQueryParams()); + 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("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()); + } + + + @Test + public void path() throws URISyntaxException { + UriComponentsBuilder builder = UriComponentsBuilder.fromPath("/foo/bar"); + UriComponents result = builder.build(); + + assertEquals("/foo/bar", result.getPath()); + assertEquals(Arrays.asList("foo", "bar"), result.getPathSegments()); + } + @Test public void pathSegments() throws URISyntaxException { UriComponentsBuilder builder = UriComponentsBuilder.newInstance(); - URI result = builder.pathSegment("foo").pathSegment("bar").build().toUri(); + UriComponents result = builder.pathSegment("foo").pathSegment("bar").build(); - URI expected = new URI("/foo/bar"); - assertEquals("Invalid result URI", expected, result); + assertEquals("/foo/bar", result.getPath()); + assertEquals(Arrays.asList("foo", "bar"), result.getPathSegments()); } @Test - public void queryParam() throws URISyntaxException { + public void queryParams() throws URISyntaxException { UriComponentsBuilder builder = UriComponentsBuilder.newInstance(); - URI result = builder.queryParam("baz", "qux", 42).build().toUri(); + UriComponents result = builder.queryParam("baz", "qux", 42).build(); - URI expected = new URI("?baz=qux&baz=42"); - assertEquals("Invalid result URI", expected, result); + assertEquals("baz=qux&baz=42", result.getQuery()); + MultiValueMap expectedQueryParams = new LinkedMultiValueMap(2); + expectedQueryParams.add("baz", "qux"); + expectedQueryParams.add("baz", "42"); + assertEquals(expectedQueryParams, result.getQueryParams()); } @Test public void emptyQueryParam() throws URISyntaxException { UriComponentsBuilder builder = UriComponentsBuilder.newInstance(); - URI result = builder.queryParam("baz").build().toUri(); + UriComponents result = builder.queryParam("baz").build(); - URI expected = new URI("?baz"); - assertEquals("Invalid result URI", expected, result); + assertEquals("baz", result.getQuery()); + MultiValueMap expectedQueryParams = new LinkedMultiValueMap(2); + expectedQueryParams.add("baz", null); + assertEquals(expectedQueryParams, result.getQueryParams()); } - @Test - public void combineWithUriTemplate() throws URISyntaxException { - UriComponentsBuilder builder = UriComponentsBuilder.fromPath("/{foo}"); - UriComponents components = builder.build(); - UriTemplate template = new UriTemplate(components); - URI uri = template.expand("bar baz"); - assertEquals(new URI("/bar%20baz"), uri); - } - - } diff --git a/org.springframework.web/src/test/java/org/springframework/web/util/UriComponentsTests.java b/org.springframework.web/src/test/java/org/springframework/web/util/UriComponentsTests.java index 93fe3fa35a..c599118c00 100644 --- a/org.springframework.web/src/test/java/org/springframework/web/util/UriComponentsTests.java +++ b/org.springframework.web/src/test/java/org/springframework/web/util/UriComponentsTests.java @@ -18,102 +18,32 @@ package org.springframework.web.util; import java.net.URI; import java.net.URISyntaxException; -import java.util.Arrays; -import java.util.Collections; -import java.util.List; -import java.util.Map; - -import org.springframework.util.LinkedMultiValueMap; -import org.springframework.util.MultiValueMap; import org.junit.Test; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertNull; +import static org.junit.Assert.*; /** @author Arjen Poutsma */ public class UriComponentsTests { - @Test - public void fromUri() { - Map result = UriComponents.fromUriString("http://www.ietf.org/rfc/rfc3986.txt"); - assertEquals("http", result.get(UriComponents.Type.SCHEME)); - assertNull(result.get(UriComponents.Type.USER_INFO)); - assertEquals("www.ietf.org", result.get(UriComponents.Type.HOST)); - assertNull(result.get(UriComponents.Type.PORT)); - assertEquals("/rfc/rfc3986.txt", result.get(UriComponents.Type.PATH)); - assertNull(result.get(UriComponents.Type.QUERY)); - assertNull(result.get(UriComponents.Type.FRAGMENT)); - - result = UriComponents.fromUriString( - "http://arjen:foobar@java.sun.com:80/javase/6/docs/api/java/util/BitSet.html?foo=bar#and(java.util.BitSet)"); - assertEquals("http", result.get(UriComponents.Type.SCHEME)); - assertEquals("arjen:foobar", result.get(UriComponents.Type.USER_INFO)); - assertEquals("java.sun.com", result.get(UriComponents.Type.HOST)); - assertEquals("80", result.get(UriComponents.Type.PORT)); - assertEquals("/javase/6/docs/api/java/util/BitSet.html", result.get(UriComponents.Type.PATH)); - assertEquals("foo=bar", result.get(UriComponents.Type.QUERY)); - assertEquals("and(java.util.BitSet)", result.get(UriComponents.Type.FRAGMENT)); - result = UriComponents.fromUriString("mailto:java-net@java.sun.com"); - assertEquals("mailto", result.get(UriComponents.Type.SCHEME)); - assertNull(result.get(UriComponents.Type.USER_INFO)); - assertNull(result.get(UriComponents.Type.HOST)); - assertNull(result.get(UriComponents.Type.PORT)); - assertEquals("java-net@java.sun.com", result.get(UriComponents.Type.PATH)); - assertNull(result.get(UriComponents.Type.QUERY)); - assertNull(result.get(UriComponents.Type.FRAGMENT)); - - result = UriComponents.fromUriString("docs/guide/collections/designfaq.html#28"); - assertNull(result.get(UriComponents.Type.SCHEME)); - assertNull(result.get(UriComponents.Type.USER_INFO)); - assertNull(result.get(UriComponents.Type.HOST)); - assertNull(result.get(UriComponents.Type.PORT)); - assertEquals("docs/guide/collections/designfaq.html", result.get(UriComponents.Type.PATH)); - assertNull(result.get(UriComponents.Type.QUERY)); - assertEquals("28", result.get(UriComponents.Type.FRAGMENT)); - } - - @Test - public void pathSegments() { - String path = "/foo/bar"; - UriComponents components = UriComponents.fromUriComponentMap(Collections.singletonMap(UriComponents.Type.PATH, path)); - List expected = Arrays.asList("foo", "bar"); - - List pathSegments = components.getPathSegments(); - assertEquals(expected, pathSegments); - } - - @Test - public void queryParams() { - String query = "foo=bar&foo=baz&qux"; - UriComponents components = UriComponents.fromUriComponentMap( - Collections.singletonMap(UriComponents.Type.QUERY, query)); - MultiValueMap expected = new LinkedMultiValueMap(1); - expected.put("foo", Arrays.asList("bar", "baz")); - expected.set("qux", null); - - MultiValueMap result = components.getQueryParams(); - assertEquals(expected, result); - } - @Test public void encode() { - UriComponents uriComponents = UriComponents.fromUriString("http://example.com/hotel list"); + UriComponents uriComponents = UriComponentsBuilder.fromPath("/hotel list").build(); UriComponents encoded = uriComponents.encode(); assertEquals("/hotel%20list", encoded.getPath()); } @Test public void toUriEncoded() throws URISyntaxException { - UriComponents uriComponents = UriComponents.fromUriString("http://example.com/hotel list/Z\u00fcrich"); + UriComponents uriComponents = UriComponentsBuilder.fromUriString("http://example.com/hotel list/Z\u00fcrich").build(); UriComponents encoded = uriComponents.encode(); assertEquals(new URI("http://example.com/hotel%20list/Z%C3%BCrich"), encoded.toUri()); } - + @Test public void toUriNotEncoded() throws URISyntaxException { - UriComponents uriComponents = UriComponents.fromUriString("http://example.com/hotel list/Z\u00fcrich"); + UriComponents uriComponents = UriComponentsBuilder.fromUriString("http://example.com/hotel list/Z\u00fcrich").build(); assertEquals(new URI("http://example.com/hotel%20list/Z\u00fcrich"), uriComponents.toUri()); } diff --git a/org.springframework.web/src/test/java/org/springframework/web/util/UriTemplateTests.java b/org.springframework.web/src/test/java/org/springframework/web/util/UriTemplateTests.java index d16ba69bed..f0099328cd 100644 --- a/org.springframework.web/src/test/java/org/springframework/web/util/UriTemplateTests.java +++ b/org.springframework.web/src/test/java/org/springframework/web/util/UriTemplateTests.java @@ -89,22 +89,6 @@ public class UriTemplateTests { assertEquals("Invalid expanded template", new URI("http://example.com/hotel%20list/Z%C3%BCrich"), result); } - - @Test(expected = IllegalArgumentException.class) - public void expandMapInvalidAmountVariables() throws Exception { - UriTemplate template = new UriTemplate("http://example.com/hotels/{hotel}/bookings/{booking}"); - template.expand(Collections.singletonMap("hotel", "1")); - } - - @Test(expected = IllegalArgumentException.class) - public void expandMapUnboundVariables() throws Exception { - Map uriVariables = new HashMap(2); - uriVariables.put("booking", "42"); - uriVariables.put("bar", "1"); - UriTemplate template = new UriTemplate("http://example.com/hotels/{hotel}/bookings/{booking}"); - template.expand(uriVariables); - } - @Test public void expandEncoded() throws Exception { UriTemplate template = new UriTemplate("http://example.com/hotel list/{hotel}"); diff --git a/org.springframework.web/src/test/java/org/springframework/web/util/UriUtilsTests.java b/org.springframework.web/src/test/java/org/springframework/web/util/UriUtilsTests.java index 53df99702a..b886b77363 100644 --- a/org.springframework.web/src/test/java/org/springframework/web/util/UriUtilsTests.java +++ b/org.springframework.web/src/test/java/org/springframework/web/util/UriUtilsTests.java @@ -20,7 +20,7 @@ import java.io.UnsupportedEncodingException; import org.junit.Test; -import static org.junit.Assert.assertEquals; +import static org.junit.Assert.*; /** * @author Arjen Poutsma @@ -111,23 +111,19 @@ public class UriUtilsTests { UriUtils.encodeUri("http://www.ietf.org/rfc/rfc3986.txt", ENC)); assertEquals("Invalid encoded URI", "https://www.ietf.org/rfc/rfc3986.txt", UriUtils.encodeUri("https://www.ietf.org/rfc/rfc3986.txt", ENC)); - assertEquals("Invalid encoded URI", "http://www.google.com/?q=Z%C3%BCrich", - UriUtils.encodeUri("http://www.google.com/?q=Z\u00fcrich", ENC)); + assertEquals("Invalid encoded URI", "http://www.google.com?q=Z%C3%BCrich", + UriUtils.encodeUri("http://www.google.com?q=Z\u00fcrich", ENC)); assertEquals("Invalid encoded URI", "http://arjen:foobar@java.sun.com:80/javase/6/docs/api/java/util/BitSet.html?foo=bar#and(java.util.BitSet)", UriUtils.encodeUri( "http://arjen:foobar@java.sun.com:80/javase/6/docs/api/java/util/BitSet.html?foo=bar#and(java.util.BitSet)", ENC)); - assertEquals("Invalid encoded URI", "mailto:java-net@java.sun.com", - UriUtils.encodeUri("mailto:java-net@java.sun.com", ENC)); - assertEquals("Invalid encoded URI", "news:comp.lang.java", UriUtils.encodeUri("news:comp.lang.java", ENC)); - assertEquals("Invalid encoded URI", "urn:isbn:096139210x", UriUtils.encodeUri("urn:isbn:096139210x", ENC)); - assertEquals("Invalid encoded URI", "http://java.sun.com/j2se/1.3/", - UriUtils.encodeUri("http://java.sun.com/j2se/1.3/", ENC)); - assertEquals("Invalid encoded URI", "docs/guide/collections/designfaq.html#28", - UriUtils.encodeUri("docs/guide/collections/designfaq.html#28", ENC)); - assertEquals("Invalid encoded URI", "../../../demo/jfc/SwingSet2/src/SwingSet2.java", - UriUtils.encodeUri("../../../demo/jfc/SwingSet2/src/SwingSet2.java", ENC)); + assertEquals("Invalid encoded URI", "http://java.sun.com/j2se/1.3", + UriUtils.encodeUri("http://java.sun.com/j2se/1.3", ENC)); + assertEquals("Invalid encoded URI", "/docs/guide/collections/designfaq.html#28", + UriUtils.encodeUri("/docs/guide/collections/designfaq.html#28", ENC)); + assertEquals("Invalid encoded URI", "/../../../demo/jfc/SwingSet2/src/SwingSet2.java", + UriUtils.encodeUri("/../../../demo/jfc/SwingSet2/src/SwingSet2.java", ENC)); assertEquals("Invalid encoded URI", "file:///~/calendar", UriUtils.encodeUri("file:///~/calendar", ENC)); assertEquals("Invalid encoded URI", "http://example.com/query=foo@bar", UriUtils.encodeUri("http://example.com/query=foo@bar", ENC)); @@ -140,8 +136,8 @@ public class UriUtilsTests { UriUtils.encodeHttpUrl("http://www.ietf.org/rfc/rfc3986.txt", ENC)); assertEquals("Invalid encoded URI", "https://www.ietf.org/rfc/rfc3986.txt", UriUtils.encodeHttpUrl("https://www.ietf.org/rfc/rfc3986.txt", ENC)); - assertEquals("Invalid encoded HTTP URL", "http://www.google.com/?q=Z%C3%BCrich", - UriUtils.encodeHttpUrl("http://www.google.com/?q=Z\u00fcrich", ENC)); + assertEquals("Invalid encoded HTTP URL", "http://www.google.com?q=Z%C3%BCrich", + UriUtils.encodeHttpUrl("http://www.google.com?q=Z\u00fcrich", ENC)); assertEquals("Invalid encoded HTTP URL", "http://ws.geonames.org/searchJSON?q=T%C5%8Dky%C5%8D&style=FULL&maxRows=300", UriUtils.encodeHttpUrl("http://ws.geonames.org/searchJSON?q=T\u014dky\u014d&style=FULL&maxRows=300", ENC)); assertEquals("Invalid encoded HTTP URL", @@ -150,8 +146,8 @@ public class UriUtilsTests { "http://arjen:foobar@java.sun.com:80/javase/6/docs/api/java/util/BitSet.html?foo=bar", ENC)); assertEquals("Invalid encoded HTTP URL", "http://search.twitter.com/search.atom?q=%23avatar", UriUtils.encodeHttpUrl("http://search.twitter.com/search.atom?q=#avatar", ENC)); - assertEquals("Invalid encoded HTTP URL", "http://java.sun.com/j2se/1.3/", - UriUtils.encodeHttpUrl("http://java.sun.com/j2se/1.3/", ENC)); + assertEquals("Invalid encoded HTTP URL", "http://java.sun.com/j2se/1.3", + UriUtils.encodeHttpUrl("http://java.sun.com/j2se/1.3", ENC)); assertEquals("Invalid encoded HTTP URL", "http://example.com/query=foo@bar", UriUtils.encodeHttpUrl("http://example.com/query=foo@bar", ENC)); }