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