diff --git a/org.springframework.web/src/main/java/org/springframework/web/util/UriBuilder.java b/org.springframework.web/src/main/java/org/springframework/web/util/UriBuilder.java new file mode 100644 index 00000000000..ec620c8ed61 --- /dev/null +++ b/org.springframework.web/src/main/java/org/springframework/web/util/UriBuilder.java @@ -0,0 +1,556 @@ +/* + * Copyright 2002-2011 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.web.util; + +import java.net.URI; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Map; + +import org.springframework.util.Assert; +import org.springframework.util.CollectionUtils; +import org.springframework.util.ObjectUtils; +import org.springframework.util.StringUtils; + +/** + * Builder for {@link URI} objects. + * + *
Typical usage involves: + *
Most of the URI component methods accept URI template variables (i.e. {@code "{foo}"}), which are expanded by + * calling {@code build}. + * one , are allowed in most components of a URI but their value is + * restricted to a particular component. E.g. + *
UriBuilder.fromPath("{arg1}").build("foo#bar");
+ * would result in encoding of the '#' such that the resulting URI is
+ * "foo%23bar". To create a URI "foo#bar" use
+ * UriBuilder.fromPath("{arg1}").fragment("{arg2}").build("foo", "bar")
+ * instead. URI template names and delimiters are never encoded but their
+ * values are encoded when a URI is built.
+ * Template parameter regular expressions are ignored when building a URI, i.e.
+ * no validation is performed.
+ * Inspired by {@link javax.ws.rs.core.UriBuilder}.
+ *
+ * @author Arjen Poutsma
+ * @since 3.1
+ * @see #newInstance()
+ * @see #fromPath(String)
+ * @see #fromUri(URI)
+ */
+public class UriBuilder {
+
+ private String scheme;
+
+ private String userInfo;
+
+ private String host;
+
+ private int port = -1;
+
+ private final List Example:
+ * 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:
+ * @param encodeUriVariableValues indicates whether uri template variables should be encoded or not
+ * @param uriVariables the map of URI variables
+ * @return the expanded URI
+ * @throws IllegalArgumentException if http://example.com/hotels/1/bookings/42uriVariables is null;
+ * or if it does not contain values for all the variable names
+ */
+ public String expandAsString(boolean encodeUriVariableValues, Map
+ * UriTemplate template = new UriTemplate("http://example.com/hotels/{hotel}/bookings/{booking}");
+ * System.out.println(template.expand("1", "42));
+ *
+ * will print:
+ * @param encodeVariableValues indicates whether uri template variables should be encoded or not
+ * @param uriVariableValues the array of URI variables
+ * @return the expanded URI
+ * @throws IllegalArgumentException if http://example.com/hotels/1/bookings/42uriVariables is null
+ * or if it does not contain sufficient variables
+ */
+ public String expandAsString(boolean encodeVariableValues, Object... uriVariableValues) {
Assert.notNull(uriVariableValues, "'uriVariableValues' must not be null");
- if (uriVariableValues.length != this.variableNames.size()) {
+ if (uriVariableValues.length < this.variableNames.size()) {
throw new IllegalArgumentException(
- "Invalid amount of variables values in [" + this.uriTemplate + "]: expected " +
+ "Not enough of variables values in [" + this.uriTemplate + "]: expected at least " +
this.variableNames.size() + "; got " + uriVariableValues.length);
}
Matcher matcher = NAMES_PATTERN.matcher(this.uriTemplate);
- StringBuffer buffer = new StringBuffer();
+ StringBuffer uriBuffer = new StringBuffer();
int i = 0;
while (matcher.find()) {
Object uriVariable = uriVariableValues[i++];
- String replacement = Matcher.quoteReplacement(uriVariable != null ? uriVariable.toString() : "");
- matcher.appendReplacement(buffer, replacement);
+ String uriVariableString = uriVariable != null ? uriVariable.toString() : "";
+ if (encodeVariableValues && uriComponent != null) {
+ uriVariableString = UriUtils.encode(uriVariableString, uriComponent, false);
+ }
+ String replacement = Matcher.quoteReplacement(uriVariableString);
+ matcher.appendReplacement(uriBuffer, replacement);
}
- matcher.appendTail(buffer);
- return encodeUri(buffer.toString());
+ matcher.appendTail(uriBuffer);
+ return uriBuffer.toString();
}
/**
@@ -220,8 +280,23 @@ public class UriTemplate implements Serializable {
int end = 0;
while (m.find()) {
this.patternBuilder.append(quote(uriTemplate, end, m.start()));
- this.patternBuilder.append(VALUE_REGEX);
- this.variableNames.add(m.group(1));
+ String match = m.group(1);
+ int colonIdx = match.indexOf(':');
+ if (colonIdx == -1) {
+ this.patternBuilder.append(DEFAULT_VARIABLE_PATTERN);
+ this.variableNames.add(match);
+ }
+ else {
+ if (colonIdx + 1 == match.length()) {
+ throw new IllegalArgumentException("No custom regular expression specified after ':' in \"" + match + "\"");
+ }
+ String variablePattern = match.substring(colonIdx + 1, match.length());
+ this.patternBuilder.append('(');
+ this.patternBuilder.append(variablePattern);
+ this.patternBuilder.append(')');
+ String variableName = match.substring(0, colonIdx);
+ this.variableNames.add(variableName);
+ }
end = m.end();
}
this.patternBuilder.append(quote(uriTemplate, end, uriTemplate.length()));
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 9053e790f9e..b48f9c516d0 100644
--- a/org.springframework.web/src/main/java/org/springframework/web/util/UriUtils.java
+++ b/org.springframework.web/src/main/java/org/springframework/web/util/UriUtils.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2010 the original author or authors.
+ * Copyright 2002-2011 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -18,7 +18,6 @@ package org.springframework.web.util;
import java.io.ByteArrayOutputStream;
import java.io.UnsupportedEncodingException;
-import java.util.BitSet;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
@@ -41,23 +40,7 @@ import org.springframework.util.Assert;
*/
public abstract class UriUtils {
- private static final BitSet SCHEME;
-
- private static final BitSet USER_INFO;
-
- private static final BitSet HOST;
-
- private static final BitSet PORT;
-
- private static final BitSet PATH;
-
- private static final BitSet SEGMENT;
-
- private static final BitSet QUERY;
-
- private static final BitSet QUERY_PARAM;
-
- private static final BitSet FRAGMENT;
+ private static final String DEFAULT_ENCODING = "UTF-8";
private static final String SCHEME_PATTERN = "([^:/?#]+):";
@@ -84,107 +67,7 @@ public abstract class UriUtils {
"^" + HTTP_PATTERN + "(//(" + USERINFO_PATTERN + "@)?" + HOST_PATTERN + "(:" + PORT_PATTERN + ")?" +
")?" + PATH_PATTERN + "(\\?" + LAST_PATTERN + ")?");
-
- static {
- // variable names refer to RFC 3986, appendix A
- BitSet alpha = new BitSet(256);
- for (int i = 'a'; i <= 'z'; i++) {
- alpha.set(i);
- }
- for (int i = 'A'; i <= 'Z'; i++) {
- alpha.set(i);
- }
- BitSet digit = new BitSet(256);
- for (int i = '0'; i <= '9'; i++) {
- digit.set(i);
- }
-
- BitSet gendelims = new BitSet(256);
- gendelims.set(':');
- gendelims.set('/');
- gendelims.set('?');
- gendelims.set('#');
- gendelims.set('[');
- gendelims.set(']');
- gendelims.set('@');
-
- BitSet subdelims = new BitSet(256);
- subdelims.set('!');
- subdelims.set('$');
- subdelims.set('&');
- subdelims.set('\'');
- subdelims.set('(');
- subdelims.set(')');
- subdelims.set('*');
- subdelims.set('+');
- subdelims.set(',');
- subdelims.set(';');
- subdelims.set('=');
-
- BitSet reserved = new BitSet(256);
- reserved.or(gendelims);
- reserved.or(subdelims);
-
- BitSet unreserved = new BitSet(256);
- unreserved.or(alpha);
- unreserved.or(digit);
- unreserved.set('-');
- unreserved.set('.');
- unreserved.set('_');
- unreserved.set('~');
-
- SCHEME = new BitSet(256);
- SCHEME.or(alpha);
- SCHEME.or(digit);
- SCHEME.set('+');
- SCHEME.set('-');
- SCHEME.set('.');
-
- USER_INFO = new BitSet(256);
- USER_INFO.or(unreserved);
- USER_INFO.or(subdelims);
- USER_INFO.set(':');
-
- HOST = new BitSet(256);
- HOST.or(unreserved);
- HOST.or(subdelims);
-
- PORT = new BitSet(256);
- PORT.or(digit);
-
- BitSet pchar = new BitSet(256);
- pchar.or(unreserved);
- pchar.or(subdelims);
- pchar.set(':');
- pchar.set('@');
-
- SEGMENT = new BitSet(256);
- SEGMENT.or(pchar);
-
- PATH = new BitSet(256);
- PATH.or(SEGMENT);
- PATH.set('/');
-
- QUERY = new BitSet(256);
- QUERY.or(pchar);
- QUERY.set('/');
- QUERY.set('?');
-
- QUERY_PARAM = new BitSet(256);
- QUERY_PARAM.or(pchar);
- QUERY_PARAM.set('/');
- QUERY_PARAM.set('?');
- QUERY_PARAM.clear('=');
- QUERY_PARAM.clear('+');
- QUERY_PARAM.clear('&');
-
- FRAGMENT = new BitSet(256);
- FRAGMENT.or(pchar);
- FRAGMENT.set('/');
- FRAGMENT.set('?');
- }
-
-
+
/**
* Encodes the given source URI into an encoded String. All various URI components
* are encoded according to their respective valid character sets.
@@ -246,6 +129,38 @@ public abstract class UriUtils {
}
}
+ /**
+ * Encodes the given source URI components into an encoded String.
+ * All various URI components are optional, but encoded according
+ * to their respective valid character sets.
+ * @param scheme the scheme
+ * @param authority the authority
+ * @param userinfo the user info
+ * @param host the host
+ * @param port the port
+ * @param path the path
+ * @param query the query
+ * @param fragment the fragment
+ * @return the encoded URI
+ * @throws IllegalArgumentException when the given uri parameter is not a valid URI
+ */
+ public static String encodeUriComponents(String scheme,
+ String authority,
+ String userinfo,
+ String host,
+ String port,
+ String path,
+ String query,
+ String fragment) {
+ try {
+ return encodeUriComponents(scheme, authority, userinfo, host, port, path, query, fragment,
+ DEFAULT_ENCODING);
+ }
+ catch (UnsupportedEncodingException e) {
+ throw new InternalError("'UTF-8' encoding not supported");
+ }
+ }
+
/**
* Encodes the given source URI components into an encoded String.
* All various URI components are optional, but encoded according
@@ -263,10 +178,16 @@ public abstract class UriUtils {
* @throws IllegalArgumentException when the given uri parameter is not a valid URI
* @throws UnsupportedEncodingException when the given encoding parameter is not supported
*/
- public static String encodeUriComponents(String scheme, String authority, String userinfo,
- String host, String port, String path, String query, String fragment, String encoding)
- throws UnsupportedEncodingException {
-
+ public static String encodeUriComponents(String scheme,
+ String authority,
+ String userinfo,
+ String host,
+ String port,
+ String path,
+ String query,
+ String fragment,
+ String encoding) throws UnsupportedEncodingException {
+
Assert.hasLength(encoding, "'encoding' must not be empty");
StringBuilder sb = new StringBuilder();
@@ -275,7 +196,7 @@ public abstract class UriUtils {
sb.append(':');
}
- if (authority != null) {
+ if (userinfo != null || host != null || port != null) {
sb.append("//");
if (userinfo != null) {
sb.append(encodeUserInfo(userinfo, encoding));
@@ -288,9 +209,14 @@ public abstract class UriUtils {
sb.append(':');
sb.append(encodePort(port, encoding));
}
+ } else if (authority != null) {
+ sb.append("//");
+ sb.append(encodeAuthority(authority, encoding));
}
- sb.append(encodePath(path, encoding));
+ if (path != null) {
+ sb.append(encodePath(path, encoding));
+ }
if (query != null) {
sb.append('?');
@@ -306,129 +232,194 @@ public abstract class UriUtils {
}
/**
- * Encodes the given URI scheme.
+ * Encodes the given URI scheme with the given encoding.
* @param scheme the scheme to be encoded
* @param encoding the character encoding to encode to
* @return the encoded scheme
* @throws UnsupportedEncodingException when the given encoding parameter is not supported
*/
public static String encodeScheme(String scheme, String encoding) throws UnsupportedEncodingException {
- return encode(scheme, encoding, SCHEME);
+ return encode(scheme, encoding, SCHEME_COMPONENT, false);
}
/**
- * Encodes the given URI user info.
+ * Encodes the given URI authority with the given encoding.
+ * @param authority the authority to be encoded
+ * @param encoding the character encoding to encode to
+ * @return the encoded authority
+ * @throws UnsupportedEncodingException when the given encoding parameter is not supported
+ */
+ public static String encodeAuthority(String authority, String encoding) throws UnsupportedEncodingException {
+ return encode(authority, encoding, AUTHORITY_COMPONENT, false);
+ }
+
+ /**
+ * Encodes the given URI user info with the given encoding.
* @param userInfo the user info to be encoded
* @param encoding the character encoding to encode to
* @return the encoded user info
* @throws UnsupportedEncodingException when the given encoding parameter is not supported
*/
public static String encodeUserInfo(String userInfo, String encoding) throws UnsupportedEncodingException {
- return encode(userInfo, encoding, USER_INFO);
+ return encode(userInfo, encoding, USER_INFO_COMPONENT, false);
}
/**
- * Encodes the given URI host.
+ * Encodes the given URI host with the given encoding.
* @param host the host to be encoded
* @param encoding the character encoding to encode to
* @return the encoded host
* @throws UnsupportedEncodingException when the given encoding parameter is not supported
*/
public static String encodeHost(String host, String encoding) throws UnsupportedEncodingException {
- return encode(host, encoding, HOST);
+ return encode(host, encoding, HOST_COMPONENT, false);
}
/**
- * Encodes the given URI port.
+ * Encodes the given URI port with the given encoding.
* @param port the port to be encoded
* @param encoding the character encoding to encode to
* @return the encoded port
* @throws UnsupportedEncodingException when the given encoding parameter is not supported
*/
public static String encodePort(String port, String encoding) throws UnsupportedEncodingException {
- return encode(port, encoding, PORT);
+ return encode(port, encoding, PORT_COMPONENT, false);
}
/**
- * Encodes the given URI path.
+ * Encodes the given URI path with the given encoding.
* @param path the path to be encoded
* @param encoding the character encoding to encode to
* @return the encoded path
* @throws UnsupportedEncodingException when the given encoding parameter is not supported
*/
public static String encodePath(String path, String encoding) throws UnsupportedEncodingException {
- return encode(path, encoding, PATH);
+ return encode(path, encoding, PATH_COMPONENT, false);
}
/**
- * Encodes the given URI path segment.
+ * Encodes the given URI path segment with the given encoding.
* @param segment the segment to be encoded
* @param encoding the character encoding to encode to
* @return the encoded segment
* @throws UnsupportedEncodingException when the given encoding parameter is not supported
*/
public static String encodePathSegment(String segment, String encoding) throws UnsupportedEncodingException {
- return encode(segment, encoding, SEGMENT);
+ return encode(segment, encoding, PATH_SEGMENT_COMPONENT, false);
}
/**
- * Encodes the given URI query.
+ * Encodes the given URI query with the given encoding.
* @param query the query to be encoded
* @param encoding the character encoding to encode to
* @return the encoded query
* @throws UnsupportedEncodingException when the given encoding parameter is not supported
*/
public static String encodeQuery(String query, String encoding) throws UnsupportedEncodingException {
- return encode(query, encoding, QUERY);
+ return encode(query, encoding, QUERY_COMPONENT, false);
}
/**
- * Encodes the given URI query parameter.
+ * Encodes the given URI query parameter with the given encoding.
* @param queryParam the query parameter to be encoded
* @param encoding the character encoding to encode to
* @return the encoded query parameter
* @throws UnsupportedEncodingException when the given encoding parameter is not supported
*/
public static String encodeQueryParam(String queryParam, String encoding) throws UnsupportedEncodingException {
- return encode(queryParam, encoding, QUERY_PARAM);
+ return encode(queryParam, encoding, QUERY_PARAM_COMPONENT, false);
}
/**
- * Encodes the given URI fragment.
+ * Encodes the given URI fragment with the given encoding.
* @param fragment the fragment to be encoded
* @param encoding the character encoding to encode to
* @return the encoded fragment
* @throws UnsupportedEncodingException when the given encoding parameter is not supported
*/
public static String encodeFragment(String fragment, String encoding) throws UnsupportedEncodingException {
- return encode(fragment, encoding, FRAGMENT);
+ return encode(fragment, encoding, FRAGMENT_COMPONENT, false);
}
- private static String encode(String source, String encoding, BitSet notEncoded)
- throws UnsupportedEncodingException {
+ /**
+ * Encodes the given source into an encoded String using the rules specified by the given component. This method
+ * encodes with the default encoding (i.e. UTF-8).
+ * @param source the source string
+ * @param uriComponent the URI component for the source
+ * @param allowTemplateVars whether URI template variables are allowed. If {@code true}, '{' and '}' characters
+ * are not encoded, even though they might not be valid for the component
+ * @return the encoded URI
+ * @throws IllegalArgumentException when the given uri parameter is not a valid URI
+ * @see #SCHEME_COMPONENT
+ * @see #AUTHORITY_COMPONENT
+ * @see #USER_INFO_COMPONENT
+ * @see #HOST_COMPONENT
+ * @see #PORT_COMPONENT
+ * @see #PATH_COMPONENT
+ * @see #PATH_SEGMENT_COMPONENT
+ * @see #QUERY_COMPONENT
+ * @see #QUERY_PARAM_COMPONENT
+ * @see #FRAGMENT_COMPONENT
+ */
+ public static String encode(String source, UriComponent uriComponent, boolean allowTemplateVars) {
+ try {
+ return encode(source, DEFAULT_ENCODING, uriComponent, allowTemplateVars);
+ }
+ catch (UnsupportedEncodingException e) {
+ throw new InternalError("'" + DEFAULT_ENCODING + "' encoding not supported");
+ }
+ }
- Assert.notNull(source, "'source' must not be null");
+ /**
+ * Encodes the given source into an encoded String using the rules specified by the given component.
+ * @param source the source string
+ * @param encoding the encoding of the source string
+ * @param uriComponent the URI component for the source
+ * @param allowTemplateVars whether URI template variables are allowed. If {@code true}, '{' and '}' characters
+ * are not encoded, even though they might not be valid for the component
+ * @return the encoded URI
+ * @throws IllegalArgumentException when the given uri parameter is not a valid URI
+ * @see #SCHEME_COMPONENT
+ * @see #AUTHORITY_COMPONENT
+ * @see #USER_INFO_COMPONENT
+ * @see #HOST_COMPONENT
+ * @see #PORT_COMPONENT
+ * @see #PATH_COMPONENT
+ * @see #PATH_SEGMENT_COMPONENT
+ * @see #QUERY_COMPONENT
+ * @see #QUERY_PARAM_COMPONENT
+ * @see #FRAGMENT_COMPONENT
+ */
+ public static String encode(String source, String encoding, UriComponent uriComponent, boolean allowTemplateVars)
+ throws UnsupportedEncodingException {
Assert.hasLength(encoding, "'encoding' must not be empty");
- byte[] bytes = encode(source.getBytes(encoding), notEncoded);
+ byte[] bytes = encodeInternal(source.getBytes(encoding), uriComponent, allowTemplateVars);
return new String(bytes, "US-ASCII");
}
- private static byte[] encode(byte[] source, BitSet notEncoded) {
+ private static byte[] encodeInternal(byte[] source, UriComponent uriComponent, boolean allowTemplateVars) {
Assert.notNull(source, "'source' must not be null");
- ByteArrayOutputStream bos = new ByteArrayOutputStream(source.length * 2);
+ Assert.notNull(uriComponent, "'uriComponent' must not be null");
+
+ ByteArrayOutputStream bos = new ByteArrayOutputStream(source.length);
for (int i = 0; i < source.length; i++) {
int b = source[i];
if (b < 0) {
b += 256;
}
- if (notEncoded.get(b)) {
+ if (uriComponent.isAllowed(b)) {
+ bos.write(b);
+ }
+ else if (allowTemplateVars && (b == '{' || b == '}')) {
bos.write(b);
}
else {
bos.write('%');
+
char hex1 = Character.toUpperCase(Character.forDigit((b >> 4) & 0xF, 16));
char hex2 = Character.toUpperCase(Character.forDigit(b & 0xF, 16));
+
bos.write(hex1);
bos.write(hex2);
}
@@ -436,6 +427,7 @@ public abstract class UriUtils {
return bos.toByteArray();
}
+
/**
* Decodes the given encoded source String into an URI. Based on the following
* rules:
@@ -486,4 +478,126 @@ public abstract class UriUtils {
return changed ? new String(bos.toByteArray(), encoding) : source;
}
+ /**
+ * Defines the contract for an URI component, i.e. scheme, host, path, etc.
+ */
+ public interface UriComponent {
+
+ /**
+ * Specifies 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
+ */
+ boolean isAllowed(int c);
+
+ }
+
+ private static abstract class AbstractUriComponent implements UriComponent {
+
+ protected boolean isAlpha(int c) {
+ return c >= 'a' && c <= 'z' || c >= 'A' && c <= 'Z';
+ }
+
+ protected boolean isDigit(int c) {
+ return c >= '0' && c <= '9';
+ }
+
+ protected boolean isGenericDelimiter(int c) {
+ return ':' == c || '/' == c || '?' == c || '#' == c || '[' == c || ']' == c || '@' == c;
+ }
+
+ protected boolean isSubDelimiter(int c) {
+ return '!' == c || '$' == c || '&' == c || '\'' == c || '(' == c || ')' == c || '*' == c || '+' == c ||
+ ',' == c || ';' == c || '=' == c;
+ }
+
+ protected boolean isReserved(char c) {
+ return isGenericDelimiter(c) || isReserved(c);
+ }
+
+ protected boolean isUnreserved(int c) {
+ return isAlpha(c) || isDigit(c) || '-' == c || '.' == c || '_' == c || '~' == c;
+ }
+
+ protected boolean isPchar(int c) {
+ return isUnreserved(c) || isSubDelimiter(c) || ':' == c || '@' == c;
+ }
+
+ }
+
+ /** The scheme URI component. */
+ public static final UriComponent SCHEME_COMPONENT = new AbstractUriComponent() {
+ public boolean isAllowed(int c) {
+ return isAlpha(c) || isDigit(c) || '+' == c || '-' == c || '.' == c;
+ }
+ };
+
+ /** The authority URI component. */
+ public static final UriComponent AUTHORITY_COMPONENT = new AbstractUriComponent() {
+ public boolean isAllowed(int c) {
+ return isUnreserved(c) || isSubDelimiter(c) || ':' == c || '@' == c;
+ }
+ };
+
+ /** The user info URI component. */
+ public static final UriComponent USER_INFO_COMPONENT = new AbstractUriComponent() {
+ public boolean isAllowed(int c) {
+ return isUnreserved(c) || isSubDelimiter(c) || ':' == c;
+ }
+ };
+
+ /** The host URI component. */
+ public static final UriComponent HOST_COMPONENT = new AbstractUriComponent() {
+ public boolean isAllowed(int c) {
+ return isUnreserved(c) || isSubDelimiter(c);
+ }
+ };
+
+ /** The port URI component. */
+ public static final UriComponent PORT_COMPONENT = new AbstractUriComponent() {
+ public boolean isAllowed(int c) {
+ return isDigit(c);
+ }
+ };
+
+ /** The path URI component. */
+ public static final UriComponent PATH_COMPONENT = new AbstractUriComponent() {
+ public boolean isAllowed(int c) {
+ return isPchar(c) || '/' == c;
+ }
+ };
+
+ /** The path segment URI component. */
+ public static final UriComponent PATH_SEGMENT_COMPONENT = new AbstractUriComponent() {
+ public boolean isAllowed(int c) {
+ return isPchar(c);
+ }
+ };
+
+ /** The query URI component. */
+ public static final UriComponent QUERY_COMPONENT = new AbstractUriComponent() {
+ public boolean isAllowed(int c) {
+ return isPchar(c) || '/' == c || '?' == c;
+ }
+ };
+
+ /** The query parameter URI component. */
+ public static final UriComponent QUERY_PARAM_COMPONENT = new AbstractUriComponent() {
+ public boolean isAllowed(int c) {
+ if ('=' == c || '+' == c || '&' == c) {
+ return false;
+ }
+ else {
+ return isPchar(c) || '/' == c || '?' == c;
+ }
+ }
+ };
+
+ /** The fragment URI component. */
+ public static final UriComponent FRAGMENT_COMPONENT = new AbstractUriComponent() {
+ public boolean isAllowed(int c) {
+ return isPchar(c) || '/' == c || '?' == c;
+ }
+ };
+
}
diff --git a/org.springframework.web/src/test/java/org/springframework/web/util/UriBuilderTests.java b/org.springframework.web/src/test/java/org/springframework/web/util/UriBuilderTests.java
new file mode 100644
index 00000000000..65c83db1bde
--- /dev/null
+++ b/org.springframework.web/src/test/java/org/springframework/web/util/UriBuilderTests.java
@@ -0,0 +1,122 @@
+/*
+ * Copyright 2002-2011 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.web.util;
+
+import java.io.UnsupportedEncodingException;
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.util.Collections;
+import java.util.Map;
+
+import org.junit.Test;
+
+import static org.junit.Assert.*;
+
+/** @author Arjen Poutsma */
+public class UriBuilderTests {
+
+ @Test
+ public void plain() throws URISyntaxException {
+ UriBuilder builder = UriBuilder.newInstance();
+ URI result = builder.scheme("http").host("example.com").path("foo").queryParam("bar").fragment("baz").build();
+
+ URI expected = new URI("http://example.com/foo?bar#baz");
+ assertEquals("Invalid result URI", expected, result);
+ }
+
+ @Test
+ public void fromUri() throws URISyntaxException {
+ URI uri = new URI("http://example.com/foo?bar#baz");
+
+ URI result = UriBuilder.fromUri(uri).build();
+ assertEquals("Invalid result URI", uri, result);
+ }
+
+ @Test
+ public void templateVarsVarArgs() throws URISyntaxException {
+ UriBuilder builder = UriBuilder.newInstance();
+ URI result = builder.scheme("http").host("example.com").path("{foo}").build("bar");
+
+ URI expected = new URI("http://example.com/bar");
+ assertEquals("Invalid result URI", expected, result);
+ }
+
+ @Test
+ public void templateVarsEncoded() throws URISyntaxException, UnsupportedEncodingException {
+ URI result = UriBuilder.fromPath("{foo}").build("bar baz");
+
+ URI expected = new URI("/bar%20baz");
+ assertEquals("Invalid result URI", expected, result);
+ }
+
+ @Test
+ public void templateVarsNotEncoded() throws URISyntaxException {
+ UriBuilder builder = UriBuilder.newInstance();
+ URI result = builder.scheme("http").host("example.com").path("{foo}").buildFromEncoded("bar%20baz");
+
+ URI expected = new URI("http://example.com/bar%20baz");
+ assertEquals("Invalid result URI", expected, result);
+ }
+
+ @Test
+ public void templateVarsMap() throws URISyntaxException {
+ Map