Uri.java revision 8e8d65ff5fdef12c6af3d003dfef19aadc39bea9
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     * Constructs a new builder, copying the attributes from this Uri.
357     */
358    public abstract Builder buildUpon();
359
360    /** Index of a component which was not found. */
361    private final static int NOT_FOUND = -1;
362
363    /** Placeholder value for an index which hasn't been calculated yet. */
364    private final static int NOT_CALCULATED = -2;
365
366    /**
367     * Error message presented when a user tries to treat an opaque URI as
368     * hierarchical.
369     */
370    private static final String NOT_HIERARCHICAL
371            = "This isn't a hierarchical URI.";
372
373    /** Default encoding. */
374    private static final String DEFAULT_ENCODING = "UTF-8";
375
376    /**
377     * Creates a Uri which parses the given encoded URI string.
378     *
379     * @param uriString an RFC 2396-compliant, encoded URI
380     * @throws NullPointerException if uriString is null
381     * @return Uri for this given uri string
382     */
383    public static Uri parse(String uriString) {
384        return new StringUri(uriString);
385    }
386
387    /**
388     * Creates a Uri from a file. The URI has the form
389     * "file://<absolute path>". Encodes path characters with the exception of
390     * '/'.
391     *
392     * <p>Example: "file:///tmp/android.txt"
393     *
394     * @throws NullPointerException if file is null
395     * @return a Uri for the given file
396     */
397    public static Uri fromFile(File file) {
398        if (file == null) {
399            throw new NullPointerException("file");
400        }
401
402        PathPart path = PathPart.fromDecoded(file.getAbsolutePath());
403        return new HierarchicalUri(
404                "file", Part.EMPTY, path, Part.NULL, Part.NULL);
405    }
406
407    /**
408     * An implementation which wraps a String URI. This URI can be opaque or
409     * hierarchical, but we extend AbstractHierarchicalUri in case we need
410     * the hierarchical functionality.
411     */
412    private static class StringUri extends AbstractHierarchicalUri {
413
414        /** Used in parcelling. */
415        static final int TYPE_ID = 1;
416
417        /** URI string representation. */
418        private final String uriString;
419
420        private StringUri(String uriString) {
421            if (uriString == null) {
422                throw new NullPointerException("uriString");
423            }
424
425            this.uriString = uriString;
426        }
427
428        static Uri readFrom(Parcel parcel) {
429            return new StringUri(parcel.readString());
430        }
431
432        public int describeContents() {
433            return 0;
434        }
435
436        public void writeToParcel(Parcel parcel, int flags) {
437            parcel.writeInt(TYPE_ID);
438            parcel.writeString(uriString);
439        }
440
441        /** Cached scheme separator index. */
442        private volatile int cachedSsi = NOT_CALCULATED;
443
444        /** Finds the first ':'. Returns -1 if none found. */
445        private int findSchemeSeparator() {
446            return cachedSsi == NOT_CALCULATED
447                    ? cachedSsi = uriString.indexOf(':')
448                    : cachedSsi;
449        }
450
451        /** Cached fragment separator index. */
452        private volatile int cachedFsi = NOT_CALCULATED;
453
454        /** Finds the first '#'. Returns -1 if none found. */
455        private int findFragmentSeparator() {
456            return cachedFsi == NOT_CALCULATED
457                    ? cachedFsi = uriString.indexOf('#', findSchemeSeparator())
458                    : cachedFsi;
459        }
460
461        public boolean isHierarchical() {
462            int ssi = findSchemeSeparator();
463
464            if (ssi == NOT_FOUND) {
465                // All relative URIs are hierarchical.
466                return true;
467            }
468
469            if (uriString.length() == ssi + 1) {
470                // No ssp.
471                return false;
472            }
473
474            // If the ssp starts with a '/', this is hierarchical.
475            return uriString.charAt(ssi + 1) == '/';
476        }
477
478        public boolean isRelative() {
479            // Note: We return true if the index is 0
480            return findSchemeSeparator() == NOT_FOUND;
481        }
482
483        private volatile String scheme = NOT_CACHED;
484
485        public String getScheme() {
486            @SuppressWarnings("StringEquality")
487            boolean cached = (scheme != NOT_CACHED);
488            return cached ? scheme : (scheme = parseScheme());
489        }
490
491        private String parseScheme() {
492            int ssi = findSchemeSeparator();
493            return ssi == NOT_FOUND ? null : uriString.substring(0, ssi);
494        }
495
496        private Part ssp;
497
498        private Part getSsp() {
499            return ssp == null ? ssp = Part.fromEncoded(parseSsp()) : ssp;
500        }
501
502        public String getEncodedSchemeSpecificPart() {
503            return getSsp().getEncoded();
504        }
505
506        public String getSchemeSpecificPart() {
507            return getSsp().getDecoded();
508        }
509
510        private String parseSsp() {
511            int ssi = findSchemeSeparator();
512            int fsi = findFragmentSeparator();
513
514            // Return everything between ssi and fsi.
515            return fsi == NOT_FOUND
516                    ? uriString.substring(ssi + 1)
517                    : uriString.substring(ssi + 1, fsi);
518        }
519
520        private Part authority;
521
522        private Part getAuthorityPart() {
523            if (authority == null) {
524                String encodedAuthority
525                        = parseAuthority(this.uriString, findSchemeSeparator());
526                return authority = Part.fromEncoded(encodedAuthority);
527            }
528
529            return authority;
530        }
531
532        public String getEncodedAuthority() {
533            return getAuthorityPart().getEncoded();
534        }
535
536        public String getAuthority() {
537            return getAuthorityPart().getDecoded();
538        }
539
540        private PathPart path;
541
542        private PathPart getPathPart() {
543            return path == null
544                    ? path = PathPart.fromEncoded(parsePath())
545                    : path;
546        }
547
548        public String getPath() {
549            return getPathPart().getDecoded();
550        }
551
552        public String getEncodedPath() {
553            return getPathPart().getEncoded();
554        }
555
556        public List<String> getPathSegments() {
557            return getPathPart().getPathSegments();
558        }
559
560        private String parsePath() {
561            String uriString = this.uriString;
562            int ssi = findSchemeSeparator();
563
564            // If the URI is absolute.
565            if (ssi > -1) {
566                // Is there anything after the ':'?
567                boolean schemeOnly = ssi + 1 == uriString.length();
568                if (schemeOnly) {
569                    // Opaque URI.
570                    return null;
571                }
572
573                // A '/' after the ':' means this is hierarchical.
574                if (uriString.charAt(ssi + 1) != '/') {
575                    // Opaque URI.
576                    return null;
577                }
578            } else {
579                // All relative URIs are hierarchical.
580            }
581
582            return parsePath(uriString, ssi);
583        }
584
585        private Part query;
586
587        private Part getQueryPart() {
588            return query == null
589                    ? query = Part.fromEncoded(parseQuery()) : query;
590        }
591
592        public String getEncodedQuery() {
593            return getQueryPart().getEncoded();
594        }
595
596        private String parseQuery() {
597            // It doesn't make sense to cache this index. We only ever
598            // calculate it once.
599            int qsi = uriString.indexOf('?', findSchemeSeparator());
600            if (qsi == NOT_FOUND) {
601                return null;
602            }
603
604            int fsi = findFragmentSeparator();
605
606            if (fsi == NOT_FOUND) {
607                return uriString.substring(qsi + 1);
608            }
609
610            if (fsi < qsi) {
611                // Invalid.
612                return null;
613            }
614
615            return uriString.substring(qsi + 1, fsi);
616        }
617
618        public String getQuery() {
619            return getQueryPart().getDecoded();
620        }
621
622        private Part fragment;
623
624        private Part getFragmentPart() {
625            return fragment == null
626                    ? fragment = Part.fromEncoded(parseFragment()) : fragment;
627        }
628
629        public String getEncodedFragment() {
630            return getFragmentPart().getEncoded();
631        }
632
633        private String parseFragment() {
634            int fsi = findFragmentSeparator();
635            return fsi == NOT_FOUND ? null : uriString.substring(fsi + 1);
636        }
637
638        public String getFragment() {
639            return getFragmentPart().getDecoded();
640        }
641
642        public String toString() {
643            return uriString;
644        }
645
646        /**
647         * Parses an authority out of the given URI string.
648         *
649         * @param uriString URI string
650         * @param ssi scheme separator index, -1 for a relative URI
651         *
652         * @return the authority or null if none is found
653         */
654        static String parseAuthority(String uriString, int ssi) {
655            int length = uriString.length();
656
657            // If "//" follows the scheme separator, we have an authority.
658            if (length > ssi + 2
659                    && uriString.charAt(ssi + 1) == '/'
660                    && uriString.charAt(ssi + 2) == '/') {
661                // We have an authority.
662
663                // Look for the start of the path, query, or fragment, or the
664                // end of the string.
665                int end = ssi + 3;
666                LOOP: while (end < length) {
667                    switch (uriString.charAt(end)) {
668                        case '/': // Start of path
669                        case '?': // Start of query
670                        case '#': // Start of fragment
671                            break LOOP;
672                    }
673                    end++;
674                }
675
676                return uriString.substring(ssi + 3, end);
677            } else {
678                return null;
679            }
680
681        }
682
683        /**
684         * Parses a path out of this given URI string.
685         *
686         * @param uriString URI string
687         * @param ssi scheme separator index, -1 for a relative URI
688         *
689         * @return the path
690         */
691        static String parsePath(String uriString, int ssi) {
692            int length = uriString.length();
693
694            // Find start of path.
695            int pathStart;
696            if (length > ssi + 2
697                    && uriString.charAt(ssi + 1) == '/'
698                    && uriString.charAt(ssi + 2) == '/') {
699                // Skip over authority to path.
700                pathStart = ssi + 3;
701                LOOP: while (pathStart < length) {
702                    switch (uriString.charAt(pathStart)) {
703                        case '?': // Start of query
704                        case '#': // Start of fragment
705                            return ""; // Empty path.
706                        case '/': // Start of path!
707                            break LOOP;
708                    }
709                    pathStart++;
710                }
711            } else {
712                // Path starts immediately after scheme separator.
713                pathStart = ssi + 1;
714            }
715
716            // Find end of path.
717            int pathEnd = pathStart;
718            LOOP: while (pathEnd < length) {
719                switch (uriString.charAt(pathEnd)) {
720                    case '?': // Start of query
721                    case '#': // Start of fragment
722                        break LOOP;
723                }
724                pathEnd++;
725            }
726
727            return uriString.substring(pathStart, pathEnd);
728        }
729
730        public Builder buildUpon() {
731            if (isHierarchical()) {
732                return new Builder()
733                        .scheme(getScheme())
734                        .authority(getAuthorityPart())
735                        .path(getPathPart())
736                        .query(getQueryPart())
737                        .fragment(getFragmentPart());
738            } else {
739                return new Builder()
740                        .scheme(getScheme())
741                        .opaquePart(getSsp())
742                        .fragment(getFragmentPart());
743            }
744        }
745    }
746
747    /**
748     * Creates an opaque Uri from the given components. Encodes the ssp
749     * which means this method cannot be used to create hierarchical URIs.
750     *
751     * @param scheme of the URI
752     * @param ssp scheme-specific-part, everything between the
753     *  scheme separator (':') and the fragment separator ('#'), which will
754     *  get encoded
755     * @param fragment fragment, everything after the '#', null if undefined,
756     *  will get encoded
757     *
758     * @throws NullPointerException if scheme or ssp is null
759     * @return Uri composed of the given scheme, ssp, and fragment
760     *
761     * @see Builder if you don't want the ssp and fragment to be encoded
762     */
763    public static Uri fromParts(String scheme, String ssp,
764            String fragment) {
765        if (scheme == null) {
766            throw new NullPointerException("scheme");
767        }
768        if (ssp == null) {
769            throw new NullPointerException("ssp");
770        }
771
772        return new OpaqueUri(scheme, Part.fromDecoded(ssp),
773                Part.fromDecoded(fragment));
774    }
775
776    /**
777     * Opaque URI.
778     */
779    private static class OpaqueUri extends Uri {
780
781        /** Used in parcelling. */
782        static final int TYPE_ID = 2;
783
784        private final String scheme;
785        private final Part ssp;
786        private final Part fragment;
787
788        private OpaqueUri(String scheme, Part ssp, Part fragment) {
789            this.scheme = scheme;
790            this.ssp = ssp;
791            this.fragment = fragment == null ? Part.NULL : fragment;
792        }
793
794        static Uri readFrom(Parcel parcel) {
795            return new OpaqueUri(
796                parcel.readString(),
797                Part.readFrom(parcel),
798                Part.readFrom(parcel)
799            );
800        }
801
802        public int describeContents() {
803            return 0;
804        }
805
806        public void writeToParcel(Parcel parcel, int flags) {
807            parcel.writeInt(TYPE_ID);
808            parcel.writeString(scheme);
809            ssp.writeTo(parcel);
810            fragment.writeTo(parcel);
811        }
812
813        public boolean isHierarchical() {
814            return false;
815        }
816
817        public boolean isRelative() {
818            return scheme == null;
819        }
820
821        public String getScheme() {
822            return this.scheme;
823        }
824
825        public String getEncodedSchemeSpecificPart() {
826            return ssp.getEncoded();
827        }
828
829        public String getSchemeSpecificPart() {
830            return ssp.getDecoded();
831        }
832
833        public String getAuthority() {
834            return null;
835        }
836
837        public String getEncodedAuthority() {
838            return null;
839        }
840
841        public String getPath() {
842            return null;
843        }
844
845        public String getEncodedPath() {
846            return null;
847        }
848
849        public String getQuery() {
850            return null;
851        }
852
853        public String getEncodedQuery() {
854            return null;
855        }
856
857        public String getFragment() {
858            return fragment.getDecoded();
859        }
860
861        public String getEncodedFragment() {
862            return fragment.getEncoded();
863        }
864
865        public List<String> getPathSegments() {
866            return Collections.emptyList();
867        }
868
869        public String getLastPathSegment() {
870            return null;
871        }
872
873        public String getUserInfo() {
874            return null;
875        }
876
877        public String getEncodedUserInfo() {
878            return null;
879        }
880
881        public String getHost() {
882            return null;
883        }
884
885        public int getPort() {
886            return -1;
887        }
888
889        private volatile String cachedString = NOT_CACHED;
890
891        public String toString() {
892            @SuppressWarnings("StringEquality")
893            boolean cached = cachedString != NOT_CACHED;
894            if (cached) {
895                return cachedString;
896            }
897
898            StringBuilder sb = new StringBuilder();
899
900            sb.append(scheme).append(':');
901            sb.append(getEncodedSchemeSpecificPart());
902
903            if (!fragment.isEmpty()) {
904                sb.append('#').append(fragment.getEncoded());
905            }
906
907            return cachedString = sb.toString();
908        }
909
910        public Builder buildUpon() {
911            return new Builder()
912                    .scheme(this.scheme)
913                    .opaquePart(this.ssp)
914                    .fragment(this.fragment);
915        }
916    }
917
918    /**
919     * Wrapper for path segment array.
920     */
921    static class PathSegments extends AbstractList<String>
922            implements RandomAccess {
923
924        static final PathSegments EMPTY = new PathSegments(null, 0);
925
926        final String[] segments;
927        final int size;
928
929        PathSegments(String[] segments, int size) {
930            this.segments = segments;
931            this.size = size;
932        }
933
934        public String get(int index) {
935            if (index >= size) {
936                throw new IndexOutOfBoundsException();
937            }
938
939            return segments[index];
940        }
941
942        public int size() {
943            return this.size;
944        }
945    }
946
947    /**
948     * Builds PathSegments.
949     */
950    static class PathSegmentsBuilder {
951
952        String[] segments;
953        int size = 0;
954
955        void add(String segment) {
956            if (segments == null) {
957                segments = new String[4];
958            } else if (size + 1 == segments.length) {
959                String[] expanded = new String[segments.length * 2];
960                System.arraycopy(segments, 0, expanded, 0, segments.length);
961                segments = expanded;
962            }
963
964            segments[size++] = segment;
965        }
966
967        PathSegments build() {
968            if (segments == null) {
969                return PathSegments.EMPTY;
970            }
971
972            try {
973                return new PathSegments(segments, size);
974            } finally {
975                // Makes sure this doesn't get reused.
976                segments = null;
977            }
978        }
979    }
980
981    /**
982     * Support for hierarchical URIs.
983     */
984    private abstract static class AbstractHierarchicalUri extends Uri {
985
986        public String getLastPathSegment() {
987            // TODO: If we haven't parsed all of the segments already, just
988            // grab the last one directly so we only allocate one string.
989
990            List<String> segments = getPathSegments();
991            int size = segments.size();
992            if (size == 0) {
993                return null;
994            }
995            return segments.get(size - 1);
996        }
997
998        private Part userInfo;
999
1000        private Part getUserInfoPart() {
1001            return userInfo == null
1002                    ? userInfo = Part.fromEncoded(parseUserInfo()) : userInfo;
1003        }
1004
1005        public final String getEncodedUserInfo() {
1006            return getUserInfoPart().getEncoded();
1007        }
1008
1009        private String parseUserInfo() {
1010            String authority = getEncodedAuthority();
1011            if (authority == null) {
1012                return null;
1013            }
1014
1015            int end = authority.indexOf('@');
1016            return end == NOT_FOUND ? null : authority.substring(0, end);
1017        }
1018
1019        public String getUserInfo() {
1020            return getUserInfoPart().getDecoded();
1021        }
1022
1023        private volatile String host = NOT_CACHED;
1024
1025        public String getHost() {
1026            @SuppressWarnings("StringEquality")
1027            boolean cached = (host != NOT_CACHED);
1028            return cached ? host
1029                    : (host = parseHost());
1030        }
1031
1032        private String parseHost() {
1033            String authority = getEncodedAuthority();
1034            if (authority == null) {
1035                return null;
1036            }
1037
1038            // Parse out user info and then port.
1039            int userInfoSeparator = authority.indexOf('@');
1040            int portSeparator = authority.indexOf(':', userInfoSeparator);
1041
1042            String encodedHost = portSeparator == NOT_FOUND
1043                    ? authority.substring(userInfoSeparator + 1)
1044                    : authority.substring(userInfoSeparator + 1, portSeparator);
1045
1046            return decode(encodedHost);
1047        }
1048
1049        private volatile int port = NOT_CALCULATED;
1050
1051        public int getPort() {
1052            return port == NOT_CALCULATED
1053                    ? port = parsePort()
1054                    : port;
1055        }
1056
1057        private int parsePort() {
1058            String authority = getEncodedAuthority();
1059            if (authority == null) {
1060                return -1;
1061            }
1062
1063            // Make sure we look for the port separtor *after* the user info
1064            // separator. We have URLs with a ':' in the user info.
1065            int userInfoSeparator = authority.indexOf('@');
1066            int portSeparator = authority.indexOf(':', userInfoSeparator);
1067
1068            if (portSeparator == NOT_FOUND) {
1069                return -1;
1070            }
1071
1072            String portString = decode(authority.substring(portSeparator + 1));
1073            try {
1074                return Integer.parseInt(portString);
1075            } catch (NumberFormatException e) {
1076                Log.w(LOG, "Error parsing port string.", e);
1077                return -1;
1078            }
1079        }
1080    }
1081
1082    /**
1083     * Hierarchical Uri.
1084     */
1085    private static class HierarchicalUri extends AbstractHierarchicalUri {
1086
1087        /** Used in parcelling. */
1088        static final int TYPE_ID = 3;
1089
1090        private final String scheme; // can be null
1091        private final Part authority;
1092        private final PathPart path;
1093        private final Part query;
1094        private final Part fragment;
1095
1096        private HierarchicalUri(String scheme, Part authority, PathPart path,
1097                Part query, Part fragment) {
1098            this.scheme = scheme;
1099            this.authority = Part.nonNull(authority);
1100            this.path = path == null ? PathPart.NULL : path;
1101            this.query = Part.nonNull(query);
1102            this.fragment = Part.nonNull(fragment);
1103        }
1104
1105        static Uri readFrom(Parcel parcel) {
1106            return new HierarchicalUri(
1107                parcel.readString(),
1108                Part.readFrom(parcel),
1109                PathPart.readFrom(parcel),
1110                Part.readFrom(parcel),
1111                Part.readFrom(parcel)
1112            );
1113        }
1114
1115        public int describeContents() {
1116            return 0;
1117        }
1118
1119        public void writeToParcel(Parcel parcel, int flags) {
1120            parcel.writeInt(TYPE_ID);
1121            parcel.writeString(scheme);
1122            authority.writeTo(parcel);
1123            path.writeTo(parcel);
1124            query.writeTo(parcel);
1125            fragment.writeTo(parcel);
1126        }
1127
1128        public boolean isHierarchical() {
1129            return true;
1130        }
1131
1132        public boolean isRelative() {
1133            return scheme == null;
1134        }
1135
1136        public String getScheme() {
1137            return scheme;
1138        }
1139
1140        private Part ssp;
1141
1142        private Part getSsp() {
1143            return ssp == null
1144                    ? ssp = Part.fromEncoded(makeSchemeSpecificPart()) : ssp;
1145        }
1146
1147        public String getEncodedSchemeSpecificPart() {
1148            return getSsp().getEncoded();
1149        }
1150
1151        public String getSchemeSpecificPart() {
1152            return getSsp().getDecoded();
1153        }
1154
1155        /**
1156         * Creates the encoded scheme-specific part from its sub parts.
1157         */
1158        private String makeSchemeSpecificPart() {
1159            StringBuilder builder = new StringBuilder();
1160            appendSspTo(builder);
1161            return builder.toString();
1162        }
1163
1164        private void appendSspTo(StringBuilder builder) {
1165            String encodedAuthority = authority.getEncoded();
1166            if (encodedAuthority != null) {
1167                // Even if the authority is "", we still want to append "//".
1168                builder.append("//").append(encodedAuthority);
1169            }
1170
1171            String encodedPath = path.getEncoded();
1172            if (encodedPath != null) {
1173                builder.append(encodedPath);
1174            }
1175
1176            if (!query.isEmpty()) {
1177                builder.append('?').append(query.getEncoded());
1178            }
1179        }
1180
1181        public String getAuthority() {
1182            return this.authority.getDecoded();
1183        }
1184
1185        public String getEncodedAuthority() {
1186            return this.authority.getEncoded();
1187        }
1188
1189        public String getEncodedPath() {
1190            return this.path.getEncoded();
1191        }
1192
1193        public String getPath() {
1194            return this.path.getDecoded();
1195        }
1196
1197        public String getQuery() {
1198            return this.query.getDecoded();
1199        }
1200
1201        public String getEncodedQuery() {
1202            return this.query.getEncoded();
1203        }
1204
1205        public String getFragment() {
1206            return this.fragment.getDecoded();
1207        }
1208
1209        public String getEncodedFragment() {
1210            return this.fragment.getEncoded();
1211        }
1212
1213        public List<String> getPathSegments() {
1214            return this.path.getPathSegments();
1215        }
1216
1217        private volatile String uriString = NOT_CACHED;
1218
1219        @Override
1220        public String toString() {
1221            @SuppressWarnings("StringEquality")
1222            boolean cached = (uriString != NOT_CACHED);
1223            return cached ? uriString
1224                    : (uriString = makeUriString());
1225        }
1226
1227        private String makeUriString() {
1228            StringBuilder builder = new StringBuilder();
1229
1230            if (scheme != null) {
1231                builder.append(scheme).append(':');
1232            }
1233
1234            appendSspTo(builder);
1235
1236            if (!fragment.isEmpty()) {
1237                builder.append('#').append(fragment.getEncoded());
1238            }
1239
1240            return builder.toString();
1241        }
1242
1243        public Builder buildUpon() {
1244            return new Builder()
1245                    .scheme(scheme)
1246                    .authority(authority)
1247                    .path(path)
1248                    .query(query)
1249                    .fragment(fragment);
1250        }
1251    }
1252
1253    /**
1254     * Helper class for building or manipulating URI references. Not safe for
1255     * concurrent use.
1256     *
1257     * <p>An absolute hierarchical URI reference follows the pattern:
1258     * {@code <scheme>://<authority><absolute path>?<query>#<fragment>}
1259     *
1260     * <p>Relative URI references (which are always hierarchical) follow one
1261     * of two patterns: {@code <relative or absolute path>?<query>#<fragment>}
1262     * or {@code //<authority><absolute path>?<query>#<fragment>}
1263     *
1264     * <p>An opaque URI follows this pattern:
1265     * {@code <scheme>:<opaque part>#<fragment>}
1266     *
1267     * <p>Use {@link Uri#buildUpon()} to obtain a builder representing an existing URI.
1268     */
1269    public static final class Builder {
1270
1271        private String scheme;
1272        private Part opaquePart;
1273        private Part authority;
1274        private PathPart path;
1275        private Part query;
1276        private Part fragment;
1277
1278        /**
1279         * Constructs a new Builder.
1280         */
1281        public Builder() {}
1282
1283        /**
1284         * Sets the scheme.
1285         *
1286         * @param scheme name or {@code null} if this is a relative Uri
1287         */
1288        public Builder scheme(String scheme) {
1289            this.scheme = scheme;
1290            return this;
1291        }
1292
1293        Builder opaquePart(Part opaquePart) {
1294            this.opaquePart = opaquePart;
1295            return this;
1296        }
1297
1298        /**
1299         * Encodes and sets the given opaque scheme-specific-part.
1300         *
1301         * @param opaquePart decoded opaque part
1302         */
1303        public Builder opaquePart(String opaquePart) {
1304            return opaquePart(Part.fromDecoded(opaquePart));
1305        }
1306
1307        /**
1308         * Sets the previously encoded opaque scheme-specific-part.
1309         *
1310         * @param opaquePart encoded opaque part
1311         */
1312        public Builder encodedOpaquePart(String opaquePart) {
1313            return opaquePart(Part.fromEncoded(opaquePart));
1314        }
1315
1316        Builder authority(Part authority) {
1317            // This URI will be hierarchical.
1318            this.opaquePart = null;
1319
1320            this.authority = authority;
1321            return this;
1322        }
1323
1324        /**
1325         * Encodes and sets the authority.
1326         */
1327        public Builder authority(String authority) {
1328            return authority(Part.fromDecoded(authority));
1329        }
1330
1331        /**
1332         * Sets the previously encoded authority.
1333         */
1334        public Builder encodedAuthority(String authority) {
1335            return authority(Part.fromEncoded(authority));
1336        }
1337
1338        Builder path(PathPart path) {
1339            // This URI will be hierarchical.
1340            this.opaquePart = null;
1341
1342            this.path = path;
1343            return this;
1344        }
1345
1346        /**
1347         * Sets the path. Leaves '/' characters intact but encodes others as
1348         * necessary.
1349         *
1350         * <p>If the path is not null and doesn't start with a '/', and if
1351         * you specify a scheme and/or authority, the builder will prepend the
1352         * given path with a '/'.
1353         */
1354        public Builder path(String path) {
1355            return path(PathPart.fromDecoded(path));
1356        }
1357
1358        /**
1359         * Sets the previously encoded path.
1360         *
1361         * <p>If the path is not null and doesn't start with a '/', and if
1362         * you specify a scheme and/or authority, the builder will prepend the
1363         * given path with a '/'.
1364         */
1365        public Builder encodedPath(String path) {
1366            return path(PathPart.fromEncoded(path));
1367        }
1368
1369        /**
1370         * Encodes the given segment and appends it to the path.
1371         */
1372        public Builder appendPath(String newSegment) {
1373            return path(PathPart.appendDecodedSegment(path, newSegment));
1374        }
1375
1376        /**
1377         * Appends the given segment to the path.
1378         */
1379        public Builder appendEncodedPath(String newSegment) {
1380            return path(PathPart.appendEncodedSegment(path, newSegment));
1381        }
1382
1383        Builder query(Part query) {
1384            // This URI will be hierarchical.
1385            this.opaquePart = null;
1386
1387            this.query = query;
1388            return this;
1389        }
1390
1391        /**
1392         * Encodes and sets the query.
1393         */
1394        public Builder query(String query) {
1395            return query(Part.fromDecoded(query));
1396        }
1397
1398        /**
1399         * Sets the previously encoded query.
1400         */
1401        public Builder encodedQuery(String query) {
1402            return query(Part.fromEncoded(query));
1403        }
1404
1405        Builder fragment(Part fragment) {
1406            this.fragment = fragment;
1407            return this;
1408        }
1409
1410        /**
1411         * Encodes and sets the fragment.
1412         */
1413        public Builder fragment(String fragment) {
1414            return fragment(Part.fromDecoded(fragment));
1415        }
1416
1417        /**
1418         * Sets the previously encoded fragment.
1419         */
1420        public Builder encodedFragment(String fragment) {
1421            return fragment(Part.fromEncoded(fragment));
1422        }
1423
1424        /**
1425         * Encodes the key and value and then appends the parameter to the
1426         * query string.
1427         *
1428         * @param key which will be encoded
1429         * @param value which will be encoded
1430         */
1431        public Builder appendQueryParameter(String key, String value) {
1432            // This URI will be hierarchical.
1433            this.opaquePart = null;
1434
1435            String encodedParameter = encode(key, null) + "="
1436                    + encode(value, null);
1437
1438            if (query == null) {
1439                query = Part.fromEncoded(encodedParameter);
1440                return this;
1441            }
1442
1443            String oldQuery = query.getEncoded();
1444            if (oldQuery == null || oldQuery.length() == 0) {
1445                query = Part.fromEncoded(encodedParameter);
1446            } else {
1447                query = Part.fromEncoded(oldQuery + "&" + encodedParameter);
1448            }
1449
1450            return this;
1451        }
1452
1453        /**
1454         * Clears the the previously set query.
1455         */
1456        public Builder clearQuery() {
1457          return query((Part) null);
1458        }
1459
1460        /**
1461         * Constructs a Uri with the current attributes.
1462         *
1463         * @throws UnsupportedOperationException if the URI is opaque and the
1464         *  scheme is null
1465         */
1466        public Uri build() {
1467            if (opaquePart != null) {
1468                if (this.scheme == null) {
1469                    throw new UnsupportedOperationException(
1470                            "An opaque URI must have a scheme.");
1471                }
1472
1473                return new OpaqueUri(scheme, opaquePart, fragment);
1474            } else {
1475                // Hierarchical URIs should not return null for getPath().
1476                PathPart path = this.path;
1477                if (path == null || path == PathPart.NULL) {
1478                    path = PathPart.EMPTY;
1479                } else {
1480                    // If we have a scheme and/or authority, the path must
1481                    // be absolute. Prepend it with a '/' if necessary.
1482                    if (hasSchemeOrAuthority()) {
1483                        path = PathPart.makeAbsolute(path);
1484                    }
1485                }
1486
1487                return new HierarchicalUri(
1488                        scheme, authority, path, query, fragment);
1489            }
1490        }
1491
1492        private boolean hasSchemeOrAuthority() {
1493            return scheme != null
1494                    || (authority != null && authority != Part.NULL);
1495
1496        }
1497
1498        @Override
1499        public String toString() {
1500            return build().toString();
1501        }
1502    }
1503
1504    /**
1505     * Returns a set of the unique names of all query parameters. Iterating
1506     * over the set will return the names in order of their first occurrence.
1507     *
1508     * @throws UnsupportedOperationException if this isn't a hierarchical URI
1509     *
1510     * @return a set of decoded names
1511     */
1512    public Set<String> getQueryParameterNames() {
1513        if (isOpaque()) {
1514            throw new UnsupportedOperationException(NOT_HIERARCHICAL);
1515        }
1516
1517        String query = getEncodedQuery();
1518        if (query == null) {
1519            return Collections.emptySet();
1520        }
1521
1522        Set<String> names = new LinkedHashSet<String>();
1523        int start = 0;
1524        do {
1525            int next = query.indexOf('&', start);
1526            int end = (next == -1) ? query.length() : next;
1527
1528            int separator = query.indexOf('=', start);
1529            if (separator > end || separator == -1) {
1530                separator = end;
1531            }
1532
1533            String name = query.substring(start, separator);
1534            names.add(decode(name));
1535
1536            // Move start to end of name.
1537            start = end + 1;
1538        } while (start < query.length());
1539
1540        return Collections.unmodifiableSet(names);
1541    }
1542
1543    /**
1544     * Searches the query string for parameter values with the given key.
1545     *
1546     * @param key which will be encoded
1547     *
1548     * @throws UnsupportedOperationException if this isn't a hierarchical URI
1549     * @throws NullPointerException if key is null
1550     * @return a list of decoded values
1551     */
1552    public List<String> getQueryParameters(String key) {
1553        if (isOpaque()) {
1554            throw new UnsupportedOperationException(NOT_HIERARCHICAL);
1555        }
1556        if (key == null) {
1557          throw new NullPointerException("key");
1558        }
1559
1560        String query = getEncodedQuery();
1561        if (query == null) {
1562            return Collections.emptyList();
1563        }
1564
1565        String encodedKey;
1566        try {
1567            encodedKey = URLEncoder.encode(key, DEFAULT_ENCODING);
1568        } catch (UnsupportedEncodingException e) {
1569            throw new AssertionError(e);
1570        }
1571
1572        ArrayList<String> values = new ArrayList<String>();
1573
1574        int start = 0;
1575        do {
1576            int nextAmpersand = query.indexOf('&', start);
1577            int end = nextAmpersand != -1 ? nextAmpersand : query.length();
1578
1579            int separator = query.indexOf('=', start);
1580            if (separator > end || separator == -1) {
1581                separator = end;
1582            }
1583
1584            if (separator - start == encodedKey.length()
1585                    && query.regionMatches(start, encodedKey, 0, encodedKey.length())) {
1586                if (separator == end) {
1587                  values.add("");
1588                } else {
1589                  values.add(decode(query.substring(separator + 1, end)));
1590                }
1591            }
1592
1593            // Move start to end of name.
1594            if (nextAmpersand != -1) {
1595                start = nextAmpersand + 1;
1596            } else {
1597                break;
1598            }
1599        } while (true);
1600
1601        return Collections.unmodifiableList(values);
1602    }
1603
1604    /**
1605     * Searches the query string for the first value with the given key.
1606     *
1607     * @param key which will be encoded
1608     * @throws UnsupportedOperationException if this isn't a hierarchical URI
1609     * @throws NullPointerException if key is null
1610     * @return the decoded value or null if no parameter is found
1611     */
1612    public String getQueryParameter(String key) {
1613        if (isOpaque()) {
1614            throw new UnsupportedOperationException(NOT_HIERARCHICAL);
1615        }
1616        if (key == null) {
1617            throw new NullPointerException("key");
1618        }
1619
1620        final String query = getEncodedQuery();
1621        if (query == null) {
1622            return null;
1623        }
1624
1625        final String encodedKey = encode(key, null);
1626        final int length = query.length();
1627        int start = 0;
1628        do {
1629            int nextAmpersand = query.indexOf('&', start);
1630            int end = nextAmpersand != -1 ? nextAmpersand : length;
1631
1632            int separator = query.indexOf('=', start);
1633            if (separator > end || separator == -1) {
1634                separator = end;
1635            }
1636
1637            if (separator - start == encodedKey.length()
1638                    && query.regionMatches(start, encodedKey, 0, encodedKey.length())) {
1639                if (separator == end) {
1640                  return "";
1641                } else {
1642                  return decode(query.substring(separator + 1, end));
1643                }
1644            }
1645
1646            // Move start to end of name.
1647            if (nextAmpersand != -1) {
1648                start = nextAmpersand + 1;
1649            } else {
1650                break;
1651            }
1652        } while (true);
1653        return null;
1654    }
1655
1656    /**
1657     * Searches the query string for the first value with the given key and interprets it
1658     * as a boolean value. "false" and "0" are interpreted as <code>false</code>, everything
1659     * else is interpreted as <code>true</code>.
1660     *
1661     * @param key which will be decoded
1662     * @param defaultValue the default value to return if there is no query parameter for key
1663     * @return the boolean interpretation of the query parameter key
1664     */
1665    public boolean getBooleanQueryParameter(String key, boolean defaultValue) {
1666        String flag = getQueryParameter(key);
1667        if (flag == null) {
1668            return defaultValue;
1669        }
1670        flag = flag.toLowerCase();
1671        return (!"false".equals(flag) && !"0".equals(flag));
1672    }
1673
1674    /** Identifies a null parcelled Uri. */
1675    private static final int NULL_TYPE_ID = 0;
1676
1677    /**
1678     * Reads Uris from Parcels.
1679     */
1680    public static final Parcelable.Creator<Uri> CREATOR
1681            = new Parcelable.Creator<Uri>() {
1682        public Uri createFromParcel(Parcel in) {
1683            int type = in.readInt();
1684            switch (type) {
1685                case NULL_TYPE_ID: return null;
1686                case StringUri.TYPE_ID: return StringUri.readFrom(in);
1687                case OpaqueUri.TYPE_ID: return OpaqueUri.readFrom(in);
1688                case HierarchicalUri.TYPE_ID:
1689                    return HierarchicalUri.readFrom(in);
1690            }
1691
1692            throw new IllegalArgumentException("Unknown URI type: " + type);
1693        }
1694
1695        public Uri[] newArray(int size) {
1696            return new Uri[size];
1697        }
1698    };
1699
1700    /**
1701     * Writes a Uri to a Parcel.
1702     *
1703     * @param out parcel to write to
1704     * @param uri to write, can be null
1705     */
1706    public static void writeToParcel(Parcel out, Uri uri) {
1707        if (uri == null) {
1708            out.writeInt(NULL_TYPE_ID);
1709        } else {
1710            uri.writeToParcel(out, 0);
1711        }
1712    }
1713
1714    private static final char[] HEX_DIGITS = "0123456789ABCDEF".toCharArray();
1715
1716    /**
1717     * Encodes characters in the given string as '%'-escaped octets
1718     * using the UTF-8 scheme. Leaves letters ("A-Z", "a-z"), numbers
1719     * ("0-9"), and unreserved characters ("_-!.~'()*") intact. Encodes
1720     * all other characters.
1721     *
1722     * @param s string to encode
1723     * @return an encoded version of s suitable for use as a URI component,
1724     *  or null if s is null
1725     */
1726    public static String encode(String s) {
1727        return encode(s, null);
1728    }
1729
1730    /**
1731     * Encodes characters in the given string as '%'-escaped octets
1732     * using the UTF-8 scheme. Leaves letters ("A-Z", "a-z"), numbers
1733     * ("0-9"), and unreserved characters ("_-!.~'()*") intact. Encodes
1734     * all other characters with the exception of those specified in the
1735     * allow argument.
1736     *
1737     * @param s string to encode
1738     * @param allow set of additional characters to allow in the encoded form,
1739     *  null if no characters should be skipped
1740     * @return an encoded version of s suitable for use as a URI component,
1741     *  or null if s is null
1742     */
1743    public static String encode(String s, String allow) {
1744        if (s == null) {
1745            return null;
1746        }
1747
1748        // Lazily-initialized buffers.
1749        StringBuilder encoded = null;
1750
1751        int oldLength = s.length();
1752
1753        // This loop alternates between copying over allowed characters and
1754        // encoding in chunks. This results in fewer method calls and
1755        // allocations than encoding one character at a time.
1756        int current = 0;
1757        while (current < oldLength) {
1758            // Start in "copying" mode where we copy over allowed chars.
1759
1760            // Find the next character which needs to be encoded.
1761            int nextToEncode = current;
1762            while (nextToEncode < oldLength
1763                    && isAllowed(s.charAt(nextToEncode), allow)) {
1764                nextToEncode++;
1765            }
1766
1767            // If there's nothing more to encode...
1768            if (nextToEncode == oldLength) {
1769                if (current == 0) {
1770                    // We didn't need to encode anything!
1771                    return s;
1772                } else {
1773                    // Presumably, we've already done some encoding.
1774                    encoded.append(s, current, oldLength);
1775                    return encoded.toString();
1776                }
1777            }
1778
1779            if (encoded == null) {
1780                encoded = new StringBuilder();
1781            }
1782
1783            if (nextToEncode > current) {
1784                // Append allowed characters leading up to this point.
1785                encoded.append(s, current, nextToEncode);
1786            } else {
1787                // assert nextToEncode == current
1788            }
1789
1790            // Switch to "encoding" mode.
1791
1792            // Find the next allowed character.
1793            current = nextToEncode;
1794            int nextAllowed = current + 1;
1795            while (nextAllowed < oldLength
1796                    && !isAllowed(s.charAt(nextAllowed), allow)) {
1797                nextAllowed++;
1798            }
1799
1800            // Convert the substring to bytes and encode the bytes as
1801            // '%'-escaped octets.
1802            String toEncode = s.substring(current, nextAllowed);
1803            try {
1804                byte[] bytes = toEncode.getBytes(DEFAULT_ENCODING);
1805                int bytesLength = bytes.length;
1806                for (int i = 0; i < bytesLength; i++) {
1807                    encoded.append('%');
1808                    encoded.append(HEX_DIGITS[(bytes[i] & 0xf0) >> 4]);
1809                    encoded.append(HEX_DIGITS[bytes[i] & 0xf]);
1810                }
1811            } catch (UnsupportedEncodingException e) {
1812                throw new AssertionError(e);
1813            }
1814
1815            current = nextAllowed;
1816        }
1817
1818        // Encoded could still be null at this point if s is empty.
1819        return encoded == null ? s : encoded.toString();
1820    }
1821
1822    /**
1823     * Returns true if the given character is allowed.
1824     *
1825     * @param c character to check
1826     * @param allow characters to allow
1827     * @return true if the character is allowed or false if it should be
1828     *  encoded
1829     */
1830    private static boolean isAllowed(char c, String allow) {
1831        return (c >= 'A' && c <= 'Z')
1832                || (c >= 'a' && c <= 'z')
1833                || (c >= '0' && c <= '9')
1834                || "_-!.~'()*".indexOf(c) != NOT_FOUND
1835                || (allow != null && allow.indexOf(c) != NOT_FOUND);
1836    }
1837
1838    /** Unicode replacement character: \\uFFFD. */
1839    private static final byte[] REPLACEMENT = { (byte) 0xFF, (byte) 0xFD };
1840
1841    /**
1842     * Decodes '%'-escaped octets in the given string using the UTF-8 scheme.
1843     * Replaces invalid octets with the unicode replacement character
1844     * ("\\uFFFD").
1845     *
1846     * @param s encoded string to decode
1847     * @return the given string with escaped octets decoded, or null if
1848     *  s is null
1849     */
1850    public static String decode(String s) {
1851        /*
1852        Compared to java.net.URLEncoderDecoder.decode(), this method decodes a
1853        chunk at a time instead of one character at a time, and it doesn't
1854        throw exceptions. It also only allocates memory when necessary--if
1855        there's nothing to decode, this method won't do much.
1856        */
1857
1858        if (s == null) {
1859            return null;
1860        }
1861
1862        // Lazily-initialized buffers.
1863        StringBuilder decoded = null;
1864        ByteArrayOutputStream out = null;
1865
1866        int oldLength = s.length();
1867
1868        // This loop alternates between copying over normal characters and
1869        // escaping in chunks. This results in fewer method calls and
1870        // allocations than decoding one character at a time.
1871        int current = 0;
1872        while (current < oldLength) {
1873            // Start in "copying" mode where we copy over normal characters.
1874
1875            // Find the next escape sequence.
1876            int nextEscape = s.indexOf('%', current);
1877
1878            if (nextEscape == NOT_FOUND) {
1879                if (decoded == null) {
1880                    // We didn't actually decode anything.
1881                    return s;
1882                } else {
1883                    // Append the remainder and return the decoded string.
1884                    decoded.append(s, current, oldLength);
1885                    return decoded.toString();
1886                }
1887            }
1888
1889            // Prepare buffers.
1890            if (decoded == null) {
1891                // Looks like we're going to need the buffers...
1892                // We know the new string will be shorter. Using the old length
1893                // may overshoot a bit, but it will save us from resizing the
1894                // buffer.
1895                decoded = new StringBuilder(oldLength);
1896                out = new ByteArrayOutputStream(4);
1897            } else {
1898                // Clear decoding buffer.
1899                out.reset();
1900            }
1901
1902            // Append characters leading up to the escape.
1903            if (nextEscape > current) {
1904                decoded.append(s, current, nextEscape);
1905
1906                current = nextEscape;
1907            } else {
1908                // assert current == nextEscape
1909            }
1910
1911            // Switch to "decoding" mode where we decode a string of escape
1912            // sequences.
1913
1914            // Decode and append escape sequences. Escape sequences look like
1915            // "%ab" where % is literal and a and b are hex digits.
1916            try {
1917                do {
1918                    if (current + 2 >= oldLength) {
1919                        // Truncated escape sequence.
1920                        out.write(REPLACEMENT);
1921                    } else {
1922                        int a = Character.digit(s.charAt(current + 1), 16);
1923                        int b = Character.digit(s.charAt(current + 2), 16);
1924
1925                        if (a == -1 || b == -1) {
1926                            // Non hex digits.
1927                            out.write(REPLACEMENT);
1928                        } else {
1929                            // Combine the hex digits into one byte and write.
1930                            out.write((a << 4) + b);
1931                        }
1932                    }
1933
1934                    // Move passed the escape sequence.
1935                    current += 3;
1936                } while (current < oldLength && s.charAt(current) == '%');
1937
1938                // Decode UTF-8 bytes into a string and append it.
1939                decoded.append(out.toString(DEFAULT_ENCODING));
1940            } catch (UnsupportedEncodingException e) {
1941                throw new AssertionError(e);
1942            } catch (IOException e) {
1943                throw new AssertionError(e);
1944            }
1945        }
1946
1947        // If we don't have a buffer, we didn't have to decode anything.
1948        return decoded == null ? s : decoded.toString();
1949    }
1950
1951    /**
1952     * Support for part implementations.
1953     */
1954    static abstract class AbstractPart {
1955
1956        /**
1957         * Enum which indicates which representation of a given part we have.
1958         */
1959        static class Representation {
1960            static final int BOTH = 0;
1961            static final int ENCODED = 1;
1962            static final int DECODED = 2;
1963        }
1964
1965        volatile String encoded;
1966        volatile String decoded;
1967
1968        AbstractPart(String encoded, String decoded) {
1969            this.encoded = encoded;
1970            this.decoded = decoded;
1971        }
1972
1973        abstract String getEncoded();
1974
1975        final String getDecoded() {
1976            @SuppressWarnings("StringEquality")
1977            boolean hasDecoded = decoded != NOT_CACHED;
1978            return hasDecoded ? decoded : (decoded = decode(encoded));
1979        }
1980
1981        final void writeTo(Parcel parcel) {
1982            @SuppressWarnings("StringEquality")
1983            boolean hasEncoded = encoded != NOT_CACHED;
1984
1985            @SuppressWarnings("StringEquality")
1986            boolean hasDecoded = decoded != NOT_CACHED;
1987
1988            if (hasEncoded && hasDecoded) {
1989                parcel.writeInt(Representation.BOTH);
1990                parcel.writeString(encoded);
1991                parcel.writeString(decoded);
1992            } else if (hasEncoded) {
1993                parcel.writeInt(Representation.ENCODED);
1994                parcel.writeString(encoded);
1995            } else if (hasDecoded) {
1996                parcel.writeInt(Representation.DECODED);
1997                parcel.writeString(decoded);
1998            } else {
1999                throw new IllegalArgumentException("Neither encoded nor decoded");
2000            }
2001        }
2002    }
2003
2004    /**
2005     * Immutable wrapper of encoded and decoded versions of a URI part. Lazily
2006     * creates the encoded or decoded version from the other.
2007     */
2008    static class Part extends AbstractPart {
2009
2010        /** A part with null values. */
2011        static final Part NULL = new EmptyPart(null);
2012
2013        /** A part with empty strings for values. */
2014        static final Part EMPTY = new EmptyPart("");
2015
2016        private Part(String encoded, String decoded) {
2017            super(encoded, decoded);
2018        }
2019
2020        boolean isEmpty() {
2021            return false;
2022        }
2023
2024        String getEncoded() {
2025            @SuppressWarnings("StringEquality")
2026            boolean hasEncoded = encoded != NOT_CACHED;
2027            return hasEncoded ? encoded : (encoded = encode(decoded));
2028        }
2029
2030        static Part readFrom(Parcel parcel) {
2031            int representation = parcel.readInt();
2032            switch (representation) {
2033                case Representation.BOTH:
2034                    return from(parcel.readString(), parcel.readString());
2035                case Representation.ENCODED:
2036                    return fromEncoded(parcel.readString());
2037                case Representation.DECODED:
2038                    return fromDecoded(parcel.readString());
2039                default:
2040                    throw new IllegalArgumentException("Unknown representation: "
2041                            + representation);
2042            }
2043        }
2044
2045        /**
2046         * Returns given part or {@link #NULL} if the given part is null.
2047         */
2048        static Part nonNull(Part part) {
2049            return part == null ? NULL : part;
2050        }
2051
2052        /**
2053         * Creates a part from the encoded string.
2054         *
2055         * @param encoded part string
2056         */
2057        static Part fromEncoded(String encoded) {
2058            return from(encoded, NOT_CACHED);
2059        }
2060
2061        /**
2062         * Creates a part from the decoded string.
2063         *
2064         * @param decoded part string
2065         */
2066        static Part fromDecoded(String decoded) {
2067            return from(NOT_CACHED, decoded);
2068        }
2069
2070        /**
2071         * Creates a part from the encoded and decoded strings.
2072         *
2073         * @param encoded part string
2074         * @param decoded part string
2075         */
2076        static Part from(String encoded, String decoded) {
2077            // We have to check both encoded and decoded in case one is
2078            // NOT_CACHED.
2079
2080            if (encoded == null) {
2081                return NULL;
2082            }
2083            if (encoded.length() == 0) {
2084                return EMPTY;
2085            }
2086
2087            if (decoded == null) {
2088                return NULL;
2089            }
2090            if (decoded .length() == 0) {
2091                return EMPTY;
2092            }
2093
2094            return new Part(encoded, decoded);
2095        }
2096
2097        private static class EmptyPart extends Part {
2098            public EmptyPart(String value) {
2099                super(value, value);
2100            }
2101
2102            @Override
2103            boolean isEmpty() {
2104                return true;
2105            }
2106        }
2107    }
2108
2109    /**
2110     * Immutable wrapper of encoded and decoded versions of a path part. Lazily
2111     * creates the encoded or decoded version from the other.
2112     */
2113    static class PathPart extends AbstractPart {
2114
2115        /** A part with null values. */
2116        static final PathPart NULL = new PathPart(null, null);
2117
2118        /** A part with empty strings for values. */
2119        static final PathPart EMPTY = new PathPart("", "");
2120
2121        private PathPart(String encoded, String decoded) {
2122            super(encoded, decoded);
2123        }
2124
2125        String getEncoded() {
2126            @SuppressWarnings("StringEquality")
2127            boolean hasEncoded = encoded != NOT_CACHED;
2128
2129            // Don't encode '/'.
2130            return hasEncoded ? encoded : (encoded = encode(decoded, "/"));
2131        }
2132
2133        /**
2134         * Cached path segments. This doesn't need to be volatile--we don't
2135         * care if other threads see the result.
2136         */
2137        private PathSegments pathSegments;
2138
2139        /**
2140         * Gets the individual path segments. Parses them if necessary.
2141         *
2142         * @return parsed path segments or null if this isn't a hierarchical
2143         *  URI
2144         */
2145        PathSegments getPathSegments() {
2146            if (pathSegments != null) {
2147                return pathSegments;
2148            }
2149
2150            String path = getEncoded();
2151            if (path == null) {
2152                return pathSegments = PathSegments.EMPTY;
2153            }
2154
2155            PathSegmentsBuilder segmentBuilder = new PathSegmentsBuilder();
2156
2157            int previous = 0;
2158            int current;
2159            while ((current = path.indexOf('/', previous)) > -1) {
2160                // This check keeps us from adding a segment if the path starts
2161                // '/' and an empty segment for "//".
2162                if (previous < current) {
2163                    String decodedSegment
2164                            = decode(path.substring(previous, current));
2165                    segmentBuilder.add(decodedSegment);
2166                }
2167                previous = current + 1;
2168            }
2169
2170            // Add in the final path segment.
2171            if (previous < path.length()) {
2172                segmentBuilder.add(decode(path.substring(previous)));
2173            }
2174
2175            return pathSegments = segmentBuilder.build();
2176        }
2177
2178        static PathPart appendEncodedSegment(PathPart oldPart,
2179                String newSegment) {
2180            // If there is no old path, should we make the new path relative
2181            // or absolute? I pick absolute.
2182
2183            if (oldPart == null) {
2184                // No old path.
2185                return fromEncoded("/" + newSegment);
2186            }
2187
2188            String oldPath = oldPart.getEncoded();
2189
2190            if (oldPath == null) {
2191                oldPath = "";
2192            }
2193
2194            int oldPathLength = oldPath.length();
2195            String newPath;
2196            if (oldPathLength == 0) {
2197                // No old path.
2198                newPath = "/" + newSegment;
2199            } else if (oldPath.charAt(oldPathLength - 1) == '/') {
2200                newPath = oldPath + newSegment;
2201            } else {
2202                newPath = oldPath + "/" + newSegment;
2203            }
2204
2205            return fromEncoded(newPath);
2206        }
2207
2208        static PathPart appendDecodedSegment(PathPart oldPart, String decoded) {
2209            String encoded = encode(decoded);
2210
2211            // TODO: Should we reuse old PathSegments? Probably not.
2212            return appendEncodedSegment(oldPart, encoded);
2213        }
2214
2215        static PathPart readFrom(Parcel parcel) {
2216            int representation = parcel.readInt();
2217            switch (representation) {
2218                case Representation.BOTH:
2219                    return from(parcel.readString(), parcel.readString());
2220                case Representation.ENCODED:
2221                    return fromEncoded(parcel.readString());
2222                case Representation.DECODED:
2223                    return fromDecoded(parcel.readString());
2224                default:
2225                    throw new IllegalArgumentException("Bad representation: " + representation);
2226            }
2227        }
2228
2229        /**
2230         * Creates a path from the encoded string.
2231         *
2232         * @param encoded part string
2233         */
2234        static PathPart fromEncoded(String encoded) {
2235            return from(encoded, NOT_CACHED);
2236        }
2237
2238        /**
2239         * Creates a path from the decoded string.
2240         *
2241         * @param decoded part string
2242         */
2243        static PathPart fromDecoded(String decoded) {
2244            return from(NOT_CACHED, decoded);
2245        }
2246
2247        /**
2248         * Creates a path from the encoded and decoded strings.
2249         *
2250         * @param encoded part string
2251         * @param decoded part string
2252         */
2253        static PathPart from(String encoded, String decoded) {
2254            if (encoded == null) {
2255                return NULL;
2256            }
2257
2258            if (encoded.length() == 0) {
2259                return EMPTY;
2260            }
2261
2262            return new PathPart(encoded, decoded);
2263        }
2264
2265        /**
2266         * Prepends path values with "/" if they're present, not empty, and
2267         * they don't already start with "/".
2268         */
2269        static PathPart makeAbsolute(PathPart oldPart) {
2270            @SuppressWarnings("StringEquality")
2271            boolean encodedCached = oldPart.encoded != NOT_CACHED;
2272
2273            // We don't care which version we use, and we don't want to force
2274            // unneccessary encoding/decoding.
2275            String oldPath = encodedCached ? oldPart.encoded : oldPart.decoded;
2276
2277            if (oldPath == null || oldPath.length() == 0
2278                    || oldPath.startsWith("/")) {
2279                return oldPart;
2280            }
2281
2282            // Prepend encoded string if present.
2283            String newEncoded = encodedCached
2284                    ? "/" + oldPart.encoded : NOT_CACHED;
2285
2286            // Prepend decoded string if present.
2287            @SuppressWarnings("StringEquality")
2288            boolean decodedCached = oldPart.decoded != NOT_CACHED;
2289            String newDecoded = decodedCached
2290                    ? "/" + oldPart.decoded
2291                    : NOT_CACHED;
2292
2293            return new PathPart(newEncoded, newDecoded);
2294        }
2295    }
2296
2297    /**
2298     * Creates a new Uri by appending an already-encoded path segment to a
2299     * base Uri.
2300     *
2301     * @param baseUri Uri to append path segment to
2302     * @param pathSegment encoded path segment to append
2303     * @return a new Uri based on baseUri with the given segment appended to
2304     *  the path
2305     * @throws NullPointerException if baseUri is null
2306     */
2307    public static Uri withAppendedPath(Uri baseUri, String pathSegment) {
2308        Builder builder = baseUri.buildUpon();
2309        builder = builder.appendEncodedPath(pathSegment);
2310        return builder.build();
2311    }
2312}
2313