Rewrite of UriBuilder

git-svn-id: https://svn.apache.org/repos/asf/httpcomponents/httpclient/trunk@1172634 13f79535-47bb-0310-9956-ffa450edef68
This commit is contained in:
Oleg Kalnichevski 2011-09-19 13:55:58 +00:00
parent 72a41afd5e
commit 40a0ed44d2
4 changed files with 319 additions and 288 deletions

View File

@ -43,8 +43,11 @@ import org.apache.http.Header;
import org.apache.http.HeaderElement; import org.apache.http.HeaderElement;
import org.apache.http.HttpEntity; import org.apache.http.HttpEntity;
import org.apache.http.NameValuePair; import org.apache.http.NameValuePair;
import org.apache.http.message.BasicHeaderValueParser;
import org.apache.http.message.BasicNameValuePair; import org.apache.http.message.BasicNameValuePair;
import org.apache.http.message.ParserCursor;
import org.apache.http.protocol.HTTP; import org.apache.http.protocol.HTTP;
import org.apache.http.util.CharArrayBuffer;
import org.apache.http.util.EntityUtils; import org.apache.http.util.EntityUtils;
/** /**
@ -73,13 +76,12 @@ public class URLEncodedUtils {
* encoding to use while parsing the query * encoding to use while parsing the query
*/ */
public static List <NameValuePair> parse (final URI uri, final String encoding) { public static List <NameValuePair> parse (final URI uri, final String encoding) {
List <NameValuePair> result = Collections.emptyList();
final String query = uri.getRawQuery(); final String query = uri.getRawQuery();
if (query != null && query.length() > 0) { if (query != null && query.length() > 0) {
result = new ArrayList <NameValuePair>(); return parse(query, encoding);
parse(result, new Scanner(query), encoding); } else {
return Collections.emptyList();
} }
return result;
} }
/** /**
@ -156,7 +158,10 @@ public class URLEncodedUtils {
* Input that contains the parameters to parse. * Input that contains the parameters to parse.
* @param encoding * @param encoding
* Encoding to use when decoding the parameters. * Encoding to use when decoding the parameters.
*
* @deprecated use {@link #parse(String, String)}
*/ */
@Deprecated
public static void parse ( public static void parse (
final List <NameValuePair> parameters, final List <NameValuePair> parameters,
final Scanner scanner, final Scanner scanner,
@ -175,6 +180,39 @@ public class URLEncodedUtils {
} }
} }
private static final char[] DELIM = new char[] { '&' };
/**
* Returns a list of {@link NameValuePair NameValuePairs} as parsed from the given string
* using the given character encoding.
*
* @param s
* text to parse.
* @param encoding
* Encoding to use when decoding the parameters.
*
* @since 4.2
*/
public static List<NameValuePair> parse (final String s, final String encoding) {
if (s == null) {
return Collections.emptyList();
}
BasicHeaderValueParser parser = BasicHeaderValueParser.DEFAULT;
CharArrayBuffer buffer = new CharArrayBuffer(s.length());
buffer.append(s);
ParserCursor cursor = new ParserCursor(0, buffer.length());
List<NameValuePair> list = new ArrayList<NameValuePair>();
while (!cursor.atEnd()) {
NameValuePair nvp = parser.parseNameValuePair(buffer, cursor, DELIM);
if (nvp.getName().length() > 0) {
list.add(new BasicNameValuePair(
decode(nvp.getName(), encoding),
decode(nvp.getValue(), encoding)));
}
}
return list;
}
/** /**
* Returns a String that is suitable for use as an <code>application/x-www-form-urlencoded</code> * Returns a String that is suitable for use as an <code>application/x-www-form-urlencoded</code>
* list of parameters in an HTTP PUT or HTTP POST. * list of parameters in an HTTP PUT or HTTP POST.
@ -188,14 +226,16 @@ public class URLEncodedUtils {
final StringBuilder result = new StringBuilder(); final StringBuilder result = new StringBuilder();
for (final NameValuePair parameter : parameters) { for (final NameValuePair parameter : parameters) {
final String encodedName = encode(parameter.getName(), encoding); final String encodedName = encode(parameter.getName(), encoding);
final String value = parameter.getValue(); final String encodedValue = encode(parameter.getValue(), encoding);
final String encodedValue = value != null ? encode(value, encoding) : ""; if (result.length() > 0) {
if (result.length() > 0)
result.append(PARAMETER_SEPARATOR); result.append(PARAMETER_SEPARATOR);
}
result.append(encodedName); result.append(encodedName);
if (encodedValue != null) {
result.append(NAME_VALUE_SEPARATOR); result.append(NAME_VALUE_SEPARATOR);
result.append(encodedValue); result.append(encodedValue);
} }
}
return result.toString(); return result.toString();
} }
@ -214,18 +254,23 @@ public class URLEncodedUtils {
final StringBuilder result = new StringBuilder(); final StringBuilder result = new StringBuilder();
for (final NameValuePair parameter : parameters) { for (final NameValuePair parameter : parameters) {
final String encodedName = encode(parameter.getName(), encoding); final String encodedName = encode(parameter.getName(), encoding);
final String value = parameter.getValue(); final String encodedValue = encode(parameter.getValue(), encoding);
final String encodedValue = value != null ? encode(value, encoding) : ""; if (result.length() > 0) {
if (result.length() > 0)
result.append(PARAMETER_SEPARATOR); result.append(PARAMETER_SEPARATOR);
}
result.append(encodedName); result.append(encodedName);
if (encodedValue != null) {
result.append(NAME_VALUE_SEPARATOR); result.append(NAME_VALUE_SEPARATOR);
result.append(encodedValue); result.append(encodedValue);
} }
}
return result.toString(); return result.toString();
} }
private static String decode (final String content, final String encoding) { private static String decode (final String content, final String encoding) {
if (content == null) {
return null;
}
try { try {
return URLDecoder.decode(content, return URLDecoder.decode(content,
encoding != null ? encoding : HTTP.DEFAULT_CONTENT_CHARSET); encoding != null ? encoding : HTTP.DEFAULT_CONTENT_CHARSET);
@ -235,6 +280,9 @@ public class URLEncodedUtils {
} }
private static String encode (final String content, final String encoding) { private static String encode (final String content, final String encoding) {
if (content == null) {
return null;
}
try { try {
return URLEncoder.encode(content, return URLEncoder.encode(content,
encoding != null ? encoding : HTTP.DEFAULT_CONTENT_CHARSET); encoding != null ? encoding : HTTP.DEFAULT_CONTENT_CHARSET);

View File

@ -26,16 +26,18 @@
package org.apache.http.client.utils; package org.apache.http.client.utils;
import java.io.UnsupportedEncodingException;
import java.net.URI; import java.net.URI;
import java.net.URISyntaxException; import java.net.URISyntaxException;
import java.net.URLDecoder; import java.util.ArrayList;
import java.net.URLEncoder; import java.util.Iterator;
import java.nio.charset.Charset; import java.util.List;
import java.util.Map;
import java.util.Set; import org.apache.http.NameValuePair;
import org.apache.http.message.BasicNameValuePair;
import org.apache.http.protocol.HTTP;
public class UriBuilder { public class UriBuilder {
private String scheme; private String scheme;
private String schemeSpecificPart; private String schemeSpecificPart;
private String authority; private String authority;
@ -43,310 +45,176 @@ public class UriBuilder {
private String host; private String host;
private int port; private int port;
private String path; private String path;
private String query; private List<NameValuePair> queryParams;
private String fragment; private String fragment;
private URI uri;
private String enc;
private boolean encOn;
private Set<String> supportedEncoding;
public UriBuilder() { public UriBuilder() {
init(); super();
this.port = -1;
} }
public UriBuilder(String string) throws URISyntaxException { public UriBuilder(final String string) throws URISyntaxException {
init(); super();
URI uri = new URI(string); digestURI(new URI(string));
from(uri);
} }
public UriBuilder(URI uri) { public UriBuilder(final URI uri) {
this.init(); super();
from(uri); digestURI(uri);
} }
public UriBuilder addParameter(String param, Object value) private List <NameValuePair> parseQuery(final String query, final String encoding) {
throws URISyntaxException { if (query != null && query.length() > 0) {
return this.addParameter(param, value.toString()); return URLEncodedUtils.parse(query, encoding);
}
return null;
}
private String formatQuery(final List<NameValuePair> parameters, final String encoding) {
if (parameters == null) {
return null;
}
return URLEncodedUtils.format(parameters, encoding);
} }
/** /**
* add a parameter-value pair into URI query * Builds a URI instance.
*
* @param param
* @param value
* @throws URISyntaxException
*/ */
public UriBuilder addParameter(String param, String value) public URI build() throws URISyntaxException {
throws URISyntaxException { if (this.schemeSpecificPart != null) {
StringBuffer sb = this.query == null ? new StringBuffer() return new URI(this.scheme, this.schemeSpecificPart, this.fragment);
: new StringBuffer(this.query); } else if (this.authority != null) {
if (sb.length() > 0 && sb.charAt(sb.length() - 1) != '&') return new URI(this.scheme, this.authority,
sb.append('&'); this.path, formatQuery(this.queryParams, HTTP.UTF_8), this.fragment);
sb.append(encode(param)).append('=').append(encode(value));
return setQuery(sb.toString());
}
/**
* build a URI instance from pre-provided information
*
* @throws RuntimeException
*/
public URI build() throws RuntimeException {
if (uri != null)
return uri;
else
throw new IllegalStateException(
"Not enough information to build URI");
}
private void digestURI(URI uri, boolean raw) {
scheme = uri.getScheme();
host = uri.getHost();
port = uri.getPort();
if (raw) {
schemeSpecificPart = uri.getRawSchemeSpecificPart();
authority = uri.getRawAuthority();
userInfo = uri.getRawUserInfo();
path = uri.getRawPath();
query = uri.getRawQuery();
fragment = uri.getRawFragment();
} else { } else {
schemeSpecificPart = uri.getSchemeSpecificPart(); return new URI(this.scheme, this.userInfo, this.host, this.port,
authority = uri.getAuthority(); this.path, formatQuery(this.queryParams, HTTP.UTF_8), this.fragment);
userInfo = uri.getUserInfo();
path = uri.getPath();
query = uri.getQuery();
fragment = uri.getFragment();
} }
} }
public UriBuilder encodingOff() { private void digestURI(final URI uri) {
this.encOn = false; this.scheme = uri.getScheme();
return this; this.schemeSpecificPart = uri.getSchemeSpecificPart();
this.authority = uri.getAuthority();
this.host = uri.getHost();
this.port = uri.getPort();
this.userInfo = uri.getUserInfo();
this.path = uri.getPath();
this.queryParams = parseQuery(uri.getRawQuery(), HTTP.UTF_8);
this.fragment = uri.getFragment();
} }
public UriBuilder encodingOn() { /**
try { * Sets URI scheme.
encodingOn(enc); */
} catch (UnsupportedEncodingException e) { public UriBuilder setScheme(final String scheme) {
e.printStackTrace(); this.scheme = scheme;
}
return this;
}
public UriBuilder encodingOn(String enc)
throws UnsupportedEncodingException {
if (enc == null)
throw new IllegalArgumentException(
"Encoding scheme cannot be null.");
// check encoding is supported
if (!supportedEncoding.contains(enc))
throw new UnsupportedEncodingException();
this.enc = enc;
this.encOn = true;
return this; return this;
} }
/** /**
* copy the uri into builder * Sets URI user-info.
*
* @param uri
* String value of a URI instance
* @throws URISyntaxException
* if uri is invalid
*/ */
public UriBuilder from(final String uri) throws URISyntaxException { public UriBuilder setUserInfo(final String userInfo) {
URI u = new URI(uri);
from(u);
return this;
}
/**
* copy uri into builder
*
* @param uri
* the URI source to clone
*/
public UriBuilder from(final URI uri) {
digestURI(uri, false);
this.uri = uri;
return this;
}
private void init() {
port = -1;
encOn = false;
enc = "UTF-8";
supportedEncoding = Charset.availableCharsets().keySet();
}
/**
* set URI fragment
*
* @param fragment
* @throws URISyntaxException
*/
public UriBuilder setFragment(final String fragment)
throws URISyntaxException {
this.fragment = encode(fragment);
update();
return this;
}
private String encode(String string) {
try {
if (encOn) {
String encodedString = URLEncoder.encode(string, enc);
return encodedString;
} else {
return URLDecoder.decode(string, enc);
}
} catch (UnsupportedEncodingException e) {
throw new IllegalStateException(e);
}
}
/**
* set URI host
*
* @param host
* @throws URISyntaxException
* if uri is invalid
*/
public UriBuilder setHost(final String host) throws URISyntaxException {
this.host = encode(host);
update();
return this;
}
/**
* set URI path
*
* @param path
* a String represent the path of a URI, e.g., "/path"
* @throws URISyntaxException
*/
public UriBuilder setPath(final String path) throws URISyntaxException {
this.path = encode(path);
update();
return this;
}
/**
* set URI port
*
* @param port
* @throws URISyntaxException
*/
public UriBuilder setPort(final int port) throws URISyntaxException {
this.port = port < 0 ? -1 : port;
update();
return this;
}
/**
* set URI query by parameter-value pairs
*
* @param paramMap
* @throws URISyntaxException
*/
public UriBuilder setQuery(final Map<String, String> paramMap)
throws URISyntaxException {
StringBuffer sb = new StringBuffer();
for (String key : paramMap.keySet())
sb.append(encode(key)).append('=')
.append(encode(paramMap.get(key))).append('&');
if (sb.charAt(sb.length() - 1) == '&')
sb.deleteCharAt(sb.length() - 1);
return setQuery(sb.toString());
}
/**
* set URI query
*
* @param query
* @throws URISyntaxException
*/
public UriBuilder setQuery(final String query) throws URISyntaxException {
this.query = query;
update();
return this;
}
/**
* set URI scheme
*
* @param scheme
* @throws URISyntaxException
* if uri is invalid
*/
public UriBuilder setScheme(final String scheme) throws URISyntaxException {
this.scheme = encode(scheme);
update();
return this;
}
/**
* set URI user-info
*
* @param userInfo
* a String represents the user-info, e.g., "username:password"
* @throws URISyntaxException
*/
public UriBuilder setUserInfo(final String userInfo)
throws URISyntaxException {
this.userInfo = userInfo; this.userInfo = userInfo;
update(); this.schemeSpecificPart = null;
this.authority = null;
return this; return this;
} }
/** /**
* set URI user-info * Sets URI user-info in a form of 'username:password'.
*
* @param username
* @param password
* @throws URISyntaxException
*/ */
public UriBuilder setUserInfo(final String username, final String password) public UriBuilder setUserInfo(final String username, final String password) {
throws URISyntaxException {
return setUserInfo(username + ':' + password); return setUserInfo(username + ':' + password);
} }
public UriBuilder removeQuery() { /**
this.query = null; * Sets URI host.
*/
public UriBuilder setHost(final String host) {
this.host = host;
this.schemeSpecificPart = null;
this.authority = null;
return this; return this;
} }
@Override /**
public String toString() { * Sets URI port.
StringBuffer sb = new StringBuffer(); */
URI uri = build(); public UriBuilder setPort(final int port) {
sb.append(uri.toString()).append('\n'); this.port = port < 0 ? -1 : port;
sb.append("scheme : ").append(scheme).append('\n'); this.schemeSpecificPart = null;
sb.append("sspart : ").append(schemeSpecificPart).append('\n'); this.authority = null;
sb.append("authority: ").append(authority).append('\n'); return this;
sb.append("user-info: ").append(userInfo).append('\n');
sb.append("host : ").append(host).append('\n');
sb.append("port : ").append(port).append('\n');
sb.append("path : ").append(path).append('\n');
sb.append("query : ").append(query).append('\n');
sb.append("fragment : ").append(fragment);
return sb.toString();
} }
private void update() throws URISyntaxException { /**
if (scheme != null && host != null) * Sets URI path.
try { */
uri = new URI(scheme, userInfo, host, port, path, query, public UriBuilder setPath(final String path) {
fragment); this.path = path;
digestURI(uri, false); this.schemeSpecificPart = null;
} catch (URISyntaxException e) { return this;
// roll back }
digestURI(uri, false);
throw e; /**
* Removes all query parameters.
*/
public UriBuilder removeQuery() {
this.queryParams = null;
this.schemeSpecificPart = null;
return this;
}
/**
* Set URI query.
*/
public UriBuilder setQuery(final String query) {
this.queryParams = parseQuery(query, HTTP.UTF_8);
this.schemeSpecificPart = null;
return this;
}
/**
* Adds a parameter-value pair to URI query.
*/
public UriBuilder addParameter(final String param, final String value) {
if (this.queryParams == null) {
this.queryParams = new ArrayList<NameValuePair>();
}
this.queryParams.add(new BasicNameValuePair(param, value));
this.schemeSpecificPart = null;
return this;
}
/**
* Sets parameter-value pair to URI query removing existing parameters with the same name.
*/
public UriBuilder setParameter(final String param, final String value) {
if (this.queryParams == null) {
this.queryParams = new ArrayList<NameValuePair>();
}
if (!this.queryParams.isEmpty()) {
for (Iterator<NameValuePair> it = this.queryParams.iterator(); it.hasNext(); ) {
NameValuePair nvp = it.next();
if (nvp.getName().equals(param)) {
it.remove();
} }
} }
}
this.queryParams.add(new BasicNameValuePair(param, value));
this.schemeSpecificPart = null;
return this;
}
/**
* Sets URI fragment.
*/
public UriBuilder setFragment(final String fragment) {
this.fragment = fragment;
return this;
}
} }

