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;
22
23import java.io.File;
24import java.io.IOException;
25import java.io.UnsupportedEncodingException;
26import java.io.ByteArrayOutputStream;
27import java.net.URLEncoder;
28import java.util.AbstractList;
29import java.util.ArrayList;
30import java.util.Collections;
31import java.util.LinkedHashSet;
32import java.util.List;
33import java.util.RandomAccess;
34import 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 */
47public 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@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@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@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@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@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@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@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@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