Added methods for managing uri query parameters
Change-Id: Ic98c1bd159740dd4d895889079f9f2abae4fc2b9
diff --git a/api/current.xml b/api/current.xml
index c26f589..d261a97 100644
--- a/api/current.xml
+++ b/api/current.xml
@@ -105431,6 +105431,17 @@
<parameter name="key" type="java.lang.String">
</parameter>
</method>
+<method name="getQueryParameterNames"
+ return="java.util.Set<java.lang.String>"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
<method name="getQueryParameters"
return="java.util.List<java.lang.String>"
abstract="false"
@@ -105677,6 +105688,17 @@
visibility="public"
>
</method>
+<method name="clearQuery"
+ return="android.net.Uri.Builder"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
<method name="encodedAuthority"
return="android.net.Uri.Builder"
abstract="false"
diff --git a/core/java/android/net/Uri.java b/core/java/android/net/Uri.java
index 6f4144b..3b21590 100644
--- a/core/java/android/net/Uri.java
+++ b/core/java/android/net/Uri.java
@@ -28,8 +28,10 @@
import java.util.AbstractList;
import java.util.ArrayList;
import java.util.Collections;
+import java.util.LinkedHashSet;
import java.util.List;
import java.util.RandomAccess;
+import java.util.Set;
/**
* Immutable URI reference. A URI reference includes a URI and a fragment, the
@@ -47,7 +49,7 @@
/*
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 @@
*
* <p>An opaque URI follows this pattern:
* {@code <scheme>:<opaque part>#<fragment>}
+ *
+ * <p>Use {@link Uri#buildUpon()} to obtain a builder representing an existing URI.
*/
public static final class Builder {
@@ -1447,6 +1451,13 @@
}
/**
+ * Clears the the previously set query.
+ */
+ public Builder clearQuery() {
+ return query((Part) null);
+ }
+
+ /**
* Constructs a Uri with the current attributes.
*
* @throws UnsupportedOperationException if the URI is opaque and the
@@ -1491,19 +1502,60 @@
}
/**
+ * 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<String> getQueryParameterNames() {
+ if (isOpaque()) {
+ throw new UnsupportedOperationException(NOT_HIERARCHICAL);
+ }
+
+ String query = getEncodedQuery();
+ if (query == null) {
+ return Collections.emptySet();
+ }
+
+ Set<String> names = new LinkedHashSet<String>();
+ 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.
*
* @param key which will be encoded
*
* @throws UnsupportedOperationException if this isn't a hierarchical URI
* @throws NullPointerException if key is null
- *
* @return a list of decoded values
*/
public List<String> 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 @@
throw new AssertionError(e);
}
- // Prepend query with "&" making the first parameter the same as the
- // rest.
- query = "&" + query;
-
- // Parameter prefix.
- String prefix = "&" + encodedKey + "=";
-
ArrayList<String> values = new ArrayList<String>();
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 @@
* @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 @@
}
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 a5fda20..fe608b5 100644
--- a/core/tests/coretests/src/android/net/UriTest.java
+++ b/core/tests/coretests/src/android/net/UriTest.java
@@ -24,6 +24,9 @@
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 @@
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 @@
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<String> 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<String> 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<String> values = uri.getQueryParameters("");
+ assertEquals(1, values.size());
+ assertEquals("", values.get(0));
+ }
+
+ public void testGetQueryParameterNames() {
+ Uri uri = Uri.parse("http://test?a=1");
+ Set<String> 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<String> names = uri.getQueryParameterNames();
+ Iterator<String> 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<String> names = uri.getQueryParameterNames();
+ Iterator<String> 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<String> names = uri.getQueryParameterNames();
+ Iterator<String> 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<String> names = uri.getQueryParameterNames();
+ Iterator<String> 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<String> names = uri.getQueryParameterNames();
+ assertEquals(2, names.size());
+ Iterator<String> 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<String> names = uri.getQueryParameterNames();
+ assertEquals(1, names.size());
+ assertEquals("foo", names.iterator().next());
+ }
}