diff --git a/api/current.xml b/api/current.xml index c26f589856718..d261a9747fa78 100644 --- a/api/current.xml +++ b/api/current.xml @@ -105431,6 +105431,17 @@ + + + + { /* This class aims to do as little up front work as possible. To accomplish - that, we vary the implementation dependending on what the user passes in. + that, we vary the implementation depending on what the user passes in. For example, we have one implementation if the user passes in a URI string (StringUri) and another if the user passes in the individual components (OpaqueUri). @@ -1261,6 +1263,8 @@ public abstract class Uri implements Parcelable, Comparable { * *

An opaque URI follows this pattern: * {@code :#} + * + *

Use {@link Uri#buildUpon()} to obtain a builder representing an existing URI. */ public static final class Builder { @@ -1446,6 +1450,13 @@ public abstract class Uri implements Parcelable, Comparable { return this; } + /** + * Clears the the previously set query. + */ + public Builder clearQuery() { + return query((Part) null); + } + /** * Constructs a Uri with the current attributes. * @@ -1490,6 +1501,45 @@ public abstract class Uri implements Parcelable, Comparable { } } + /** + * Returns a set of the unique names of all query parameters. Iterating + * over the set will return the names in order of their first occurrence. + * + * @throws UnsupportedOperationException if this isn't a hierarchical URI + * + * @return a set of decoded names + */ + public Set getQueryParameterNames() { + if (isOpaque()) { + throw new UnsupportedOperationException(NOT_HIERARCHICAL); + } + + String query = getEncodedQuery(); + if (query == null) { + return Collections.emptySet(); + } + + Set names = new LinkedHashSet(); + int start = 0; + do { + int next = query.indexOf('&', start); + int end = (next == -1) ? query.length() : next; + + int separator = query.indexOf('=', start); + if (separator > end || separator == -1) { + separator = end; + } + + String name = query.substring(start, separator); + names.add(decode(name)); + + // Move start to end of name. + start = end + 1; + } while (start < query.length()); + + return Collections.unmodifiableSet(names); + } + /** * Searches the query string for parameter values with the given key. * @@ -1497,13 +1547,15 @@ public abstract class Uri implements Parcelable, Comparable { * * @throws UnsupportedOperationException if this isn't a hierarchical URI * @throws NullPointerException if key is null - * * @return a list of decoded values */ public List getQueryParameters(String key) { if (isOpaque()) { throw new UnsupportedOperationException(NOT_HIERARCHICAL); } + if (key == null) { + throw new NullPointerException("key"); + } String query = getEncodedQuery(); if (query == null) { @@ -1517,39 +1569,34 @@ public abstract class Uri implements Parcelable, Comparable { throw new AssertionError(e); } - // Prepend query with "&" making the first parameter the same as the - // rest. - query = "&" + query; - - // Parameter prefix. - String prefix = "&" + encodedKey + "="; - ArrayList values = new ArrayList(); int start = 0; - int length = query.length(); - while (start < length) { - start = query.indexOf(prefix, start); + do { + int nextAmpersand = query.indexOf('&', start); + int end = nextAmpersand != -1 ? nextAmpersand : query.length(); - if (start == -1) { - // No more values. + int separator = query.indexOf('=', start); + if (separator > end || separator == -1) { + separator = end; + } + + if (separator - start == encodedKey.length() + && query.regionMatches(start, encodedKey, 0, encodedKey.length())) { + if (separator == end) { + values.add(""); + } else { + values.add(decode(query.substring(separator + 1, end))); + } + } + + // Move start to end of name. + if (nextAmpersand != -1) { + start = nextAmpersand + 1; + } else { break; } - - // Move start to start of value. - start += prefix.length(); - - // Find end of value. - int end = query.indexOf('&', start); - if (end == -1) { - end = query.length(); - } - - String value = query.substring(start, end); - values.add(decode(value)); - - start = end; - } + } while (true); return Collections.unmodifiableList(values); } @@ -1560,7 +1607,6 @@ public abstract class Uri implements Parcelable, Comparable { * @param key which will be encoded * @throws UnsupportedOperationException if this isn't a hierarchical URI * @throws NullPointerException if key is null - * * @return the decoded value or null if no parameter is found */ public String getQueryParameter(String key) { @@ -1577,34 +1623,33 @@ public abstract class Uri implements Parcelable, Comparable { } final String encodedKey = encode(key, null); - final int encodedKeyLength = encodedKey.length(); + final int length = query.length(); + int start = 0; + do { + int nextAmpersand = query.indexOf('&', start); + int end = nextAmpersand != -1 ? nextAmpersand : length; - int encodedKeySearchIndex = 0; - final int encodedKeySearchEnd = query.length() - (encodedKeyLength + 1); + int separator = query.indexOf('=', start); + if (separator > end || separator == -1) { + separator = end; + } - while (encodedKeySearchIndex <= encodedKeySearchEnd) { - int keyIndex = query.indexOf(encodedKey, encodedKeySearchIndex); - if (keyIndex == -1) { - break; - } - final int equalsIndex = keyIndex + encodedKeyLength; - if (equalsIndex >= query.length()) { - break; - } - if (query.charAt(equalsIndex) != '=') { - encodedKeySearchIndex = equalsIndex + 1; - continue; - } - if (keyIndex == 0 || query.charAt(keyIndex - 1) == '&') { - int end = query.indexOf('&', equalsIndex); - if (end == -1) { - end = query.length(); + if (separator - start == encodedKey.length() + && query.regionMatches(start, encodedKey, 0, encodedKey.length())) { + if (separator == end) { + return ""; + } else { + return decode(query.substring(separator + 1, end)); } - return decode(query.substring(equalsIndex + 1, end)); - } else { - encodedKeySearchIndex = equalsIndex + 1; } - } + + // Move start to end of name. + if (nextAmpersand != -1) { + start = nextAmpersand + 1; + } else { + break; + } + } while (true); return null; } diff --git a/core/tests/coretests/src/android/net/UriTest.java b/core/tests/coretests/src/android/net/UriTest.java index a5fda20c22212..fe608b5f31e4b 100644 --- a/core/tests/coretests/src/android/net/UriTest.java +++ b/core/tests/coretests/src/android/net/UriTest.java @@ -24,6 +24,9 @@ import junit.framework.TestCase; import java.io.File; import java.util.Arrays; +import java.util.Iterator; +import java.util.List; +import java.util.Set; public class UriTest extends TestCase { @@ -52,20 +55,20 @@ public class UriTest extends TestCase { private void parcelAndUnparcel(Uri u) { Parcel p = Parcel.obtain(); - try { - Uri.writeToParcel(p, u); - p.setDataPosition(0); - assertEquals(u, Uri.CREATOR.createFromParcel(p)); + try { + Uri.writeToParcel(p, u); + p.setDataPosition(0); + assertEquals(u, Uri.CREATOR.createFromParcel(p)); - p.setDataPosition(0); - u = u.buildUpon().build(); - Uri.writeToParcel(p, u); - p.setDataPosition(0); - assertEquals(u, Uri.CREATOR.createFromParcel(p)); - } - finally { - p.recycle(); - } + p.setDataPosition(0); + u = u.buildUpon().build(); + Uri.writeToParcel(p, u); + p.setDataPosition(0); + assertEquals(u, Uri.CREATOR.createFromParcel(p)); + } + finally { + p.recycle(); + } } @SmallTest @@ -603,4 +606,122 @@ public class UriTest extends TestCase { assertEquals("", uri.getQueryParameter("b")); assertEquals("", uri.getQueryParameter("c")); } + + public void testGetQueryParameterEmptyKey() { + Uri uri = Uri.parse("http://www.google.com/?=b"); + assertEquals("b", uri.getQueryParameter("")); + } + + public void testGetQueryParameterEmptyKey2() { + Uri uri = Uri.parse("http://www.google.com/?a=b&&c=d"); + assertEquals("", uri.getQueryParameter("")); + } + + public void testGetQueryParameterEmptyKey3() { + Uri uri = Uri.parse("http://www.google.com?"); + assertEquals("", uri.getQueryParameter("")); + } + + public void testGetQueryParameterEmptyKey4() { + Uri uri = Uri.parse("http://www.google.com?a=b&"); + assertEquals("", uri.getQueryParameter("")); + } + + public void testGetQueryParametersEmptyKey() { + Uri uri = Uri.parse("http://www.google.com/?=b&"); + List values = uri.getQueryParameters(""); + assertEquals(2, values.size()); + assertEquals("b", values.get(0)); + assertEquals("", values.get(1)); + } + + public void testGetQueryParametersEmptyKey2() { + Uri uri = Uri.parse("http://www.google.com?"); + List values = uri.getQueryParameters(""); + assertEquals(1, values.size()); + assertEquals("", values.get(0)); + } + + public void testGetQueryParametersEmptyKey3() { + Uri uri = Uri.parse("http://www.google.com/?a=b&&c=d"); + List values = uri.getQueryParameters(""); + assertEquals(1, values.size()); + assertEquals("", values.get(0)); + } + + public void testGetQueryParameterNames() { + Uri uri = Uri.parse("http://test?a=1"); + Set names = uri.getQueryParameterNames(); + assertEquals(1, names.size()); + assertEquals("a", names.iterator().next()); + } + + public void testGetQueryParameterNamesEmptyKey() { + Uri uri = Uri.parse("http://www.google.com/?a=x&&c=z"); + Set names = uri.getQueryParameterNames(); + Iterator iter = names.iterator(); + assertEquals(3, names.size()); + assertEquals("a", iter.next()); + assertEquals("", iter.next()); + assertEquals("c", iter.next()); + } + + public void testGetQueryParameterNamesEmptyKey2() { + Uri uri = Uri.parse("http://www.google.com/?a=x&=d&c=z"); + Set names = uri.getQueryParameterNames(); + Iterator iter = names.iterator(); + assertEquals(3, names.size()); + assertEquals("a", iter.next()); + assertEquals("", iter.next()); + assertEquals("c", iter.next()); + } + + public void testGetQueryParameterNamesEmptyValues() { + Uri uri = Uri.parse("http://www.google.com/?a=foo&b=&c="); + Set names = uri.getQueryParameterNames(); + Iterator iter = names.iterator(); + assertEquals(3, names.size()); + assertEquals("a", iter.next()); + assertEquals("b", iter.next()); + assertEquals("c", iter.next()); + } + + public void testGetQueryParameterNamesEdgeCases() { + Uri uri = Uri.parse("http://foo?a=bar&b=bar&c=&&d=baz&e&f&g=buzz&&&a&b=bar&h"); + Set names = uri.getQueryParameterNames(); + Iterator iter = names.iterator(); + assertEquals(9, names.size()); + assertEquals("a", iter.next()); + assertEquals("b", iter.next()); + assertEquals("c", iter.next()); + assertEquals("", iter.next()); + assertEquals("d", iter.next()); + assertEquals("e", iter.next()); + assertEquals("f", iter.next()); + assertEquals("g", iter.next()); + assertEquals("h", iter.next()); + } + + public void testGetQueryParameterNamesEscapedKeys() { + Uri uri = Uri.parse("http://www.google.com/?a%20b=foo&c%20d="); + Set names = uri.getQueryParameterNames(); + assertEquals(2, names.size()); + Iterator iter = names.iterator(); + assertEquals("a b", iter.next()); + assertEquals("c d", iter.next()); + } + + public void testGetQueryParameterEscapedKeys() { + Uri uri = Uri.parse("http://www.google.com/?a%20b=foo&c%20d="); + String value = uri.getQueryParameter("a b"); + assertEquals("foo", value); + } + + public void testClearQueryParameters() { + Uri uri = Uri.parse("http://www.google.com/?a=x&b=y&c=z").buildUpon() + .clearQuery().appendQueryParameter("foo", "bar").build(); + Set names = uri.getQueryParameterNames(); + assertEquals(1, names.size()); + assertEquals("foo", names.iterator().next()); + } }