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