View File

@ -0,0 +1,107 @@
/*
* ====================================================================
*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You 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.
* ====================================================================
*
* This software consists of voluntary contributions made by many
* individuals on behalf of the Apache Software Foundation. For more
* information on the Apache Software Foundation, please see
* <http://www.apache.org/>.
*
*/
package org.apache.http.client.utils;
import java.net.URI;
import org.junit.Assert;
import org.junit.Test;
public class TestURIBuilder {
@Test
public void testHierarchicalUri() throws Exception {
URI uri = new URI("http", "stuff", "localhost", 80, "/some stuff", "param=stuff", "fragment");
UriBuilder uribuilder = new UriBuilder(uri);
URI result = uribuilder.build();
Assert.assertEquals(uri, result);
}
@Test
public void testOpaqueUri() throws Exception {
URI uri = new URI("stuff", "some-stuff", "fragment");
UriBuilder uribuilder = new UriBuilder(uri);
URI result = uribuilder.build();
Assert.assertEquals(uri, result);
}
@Test
public void testOpaqueUriMutation() throws Exception {
URI uri = new URI("stuff", "some-stuff", "fragment");
UriBuilder uribuilder = new UriBuilder(uri).setQuery("param1&param2=stuff").setFragment(null);
Assert.assertEquals(new URI("stuff:?param1&param2=stuff"), uribuilder.build());
}
@Test
public void testHierarchicalUriMutation() throws Exception {
UriBuilder uribuilder = new UriBuilder("/").setScheme("http").setHost("localhost").setPort(80).setPath("/stuff");
Assert.assertEquals(new URI("http://localhost:80/stuff"), uribuilder.build());
}
@Test
public void testEmpty() throws Exception {
UriBuilder uribuilder = new UriBuilder();
URI result = uribuilder.build();
Assert.assertEquals(new URI(""), result);
}
@Test
public void testSetUserInfo() throws Exception {
URI uri = new URI("http", null, "localhost", 80, "/", "param=stuff", null);
UriBuilder uribuilder = new UriBuilder(uri).setUserInfo("user", "password");
URI result = uribuilder.build();
Assert.assertEquals(new URI("http://user:password@localhost:80/?param=stuff"), result);
}
@Test
public void testRemoveParameters() throws Exception {
URI uri = new URI("http", null, "localhost", 80, "/", "param=stuff", null);
UriBuilder uribuilder = new UriBuilder(uri).removeQuery();
URI result = uribuilder.build();
Assert.assertEquals(new URI("http://localhost:80/"), result);
}
@Test
public void testSetParameter() throws Exception {
URI uri = new URI("http", null, "localhost", 80, "/", "param=stuff&blah&blah", null);
UriBuilder uribuilder = new UriBuilder(uri).setParameter("param", "some other stuff")
.setParameter("blah", "blah");
URI result = uribuilder.build();
Assert.assertEquals(new URI("http://localhost:80/?param=some+other+stuff&blah=blah"), result);
}
@Test
public void testAddParameter() throws Exception {
URI uri = new URI("http", null, "localhost", 80, "/", "param=stuff&blah&blah", null);
UriBuilder uribuilder = new UriBuilder(uri).addParameter("param", "some other stuff")
.addParameter("blah", "blah");
URI result = uribuilder.build();
Assert.assertEquals(new URI("http://localhost:80/?param=stuff&blah&blah&" +
"param=some+other+stuff&blah=blah"), result);
}
}

