Home | History | Annotate | Download | only in net
      1 /*
      2  * Copyright (C) 2007 The Android Open Source Project
      3  *
      4  * Licensed under the Apache License, Version 2.0 (the "License");
      5  * you may not use this file except in compliance with the License.
      6  * You may obtain a copy of the License at
      7  *
      8  *      http://www.apache.org/licenses/LICENSE-2.0
      9  *
     10  * Unless required by applicable law or agreed to in writing, software
     11  * distributed under the License is distributed on an "AS IS" BASIS,
     12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13  * See the License for the specific language governing permissions and
     14  * limitations under the License.
     15  */
     16 
     17 package android.net;
     18 
     19 import android.os.Parcel;
     20 import android.os.Parcelable;
     21 import android.util.Log;
     22 
     23 import java.io.File;
     24 import java.io.IOException;
     25 import java.io.UnsupportedEncodingException;
     26 import java.io.ByteArrayOutputStream;
     27 import java.net.URLEncoder;
     28 import java.util.AbstractList;
     29 import java.util.ArrayList;
     30 import java.util.Collections;
     31 import java.util.LinkedHashSet;
     32 import java.util.List;
     33 import java.util.RandomAccess;
     34 import java.util.Set;
     35 
     36 /**
     37  * Immutable URI reference. A URI reference includes a URI and a fragment, the
     38  * component of the URI following a '#'. Builds and parses URI references
     39  * which conform to
     40  * <a href="http://www.faqs.org/rfcs/rfc2396.html">RFC 2396</a>.
     41  *
     42  * <p>In the interest of performance, this class performs little to no
     43  * validation. Behavior is undefined for invalid input. This class is very
     44  * forgiving--in the face of invalid input, it will return garbage
     45  * rather than throw an exception unless otherwise specified.
     46  */
     47 public abstract class Uri implements Parcelable, Comparable<Uri> {
     48 
     49     /*
     50 
     51     This class aims to do as little up front work as possible. To accomplish
     52     that, we vary the implementation depending on what the user passes in.
     53     For example, we have one implementation if the user passes in a
     54     URI string (StringUri) and another if the user passes in the
     55     individual components (OpaqueUri).
     56 
     57     *Concurrency notes*: Like any truly immutable object, this class is safe
     58     for concurrent use. This class uses a caching pattern in some places where
     59     it doesn't use volatile or synchronized. This is safe to do with ints
     60     because getting or setting an int is atomic. It's safe to do with a String
     61     because the internal fields are final and the memory model guarantees other
     62     threads won't see a partially initialized instance. We are not guaranteed
     63     that some threads will immediately see changes from other threads on
     64     certain platforms, but we don't mind if those threads reconstruct the
     65     cached result. As a result, we get thread safe caching with no concurrency
     66     overhead, which means the most common case, access from a single thread,
     67     is as fast as possible.
     68 
     69     From the Java Language spec.:
     70 
     71     "17.5 Final Field Semantics
     72 
     73     ... when the object is seen by another thread, that thread will always
     74     see the correctly constructed version of that object's final fields.
     75     It will also see versions of any object or array referenced by
     76     those final fields that are at least as up-to-date as the final fields
     77     are."
     78 
     79     In that same vein, all non-transient fields within Uri
     80     implementations should be final and immutable so as to ensure true
     81     immutability for clients even when they don't use proper concurrency
     82     control.
     83 
     84     For reference, from RFC 2396:
     85 
     86     "4.3. Parsing a URI Reference
     87 
     88        A URI reference is typically parsed according to the four main
     89        components and fragment identifier in order to determine what
     90        components are present and whether the reference is relative or
     91        absolute.  The individual components are then parsed for their
     92        subparts and, if not opaque, to verify their validity.
     93 
     94        Although the BNF defines what is allowed in each component, it is
     95        ambiguous in terms of differentiating between an authority component
     96        and a path component that begins with two slash characters.  The
     97        greedy algorithm is used for disambiguation: the left-most matching
     98        rule soaks up as much of the URI reference string as it is capable of
     99        matching.  In other words, the authority component wins."
    100 
    101     The "four main components" of a hierarchical URI consist of
    102     <scheme>://<authority><path>?<query>
    103 
    104     */
    105 
    106     /** Log tag. */
    107     private static final String LOG = Uri.class.getSimpleName();
    108 
    109     /**
    110      * NOTE: EMPTY accesses this field during its own initialization, so this
    111      * field *must* be initialized first, or else EMPTY will see a null value!
    112      *
    113      * Placeholder for strings which haven't been cached. This enables us
    114      * to cache null. We intentionally create a new String instance so we can
    115      * compare its identity and there is no chance we will confuse it with
    116      * user data.
    117      */
    118     @SuppressWarnings("RedundantStringConstructorCall")
    119     private static final String NOT_CACHED = new String("NOT CACHED");
    120 
    121     /**
    122      * The empty URI, equivalent to "".
    123      */
    124     public static final Uri EMPTY = new HierarchicalUri(null, Part.NULL,
    125             PathPart.EMPTY, Part.NULL, Part.NULL);
    126 
    127     /**
    128      * Prevents external subclassing.
    129      */
    130     private Uri() {}
    131 
    132     /**
    133      * Returns true if this URI is hierarchical like "http://google.com".
    134      * Absolute URIs are hierarchical if the scheme-specific part starts with
    135      * a '/'. Relative URIs are always hierarchical.
    136      */
    137     public abstract boolean isHierarchical();
    138 
    139     /**
    140      * Returns true if this URI is opaque like "mailto:nobody (at) google.com". The
    141      * scheme-specific part of an opaque URI cannot start with a '/'.
    142      */
    143     public boolean isOpaque() {
    144         return !isHierarchical();
    145     }
    146 
    147     /**
    148      * Returns true if this URI is relative, i.e. if it doesn't contain an
    149      * explicit scheme.
    150      *
    151      * @return true if this URI is relative, false if it's absolute
    152      */
    153     public abstract boolean isRelative();
    154 
    155     /**
    156      * Returns true if this URI is absolute, i.e. if it contains an
    157      * explicit scheme.
    158      *
    159      * @return true if this URI is absolute, false if it's relative
    160      */
    161     public boolean isAbsolute() {
    162         return !isRelative();
    163     }
    164 
    165     /**
    166      * Gets the scheme of this URI. Example: "http"
    167      *
    168      * @return the scheme or null if this is a relative URI
    169      */
    170     public abstract String getScheme();
    171 
    172     /**
    173      * Gets the scheme-specific part of this URI, i.e. everything between the
    174      * scheme separator ':' and the fragment separator '#'. If this is a
    175      * relative URI, this method returns the entire URI. Decodes escaped octets.
    176      *
    177      * <p>Example: "//www.google.com/search?q=android"
    178      *
    179      * @return the decoded scheme-specific-part
    180      */
    181     public abstract String getSchemeSpecificPart();
    182 
    183     /**
    184      * Gets the scheme-specific part of this URI, i.e. everything between the
    185      * scheme separator ':' and the fragment separator '#'. If this is a
    186      * relative URI, this method returns the entire URI. Leaves escaped octets
    187      * intact.
    188      *
    189      * <p>Example: "//www.google.com/search?q=android"
    190      *
    191      * @return the decoded scheme-specific-part
    192      */
    193     public abstract String getEncodedSchemeSpecificPart();
    194 
    195     /**
    196      * Gets the decoded authority part of this URI. For
    197      * server addresses, the authority is structured as follows:
    198      * {@code [ userinfo '@' ] host [ ':' port ]}
    199      *
    200      * <p>Examples: "google.com", "bob (at) google.com:80"
    201      *
    202      * @return the authority for this URI or null if not present
    203      */
    204     public abstract String getAuthority();
    205 
    206     /**
    207      * Gets the encoded authority part of this URI. For
    208      * server addresses, the authority is structured as follows:
    209      * {@code [ userinfo '@' ] host [ ':' port ]}
    210      *
    211      * <p>Examples: "google.com", "bob (at) google.com:80"
    212      *
    213      * @return the authority for this URI or null if not present
    214      */
    215     public abstract String getEncodedAuthority();
    216 
    217     /**
    218      * Gets the decoded user information from the authority.
    219      * For example, if the authority is "nobody (at) google.com", this method will
    220      * return "nobody".
    221      *
    222      * @return the user info for this URI or null if not present
    223      */
    224     public abstract String getUserInfo();
    225 
    226     /**
    227      * Gets the encoded user information from the authority.
    228      * For example, if the authority is "nobody (at) google.com", this method will
    229      * return "nobody".
    230      *
    231      * @return the user info for this URI or null if not present
    232      */
    233     public abstract String getEncodedUserInfo();
    234 
    235     /**
    236      * Gets the encoded host from the authority for this URI. For example,
    237      * if the authority is "bob (at) google.com", this method will return
    238      * "google.com".
    239      *
    240      * @return the host for this URI or null if not present
    241      */
    242     public abstract String getHost();
    243 
    244     /**
    245      * Gets the port from the authority for this URI. For example,
    246      * if the authority is "google.com:80", this method will return 80.
    247      *
    248      * @return the port for this URI or -1 if invalid or not present
    249      */
    250     public abstract int getPort();
    251 
    252     /**
    253      * Gets the decoded path.
    254      *
    255      * @return the decoded path, or null if this is not a hierarchical URI
    256      * (like "mailto:nobody (at) google.com") or the URI is invalid
    257      */
    258     public abstract String getPath();
    259 
    260     /**
    261      * Gets the encoded path.
    262      *
    263      * @return the encoded path, or null if this is not a hierarchical URI
    264      * (like "mailto:nobody (at) google.com") or the URI is invalid
    265      */
    266     public abstract String getEncodedPath();
    267 
    268     /**
    269      * Gets the decoded query component from this URI. The query comes after
    270      * the query separator ('?') and before the fragment separator ('#'). This
    271      * method would return "q=android" for
    272      * "http://www.google.com/search?q=android".
    273      *
    274      * @return the decoded query or null if there isn't one
    275      */
    276     public abstract String getQuery();
    277 
    278     /**
    279      * Gets the encoded query component from this URI. The query comes after
    280      * the query separator ('?') and before the fragment separator ('#'). This
    281      * method would return "q=android" for
    282      * "http://www.google.com/search?q=android".
    283      *
    284      * @return the encoded query or null if there isn't one
    285      */
    286     public abstract String getEncodedQuery();
    287 
    288     /**
    289      * Gets the decoded fragment part of this URI, everything after the '#'.
    290      *
    291      * @return the decoded fragment or null if there isn't one
    292      */
    293     public abstract String getFragment();
    294 
    295     /**
    296      * Gets the encoded fragment part of this URI, everything after the '#'.
    297      *
    298      * @return the encoded fragment or null if there isn't one
    299      */
    300     public abstract String getEncodedFragment();
    301 
    302     /**
    303      * Gets the decoded path segments.
    304      *
    305      * @return decoded path segments, each without a leading or trailing '/'
    306      */
    307     public abstract List<String> getPathSegments();
    308 
    309     /**
    310      * Gets the decoded last segment in the path.
    311      *
    312      * @return the decoded last segment or null if the path is empty
    313      */
    314     public abstract String getLastPathSegment();
    315 
    316     /**
    317      * Compares this Uri to another object for equality. Returns true if the
    318      * encoded string representations of this Uri and the given Uri are
    319      * equal. Case counts. Paths are not normalized. If one Uri specifies a
    320      * default port explicitly and the other leaves it implicit, they will not
    321      * be considered equal.
    322      */
    323     public boolean equals(Object o) {
    324         if (!(o instanceof Uri)) {
    325             return false;
    326         }
    327 
    328         Uri other = (Uri) o;
    329 
    330         return toString().equals(other.toString());
    331     }
    332 
    333     /**
    334      * Hashes the encoded string represention of this Uri consistently with
    335      * {@link #equals(Object)}.
    336      */
    337     public int hashCode() {
    338         return toString().hashCode();
    339     }
    340 
    341     /**
    342      * Compares the string representation of this Uri with that of
    343      * another.
    344      */
    345     public int compareTo(Uri other) {
    346         return toString().compareTo(other.toString());
    347     }
    348 
    349     /**
    350      * Returns the encoded string representation of this URI.
    351      * Example: "http://google.com/"
    352      */
    353     public abstract String toString();
    354 
    355     /**
    356      * Return a string representation of the URI that is safe to print
    357      * to logs and other places where PII should be avoided.
    358      * @hide
    359      */
    360     public String toSafeString() {
    361         String scheme = getScheme();
    362         String ssp = getSchemeSpecificPart();
    363         if (scheme != null) {
    364             if (scheme.equalsIgnoreCase("tel") || scheme.equalsIgnoreCase("sip")
    365                     || scheme.equalsIgnoreCase("sms") || scheme.equalsIgnoreCase("smsto")
    366                     || scheme.equalsIgnoreCase("mailto")) {
    367                 StringBuilder builder = new StringBuilder(64);
    368                 builder.append(scheme);
    369                 builder.append(':');
    370                 if (ssp != null) {
    371                     for (int i=0; i<ssp.length(); i++) {
    372                         char c = ssp.charAt(i);
    373                         if (c == '-' || c == '@' || c == '.') {
    374                             builder.append(c);
    375                         } else {
    376                             builder.append('x');
    377                         }
    378                     }
    379                 }
    380                 return builder.toString();
    381             }
    382         }
    383         // Not a sensitive scheme, but let's still be conservative about
    384         // the data we include -- only the ssp, not the query params or
    385         // fragment, because those can often have sensitive info.
    386         StringBuilder builder = new StringBuilder(64);
    387         if (scheme != null) {
    388             builder.append(scheme);
    389             builder.append(':');
    390         }
    391         if (ssp != null) {
    392             builder.append(ssp);
    393         }
    394         return builder.toString();
    395     }
    396 
    397     /**
    398      * Constructs a new builder, copying the attributes from this Uri.
    399      */
    400     public abstract Builder buildUpon();
    401 
    402     /** Index of a component which was not found. */
    403     private final static int NOT_FOUND = -1;
    404 
    405     /** Placeholder value for an index which hasn't been calculated yet. */
    406     private final static int NOT_CALCULATED = -2;
    407 
    408     /**
    409      * Error message presented when a user tries to treat an opaque URI as
    410      * hierarchical.
    411      */
    412     private static final String NOT_HIERARCHICAL
    413             = "This isn't a hierarchical URI.";
    414 
    415     /** Default encoding. */
    416     private static final String DEFAULT_ENCODING = "UTF-8";
    417 
    418     /**
    419      * Creates a Uri which parses the given encoded URI string.
    420      *
    421      * @param uriString an RFC 2396-compliant, encoded URI
    422      * @throws NullPointerException if uriString is null
    423      * @return Uri for this given uri string
    424      */
    425     public static Uri parse(String uriString) {
    426         return new StringUri(uriString);
    427     }
    428 
    429     /**
    430      * Creates a Uri from a file. The URI has the form
    431      * "file://<absolute path>". Encodes path characters with the exception of
    432      * '/'.
    433      *
    434      * <p>Example: "file:///tmp/android.txt"
    435      *
    436      * @throws NullPointerException if file is null
    437      * @return a Uri for the given file
    438      */
    439     public static Uri fromFile(File file) {
    440         if (file == null) {
    441             throw new NullPointerException("file");
    442         }
    443 
    444         PathPart path = PathPart.fromDecoded(file.getAbsolutePath());
    445         return new HierarchicalUri(
    446                 "file", Part.EMPTY, path, Part.NULL, Part.NULL);
    447     }
    448 
    449     /**
    450      * An implementation which wraps a String URI. This URI can be opaque or
    451      * hierarchical, but we extend AbstractHierarchicalUri in case we need
    452      * the hierarchical functionality.
    453      */
    454     private static class StringUri extends AbstractHierarchicalUri {
    455 
    456         /** Used in parcelling. */
    457         static final int TYPE_ID = 1;
    458 
    459         /** URI string representation. */
    460         private final String uriString;
    461 
    462         private StringUri(String uriString) {
    463             if (uriString == null) {
    464                 throw new NullPointerException("uriString");
    465             }
    466 
    467             this.uriString = uriString;
    468         }
    469 
    470         static Uri readFrom(Parcel parcel) {
    471             return new StringUri(parcel.readString());
    472         }
    473 
    474         public int describeContents() {
    475             return 0;
    476         }
    477 
    478         public void writeToParcel(Parcel parcel, int flags) {
    479             parcel.writeInt(TYPE_ID);
    480             parcel.writeString(uriString);
    481         }
    482 
    483         /** Cached scheme separator index. */
    484         private volatile int cachedSsi = NOT_CALCULATED;
    485 
    486         /** Finds the first ':'. Returns -1 if none found. */
    487         private int findSchemeSeparator() {
    488             return cachedSsi == NOT_CALCULATED
    489                     ? cachedSsi = uriString.indexOf(':')
    490                     : cachedSsi;
    491         }
    492 
    493         /** Cached fragment separator index. */
    494         private volatile int cachedFsi = NOT_CALCULATED;
    495 
    496         /** Finds the first '#'. Returns -1 if none found. */
    497         private int findFragmentSeparator() {
    498             return cachedFsi == NOT_CALCULATED
    499                     ? cachedFsi = uriString.indexOf('#', findSchemeSeparator())
    500                     : cachedFsi;
    501         }
    502 
    503         public boolean isHierarchical() {
    504             int ssi = findSchemeSeparator();
    505 
    506             if (ssi == NOT_FOUND) {
    507                 // All relative URIs are hierarchical.
    508                 return true;
    509             }
    510 
    511             if (uriString.length() == ssi + 1) {
    512                 // No ssp.
    513                 return false;
    514             }
    515 
    516             // If the ssp starts with a '/', this is hierarchical.
    517             return uriString.charAt(ssi + 1) == '/';
    518         }
    519 
    520         public boolean isRelative() {
    521             // Note: We return true if the index is 0
    522             return findSchemeSeparator() == NOT_FOUND;
    523         }
    524 
    525         private volatile String scheme = NOT_CACHED;
    526 
    527         public String getScheme() {
    528             @SuppressWarnings("StringEquality")
    529             boolean cached = (scheme != NOT_CACHED);
    530             return cached ? scheme : (scheme = parseScheme());
    531         }
    532 
    533         private String parseScheme() {
    534             int ssi = findSchemeSeparator();
    535             return ssi == NOT_FOUND ? null : uriString.substring(0, ssi);
    536         }
    537 
    538         private Part ssp;
    539 
    540         private Part getSsp() {
    541             return ssp == null ? ssp = Part.fromEncoded(parseSsp()) : ssp;
    542         }
    543 
    544         public String getEncodedSchemeSpecificPart() {
    545             return getSsp().getEncoded();
    546         }
    547 
    548         public String getSchemeSpecificPart() {
    549             return getSsp().getDecoded();
    550         }
    551 
    552         private String parseSsp() {
    553             int ssi = findSchemeSeparator();
    554             int fsi = findFragmentSeparator();
    555 
    556             // Return everything between ssi and fsi.
    557             return fsi == NOT_FOUND
    558                     ? uriString.substring(ssi + 1)
    559                     : uriString.substring(ssi + 1, fsi);
    560         }
    561 
    562         private Part authority;
    563 
    564         private Part getAuthorityPart() {
    565             if (authority == null) {
    566                 String encodedAuthority
    567                         = parseAuthority(this.uriString, findSchemeSeparator());
    568                 return authority = Part.fromEncoded(encodedAuthority);
    569             }
    570 
    571             return authority;
    572         }
    573 
    574         public String getEncodedAuthority() {
    575             return getAuthorityPart().getEncoded();
    576         }
    577 
    578         public String getAuthority() {
    579             return getAuthorityPart().getDecoded();
    580         }
    581 
    582         private PathPart path;
    583 
    584         private PathPart getPathPart() {
    585             return path == null
    586                     ? path = PathPart.fromEncoded(parsePath())
    587                     : path;
    588         }
    589 
    590         public String getPath() {
    591             return getPathPart().getDecoded();
    592         }
    593 
    594         public String getEncodedPath() {
    595             return getPathPart().getEncoded();
    596         }
    597 
    598         public List<String> getPathSegments() {
    599             return getPathPart().getPathSegments();
    600         }
    601 
    602         private String parsePath() {
    603             String uriString = this.uriString;
    604             int ssi = findSchemeSeparator();
    605 
    606             // If the URI is absolute.
    607             if (ssi > -1) {
    608                 // Is there anything after the ':'?
    609                 boolean schemeOnly = ssi + 1 == uriString.length();
    610                 if (schemeOnly) {
    611                     // Opaque URI.
    612                     return null;
    613                 }
    614 
    615                 // A '/' after the ':' means this is hierarchical.
    616                 if (uriString.charAt(ssi + 1) != '/') {
    617                     // Opaque URI.
    618                     return null;
    619                 }
    620             } else {
    621                 // All relative URIs are hierarchical.
    622             }
    623 
    624             return parsePath(uriString, ssi);
    625         }
    626 
    627         private Part query;
    628 
    629         private Part getQueryPart() {
    630             return query == null
    631                     ? query = Part.fromEncoded(parseQuery()) : query;
    632         }
    633 
    634         public String getEncodedQuery() {
    635             return getQueryPart().getEncoded();
    636         }
    637 
    638         private String parseQuery() {
    639             // It doesn't make sense to cache this index. We only ever
    640             // calculate it once.
    641             int qsi = uriString.indexOf('?', findSchemeSeparator());
    642             if (qsi == NOT_FOUND) {
    643                 return null;
    644             }
    645 
    646             int fsi = findFragmentSeparator();
    647 
    648             if (fsi == NOT_FOUND) {
    649                 return uriString.substring(qsi + 1);
    650             }
    651 
    652             if (fsi < qsi) {
    653                 // Invalid.
    654                 return null;
    655             }
    656 
    657             return uriString.substring(qsi + 1, fsi);
    658         }
    659 
    660         public String getQuery() {
    661             return getQueryPart().getDecoded();
    662         }
    663 
    664         private Part fragment;
    665 
    666         private Part getFragmentPart() {
    667             return fragment == null
    668                     ? fragment = Part.fromEncoded(parseFragment()) : fragment;
    669         }
    670 
    671         public String getEncodedFragment() {
    672             return getFragmentPart().getEncoded();
    673         }
    674 
    675         private String parseFragment() {
    676             int fsi = findFragmentSeparator();
    677             return fsi == NOT_FOUND ? null : uriString.substring(fsi + 1);
    678         }
    679 
    680         public String getFragment() {
    681             return getFragmentPart().getDecoded();
    682         }
    683 
    684         public String toString() {
    685             return uriString;
    686         }
    687 
    688         /**
    689          * Parses an authority out of the given URI string.
    690          *
    691          * @param uriString URI string
    692          * @param ssi scheme separator index, -1 for a relative URI
    693          *
    694          * @return the authority or null if none is found
    695          */
    696         static String parseAuthority(String uriString, int ssi) {
    697             int length = uriString.length();
    698 
    699             // If "//" follows the scheme separator, we have an authority.
    700             if (length > ssi + 2
    701                     && uriString.charAt(ssi + 1) == '/'
    702                     && uriString.charAt(ssi + 2) == '/') {
    703                 // We have an authority.
    704 
    705                 // Look for the start of the path, query, or fragment, or the
    706                 // end of the string.
    707                 int end = ssi + 3;
    708                 LOOP: while (end < length) {
    709                     switch (uriString.charAt(end)) {
    710                         case '/': // Start of path
    711                         case '?': // Start of query
    712                         case '#': // Start of fragment
    713                             break LOOP;
    714                     }
    715                     end++;
    716                 }
    717 
    718                 return uriString.substring(ssi + 3, end);
    719             } else {
    720                 return null;
    721             }
    722 
    723         }
    724 
    725         /**
    726          * Parses a path out of this given URI string.
    727          *
    728          * @param uriString URI string
    729          * @param ssi scheme separator index, -1 for a relative URI
    730          *
    731          * @return the path
    732          */
    733         static String parsePath(String uriString, int ssi) {
    734             int length = uriString.length();
    735 
    736             // Find start of path.
    737             int pathStart;
    738             if (length > ssi + 2
    739                     && uriString.charAt(ssi + 1) == '/'
    740                     && uriString.charAt(ssi + 2) == '/') {
    741                 // Skip over authority to path.
    742                 pathStart = ssi + 3;
    743                 LOOP: while (pathStart < length) {
    744                     switch (uriString.charAt(pathStart)) {
    745                         case '?': // Start of query
    746                         case '#': // Start of fragment
    747                             return ""; // Empty path.
    748                         case '/': // Start of path!
    749                             break LOOP;
    750                     }
    751                     pathStart++;
    752                 }
    753             } else {
    754                 // Path starts immediately after scheme separator.
    755                 pathStart = ssi + 1;
    756             }
    757 
    758             // Find end of path.
    759             int pathEnd = pathStart;
    760             LOOP: while (pathEnd < length) {
    761                 switch (uriString.charAt(pathEnd)) {
    762                     case '?': // Start of query
    763                     case '#': // Start of fragment
    764                         break LOOP;
    765                 }
    766                 pathEnd++;
    767             }
    768 
    769             return uriString.substring(pathStart, pathEnd);
    770         }
    771 
    772         public Builder buildUpon() {
    773             if (isHierarchical()) {
    774                 return new Builder()
    775                         .scheme(getScheme())
    776                         .authority(getAuthorityPart())
    777                         .path(getPathPart())
    778                         .query(getQueryPart())
    779                         .fragment(getFragmentPart());
    780             } else {
    781                 return new Builder()
    782                         .scheme(getScheme())
    783                         .opaquePart(getSsp())
    784                         .fragment(getFragmentPart());
    785             }
    786         }
    787     }
    788 
    789     /**
    790      * Creates an opaque Uri from the given components. Encodes the ssp
    791      * which means this method cannot be used to create hierarchical URIs.
    792      *
    793      * @param scheme of the URI
    794      * @param ssp scheme-specific-part, everything between the
    795      *  scheme separator (':') and the fragment separator ('#'), which will
    796      *  get encoded
    797      * @param fragment fragment, everything after the '#', null if undefined,
    798      *  will get encoded
    799      *
    800      * @throws NullPointerException if scheme or ssp is null
    801      * @return Uri composed of the given scheme, ssp, and fragment
    802      *
    803      * @see Builder if you don't want the ssp and fragment to be encoded
    804      */
    805     public static Uri fromParts(String scheme, String ssp,
    806             String fragment) {
    807         if (scheme == null) {
    808             throw new NullPointerException("scheme");
    809         }
    810         if (ssp == null) {
    811             throw new NullPointerException("ssp");
    812         }
    813 
    814         return new OpaqueUri(scheme, Part.fromDecoded(ssp),
    815                 Part.fromDecoded(fragment));
    816     }
    817 
    818     /**
    819      * Opaque URI.
    820      */
    821     private static class OpaqueUri extends Uri {
    822 
    823         /** Used in parcelling. */
    824         static final int TYPE_ID = 2;
    825 
    826         private final String scheme;
    827         private final Part ssp;
    828         private final Part fragment;
    829 
    830         private OpaqueUri(String scheme, Part ssp, Part fragment) {
    831             this.scheme = scheme;
    832             this.ssp = ssp;
    833             this.fragment = fragment == null ? Part.NULL : fragment;
    834         }
    835 
    836         static Uri readFrom(Parcel parcel) {
    837             return new OpaqueUri(
    838                 parcel.readString(),
    839                 Part.readFrom(parcel),
    840                 Part.readFrom(parcel)
    841             );
    842         }
    843 
    844         public int describeContents() {
    845             return 0;
    846         }
    847 
    848         public void writeToParcel(Parcel parcel, int flags) {
    849             parcel.writeInt(TYPE_ID);
    850             parcel.writeString(scheme);
    851             ssp.writeTo(parcel);
    852             fragment.writeTo(parcel);
    853         }
    854 
    855         public boolean isHierarchical() {
    856             return false;
    857         }
    858 
    859         public boolean isRelative() {
    860             return scheme == null;
    861         }
    862 
    863         public String getScheme() {
    864             return this.scheme;
    865         }
    866 
    867         public String getEncodedSchemeSpecificPart() {
    868             return ssp.getEncoded();
    869         }
    870 
    871         public String getSchemeSpecificPart() {
    872             return ssp.getDecoded();
    873         }
    874 
    875         public String getAuthority() {
    876             return null;
    877         }
    878 
    879         public String getEncodedAuthority() {
    880             return null;
    881         }
    882 
    883         public String getPath() {
    884             return null;
    885         }
    886 
    887         public String getEncodedPath() {
    888             return null;
    889         }
    890 
    891         public String getQuery() {
    892             return null;
    893         }
    894 
    895         public String getEncodedQuery() {
    896             return null;
    897         }
    898 
    899         public String getFragment() {
    900             return fragment.getDecoded();
    901         }
    902 
    903         public String getEncodedFragment() {
    904             return fragment.getEncoded();
    905         }
    906 
    907         public List<String> getPathSegments() {
    908             return Collections.emptyList();
    909         }
    910 
    911         public String getLastPathSegment() {
    912             return null;
    913         }
    914 
    915         public String getUserInfo() {
    916             return null;
    917         }
    918 
    919         public String getEncodedUserInfo() {
    920             return null;
    921         }
    922 
    923         public String getHost() {
    924             return null;
    925         }
    926 
    927         public int getPort() {
    928             return -1;
    929         }
    930 
    931         private volatile String cachedString = NOT_CACHED;
    932 
    933         public String toString() {
    934             @SuppressWarnings("StringEquality")
    935             boolean cached = cachedString != NOT_CACHED;
    936             if (cached) {
    937                 return cachedString;
    938             }
    939 
    940             StringBuilder sb = new StringBuilder();
    941 
    942             sb.append(scheme).append(':');
    943             sb.append(getEncodedSchemeSpecificPart());
    944 
    945             if (!fragment.isEmpty()) {
    946                 sb.append('#').append(fragment.getEncoded());
    947             }
    948 
    949             return cachedString = sb.toString();
    950         }
    951 
    952         public Builder buildUpon() {
    953             return new Builder()
    954                     .scheme(this.scheme)
    955                     .opaquePart(this.ssp)
    956                     .fragment(this.fragment);
    957         }
    958     }
    959 
    960     /**
    961      * Wrapper for path segment array.
    962      */
    963     static class PathSegments extends AbstractList<String>
    964             implements RandomAccess {
    965 
    966         static final PathSegments EMPTY = new PathSegments(null, 0);
    967 
    968         final String[] segments;
    969         final int size;
    970 
    971         PathSegments(String[] segments, int size) {
    972             this.segments = segments;
    973             this.size = size;
    974         }
    975 
    976         public String get(int index) {
    977             if (index >= size) {
    978                 throw new IndexOutOfBoundsException();
    979             }
    980 
    981             return segments[index];
    982         }
    983 
    984         public int size() {
    985             return this.size;
    986         }
    987     }
    988 
    989     /**
    990      * Builds PathSegments.
    991      */
    992     static class PathSegmentsBuilder {
    993 
    994         String[] segments;
    995         int size = 0;
    996 
    997         void add(String segment) {
    998             if (segments == null) {
    999                 segments = new String[4];
   1000             } else if (size + 1 == segments.length) {
   1001                 String[] expanded = new String[segments.length * 2];
   1002                 System.arraycopy(segments, 0, expanded, 0, segments.length);
   1003                 segments = expanded;
   1004             }
   1005 
   1006             segments[size++] = segment;
   1007         }
   1008 
   1009         PathSegments build() {
   1010             if (segments == null) {
   1011                 return PathSegments.EMPTY;
   1012             }
   1013 
   1014             try {
   1015                 return new PathSegments(segments, size);
   1016             } finally {
   1017                 // Makes sure this doesn't get reused.
   1018                 segments = null;
   1019             }
   1020         }
   1021     }
   1022 
   1023     /**
   1024      * Support for hierarchical URIs.
   1025      */
   1026     private abstract static class AbstractHierarchicalUri extends Uri {
   1027 
   1028         public String getLastPathSegment() {
   1029             // TODO: If we haven't parsed all of the segments already, just
   1030             // grab the last one directly so we only allocate one string.
   1031 
   1032             List<String> segments = getPathSegments();
   1033             int size = segments.size();
   1034             if (size == 0) {
   1035                 return null;
   1036             }
   1037             return segments.get(size - 1);
   1038         }
   1039 
   1040         private Part userInfo;
   1041 
   1042         private Part getUserInfoPart() {
   1043             return userInfo == null
   1044                     ? userInfo = Part.fromEncoded(parseUserInfo()) : userInfo;
   1045         }
   1046 
   1047         public final String getEncodedUserInfo() {
   1048             return getUserInfoPart().getEncoded();
   1049         }
   1050 
   1051         private String parseUserInfo() {
   1052             String authority = getEncodedAuthority();
   1053             if (authority == null) {
   1054                 return null;
   1055             }
   1056 
   1057             int end = authority.indexOf('@');
   1058             return end == NOT_FOUND ? null : authority.substring(0, end);
   1059         }
   1060 
   1061         public String getUserInfo() {
   1062             return getUserInfoPart().getDecoded();
   1063         }
   1064 
   1065         private volatile String host = NOT_CACHED;
   1066 
   1067         public String getHost() {
   1068             @SuppressWarnings("StringEquality")
   1069             boolean cached = (host != NOT_CACHED);
   1070             return cached ? host
   1071                     : (host = parseHost());
   1072         }
   1073 
   1074         private String parseHost() {
   1075             String authority = getEncodedAuthority();
   1076             if (authority == null) {
   1077                 return null;
   1078             }
   1079 
   1080             // Parse out user info and then port.
   1081             int userInfoSeparator = authority.indexOf('@');
   1082             int portSeparator = authority.indexOf(':', userInfoSeparator);
   1083 
   1084             String encodedHost = portSeparator == NOT_FOUND
   1085                     ? authority.substring(userInfoSeparator + 1)
   1086                     : authority.substring(userInfoSeparator + 1, portSeparator);
   1087 
   1088             return decode(encodedHost);
   1089         }
   1090 
   1091         private volatile int port = NOT_CALCULATED;
   1092 
   1093         public int getPort() {
   1094             return port == NOT_CALCULATED
   1095                     ? port = parsePort()
   1096                     : port;
   1097         }
   1098 
   1099         private int parsePort() {
   1100             String authority = getEncodedAuthority();
   1101             if (authority == null) {
   1102                 return -1;
   1103             }
   1104 
   1105             // Make sure we look for the port separtor *after* the user info
   1106             // separator. We have URLs with a ':' in the user info.
   1107             int userInfoSeparator = authority.indexOf('@');
   1108             int portSeparator = authority.indexOf(':', userInfoSeparator);
   1109 
   1110             if (portSeparator == NOT_FOUND) {
   1111                 return -1;
   1112             }
   1113 
   1114             String portString = decode(authority.substring(portSeparator + 1));
   1115             try {
   1116                 return Integer.parseInt(portString);
   1117             } catch (NumberFormatException e) {
   1118                 Log.w(LOG, "Error parsing port string.", e);
   1119                 return -1;
   1120             }
   1121         }
   1122     }
   1123 
   1124     /**
   1125      * Hierarchical Uri.
   1126      */
   1127     private static class HierarchicalUri extends AbstractHierarchicalUri {
   1128 
   1129         /** Used in parcelling. */
   1130         static final int TYPE_ID = 3;
   1131 
   1132         private final String scheme; // can be null
   1133         private final Part authority;
   1134         private final PathPart path;
   1135         private final Part query;
   1136         private final Part fragment;
   1137 
   1138         private HierarchicalUri(String scheme, Part authority, PathPart path,
   1139                 Part query, Part fragment) {
   1140             this.scheme = scheme;
   1141             this.authority = Part.nonNull(authority);
   1142             this.path = path == null ? PathPart.NULL : path;
   1143             this.query = Part.nonNull(query);
   1144             this.fragment = Part.nonNull(fragment);
   1145         }
   1146 
   1147         static Uri readFrom(Parcel parcel) {
   1148             return new HierarchicalUri(
   1149                 parcel.readString(),
   1150                 Part.readFrom(parcel),
   1151                 PathPart.readFrom(parcel),
   1152                 Part.readFrom(parcel),
   1153                 Part.readFrom(parcel)
   1154             );
   1155         }
   1156 
   1157         public int describeContents() {
   1158             return 0;
   1159         }
   1160 
   1161         public void writeToParcel(Parcel parcel, int flags) {
   1162             parcel.writeInt(TYPE_ID);
   1163             parcel.writeString(scheme);
   1164             authority.writeTo(parcel);
   1165             path.writeTo(parcel);
   1166             query.writeTo(parcel);
   1167             fragment.writeTo(parcel);
   1168         }
   1169 
   1170         public boolean isHierarchical() {
   1171             return true;
   1172         }
   1173 
   1174         public boolean isRelative() {
   1175             return scheme == null;
   1176         }
   1177 
   1178         public String getScheme() {
   1179             return scheme;
   1180         }
   1181 
   1182         private Part ssp;
   1183 
   1184         private Part getSsp() {
   1185             return ssp == null
   1186                     ? ssp = Part.fromEncoded(makeSchemeSpecificPart()) : ssp;
   1187         }
   1188 
   1189         public String getEncodedSchemeSpecificPart() {
   1190             return getSsp().getEncoded();
   1191         }
   1192 
   1193         public String getSchemeSpecificPart() {
   1194             return getSsp().getDecoded();
   1195         }
   1196 
   1197         /**
   1198          * Creates the encoded scheme-specific part from its sub parts.
   1199          */
   1200         private String makeSchemeSpecificPart() {
   1201             StringBuilder builder = new StringBuilder();
   1202             appendSspTo(builder);
   1203             return builder.toString();
   1204         }
   1205 
   1206         private void appendSspTo(StringBuilder builder) {
   1207             String encodedAuthority = authority.getEncoded();
   1208             if (encodedAuthority != null) {
   1209                 // Even if the authority is "", we still want to append "//".
   1210                 builder.append("//").append(encodedAuthority);
   1211             }
   1212 
   1213             String encodedPath = path.getEncoded();
   1214             if (encodedPath != null) {
   1215                 builder.append(encodedPath);
   1216             }
   1217 
   1218             if (!query.isEmpty()) {
   1219                 builder.append('?').append(query.getEncoded());
   1220             }
   1221         }
   1222 
   1223         public String getAuthority() {
   1224             return this.authority.getDecoded();
   1225         }
   1226 
   1227         public String getEncodedAuthority() {
   1228             return this.authority.getEncoded();
   1229         }
   1230 
   1231         public String getEncodedPath() {
   1232             return this.path.getEncoded();
   1233         }
   1234 
   1235         public String getPath() {
   1236             return this.path.getDecoded();
   1237         }
   1238 
   1239         public String getQuery() {
   1240             return this.query.getDecoded();
   1241         }
   1242 
   1243         public String getEncodedQuery() {
   1244             return this.query.getEncoded();
   1245         }
   1246 
   1247         public String getFragment() {
   1248             return this.fragment.getDecoded();
   1249         }
   1250 
   1251         public String getEncodedFragment() {
   1252             return this.fragment.getEncoded();
   1253         }
   1254 
   1255         public List<String> getPathSegments() {
   1256             return this.path.getPathSegments();
   1257         }
   1258 
   1259         private volatile String uriString = NOT_CACHED;
   1260 
   1261         @Override
   1262         public String toString() {
   1263             @SuppressWarnings("StringEquality")
   1264             boolean cached = (uriString != NOT_CACHED);
   1265             return cached ? uriString
   1266                     : (uriString = makeUriString());
   1267         }
   1268 
   1269         private String makeUriString() {
   1270             StringBuilder builder = new StringBuilder();
   1271 
   1272             if (scheme != null) {
   1273                 builder.append(scheme).append(':');
   1274             }
   1275 
   1276             appendSspTo(builder);
   1277 
   1278             if (!fragment.isEmpty()) {
   1279                 builder.append('#').append(fragment.getEncoded());
   1280             }
   1281 
   1282             return builder.toString();
   1283         }
   1284 
   1285         public Builder buildUpon() {
   1286             return new Builder()
   1287                     .scheme(scheme)
   1288                     .authority(authority)
   1289                     .path(path)
   1290                     .query(query)
   1291                     .fragment(fragment);
   1292         }
   1293     }
   1294 
   1295     /**
   1296      * Helper class for building or manipulating URI references. Not safe for
   1297      * concurrent use.
   1298      *
   1299      * <p>An absolute hierarchical URI reference follows the pattern:
   1300      * {@code <scheme>://<authority><absolute path>?<query>#<fragment>}
   1301      *
   1302      * <p>Relative URI references (which are always hierarchical) follow one
   1303      * of two patterns: {@code <relative or absolute path>?<query>#<fragment>}
   1304      * or {@code //<authority><absolute path>?<query>#<fragment>}
   1305      *
   1306      * <p>An opaque URI follows this pattern:
   1307      * {@code <scheme>:<opaque part>#<fragment>}
   1308      *
   1309      * <p>Use {@link Uri#buildUpon()} to obtain a builder representing an existing URI.
   1310      */
   1311     public static final class Builder {
   1312 
   1313         private String scheme;
   1314         private Part opaquePart;
   1315         private Part authority;
   1316         private PathPart path;
   1317         private Part query;
   1318         private Part fragment;
   1319 
   1320         /**
   1321          * Constructs a new Builder.
   1322          */
   1323         public Builder() {}
   1324 
   1325         /**
   1326          * Sets the scheme.
   1327          *
   1328          * @param scheme name or {@code null} if this is a relative Uri
   1329          */
   1330         public Builder scheme(String scheme) {
   1331             this.scheme = scheme;
   1332             return this;
   1333         }
   1334 
   1335         Builder opaquePart(Part opaquePart) {
   1336             this.opaquePart = opaquePart;
   1337             return this;
   1338         }
   1339 
   1340         /**
   1341          * Encodes and sets the given opaque scheme-specific-part.
   1342          *
   1343          * @param opaquePart decoded opaque part
   1344          */
   1345         public Builder opaquePart(String opaquePart) {
   1346             return opaquePart(Part.fromDecoded(opaquePart));
   1347         }
   1348 
   1349         /**
   1350          * Sets the previously encoded opaque scheme-specific-part.
   1351          *
   1352          * @param opaquePart encoded opaque part
   1353          */
   1354         public Builder encodedOpaquePart(String opaquePart) {
   1355             return opaquePart(Part.fromEncoded(opaquePart));
   1356         }
   1357 
   1358         Builder authority(Part authority) {
   1359             // This URI will be hierarchical.
   1360             this.opaquePart = null;
   1361 
   1362             this.authority = authority;
   1363             return this;
   1364         }
   1365 
   1366         /**
   1367          * Encodes and sets the authority.
   1368          */
   1369         public Builder authority(String authority) {
   1370             return authority(Part.fromDecoded(authority));
   1371         }
   1372 
   1373         /**
   1374          * Sets the previously encoded authority.
   1375          */
   1376         public Builder encodedAuthority(String authority) {
   1377             return authority(Part.fromEncoded(authority));
   1378         }
   1379 
   1380         Builder path(PathPart path) {
   1381             // This URI will be hierarchical.
   1382             this.opaquePart = null;
   1383 
   1384             this.path = path;
   1385             return this;
   1386         }
   1387 
   1388         /**
   1389          * Sets the path. Leaves '/' characters intact but encodes others as
   1390          * necessary.
   1391          *
   1392          * <p>If the path is not null and doesn't start with a '/', and if
   1393          * you specify a scheme and/or authority, the builder will prepend the
   1394          * given path with a '/'.
   1395          */
   1396         public Builder path(String path) {
   1397             return path(PathPart.fromDecoded(path));
   1398         }
   1399 
   1400         /**
   1401          * Sets the previously encoded path.
   1402          *
   1403          * <p>If the path is not null and doesn't start with a '/', and if
   1404          * you specify a scheme and/or authority, the builder will prepend the
   1405          * given path with a '/'.
   1406          */
   1407         public Builder encodedPath(String path) {
   1408             return path(PathPart.fromEncoded(path));
   1409         }
   1410 
   1411         /**
   1412          * Encodes the given segment and appends it to the path.
   1413          */
   1414         public Builder appendPath(String newSegment) {
   1415             return path(PathPart.appendDecodedSegment(path, newSegment));
   1416         }
   1417 
   1418         /**
   1419          * Appends the given segment to the path.
   1420          */
   1421         public Builder appendEncodedPath(String newSegment) {
   1422             return path(PathPart.appendEncodedSegment(path, newSegment));
   1423         }
   1424 
   1425         Builder query(Part query) {
   1426             // This URI will be hierarchical.
   1427             this.opaquePart = null;
   1428 
   1429             this.query = query;
   1430             return this;
   1431         }
   1432 
   1433         /**
   1434          * Encodes and sets the query.
   1435          */
   1436         public Builder query(String query) {
   1437             return query(Part.fromDecoded(query));
   1438         }
   1439 
   1440         /**
   1441          * Sets the previously encoded query.
   1442          */
   1443         public Builder encodedQuery(String query) {
   1444             return query(Part.fromEncoded(query));
   1445         }
   1446 
   1447         Builder fragment(Part fragment) {
   1448             this.fragment = fragment;
   1449             return this;
   1450         }
   1451 
   1452         /**
   1453          * Encodes and sets the fragment.
   1454          */
   1455         public Builder fragment(String fragment) {
   1456             return fragment(Part.fromDecoded(fragment));
   1457         }
   1458 
   1459         /**
   1460          * Sets the previously encoded fragment.
   1461          */
   1462         public Builder encodedFragment(String fragment) {
   1463             return fragment(Part.fromEncoded(fragment));
   1464         }
   1465 
   1466         /**
   1467          * Encodes the key and value and then appends the parameter to the
   1468          * query string.
   1469          *
   1470          * @param key which will be encoded
   1471          * @param value which will be encoded
   1472          */
   1473         public Builder appendQueryParameter(String key, String value) {
   1474             // This URI will be hierarchical.
   1475             this.opaquePart = null;
   1476 
   1477             String encodedParameter = encode(key, null) + "="
   1478                     + encode(value, null);
   1479 
   1480             if (query == null) {
   1481                 query = Part.fromEncoded(encodedParameter);
   1482                 return this;
   1483             }
   1484 
   1485             String oldQuery = query.getEncoded();
   1486             if (oldQuery == null || oldQuery.length() == 0) {
   1487                 query = Part.fromEncoded(encodedParameter);
   1488             } else {
   1489                 query = Part.fromEncoded(oldQuery + "&" + encodedParameter);
   1490             }
   1491 
   1492             return this;
   1493         }
   1494 
   1495         /**
   1496          * Clears the the previously set query.
   1497          */
   1498         public Builder clearQuery() {
   1499           return query((Part) null);
   1500         }
   1501 
   1502         /**
   1503          * Constructs a Uri with the current attributes.
   1504          *
   1505          * @throws UnsupportedOperationException if the URI is opaque and the
   1506          *  scheme is null
   1507          */
   1508         public Uri build() {
   1509             if (opaquePart != null) {
   1510                 if (this.scheme == null) {
   1511                     throw new UnsupportedOperationException(
   1512                             "An opaque URI must have a scheme.");
   1513                 }
   1514 
   1515                 return new OpaqueUri(scheme, opaquePart, fragment);
   1516             } else {
   1517                 // Hierarchical URIs should not return null for getPath().
   1518                 PathPart path = this.path;
   1519                 if (path == null || path == PathPart.NULL) {
   1520                     path = PathPart.EMPTY;
   1521                 } else {
   1522                     // If we have a scheme and/or authority, the path must
   1523                     // be absolute. Prepend it with a '/' if necessary.
   1524                     if (hasSchemeOrAuthority()) {
   1525                         path = PathPart.makeAbsolute(path);
   1526                     }
   1527                 }
   1528 
   1529                 return new HierarchicalUri(
   1530                         scheme, authority, path, query, fragment);
   1531             }
   1532         }
   1533 
   1534         private boolean hasSchemeOrAuthority() {
   1535             return scheme != null
   1536                     || (authority != null && authority != Part.NULL);
   1537 
   1538         }
   1539 
   1540         @Override
   1541         public String toString() {
   1542             return build().toString();
   1543         }
   1544     }
   1545 
   1546     /**
   1547      * Returns a set of the unique names of all query parameters. Iterating
   1548      * over the set will return the names in order of their first occurrence.
   1549      *
   1550      * @throws UnsupportedOperationException if this isn't a hierarchical URI
   1551      *
   1552      * @return a set of decoded names
   1553      */
   1554     public Set<String> getQueryParameterNames() {
   1555         if (isOpaque()) {
   1556             throw new UnsupportedOperationException(NOT_HIERARCHICAL);
   1557         }
   1558 
   1559         String query = getEncodedQuery();
   1560         if (query == null) {
   1561             return Collections.emptySet();
   1562         }
   1563 
   1564         Set<String> names = new LinkedHashSet<String>();
   1565         int start = 0;
   1566         do {
   1567             int next = query.indexOf('&', start);
   1568             int end = (next == -1) ? query.length() : next;
   1569 
   1570             int separator = query.indexOf('=', start);
   1571             if (separator > end || separator == -1) {
   1572                 separator = end;
   1573             }
   1574 
   1575             String name = query.substring(start, separator);
   1576             names.add(decode(name));
   1577 
   1578             // Move start to end of name.
   1579             start = end + 1;
   1580         } while (start < query.length());
   1581 
   1582         return Collections.unmodifiableSet(names);
   1583     }
   1584 
   1585     /**
   1586      * Searches the query string for parameter values with the given key.
   1587      *
   1588      * @param key which will be encoded
   1589      *
   1590      * @throws UnsupportedOperationException if this isn't a hierarchical URI
   1591      * @throws NullPointerException if key is null
   1592      * @return a list of decoded values
   1593      */
   1594     public List<String> getQueryParameters(String key) {
   1595         if (isOpaque()) {
   1596             throw new UnsupportedOperationException(NOT_HIERARCHICAL);
   1597         }
   1598         if (key == null) {
   1599           throw new NullPointerException("key");
   1600         }
   1601 
   1602         String query = getEncodedQuery();
   1603         if (query == null) {
   1604             return Collections.emptyList();
   1605         }
   1606 
   1607         String encodedKey;
   1608         try {
   1609             encodedKey = URLEncoder.encode(key, DEFAULT_ENCODING);
   1610         } catch (UnsupportedEncodingException e) {
   1611             throw new AssertionError(e);
   1612         }
   1613 
   1614         ArrayList<String> values = new ArrayList<String>();
   1615 
   1616         int start = 0;
   1617         do {
   1618             int nextAmpersand = query.indexOf('&', start);
   1619             int end = nextAmpersand != -1 ? nextAmpersand : query.length();
   1620 
   1621             int separator = query.indexOf('=', start);
   1622             if (separator > end || separator == -1) {
   1623                 separator = end;
   1624             }
   1625 
   1626             if (separator - start == encodedKey.length()
   1627                     && query.regionMatches(start, encodedKey, 0, encodedKey.length())) {
   1628                 if (separator == end) {
   1629                   values.add("");
   1630                 } else {
   1631                   values.add(decode(query.substring(separator + 1, end)));
   1632                 }
   1633             }
   1634 
   1635             // Move start to end of name.
   1636             if (nextAmpersand != -1) {
   1637                 start = nextAmpersand + 1;
   1638             } else {
   1639                 break;
   1640             }
   1641         } while (true);
   1642 
   1643         return Collections.unmodifiableList(values);
   1644     }
   1645 
   1646     /**
   1647      * Searches the query string for the first value with the given key.
   1648      *
   1649      * @param key which will be encoded
   1650      * @throws UnsupportedOperationException if this isn't a hierarchical URI
   1651      * @throws NullPointerException if key is null
   1652      * @return the decoded value or null if no parameter is found
   1653      */
   1654     public String getQueryParameter(String key) {
   1655         if (isOpaque()) {
   1656             throw new UnsupportedOperationException(NOT_HIERARCHICAL);
   1657         }
   1658         if (key == null) {
   1659             throw new NullPointerException("key");
   1660         }
   1661 
   1662         final String query = getEncodedQuery();
   1663         if (query == null) {
   1664             return null;
   1665         }
   1666 
   1667         final String encodedKey = encode(key, null);
   1668         final int length = query.length();
   1669         int start = 0;
   1670         do {
   1671             int nextAmpersand = query.indexOf('&', start);
   1672             int end = nextAmpersand != -1 ? nextAmpersand : length;
   1673 
   1674             int separator = query.indexOf('=', start);
   1675             if (separator > end || separator == -1) {
   1676                 separator = end;
   1677             }
   1678 
   1679             if (separator - start == encodedKey.length()
   1680                     && query.regionMatches(start, encodedKey, 0, encodedKey.length())) {
   1681                 if (separator == end) {
   1682                   return "";
   1683                 } else {
   1684                   return decode(query.substring(separator + 1, end));
   1685                 }
   1686             }
   1687 
   1688             // Move start to end of name.
   1689             if (nextAmpersand != -1) {
   1690                 start = nextAmpersand + 1;
   1691             } else {
   1692                 break;
   1693             }
   1694         } while (true);
   1695         return null;
   1696     }
   1697 
   1698     /**
   1699      * Searches the query string for the first value with the given key and interprets it
   1700      * as a boolean value. "false" and "0" are interpreted as <code>false</code>, everything
   1701      * else is interpreted as <code>true</code>.
   1702      *
   1703      * @param key which will be decoded
   1704      * @param defaultValue the default value to return if there is no query parameter for key
   1705      * @return the boolean interpretation of the query parameter key
   1706      */
   1707     public boolean getBooleanQueryParameter(String key, boolean defaultValue) {
   1708         String flag = getQueryParameter(key);
   1709         if (flag == null) {
   1710             return defaultValue;
   1711         }
   1712         flag = flag.toLowerCase();
   1713         return (!"false".equals(flag) && !"0".equals(flag));
   1714     }
   1715 
   1716     /** Identifies a null parcelled Uri. */
   1717     private static final int NULL_TYPE_ID = 0;
   1718 
   1719     /**
   1720      * Reads Uris from Parcels.
   1721      */
   1722     public static final Parcelable.Creator<Uri> CREATOR
   1723             = new Parcelable.Creator<Uri>() {
   1724         public Uri createFromParcel(Parcel in) {
   1725             int type = in.readInt();
   1726             switch (type) {
   1727                 case NULL_TYPE_ID: return null;
   1728                 case StringUri.TYPE_ID: return StringUri.readFrom(in);
   1729                 case OpaqueUri.TYPE_ID: return OpaqueUri.readFrom(in);
   1730                 case HierarchicalUri.TYPE_ID:
   1731                     return HierarchicalUri.readFrom(in);
   1732             }
   1733 
   1734             throw new IllegalArgumentException("Unknown URI type: " + type);
   1735         }
   1736 
   1737         public Uri[] newArray(int size) {
   1738             return new Uri[size];
   1739         }
   1740     };
   1741 
   1742     /**
   1743      * Writes a Uri to a Parcel.
   1744      *
   1745      * @param out parcel to write to
   1746      * @param uri to write, can be null
   1747      */
   1748     public static void writeToParcel(Parcel out, Uri uri) {
   1749         if (uri == null) {
   1750             out.writeInt(NULL_TYPE_ID);
   1751         } else {
   1752             uri.writeToParcel(out, 0);
   1753         }
   1754     }
   1755 
   1756     private static final char[] HEX_DIGITS = "0123456789ABCDEF".toCharArray();
   1757 
   1758     /**
   1759      * Encodes characters in the given string as '%'-escaped octets
   1760      * using the UTF-8 scheme. Leaves letters ("A-Z", "a-z"), numbers
   1761      * ("0-9"), and unreserved characters ("_-!.~'()*") intact. Encodes
   1762      * all other characters.
   1763      *
   1764      * @param s string to encode
   1765      * @return an encoded version of s suitable for use as a URI component,
   1766      *  or null if s is null
   1767      */
   1768     public static String encode(String s) {
   1769         return encode(s, null);
   1770     }
   1771 
   1772     /**
   1773      * Encodes characters in the given string as '%'-escaped octets
   1774      * using the UTF-8 scheme. Leaves letters ("A-Z", "a-z"), numbers
   1775      * ("0-9"), and unreserved characters ("_-!.~'()*") intact. Encodes
   1776      * all other characters with the exception of those specified in the
   1777      * allow argument.
   1778      *
   1779      * @param s string to encode
   1780      * @param allow set of additional characters to allow in the encoded form,
   1781      *  null if no characters should be skipped
   1782      * @return an encoded version of s suitable for use as a URI component,
   1783      *  or null if s is null
   1784      */
   1785     public static String encode(String s, String allow) {
   1786         if (s == null) {
   1787             return null;
   1788         }
   1789 
   1790         // Lazily-initialized buffers.
   1791         StringBuilder encoded = null;
   1792 
   1793         int oldLength = s.length();
   1794 
   1795         // This loop alternates between copying over allowed characters and
   1796         // encoding in chunks. This results in fewer method calls and
   1797         // allocations than encoding one character at a time.
   1798         int current = 0;
   1799         while (current < oldLength) {
   1800             // Start in "copying" mode where we copy over allowed chars.
   1801 
   1802             // Find the next character which needs to be encoded.
   1803             int nextToEncode = current;
   1804             while (nextToEncode < oldLength
   1805                     && isAllowed(s.charAt(nextToEncode), allow)) {
   1806                 nextToEncode++;
   1807             }
   1808 
   1809             // If there's nothing more to encode...
   1810             if (nextToEncode == oldLength) {
   1811                 if (current == 0) {
   1812                     // We didn't need to encode anything!
   1813                     return s;
   1814                 } else {
   1815                     // Presumably, we've already done some encoding.
   1816                     encoded.append(s, current, oldLength);
   1817                     return encoded.toString();
   1818                 }
   1819             }
   1820 
   1821             if (encoded == null) {
   1822                 encoded = new StringBuilder();
   1823             }
   1824 
   1825             if (nextToEncode > current) {
   1826                 // Append allowed characters leading up to this point.
   1827                 encoded.append(s, current, nextToEncode);
   1828             } else {
   1829                 // assert nextToEncode == current
   1830             }
   1831 
   1832             // Switch to "encoding" mode.
   1833 
   1834             // Find the next allowed character.
   1835             current = nextToEncode;
   1836             int nextAllowed = current + 1;
   1837             while (nextAllowed < oldLength
   1838                     && !isAllowed(s.charAt(nextAllowed), allow)) {
   1839                 nextAllowed++;
   1840             }
   1841 
   1842             // Convert the substring to bytes and encode the bytes as
   1843             // '%'-escaped octets.
   1844             String toEncode = s.substring(current, nextAllowed);
   1845             try {
   1846                 byte[] bytes = toEncode.getBytes(DEFAULT_ENCODING);
   1847                 int bytesLength = bytes.length;
   1848                 for (int i = 0; i < bytesLength; i++) {
   1849                     encoded.append('%');
   1850                     encoded.append(HEX_DIGITS[(bytes[i] & 0xf0) >> 4]);
   1851                     encoded.append(HEX_DIGITS[bytes[i] & 0xf]);
   1852                 }
   1853             } catch (UnsupportedEncodingException e) {
   1854                 throw new AssertionError(e);
   1855             }
   1856 
   1857             current = nextAllowed;
   1858         }
   1859 
   1860         // Encoded could still be null at this point if s is empty.
   1861         return encoded == null ? s : encoded.toString();
   1862     }
   1863 
   1864     /**
   1865      * Returns true if the given character is allowed.
   1866      *
   1867      * @param c character to check
   1868      * @param allow characters to allow
   1869      * @return true if the character is allowed or false if it should be
   1870      *  encoded
   1871      */
   1872     private static boolean isAllowed(char c, String allow) {
   1873         return (c >= 'A' && c <= 'Z')
   1874                 || (c >= 'a' && c <= 'z')
   1875                 || (c >= '0' && c <= '9')
   1876                 || "_-!.~'()*".indexOf(c) != NOT_FOUND
   1877                 || (allow != null && allow.indexOf(c) != NOT_FOUND);
   1878     }
   1879 
   1880     /** Unicode replacement character: \\uFFFD. */
   1881     private static final byte[] REPLACEMENT = { (byte) 0xFF, (byte) 0xFD };
   1882 
   1883     /**
   1884      * Decodes '%'-escaped octets in the given string using the UTF-8 scheme.
   1885      * Replaces invalid octets with the unicode replacement character
   1886      * ("\\uFFFD").
   1887      *
   1888      * @param s encoded string to decode
   1889      * @return the given string with escaped octets decoded, or null if
   1890      *  s is null
   1891      */
   1892     public static String decode(String s) {
   1893         /*
   1894         Compared to java.net.URLEncoderDecoder.decode(), this method decodes a
   1895         chunk at a time instead of one character at a time, and it doesn't
   1896         throw exceptions. It also only allocates memory when necessary--if
   1897         there's nothing to decode, this method won't do much.
   1898         */
   1899 
   1900         if (s == null) {
   1901             return null;
   1902         }
   1903 
   1904         // Lazily-initialized buffers.
   1905         StringBuilder decoded = null;
   1906         ByteArrayOutputStream out = null;
   1907 
   1908         int oldLength = s.length();
   1909 
   1910         // This loop alternates between copying over normal characters and
   1911         // escaping in chunks. This results in fewer method calls and
   1912         // allocations than decoding one character at a time.
   1913         int current = 0;
   1914         while (current < oldLength) {
   1915             // Start in "copying" mode where we copy over normal characters.
   1916 
   1917             // Find the next escape sequence.
   1918             int nextEscape = s.indexOf('%', current);
   1919 
   1920             if (nextEscape == NOT_FOUND) {
   1921                 if (decoded == null) {
   1922                     // We didn't actually decode anything.
   1923                     return s;
   1924                 } else {
   1925                     // Append the remainder and return the decoded string.
   1926                     decoded.append(s, current, oldLength);
   1927                     return decoded.toString();
   1928                 }
   1929             }
   1930 
   1931             // Prepare buffers.
   1932             if (decoded == null) {
   1933                 // Looks like we're going to need the buffers...
   1934                 // We know the new string will be shorter. Using the old length
   1935                 // may overshoot a bit, but it will save us from resizing the
   1936                 // buffer.
   1937                 decoded = new StringBuilder(oldLength);
   1938                 out = new ByteArrayOutputStream(4);
   1939             } else {
   1940                 // Clear decoding buffer.
   1941                 out.reset();
   1942             }
   1943 
   1944             // Append characters leading up to the escape.
   1945             if (nextEscape > current) {
   1946                 decoded.append(s, current, nextEscape);
   1947 
   1948                 current = nextEscape;
   1949             } else {
   1950                 // assert current == nextEscape
   1951             }
   1952 
   1953             // Switch to "decoding" mode where we decode a string of escape
   1954             // sequences.
   1955 
   1956             // Decode and append escape sequences. Escape sequences look like
   1957             // "%ab" where % is literal and a and b are hex digits.
   1958             try {
   1959                 do {
   1960                     if (current + 2 >= oldLength) {
   1961                         // Truncated escape sequence.
   1962                         out.write(REPLACEMENT);
   1963                     } else {
   1964                         int a = Character.digit(s.charAt(current + 1), 16);
   1965                         int b = Character.digit(s.charAt(current + 2), 16);
   1966 
   1967                         if (a == -1 || b == -1) {
   1968                             // Non hex digits.
   1969                             out.write(REPLACEMENT);
   1970                         } else {
   1971                             // Combine the hex digits into one byte and write.
   1972                             out.write((a << 4) + b);
   1973                         }
   1974                     }
   1975 
   1976                     // Move passed the escape sequence.
   1977                     current += 3;
   1978                 } while (current < oldLength && s.charAt(current) == '%');
   1979 
   1980                 // Decode UTF-8 bytes into a string and append it.
   1981                 decoded.append(out.toString(DEFAULT_ENCODING));
   1982             } catch (UnsupportedEncodingException e) {
   1983                 throw new AssertionError(e);
   1984             } catch (IOException e) {
   1985                 throw new AssertionError(e);
   1986             }
   1987         }
   1988 
   1989         // If we don't have a buffer, we didn't have to decode anything.
   1990         return decoded == null ? s : decoded.toString();
   1991     }
   1992 
   1993     /**
   1994      * Support for part implementations.
   1995      */
   1996     static abstract class AbstractPart {
   1997 
   1998         /**
   1999          * Enum which indicates which representation of a given part we have.
   2000          */
   2001         static class Representation {
   2002             static final int BOTH = 0;
   2003             static final int ENCODED = 1;
   2004             static final int DECODED = 2;
   2005         }
   2006 
   2007         volatile String encoded;
   2008         volatile String decoded;
   2009 
   2010         AbstractPart(String encoded, String decoded) {
   2011             this.encoded = encoded;
   2012             this.decoded = decoded;
   2013         }
   2014 
   2015         abstract String getEncoded();
   2016 
   2017         final String getDecoded() {
   2018             @SuppressWarnings("StringEquality")
   2019             boolean hasDecoded = decoded != NOT_CACHED;
   2020             return hasDecoded ? decoded : (decoded = decode(encoded));
   2021         }
   2022 
   2023         final void writeTo(Parcel parcel) {
   2024             @SuppressWarnings("StringEquality")
   2025             boolean hasEncoded = encoded != NOT_CACHED;
   2026 
   2027             @SuppressWarnings("StringEquality")
   2028             boolean hasDecoded = decoded != NOT_CACHED;
   2029 
   2030             if (hasEncoded && hasDecoded) {
   2031                 parcel.writeInt(Representation.BOTH);
   2032                 parcel.writeString(encoded);
   2033                 parcel.writeString(decoded);
   2034             } else if (hasEncoded) {
   2035                 parcel.writeInt(Representation.ENCODED);
   2036                 parcel.writeString(encoded);
   2037             } else if (hasDecoded) {
   2038                 parcel.writeInt(Representation.DECODED);
   2039                 parcel.writeString(decoded);
   2040             } else {
   2041                 throw new IllegalArgumentException("Neither encoded nor decoded");
   2042             }
   2043         }
   2044     }
   2045 
   2046     /**
   2047      * Immutable wrapper of encoded and decoded versions of a URI part. Lazily
   2048      * creates the encoded or decoded version from the other.
   2049      */
   2050     static class Part extends AbstractPart {
   2051 
   2052         /** A part with null values. */
   2053         static final Part NULL = new EmptyPart(null);
   2054 
   2055         /** A part with empty strings for values. */
   2056         static final Part EMPTY = new EmptyPart("");
   2057 
   2058         private Part(String encoded, String decoded) {
   2059             super(encoded, decoded);
   2060         }
   2061 
   2062         boolean isEmpty() {
   2063             return false;
   2064         }
   2065 
   2066         String getEncoded() {
   2067             @SuppressWarnings("StringEquality")
   2068             boolean hasEncoded = encoded != NOT_CACHED;
   2069             return hasEncoded ? encoded : (encoded = encode(decoded));
   2070         }
   2071 
   2072         static Part readFrom(Parcel parcel) {
   2073             int representation = parcel.readInt();
   2074             switch (representation) {
   2075                 case Representation.BOTH:
   2076                     return from(parcel.readString(), parcel.readString());
   2077                 case Representation.ENCODED:
   2078                     return fromEncoded(parcel.readString());
   2079                 case Representation.DECODED:
   2080                     return fromDecoded(parcel.readString());
   2081                 default:
   2082                     throw new IllegalArgumentException("Unknown representation: "
   2083                             + representation);
   2084             }
   2085         }
   2086 
   2087         /**
   2088          * Returns given part or {@link #NULL} if the given part is null.
   2089          */
   2090         static Part nonNull(Part part) {
   2091             return part == null ? NULL : part;
   2092         }
   2093 
   2094         /**
   2095          * Creates a part from the encoded string.
   2096          *
   2097          * @param encoded part string
   2098          */
   2099         static Part fromEncoded(String encoded) {
   2100             return from(encoded, NOT_CACHED);
   2101         }
   2102 
   2103         /**
   2104          * Creates a part from the decoded string.
   2105          *
   2106          * @param decoded part string
   2107          */
   2108         static Part fromDecoded(String decoded) {
   2109             return from(NOT_CACHED, decoded);
   2110         }
   2111 
   2112         /**
   2113          * Creates a part from the encoded and decoded strings.
   2114          *
   2115          * @param encoded part string
   2116          * @param decoded part string
   2117          */
   2118         static Part from(String encoded, String decoded) {
   2119             // We have to check both encoded and decoded in case one is
   2120             // NOT_CACHED.
   2121 
   2122             if (encoded == null) {
   2123                 return NULL;
   2124             }
   2125             if (encoded.length() == 0) {
   2126                 return EMPTY;
   2127             }
   2128 
   2129             if (decoded == null) {
   2130                 return NULL;
   2131             }
   2132             if (decoded .length() == 0) {
   2133                 return EMPTY;
   2134             }
   2135 
   2136             return new Part(encoded, decoded);
   2137         }
   2138 
   2139         private static class EmptyPart extends Part {
   2140             public EmptyPart(String value) {
   2141                 super(value, value);
   2142             }
   2143 
   2144             @Override
   2145             boolean isEmpty() {
   2146                 return true;
   2147             }
   2148         }
   2149     }
   2150 
   2151     /**
   2152      * Immutable wrapper of encoded and decoded versions of a path part. Lazily
   2153      * creates the encoded or decoded version from the other.
   2154      */
   2155     static class PathPart extends AbstractPart {
   2156 
   2157         /** A part with null values. */
   2158         static final PathPart NULL = new PathPart(null, null);
   2159 
   2160         /** A part with empty strings for values. */
   2161         static final PathPart EMPTY = new PathPart("", "");
   2162 
   2163         private PathPart(String encoded, String decoded) {
   2164             super(encoded, decoded);
   2165         }
   2166 
   2167         String getEncoded() {
   2168             @SuppressWarnings("StringEquality")
   2169             boolean hasEncoded = encoded != NOT_CACHED;
   2170 
   2171             // Don't encode '/'.
   2172             return hasEncoded ? encoded : (encoded = encode(decoded, "/"));
   2173         }
   2174 
   2175         /**
   2176          * Cached path segments. This doesn't need to be volatile--we don't
   2177          * care if other threads see the result.
   2178          */
   2179         private PathSegments pathSegments;
   2180 
   2181         /**
   2182          * Gets the individual path segments. Parses them if necessary.
   2183          *
   2184          * @return parsed path segments or null if this isn't a hierarchical
   2185          *  URI
   2186          */
   2187         PathSegments getPathSegments() {
   2188             if (pathSegments != null) {
   2189                 return pathSegments;
   2190             }
   2191 
   2192             String path = getEncoded();
   2193             if (path == null) {
   2194                 return pathSegments = PathSegments.EMPTY;
   2195             }
   2196 
   2197             PathSegmentsBuilder segmentBuilder = new PathSegmentsBuilder();
   2198 
   2199             int previous = 0;
   2200             int current;
   2201             while ((current = path.indexOf('/', previous)) > -1) {
   2202                 // This check keeps us from adding a segment if the path starts
   2203                 // '/' and an empty segment for "//".
   2204                 if (previous < current) {
   2205                     String decodedSegment
   2206                             = decode(path.substring(previous, current));
   2207                     segmentBuilder.add(decodedSegment);
   2208                 }
   2209                 previous = current + 1;
   2210             }
   2211 
   2212             // Add in the final path segment.
   2213             if (previous < path.length()) {
   2214                 segmentBuilder.add(decode(path.substring(previous)));
   2215             }
   2216 
   2217             return pathSegments = segmentBuilder.build();
   2218         }
   2219 
   2220         static PathPart appendEncodedSegment(PathPart oldPart,
   2221                 String newSegment) {
   2222             // If there is no old path, should we make the new path relative
   2223             // or absolute? I pick absolute.
   2224 
   2225             if (oldPart == null) {
   2226                 // No old path.
   2227                 return fromEncoded("/" + newSegment);
   2228             }
   2229 
   2230             String oldPath = oldPart.getEncoded();
   2231 
   2232             if (oldPath == null) {
   2233                 oldPath = "";
   2234             }
   2235 
   2236             int oldPathLength = oldPath.length();
   2237             String newPath;
   2238             if (oldPathLength == 0) {
   2239                 // No old path.
   2240                 newPath = "/" + newSegment;
   2241             } else if (oldPath.charAt(oldPathLength - 1) == '/') {
   2242                 newPath = oldPath + newSegment;
   2243             } else {
   2244                 newPath = oldPath + "/" + newSegment;
   2245             }
   2246 
   2247             return fromEncoded(newPath);
   2248         }
   2249 
   2250         static PathPart appendDecodedSegment(PathPart oldPart, String decoded) {
   2251             String encoded = encode(decoded);
   2252 
   2253             // TODO: Should we reuse old PathSegments? Probably not.
   2254             return appendEncodedSegment(oldPart, encoded);
   2255         }
   2256 
   2257         static PathPart readFrom(Parcel parcel) {
   2258             int representation = parcel.readInt();
   2259             switch (representation) {
   2260                 case Representation.BOTH:
   2261                     return from(parcel.readString(), parcel.readString());
   2262                 case Representation.ENCODED:
   2263                     return fromEncoded(parcel.readString());
   2264                 case Representation.DECODED:
   2265                     return fromDecoded(parcel.readString());
   2266                 default:
   2267                     throw new IllegalArgumentException("Bad representation: " + representation);
   2268             }
   2269         }
   2270 
   2271         /**
   2272          * Creates a path from the encoded string.
   2273          *
   2274          * @param encoded part string
   2275          */
   2276         static PathPart fromEncoded(String encoded) {
   2277             return from(encoded, NOT_CACHED);
   2278         }
   2279 
   2280         /**
   2281          * Creates a path from the decoded string.
   2282          *
   2283          * @param decoded part string
   2284          */
   2285         static PathPart fromDecoded(String decoded) {
   2286             return from(NOT_CACHED, decoded);
   2287         }
   2288 
   2289         /**
   2290          * Creates a path from the encoded and decoded strings.
   2291          *
   2292          * @param encoded part string
   2293          * @param decoded part string
   2294          */
   2295         static PathPart from(String encoded, String decoded) {
   2296             if (encoded == null) {
   2297                 return NULL;
   2298             }
   2299 
   2300             if (encoded.length() == 0) {
   2301                 return EMPTY;
   2302             }
   2303 
   2304             return new PathPart(encoded, decoded);
   2305         }
   2306 
   2307         /**
   2308          * Prepends path values with "/" if they're present, not empty, and
   2309          * they don't already start with "/".
   2310          */
   2311         static PathPart makeAbsolute(PathPart oldPart) {
   2312             @SuppressWarnings("StringEquality")
   2313             boolean encodedCached = oldPart.encoded != NOT_CACHED;
   2314 
   2315             // We don't care which version we use, and we don't want to force
   2316             // unneccessary encoding/decoding.
   2317             String oldPath = encodedCached ? oldPart.encoded : oldPart.decoded;
   2318 
   2319             if (oldPath == null || oldPath.length() == 0
   2320                     || oldPath.startsWith("/")) {
   2321                 return oldPart;
   2322             }
   2323 
   2324             // Prepend encoded string if present.
   2325             String newEncoded = encodedCached
   2326                     ? "/" + oldPart.encoded : NOT_CACHED;
   2327 
   2328             // Prepend decoded string if present.
   2329             @SuppressWarnings("StringEquality")
   2330             boolean decodedCached = oldPart.decoded != NOT_CACHED;
   2331             String newDecoded = decodedCached
   2332                     ? "/" + oldPart.decoded
   2333                     : NOT_CACHED;
   2334 
   2335             return new PathPart(newEncoded, newDecoded);
   2336         }
   2337     }
   2338 
   2339     /**
   2340      * Creates a new Uri by appending an already-encoded path segment to a
   2341      * base Uri.
   2342      *
   2343      * @param baseUri Uri to append path segment to
   2344      * @param pathSegment encoded path segment to append
   2345      * @return a new Uri based on baseUri with the given segment appended to
   2346      *  the path
   2347      * @throws NullPointerException if baseUri is null
   2348      */
   2349     public static Uri withAppendedPath(Uri baseUri, String pathSegment) {
   2350         Builder builder = baseUri.buildUpon();
   2351         builder = builder.appendEncodedPath(pathSegment);
   2352         return builder.build();
   2353     }
   2354 }
   2355