//------------------------------------------------------------------------------ // Copyright (c) 2012 Microsoft Corporation. All rights reserved. // // Description: See the class level JavaDoc comments. //------------------------------------------------------------------------------ package com.microsoft.live; import java.util.Iterator; import java.util.LinkedList; import android.net.Uri; import android.text.TextUtils; import android.util.Log; /** * Class for building URIs. The most useful benefit of this class is its query parameter * management. It stores all the query parameters in a LinkedList, so parameters can * be looked up, removed, and added easily. */ class UriBuilder { public static class QueryParameter { private final String key; private final String value; /** * Constructs a query parameter with no value (e.g., download). * * @param key */ public QueryParameter(String key) { assert key != null; this.key = key; this.value = null; } public QueryParameter(String key, String value) { assert key != null; assert value != null; this.key = key; this.value = value; } public String getKey() { return this.key; } public String getValue() { return this.value; } public boolean hasValue() { return this.value != null; } @Override public String toString() { if (this.hasValue()) { return this.key + "=" + this.value; } return this.key; } } private static final String EQUAL = "="; private static final String AMPERSAND = "&"; private static final char FORWARD_SLASH = '/'; private String scheme; private String host; private StringBuilder path; private final LinkedList<QueryParameter> queryParameters; /** * Constructs a new UriBuilder from the given Uri. * * @return a new Uri Builder based off the given Uri. */ public static UriBuilder newInstance(Uri uri) { return new UriBuilder().scheme(uri.getScheme()) .host(uri.getHost()) .path(uri.getPath()) .query(uri.getQuery()); } public UriBuilder() { this.queryParameters = new LinkedList<QueryParameter>(); } /** * Appends a new query parameter to the UriBuilder's query string. * * (e.g., appendQueryParameter("k1", "v1") when UriBuilder's query string is * k2=v2&k3=v3 results in k2=v2&k3=v3&k1=v1). * * @param key Key of the new query parameter. * @param value Value of the new query parameter. * @return this UriBuilder object. Useful for chaining. */ public UriBuilder appendQueryParameter(String key, String value) { assert key != null; assert value != null; this.queryParameters.add(new QueryParameter(key, value)); return this; } /** * Appends the given query string on to the existing UriBuilder's query parameters. * * (e.g., UriBuilder's queryString k1=v1&k2=v2 and given queryString k3=v3&k4=v4, results in * k1=v1&k2=v2&k3=v3&k4=v4). * * @param queryString Key-Value pairs separated by & and = (e.g., k1=v1&k2=v2&k3=k3). * @return this UriBuilder object. Useful for chaining. */ public UriBuilder appendQueryString(String queryString) { if (queryString == null) { return this; } String[] pairs = TextUtils.split(queryString, UriBuilder.AMPERSAND); for(String pair : pairs) { String[] splitPair = TextUtils.split(pair, UriBuilder.EQUAL); if (splitPair.length == 2) { String key = splitPair[0]; String value = splitPair[1]; this.queryParameters.add(new QueryParameter(key, value)); } else if (splitPair.length == 1){ String key = splitPair[0]; this.queryParameters.add(new QueryParameter(key)); } else { Log.w("com.microsoft.live.UriBuilder", "Invalid query parameter: " + pair); } } return this; } /** * Appends the given path to the UriBuilder's current path. * * @param path The path to append onto this UriBuilder's path. * @return this UriBuilder object. Useful for chaining. */ public UriBuilder appendToPath(String path) { assert path != null; if (this.path == null) { this.path = new StringBuilder(path); } else { boolean endsWithSlash = TextUtils.isEmpty(this.path) ? false : this.path.charAt(this.path.length() - 1) == UriBuilder.FORWARD_SLASH; boolean pathIsEmpty = TextUtils.isEmpty(path); boolean beginsWithSlash = pathIsEmpty ? false : path.charAt(0) == UriBuilder.FORWARD_SLASH; if (endsWithSlash && beginsWithSlash) { if (path.length() > 1) { this.path.append(path.substring(1)); } } else if (!endsWithSlash && !beginsWithSlash) { if (!pathIsEmpty) { this.path.append(UriBuilder.FORWARD_SLASH).append(path); } } else { this.path.append(path); } } return this; } /** * Builds the Uri by converting into a android.net.Uri object. * * @return a new android.net.Uri defined by what was given to the builder. */ public Uri build() { return new Uri.Builder().scheme(this.scheme) .authority(this.host) .path(this.path == null ? "" : this.path.toString()) .encodedQuery(TextUtils.join("&", this.queryParameters)) .build(); } /** * Sets the host part of the Uri. * * @return this UriBuilder object. Useful for chaining. */ public UriBuilder host(String host) { assert host != null; this.host = host; return this; } /** * Sets the path and removes any previously existing path. * * @param path The path to set on this UriBuilder. * @return this UriBuilder object. Useful for chaining. */ public UriBuilder path(String path) { assert path != null; this.path = new StringBuilder(path); return this; } /** * Takes a query string and puts it in the Uri Builder's query string removing * any existing query parameters. * * @param queryString Key-Value pairs separated by & and = (e.g., k1=v1&k2=v2&k3=k3). * @return this UriBuilder object. Useful for chaining. */ public UriBuilder query(String queryString) { this.queryParameters.clear(); return this.appendQueryString(queryString); } /** * Removes all query parameters from the UriBuilder that has the given key. * * (e.g., removeQueryParametersWithKey("k1") when UriBuilder's query string of k1=v1&k2=v2&k1=v3 * results in k2=v2). * * @param key Query parameter's key to remove * @return this UriBuilder object. Useful for chaining. */ public UriBuilder removeQueryParametersWithKey(String key) { // There could be multiple query parameters with this key and // we want to remove all of them. Iterator<QueryParameter> it = this.queryParameters.iterator(); while (it.hasNext()) { QueryParameter qp = it.next(); if (qp.getKey().equals(key)) { it.remove(); } } return this; } /** * Sets the scheme part of the Uri. * * @return this UriBuilder object. Useful for chaining. */ public UriBuilder scheme(String scheme) { assert scheme != null; this.scheme = scheme; return this; } /** * Returns the URI in string format (e.g., http://foo.com/bar?k1=v2). */ @Override public String toString() { return this.build().toString(); } }