View File

@ -47,13 +47,17 @@ public class TestURLEncodedUtils {
result = parse("", null); result = parse("", null);
Assert.assertTrue(result.isEmpty()); Assert.assertTrue(result.isEmpty());
result = parse("Name0", null);
Assert.assertEquals(1, result.size());
assertNameValuePair(result.get(0), "Name0", null);
result = parse("Name1=Value1", null); result = parse("Name1=Value1", null);
Assert.assertEquals(1, result.size()); Assert.assertEquals(1, result.size());
assertNameValuePair(result.get(0), "Name1", "Value1"); assertNameValuePair(result.get(0), "Name1", "Value1");
result = parse("Name2=", null); result = parse("Name2=", null);
Assert.assertEquals(1, result.size()); Assert.assertEquals(1, result.size());
assertNameValuePair(result.get(0), "Name2", null); assertNameValuePair(result.get(0), "Name2", "");
result = parse("Name3", null); result = parse("Name3", null);
Assert.assertEquals(1, result.size()); Assert.assertEquals(1, result.size());
@ -159,12 +163,16 @@ public class TestURLEncodedUtils {
final List <NameValuePair> params = new ArrayList <NameValuePair>(); final List <NameValuePair> params = new ArrayList <NameValuePair>();
Assert.assertEquals(0, URLEncodedUtils.format(params, null).length()); Assert.assertEquals(0, URLEncodedUtils.format(params, null).length());
params.clear();
params.add(new BasicNameValuePair("Name0", null));
Assert.assertEquals("Name0", URLEncodedUtils.format(params, null));
params.clear(); params.clear();
params.add(new BasicNameValuePair("Name1", "Value1")); params.add(new BasicNameValuePair("Name1", "Value1"));
Assert.assertEquals("Name1=Value1", URLEncodedUtils.format(params, null)); Assert.assertEquals("Name1=Value1", URLEncodedUtils.format(params, null));
params.clear(); params.clear();
params.add(new BasicNameValuePair("Name2", null)); params.add(new BasicNameValuePair("Name2", ""));
Assert.assertEquals("Name2=", URLEncodedUtils.format(params, null)); Assert.assertEquals("Name2=", URLEncodedUtils.format(params, null));
params.clear(); params.clear();