• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2007 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package android.net;
18 
19 import android.os.Parcel;
20 import android.os.Parcelable;
21 import android.util.Log;
22 
23 import java.io.File;
24 import java.io.IOException;
25 import java.io.UnsupportedEncodingException;
26 import java.io.ByteArrayOutputStream;
27 import java.net.URLEncoder;
28 import java.util.AbstractList;
29 import java.util.ArrayList;
30 import java.util.Collections;
31 import java.util.List;
32 import 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  */
45 public 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      */
Uri()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      */
isHierarchical()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      */
isOpaque()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      */
isRelative()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      */
isAbsolute()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      */
getScheme()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      */
getSchemeSpecificPart()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      */
getEncodedSchemeSpecificPart()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      */
getAuthority()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      */
getEncodedAuthority()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      */
getUserInfo()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      */
getEncodedUserInfo()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      */
getHost()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      */
getPort()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      */
getPath()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      */
getEncodedPath()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      */
getQuery()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      */
getEncodedQuery()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      */
getFragment()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      */
getEncodedFragment()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      */
getPathSegments()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      */
getLastPathSegment()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      */
equals(Object o)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      */
hashCode()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      */
compareTo(Uri other)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      */
toString()351     public abstract String toString();
352 
353     /**
354      * Constructs a new builder, copying the attributes from this Uri.
355      */
buildUpon()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      */
parse(String uriString)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      */
fromFile(File file)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 
StringUri(String uriString)418         private StringUri(String uriString) {
419             if (uriString == null) {
420                 throw new NullPointerException("uriString");
421             }
422 
423             this.uriString = uriString;
424         }
425 
readFrom(Parcel parcel)426         static Uri readFrom(Parcel parcel) {
427             return new StringUri(parcel.readString());
428         }
429 
describeContents()430         public int describeContents() {
431             return 0;
432         }
433 
writeToParcel(Parcel parcel, int flags)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. */
findSchemeSeparator()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. */
findFragmentSeparator()453         private int findFragmentSeparator() {
454             return cachedFsi == NOT_CALCULATED
455                     ? cachedFsi = uriString.indexOf('#', findSchemeSeparator())
456                     : cachedFsi;
457         }
458 
isHierarchical()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 
isRelative()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 
getScheme()483         public String getScheme() {
484             @SuppressWarnings("StringEquality")
485             boolean cached = (scheme != NOT_CACHED);
486             return cached ? scheme : (scheme = parseScheme());
487         }
488 
parseScheme()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 
getSsp()496         private Part getSsp() {
497             return ssp == null ? ssp = Part.fromEncoded(parseSsp()) : ssp;
498         }
499 
getEncodedSchemeSpecificPart()500         public String getEncodedSchemeSpecificPart() {
501             return getSsp().getEncoded();
502         }
503 
getSchemeSpecificPart()504         public String getSchemeSpecificPart() {
505             return getSsp().getDecoded();
506         }
507 
parseSsp()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 
getAuthorityPart()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 
getEncodedAuthority()530         public String getEncodedAuthority() {
531             return getAuthorityPart().getEncoded();
532         }
533 
getAuthority()534         public String getAuthority() {
535             return getAuthorityPart().getDecoded();
536         }
537 
538         private PathPart path;
539 
getPathPart()540         private PathPart getPathPart() {
541             return path == null
542                     ? path = PathPart.fromEncoded(parsePath())
543                     : path;
544         }
545 
getPath()546         public String getPath() {
547             return getPathPart().getDecoded();
548         }
549 
getEncodedPath()550         public String getEncodedPath() {
551             return getPathPart().getEncoded();
552         }
553 
getPathSegments()554         public List<String> getPathSegments() {
555             return getPathPart().getPathSegments();
556         }
557 
parsePath()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 
getQueryPart()585         private Part getQueryPart() {
586             return query == null
587                     ? query = Part.fromEncoded(parseQuery()) : query;
588         }
589 
getEncodedQuery()590         public String getEncodedQuery() {
591             return getQueryPart().getEncoded();
592         }
593 
parseQuery()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 
getQuery()616         public String getQuery() {
617             return getQueryPart().getDecoded();
618         }
619 
620         private Part fragment;
621 
getFragmentPart()622         private Part getFragmentPart() {
623             return fragment == null
624                     ? fragment = Part.fromEncoded(parseFragment()) : fragment;
625         }
626 
getEncodedFragment()627         public String getEncodedFragment() {
628             return getFragmentPart().getEncoded();
629         }
630 
parseFragment()631         private String parseFragment() {
632             int fsi = findFragmentSeparator();
633             return fsi == NOT_FOUND ? null : uriString.substring(fsi + 1);
634         }
635 
getFragment()636         public String getFragment() {
637             return getFragmentPart().getDecoded();
638         }
639 
toString()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          */
parseAuthority(String uriString, int ssi)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          */
parsePath(String uriString, int ssi)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 
buildUpon()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      */
fromParts(String scheme, String ssp, String fragment)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 
OpaqueUri(String scheme, Part ssp, Part fragment)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 
readFrom(Parcel parcel)792         static Uri readFrom(Parcel parcel) {
793             return new OpaqueUri(
794                 parcel.readString(),
795                 Part.readFrom(parcel),
796                 Part.readFrom(parcel)
797             );
798         }
799 
describeContents()800         public int describeContents() {
801             return 0;
802         }
803 
writeToParcel(Parcel parcel, int flags)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 
isHierarchical()811         public boolean isHierarchical() {
812             return false;
813         }
814 
isRelative()815         public boolean isRelative() {
816             return scheme == null;
817         }
818 
getScheme()819         public String getScheme() {
820             return this.scheme;
821         }
822 
getEncodedSchemeSpecificPart()823         public String getEncodedSchemeSpecificPart() {
824             return ssp.getEncoded();
825         }
826 
getSchemeSpecificPart()827         public String getSchemeSpecificPart() {
828             return ssp.getDecoded();
829         }
830 
getAuthority()831         public String getAuthority() {
832             return null;
833         }
834 
getEncodedAuthority()835         public String getEncodedAuthority() {
836             return null;
837         }
838 
getPath()839         public String getPath() {
840             return null;
841         }
842 
getEncodedPath()843         public String getEncodedPath() {
844             return null;
845         }
846 
getQuery()847         public String getQuery() {
848             return null;
849         }
850 
getEncodedQuery()851         public String getEncodedQuery() {
852             return null;
853         }
854 
getFragment()855         public String getFragment() {
856             return fragment.getDecoded();
857         }
858 
getEncodedFragment()859         public String getEncodedFragment() {
860             return fragment.getEncoded();
861         }
862 
getPathSegments()863         public List<String> getPathSegments() {
864             return Collections.emptyList();
865         }
866 
getLastPathSegment()867         public String getLastPathSegment() {
868             return null;
869         }
870 
getUserInfo()871         public String getUserInfo() {
872             return null;
873         }
874 
getEncodedUserInfo()875         public String getEncodedUserInfo() {
876             return null;
877         }
878 
getHost()879         public String getHost() {
880             return null;
881         }
882 
getPort()883         public int getPort() {
884             return -1;
885         }
886 
887         private volatile String cachedString = NOT_CACHED;
888 
toString()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 
buildUpon()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 
PathSegments(String[] segments, int size)927         PathSegments(String[] segments, int size) {
928             this.segments = segments;
929             this.size = size;
930         }
931 
get(int index)932         public String get(int index) {
933             if (index >= size) {
934                 throw new IndexOutOfBoundsException();
935             }
936 
937             return segments[index];
938         }
939 
size()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 
add(String segment)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 
build()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 
getLastPathSegment()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 
getUserInfoPart()998         private Part getUserInfoPart() {
999             return userInfo == null
1000                     ? userInfo = Part.fromEncoded(parseUserInfo()) : userInfo;
1001         }
1002 
getEncodedUserInfo()1003         public final String getEncodedUserInfo() {
1004             return getUserInfoPart().getEncoded();
1005         }
1006 
parseUserInfo()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 
getUserInfo()1017         public String getUserInfo() {
1018             return getUserInfoPart().getDecoded();
1019         }
1020 
1021         private volatile String host = NOT_CACHED;
1022 
getHost()1023         public String getHost() {
1024             @SuppressWarnings("StringEquality")
1025             boolean cached = (host != NOT_CACHED);
1026             return cached ? host
1027                     : (host = parseHost());
1028         }
1029 
parseHost()1030         private String parseHost() {
1031             String authority = getAuthority();
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             return portSeparator == NOT_FOUND
1041                     ? authority.substring(userInfoSeparator + 1)
1042                     : authority.substring(userInfoSeparator + 1, portSeparator);
1043         }
1044 
1045         private volatile int port = NOT_CALCULATED;
1046 
getPort()1047         public int getPort() {
1048             return port == NOT_CALCULATED
1049                     ? port = parsePort()
1050                     : port;
1051         }
1052 
parsePort()1053         private int parsePort() {
1054             String authority = getAuthority();
1055             if (authority == null) {
1056                 return -1;
1057             }
1058 
1059             // Make sure we look for the port separtor *after* the user info
1060             // separator. We have URLs with a ':' in the user info.
1061             int userInfoSeparator = authority.indexOf('@');
1062             int portSeparator = authority.indexOf(':', userInfoSeparator);
1063 
1064             if (portSeparator == NOT_FOUND) {
1065                 return -1;
1066             }
1067 
1068             String portString = authority.substring(portSeparator + 1);
1069             try {
1070                 return Integer.parseInt(portString);
1071             } catch (NumberFormatException e) {
1072                 Log.w(LOG, "Error parsing port string.", e);
1073                 return -1;
1074             }
1075         }
1076     }
1077 
1078     /**
1079      * Hierarchical Uri.
1080      */
1081     private static class HierarchicalUri extends AbstractHierarchicalUri {
1082 
1083         /** Used in parcelling. */
1084         static final int TYPE_ID = 3;
1085 
1086         private final String scheme; // can be null
1087         private final Part authority;
1088         private final PathPart path;
1089         private final Part query;
1090         private final Part fragment;
1091 
HierarchicalUri(String scheme, Part authority, PathPart path, Part query, Part fragment)1092         private HierarchicalUri(String scheme, Part authority, PathPart path,
1093                 Part query, Part fragment) {
1094             this.scheme = scheme;
1095             this.authority = Part.nonNull(authority);
1096             this.path = path == null ? PathPart.NULL : path;
1097             this.query = Part.nonNull(query);
1098             this.fragment = Part.nonNull(fragment);
1099         }
1100 
readFrom(Parcel parcel)1101         static Uri readFrom(Parcel parcel) {
1102             return new HierarchicalUri(
1103                 parcel.readString(),
1104                 Part.readFrom(parcel),
1105                 PathPart.readFrom(parcel),
1106                 Part.readFrom(parcel),
1107                 Part.readFrom(parcel)
1108             );
1109         }
1110 
describeContents()1111         public int describeContents() {
1112             return 0;
1113         }
1114 
writeToParcel(Parcel parcel, int flags)1115         public void writeToParcel(Parcel parcel, int flags) {
1116             parcel.writeInt(TYPE_ID);
1117             parcel.writeString(scheme);
1118             authority.writeTo(parcel);
1119             path.writeTo(parcel);
1120             query.writeTo(parcel);
1121             fragment.writeTo(parcel);
1122         }
1123 
isHierarchical()1124         public boolean isHierarchical() {
1125             return true;
1126         }
1127 
isRelative()1128         public boolean isRelative() {
1129             return scheme == null;
1130         }
1131 
getScheme()1132         public String getScheme() {
1133             return scheme;
1134         }
1135 
1136         private Part ssp;
1137 
getSsp()1138         private Part getSsp() {
1139             return ssp == null
1140                     ? ssp = Part.fromEncoded(makeSchemeSpecificPart()) : ssp;
1141         }
1142 
getEncodedSchemeSpecificPart()1143         public String getEncodedSchemeSpecificPart() {
1144             return getSsp().getEncoded();
1145         }
1146 
getSchemeSpecificPart()1147         public String getSchemeSpecificPart() {
1148             return getSsp().getDecoded();
1149         }
1150 
1151         /**
1152          * Creates the encoded scheme-specific part from its sub parts.
1153          */
makeSchemeSpecificPart()1154         private String makeSchemeSpecificPart() {
1155             StringBuilder builder = new StringBuilder();
1156             appendSspTo(builder);
1157             return builder.toString();
1158         }
1159 
appendSspTo(StringBuilder builder)1160         private void appendSspTo(StringBuilder builder) {
1161             String encodedAuthority = authority.getEncoded();
1162             if (encodedAuthority != null) {
1163                 // Even if the authority is "", we still want to append "//".
1164                 builder.append("//").append(encodedAuthority);
1165             }
1166 
1167             String encodedPath = path.getEncoded();
1168             if (encodedPath != null) {
1169                 builder.append(encodedPath);
1170             }
1171 
1172             if (!query.isEmpty()) {
1173                 builder.append('?').append(query.getEncoded());
1174             }
1175         }
1176 
getAuthority()1177         public String getAuthority() {
1178             return this.authority.getDecoded();
1179         }
1180 
getEncodedAuthority()1181         public String getEncodedAuthority() {
1182             return this.authority.getEncoded();
1183         }
1184 
getEncodedPath()1185         public String getEncodedPath() {
1186             return this.path.getEncoded();
1187         }
1188 
getPath()1189         public String getPath() {
1190             return this.path.getDecoded();
1191         }
1192 
getQuery()1193         public String getQuery() {
1194             return this.query.getDecoded();
1195         }
1196 
getEncodedQuery()1197         public String getEncodedQuery() {
1198             return this.query.getEncoded();
1199         }
1200 
getFragment()1201         public String getFragment() {
1202             return this.fragment.getDecoded();
1203         }
1204 
getEncodedFragment()1205         public String getEncodedFragment() {
1206             return this.fragment.getEncoded();
1207         }
1208 
getPathSegments()1209         public List<String> getPathSegments() {
1210             return this.path.getPathSegments();
1211         }
1212 
1213         private volatile String uriString = NOT_CACHED;
1214 
1215         @Override
toString()1216         public String toString() {
1217             @SuppressWarnings("StringEquality")
1218             boolean cached = (uriString != NOT_CACHED);
1219             return cached ? uriString
1220                     : (uriString = makeUriString());
1221         }
1222 
makeUriString()1223         private String makeUriString() {
1224             StringBuilder builder = new StringBuilder();
1225 
1226             if (scheme != null) {
1227                 builder.append(scheme).append(':');
1228             }
1229 
1230             appendSspTo(builder);
1231 
1232             if (!fragment.isEmpty()) {
1233                 builder.append('#').append(fragment.getEncoded());
1234             }
1235 
1236             return builder.toString();
1237         }
1238 
buildUpon()1239         public Builder buildUpon() {
1240             return new Builder()
1241                     .scheme(scheme)
1242                     .authority(authority)
1243                     .path(path)
1244                     .query(query)
1245                     .fragment(fragment);
1246         }
1247     }
1248 
1249     /**
1250      * Helper class for building or manipulating URI references. Not safe for
1251      * concurrent use.
1252      *
1253      * <p>An absolute hierarchical URI reference follows the pattern:
1254      * {@code &lt;scheme&gt;://&lt;authority&gt;&lt;absolute path&gt;?&lt;query&gt;#&lt;fragment&gt;}
1255      *
1256      * <p>Relative URI references (which are always hierarchical) follow one
1257      * of two patterns: {@code &lt;relative or absolute path&gt;?&lt;query&gt;#&lt;fragment&gt;}
1258      * or {@code //&lt;authority&gt;&lt;absolute path&gt;?&lt;query&gt;#&lt;fragment&gt;}
1259      *
1260      * <p>An opaque URI follows this pattern:
1261      * {@code &lt;scheme&gt;:&lt;opaque part&gt;#&lt;fragment&gt;}
1262      */
1263     public static final class Builder {
1264 
1265         private String scheme;
1266         private Part opaquePart;
1267         private Part authority;
1268         private PathPart path;
1269         private Part query;
1270         private Part fragment;
1271 
1272         /**
1273          * Constructs a new Builder.
1274          */
Builder()1275         public Builder() {}
1276 
1277         /**
1278          * Sets the scheme.
1279          *
1280          * @param scheme name or {@code null} if this is a relative Uri
1281          */
scheme(String scheme)1282         public Builder scheme(String scheme) {
1283             this.scheme = scheme;
1284             return this;
1285         }
1286 
opaquePart(Part opaquePart)1287         Builder opaquePart(Part opaquePart) {
1288             this.opaquePart = opaquePart;
1289             return this;
1290         }
1291 
1292         /**
1293          * Encodes and sets the given opaque scheme-specific-part.
1294          *
1295          * @param opaquePart decoded opaque part
1296          */
opaquePart(String opaquePart)1297         public Builder opaquePart(String opaquePart) {
1298             return opaquePart(Part.fromDecoded(opaquePart));
1299         }
1300 
1301         /**
1302          * Sets the previously encoded opaque scheme-specific-part.
1303          *
1304          * @param opaquePart encoded opaque part
1305          */
encodedOpaquePart(String opaquePart)1306         public Builder encodedOpaquePart(String opaquePart) {
1307             return opaquePart(Part.fromEncoded(opaquePart));
1308         }
1309 
authority(Part authority)1310         Builder authority(Part authority) {
1311             // This URI will be hierarchical.
1312             this.opaquePart = null;
1313 
1314             this.authority = authority;
1315             return this;
1316         }
1317 
1318         /**
1319          * Encodes and sets the authority.
1320          */
authority(String authority)1321         public Builder authority(String authority) {
1322             return authority(Part.fromDecoded(authority));
1323         }
1324 
1325         /**
1326          * Sets the previously encoded authority.
1327          */
encodedAuthority(String authority)1328         public Builder encodedAuthority(String authority) {
1329             return authority(Part.fromEncoded(authority));
1330         }
1331 
path(PathPart path)1332         Builder path(PathPart path) {
1333             // This URI will be hierarchical.
1334             this.opaquePart = null;
1335 
1336             this.path = path;
1337             return this;
1338         }
1339 
1340         /**
1341          * Sets the path. Leaves '/' characters intact but encodes others as
1342          * necessary.
1343          *
1344          * <p>If the path is not null and doesn't start with a '/', and if
1345          * you specify a scheme and/or authority, the builder will prepend the
1346          * given path with a '/'.
1347          */
path(String path)1348         public Builder path(String path) {
1349             return path(PathPart.fromDecoded(path));
1350         }
1351 
1352         /**
1353          * Sets the previously encoded path.
1354          *
1355          * <p>If the path is not null and doesn't start with a '/', and if
1356          * you specify a scheme and/or authority, the builder will prepend the
1357          * given path with a '/'.
1358          */
encodedPath(String path)1359         public Builder encodedPath(String path) {
1360             return path(PathPart.fromEncoded(path));
1361         }
1362 
1363         /**
1364          * Encodes the given segment and appends it to the path.
1365          */
appendPath(String newSegment)1366         public Builder appendPath(String newSegment) {
1367             return path(PathPart.appendDecodedSegment(path, newSegment));
1368         }
1369 
1370         /**
1371          * Appends the given segment to the path.
1372          */
appendEncodedPath(String newSegment)1373         public Builder appendEncodedPath(String newSegment) {
1374             return path(PathPart.appendEncodedSegment(path, newSegment));
1375         }
1376 
query(Part query)1377         Builder query(Part query) {
1378             // This URI will be hierarchical.
1379             this.opaquePart = null;
1380 
1381             this.query = query;
1382             return this;
1383         }
1384 
1385         /**
1386          * Encodes and sets the query.
1387          */
query(String query)1388         public Builder query(String query) {
1389             return query(Part.fromDecoded(query));
1390         }
1391 
1392         /**
1393          * Sets the previously encoded query.
1394          */
encodedQuery(String query)1395         public Builder encodedQuery(String query) {
1396             return query(Part.fromEncoded(query));
1397         }
1398 
fragment(Part fragment)1399         Builder fragment(Part fragment) {
1400             this.fragment = fragment;
1401             return this;
1402         }
1403 
1404         /**
1405          * Encodes and sets the fragment.
1406          */
fragment(String fragment)1407         public Builder fragment(String fragment) {
1408             return fragment(Part.fromDecoded(fragment));
1409         }
1410 
1411         /**
1412          * Sets the previously encoded fragment.
1413          */
encodedFragment(String fragment)1414         public Builder encodedFragment(String fragment) {
1415             return fragment(Part.fromEncoded(fragment));
1416         }
1417 
1418         /**
1419          * Encodes the key and value and then appends the parameter to the
1420          * query string.
1421          *
1422          * @param key which will be encoded
1423          * @param value which will be encoded
1424          */
appendQueryParameter(String key, String value)1425         public Builder appendQueryParameter(String key, String value) {
1426             // This URI will be hierarchical.
1427             this.opaquePart = null;
1428 
1429             String encodedParameter = encode(key, null) + "="
1430                     + encode(value, null);
1431 
1432             if (query == null) {
1433                 query = Part.fromEncoded(encodedParameter);
1434                 return this;
1435             }
1436 
1437             String oldQuery = query.getEncoded();
1438             if (oldQuery == null || oldQuery.length() == 0) {
1439                 query = Part.fromEncoded(encodedParameter);
1440             } else {
1441                 query = Part.fromEncoded(oldQuery + "&" + encodedParameter);
1442             }
1443 
1444             return this;
1445         }
1446 
1447         /**
1448          * Constructs a Uri with the current attributes.
1449          *
1450          * @throws UnsupportedOperationException if the URI is opaque and the
1451          *  scheme is null
1452          */
build()1453         public Uri build() {
1454             if (opaquePart != null) {
1455                 if (this.scheme == null) {
1456                     throw new UnsupportedOperationException(
1457                             "An opaque URI must have a scheme.");
1458                 }
1459 
1460                 return new OpaqueUri(scheme, opaquePart, fragment);
1461             } else {
1462                 // Hierarchical URIs should not return null for getPath().
1463                 PathPart path = this.path;
1464                 if (path == null || path == PathPart.NULL) {
1465                     path = PathPart.EMPTY;
1466                 } else {
1467                     // If we have a scheme and/or authority, the path must
1468                     // be absolute. Prepend it with a '/' if necessary.
1469                     if (hasSchemeOrAuthority()) {
1470                         path = PathPart.makeAbsolute(path);
1471                     }
1472                 }
1473 
1474                 return new HierarchicalUri(
1475                         scheme, authority, path, query, fragment);
1476             }
1477         }
1478 
hasSchemeOrAuthority()1479         private boolean hasSchemeOrAuthority() {
1480             return scheme != null
1481                     || (authority != null && authority != Part.NULL);
1482 
1483         }
1484 
1485         @Override
toString()1486         public String toString() {
1487             return build().toString();
1488         }
1489     }
1490 
1491     /**
1492      * Searches the query string for parameter values with the given key.
1493      *
1494      * @param key which will be encoded
1495      *
1496      * @throws UnsupportedOperationException if this isn't a hierarchical URI
1497      * @throws NullPointerException if key is null
1498      *
1499      * @return a list of decoded values
1500      */
getQueryParameters(String key)1501     public List<String> getQueryParameters(String key) {
1502         if (isOpaque()) {
1503             throw new UnsupportedOperationException(NOT_HIERARCHICAL);
1504         }
1505 
1506         String query = getEncodedQuery();
1507         if (query == null) {
1508             return Collections.emptyList();
1509         }
1510 
1511         String encodedKey;
1512         try {
1513             encodedKey = URLEncoder.encode(key, DEFAULT_ENCODING);
1514         } catch (UnsupportedEncodingException e) {
1515             throw new AssertionError(e);
1516         }
1517 
1518         // Prepend query with "&" making the first parameter the same as the
1519         // rest.
1520         query = "&" + query;
1521 
1522         // Parameter prefix.
1523         String prefix = "&" + encodedKey + "=";
1524 
1525         ArrayList<String> values = new ArrayList<String>();
1526 
1527         int start = 0;
1528         int length = query.length();
1529         while (start < length) {
1530             start = query.indexOf(prefix, start);
1531 
1532             if (start == -1) {
1533                 // No more values.
1534                 break;
1535             }
1536 
1537             // Move start to start of value.
1538             start += prefix.length();
1539 
1540             // Find end of value.
1541             int end = query.indexOf('&', start);
1542             if (end == -1) {
1543                 end = query.length();
1544             }
1545 
1546             String value = query.substring(start, end);
1547             values.add(decode(value));
1548 
1549             start = end;
1550         }
1551 
1552         return Collections.unmodifiableList(values);
1553     }
1554 
1555     /**
1556      * Searches the query string for the first value with the given key.
1557      *
1558      * @param key which will be encoded
1559      * @throws UnsupportedOperationException if this isn't a hierarchical URI
1560      * @throws NullPointerException if key is null
1561      *
1562      * @return the decoded value or null if no parameter is found
1563      */
getQueryParameter(String key)1564     public String getQueryParameter(String key) {
1565         if (isOpaque()) {
1566             throw new UnsupportedOperationException(NOT_HIERARCHICAL);
1567         }
1568 
1569         String query = getEncodedQuery();
1570 
1571         if (query == null) {
1572             return null;
1573         }
1574 
1575         String encodedKey;
1576         try {
1577             encodedKey = URLEncoder.encode(key, DEFAULT_ENCODING);
1578         } catch (UnsupportedEncodingException e) {
1579             throw new AssertionError(e);
1580         }
1581 
1582         String prefix = encodedKey + "=";
1583 
1584         if (query.length() < prefix.length()) {
1585             return null;
1586         }
1587 
1588         int start;
1589         if (query.startsWith(prefix)) {
1590             // It's the first parameter.
1591             start = prefix.length();
1592         } else {
1593             // It must be later in the query string.
1594             prefix = "&" + prefix;
1595             start = query.indexOf(prefix);
1596 
1597             if (start == -1) {
1598                 // Not found.
1599                 return null;
1600             }
1601 
1602             start += prefix.length();
1603         }
1604 
1605         // Find end of value.
1606         int end = query.indexOf('&', start);
1607         if (end == -1) {
1608             end = query.length();
1609         }
1610 
1611         String value = query.substring(start, end);
1612         return decode(value);
1613     }
1614 
1615     /** Identifies a null parcelled Uri. */
1616     private static final int NULL_TYPE_ID = 0;
1617 
1618     /**
1619      * Reads Uris from Parcels.
1620      */
1621     public static final Parcelable.Creator<Uri> CREATOR
1622             = new Parcelable.Creator<Uri>() {
1623         public Uri createFromParcel(Parcel in) {
1624             int type = in.readInt();
1625             switch (type) {
1626                 case NULL_TYPE_ID: return null;
1627                 case StringUri.TYPE_ID: return StringUri.readFrom(in);
1628                 case OpaqueUri.TYPE_ID: return OpaqueUri.readFrom(in);
1629                 case HierarchicalUri.TYPE_ID:
1630                     return HierarchicalUri.readFrom(in);
1631             }
1632 
1633             throw new AssertionError("Unknown URI type: " + type);
1634         }
1635 
1636         public Uri[] newArray(int size) {
1637             return new Uri[size];
1638         }
1639     };
1640 
1641     /**
1642      * Writes a Uri to a Parcel.
1643      *
1644      * @param out parcel to write to
1645      * @param uri to write, can be null
1646      */
writeToParcel(Parcel out, Uri uri)1647     public static void writeToParcel(Parcel out, Uri uri) {
1648         if (uri == null) {
1649             out.writeInt(NULL_TYPE_ID);
1650         } else {
1651             uri.writeToParcel(out, 0);
1652         }
1653     }
1654 
1655     private static final char[] HEX_DIGITS = "0123456789ABCDEF".toCharArray();
1656 
1657     /**
1658      * Encodes characters in the given string as '%'-escaped octets
1659      * using the UTF-8 scheme. Leaves letters ("A-Z", "a-z"), numbers
1660      * ("0-9"), and unreserved characters ("_-!.~'()*") intact. Encodes
1661      * all other characters.
1662      *
1663      * @param s string to encode
1664      * @return an encoded version of s suitable for use as a URI component,
1665      *  or null if s is null
1666      */
encode(String s)1667     public static String encode(String s) {
1668         return encode(s, null);
1669     }
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 with the exception of those specified in the
1676      * allow argument.
1677      *
1678      * @param s string to encode
1679      * @param allow set of additional characters to allow in the encoded form,
1680      *  null if no characters should be skipped
1681      * @return an encoded version of s suitable for use as a URI component,
1682      *  or null if s is null
1683      */
encode(String s, String allow)1684     public static String encode(String s, String allow) {
1685         if (s == null) {
1686             return null;
1687         }
1688 
1689         // Lazily-initialized buffers.
1690         StringBuilder encoded = null;
1691 
1692         int oldLength = s.length();
1693 
1694         // This loop alternates between copying over allowed characters and
1695         // encoding in chunks. This results in fewer method calls and
1696         // allocations than encoding one character at a time.
1697         int current = 0;
1698         while (current < oldLength) {
1699             // Start in "copying" mode where we copy over allowed chars.
1700 
1701             // Find the next character which needs to be encoded.
1702             int nextToEncode = current;
1703             while (nextToEncode < oldLength
1704                     && isAllowed(s.charAt(nextToEncode), allow)) {
1705                 nextToEncode++;
1706             }
1707 
1708             // If there's nothing more to encode...
1709             if (nextToEncode == oldLength) {
1710                 if (current == 0) {
1711                     // We didn't need to encode anything!
1712                     return s;
1713                 } else {
1714                     // Presumably, we've already done some encoding.
1715                     encoded.append(s, current, oldLength);
1716                     return encoded.toString();
1717                 }
1718             }
1719 
1720             if (encoded == null) {
1721                 encoded = new StringBuilder();
1722             }
1723 
1724             if (nextToEncode > current) {
1725                 // Append allowed characters leading up to this point.
1726                 encoded.append(s, current, nextToEncode);
1727             } else {
1728                 // assert nextToEncode == current
1729             }
1730 
1731             // Switch to "encoding" mode.
1732 
1733             // Find the next allowed character.
1734             current = nextToEncode;
1735             int nextAllowed = current + 1;
1736             while (nextAllowed < oldLength
1737                     && !isAllowed(s.charAt(nextAllowed), allow)) {
1738                 nextAllowed++;
1739             }
1740 
1741             // Convert the substring to bytes and encode the bytes as
1742             // '%'-escaped octets.
1743             String toEncode = s.substring(current, nextAllowed);
1744             try {
1745                 byte[] bytes = toEncode.getBytes(DEFAULT_ENCODING);
1746                 int bytesLength = bytes.length;
1747                 for (int i = 0; i < bytesLength; i++) {
1748                     encoded.append('%');
1749                     encoded.append(HEX_DIGITS[(bytes[i] & 0xf0) >> 4]);
1750                     encoded.append(HEX_DIGITS[bytes[i] & 0xf]);
1751                 }
1752             } catch (UnsupportedEncodingException e) {
1753                 throw new AssertionError(e);
1754             }
1755 
1756             current = nextAllowed;
1757         }
1758 
1759         // Encoded could still be null at this point if s is empty.
1760         return encoded == null ? s : encoded.toString();
1761     }
1762 
1763     /**
1764      * Returns true if the given character is allowed.
1765      *
1766      * @param c character to check
1767      * @param allow characters to allow
1768      * @return true if the character is allowed or false if it should be
1769      *  encoded
1770      */
isAllowed(char c, String allow)1771     private static boolean isAllowed(char c, String allow) {
1772         return (c >= 'A' && c <= 'Z')
1773                 || (c >= 'a' && c <= 'z')
1774                 || (c >= '0' && c <= '9')
1775                 || "_-!.~'()*".indexOf(c) != NOT_FOUND
1776                 || (allow != null && allow.indexOf(c) != NOT_FOUND);
1777     }
1778 
1779     /** Unicode replacement character: \\uFFFD. */
1780     private static final byte[] REPLACEMENT = { (byte) 0xFF, (byte) 0xFD };
1781 
1782     /**
1783      * Decodes '%'-escaped octets in the given string using the UTF-8 scheme.
1784      * Replaces invalid octets with the unicode replacement character
1785      * ("\\uFFFD").
1786      *
1787      * @param s encoded string to decode
1788      * @return the given string with escaped octets decoded, or null if
1789      *  s is null
1790      */
decode(String s)1791     public static String decode(String s) {
1792         /*
1793         Compared to java.net.URLEncoderDecoder.decode(), this method decodes a
1794         chunk at a time instead of one character at a time, and it doesn't
1795         throw exceptions. It also only allocates memory when necessary--if
1796         there's nothing to decode, this method won't do much.
1797         */
1798 
1799         if (s == null) {
1800             return null;
1801         }
1802 
1803         // Lazily-initialized buffers.
1804         StringBuilder decoded = null;
1805         ByteArrayOutputStream out = null;
1806 
1807         int oldLength = s.length();
1808 
1809         // This loop alternates between copying over normal characters and
1810         // escaping in chunks. This results in fewer method calls and
1811         // allocations than decoding one character at a time.
1812         int current = 0;
1813         while (current < oldLength) {
1814             // Start in "copying" mode where we copy over normal characters.
1815 
1816             // Find the next escape sequence.
1817             int nextEscape = s.indexOf('%', current);
1818 
1819             if (nextEscape == NOT_FOUND) {
1820                 if (decoded == null) {
1821                     // We didn't actually decode anything.
1822                     return s;
1823                 } else {
1824                     // Append the remainder and return the decoded string.
1825                     decoded.append(s, current, oldLength);
1826                     return decoded.toString();
1827                 }
1828             }
1829 
1830             // Prepare buffers.
1831             if (decoded == null) {
1832                 // Looks like we're going to need the buffers...
1833                 // We know the new string will be shorter. Using the old length
1834                 // may overshoot a bit, but it will save us from resizing the
1835                 // buffer.
1836                 decoded = new StringBuilder(oldLength);
1837                 out = new ByteArrayOutputStream(4);
1838             } else {
1839                 // Clear decoding buffer.
1840                 out.reset();
1841             }
1842 
1843             // Append characters leading up to the escape.
1844             if (nextEscape > current) {
1845                 decoded.append(s, current, nextEscape);
1846 
1847                 current = nextEscape;
1848             } else {
1849                 // assert current == nextEscape
1850             }
1851 
1852             // Switch to "decoding" mode where we decode a string of escape
1853             // sequences.
1854 
1855             // Decode and append escape sequences. Escape sequences look like
1856             // "%ab" where % is literal and a and b are hex digits.
1857             try {
1858                 do {
1859                     if (current + 2 >= oldLength) {
1860                         // Truncated escape sequence.
1861                         out.write(REPLACEMENT);
1862                     } else {
1863                         int a = Character.digit(s.charAt(current + 1), 16);
1864                         int b = Character.digit(s.charAt(current + 2), 16);
1865 
1866                         if (a == -1 || b == -1) {
1867                             // Non hex digits.
1868                             out.write(REPLACEMENT);
1869                         } else {
1870                             // Combine the hex digits into one byte and write.
1871                             out.write((a << 4) + b);
1872                         }
1873                     }
1874 
1875                     // Move passed the escape sequence.
1876                     current += 3;
1877                 } while (current < oldLength && s.charAt(current) == '%');
1878 
1879                 // Decode UTF-8 bytes into a string and append it.
1880                 decoded.append(out.toString(DEFAULT_ENCODING));
1881             } catch (UnsupportedEncodingException e) {
1882                 throw new AssertionError(e);
1883             } catch (IOException e) {
1884                 throw new AssertionError(e);
1885             }
1886         }
1887 
1888         // If we don't have a buffer, we didn't have to decode anything.
1889         return decoded == null ? s : decoded.toString();
1890     }
1891 
1892     /**
1893      * Support for part implementations.
1894      */
1895     static abstract class AbstractPart {
1896 
1897         /**
1898          * Enum which indicates which representation of a given part we have.
1899          */
1900         static class Representation {
1901             static final int BOTH = 0;
1902             static final int ENCODED = 1;
1903             static final int DECODED = 2;
1904         }
1905 
1906         volatile String encoded;
1907         volatile String decoded;
1908 
AbstractPart(String encoded, String decoded)1909         AbstractPart(String encoded, String decoded) {
1910             this.encoded = encoded;
1911             this.decoded = decoded;
1912         }
1913 
getEncoded()1914         abstract String getEncoded();
1915 
getDecoded()1916         final String getDecoded() {
1917             @SuppressWarnings("StringEquality")
1918             boolean hasDecoded = decoded != NOT_CACHED;
1919             return hasDecoded ? decoded : (decoded = decode(encoded));
1920         }
1921 
writeTo(Parcel parcel)1922         final void writeTo(Parcel parcel) {
1923             @SuppressWarnings("StringEquality")
1924             boolean hasEncoded = encoded != NOT_CACHED;
1925 
1926             @SuppressWarnings("StringEquality")
1927             boolean hasDecoded = decoded != NOT_CACHED;
1928 
1929             if (hasEncoded && hasDecoded) {
1930                 parcel.writeInt(Representation.BOTH);
1931                 parcel.writeString(encoded);
1932                 parcel.writeString(decoded);
1933             } else if (hasEncoded) {
1934                 parcel.writeInt(Representation.ENCODED);
1935                 parcel.writeString(encoded);
1936             } else if (hasDecoded) {
1937                 parcel.writeInt(Representation.DECODED);
1938                 parcel.writeString(decoded);
1939             } else {
1940                 throw new AssertionError();
1941             }
1942         }
1943     }
1944 
1945     /**
1946      * Immutable wrapper of encoded and decoded versions of a URI part. Lazily
1947      * creates the encoded or decoded version from the other.
1948      */
1949     static class Part extends AbstractPart {
1950 
1951         /** A part with null values. */
1952         static final Part NULL = new EmptyPart(null);
1953 
1954         /** A part with empty strings for values. */
1955         static final Part EMPTY = new EmptyPart("");
1956 
Part(String encoded, String decoded)1957         private Part(String encoded, String decoded) {
1958             super(encoded, decoded);
1959         }
1960 
isEmpty()1961         boolean isEmpty() {
1962             return false;
1963         }
1964 
getEncoded()1965         String getEncoded() {
1966             @SuppressWarnings("StringEquality")
1967             boolean hasEncoded = encoded != NOT_CACHED;
1968             return hasEncoded ? encoded : (encoded = encode(decoded));
1969         }
1970 
readFrom(Parcel parcel)1971         static Part readFrom(Parcel parcel) {
1972             int representation = parcel.readInt();
1973             switch (representation) {
1974                 case Representation.BOTH:
1975                     return from(parcel.readString(), parcel.readString());
1976                 case Representation.ENCODED:
1977                     return fromEncoded(parcel.readString());
1978                 case Representation.DECODED:
1979                     return fromDecoded(parcel.readString());
1980                 default:
1981                     throw new AssertionError();
1982             }
1983         }
1984 
1985         /**
1986          * Returns given part or {@link #NULL} if the given part is null.
1987          */
nonNull(Part part)1988         static Part nonNull(Part part) {
1989             return part == null ? NULL : part;
1990         }
1991 
1992         /**
1993          * Creates a part from the encoded string.
1994          *
1995          * @param encoded part string
1996          */
fromEncoded(String encoded)1997         static Part fromEncoded(String encoded) {
1998             return from(encoded, NOT_CACHED);
1999         }
2000 
2001         /**
2002          * Creates a part from the decoded string.
2003          *
2004          * @param decoded part string
2005          */
fromDecoded(String decoded)2006         static Part fromDecoded(String decoded) {
2007             return from(NOT_CACHED, decoded);
2008         }
2009 
2010         /**
2011          * Creates a part from the encoded and decoded strings.
2012          *
2013          * @param encoded part string
2014          * @param decoded part string
2015          */
from(String encoded, String decoded)2016         static Part from(String encoded, String decoded) {
2017             // We have to check both encoded and decoded in case one is
2018             // NOT_CACHED.
2019 
2020             if (encoded == null) {
2021                 return NULL;
2022             }
2023             if (encoded.length() == 0) {
2024                 return EMPTY;
2025             }
2026 
2027             if (decoded == null) {
2028                 return NULL;
2029             }
2030             if (decoded .length() == 0) {
2031                 return EMPTY;
2032             }
2033 
2034             return new Part(encoded, decoded);
2035         }
2036 
2037         private static class EmptyPart extends Part {
EmptyPart(String value)2038             public EmptyPart(String value) {
2039                 super(value, value);
2040             }
2041 
2042             @Override
isEmpty()2043             boolean isEmpty() {
2044                 return true;
2045             }
2046         }
2047     }
2048 
2049     /**
2050      * Immutable wrapper of encoded and decoded versions of a path part. Lazily
2051      * creates the encoded or decoded version from the other.
2052      */
2053     static class PathPart extends AbstractPart {
2054 
2055         /** A part with null values. */
2056         static final PathPart NULL = new PathPart(null, null);
2057 
2058         /** A part with empty strings for values. */
2059         static final PathPart EMPTY = new PathPart("", "");
2060 
PathPart(String encoded, String decoded)2061         private PathPart(String encoded, String decoded) {
2062             super(encoded, decoded);
2063         }
2064 
getEncoded()2065         String getEncoded() {
2066             @SuppressWarnings("StringEquality")
2067             boolean hasEncoded = encoded != NOT_CACHED;
2068 
2069             // Don't encode '/'.
2070             return hasEncoded ? encoded : (encoded = encode(decoded, "/"));
2071         }
2072 
2073         /**
2074          * Cached path segments. This doesn't need to be volatile--we don't
2075          * care if other threads see the result.
2076          */
2077         private PathSegments pathSegments;
2078 
2079         /**
2080          * Gets the individual path segments. Parses them if necessary.
2081          *
2082          * @return parsed path segments or null if this isn't a hierarchical
2083          *  URI
2084          */
getPathSegments()2085         PathSegments getPathSegments() {
2086             if (pathSegments != null) {
2087                 return pathSegments;
2088             }
2089 
2090             String path = getEncoded();
2091             if (path == null) {
2092                 return pathSegments = PathSegments.EMPTY;
2093             }
2094 
2095             PathSegmentsBuilder segmentBuilder = new PathSegmentsBuilder();
2096 
2097             int previous = 0;
2098             int current;
2099             while ((current = path.indexOf('/', previous)) > -1) {
2100                 // This check keeps us from adding a segment if the path starts
2101                 // '/' and an empty segment for "//".
2102                 if (previous < current) {
2103                     String decodedSegment
2104                             = decode(path.substring(previous, current));
2105                     segmentBuilder.add(decodedSegment);
2106                 }
2107                 previous = current + 1;
2108             }
2109 
2110             // Add in the final path segment.
2111             if (previous < path.length()) {
2112                 segmentBuilder.add(decode(path.substring(previous)));
2113             }
2114 
2115             return pathSegments = segmentBuilder.build();
2116         }
2117 
appendEncodedSegment(PathPart oldPart, String newSegment)2118         static PathPart appendEncodedSegment(PathPart oldPart,
2119                 String newSegment) {
2120             // If there is no old path, should we make the new path relative
2121             // or absolute? I pick absolute.
2122 
2123             if (oldPart == null) {
2124                 // No old path.
2125                 return fromEncoded("/" + newSegment);
2126             }
2127 
2128             String oldPath = oldPart.getEncoded();
2129 
2130             if (oldPath == null) {
2131                 oldPath = "";
2132             }
2133 
2134             int oldPathLength = oldPath.length();
2135             String newPath;
2136             if (oldPathLength == 0) {
2137                 // No old path.
2138                 newPath = "/" + newSegment;
2139             } else if (oldPath.charAt(oldPathLength - 1) == '/') {
2140                 newPath = oldPath + newSegment;
2141             } else {
2142                 newPath = oldPath + "/" + newSegment;
2143             }
2144 
2145             return fromEncoded(newPath);
2146         }
2147 
appendDecodedSegment(PathPart oldPart, String decoded)2148         static PathPart appendDecodedSegment(PathPart oldPart, String decoded) {
2149             String encoded = encode(decoded);
2150 
2151             // TODO: Should we reuse old PathSegments? Probably not.
2152             return appendEncodedSegment(oldPart, encoded);
2153         }
2154 
readFrom(Parcel parcel)2155         static PathPart readFrom(Parcel parcel) {
2156             int representation = parcel.readInt();
2157             switch (representation) {
2158                 case Representation.BOTH:
2159                     return from(parcel.readString(), parcel.readString());
2160                 case Representation.ENCODED:
2161                     return fromEncoded(parcel.readString());
2162                 case Representation.DECODED:
2163                     return fromDecoded(parcel.readString());
2164                 default:
2165                     throw new AssertionError();
2166             }
2167         }
2168 
2169         /**
2170          * Creates a path from the encoded string.
2171          *
2172          * @param encoded part string
2173          */
fromEncoded(String encoded)2174         static PathPart fromEncoded(String encoded) {
2175             return from(encoded, NOT_CACHED);
2176         }
2177 
2178         /**
2179          * Creates a path from the decoded string.
2180          *
2181          * @param decoded part string
2182          */
fromDecoded(String decoded)2183         static PathPart fromDecoded(String decoded) {
2184             return from(NOT_CACHED, decoded);
2185         }
2186 
2187         /**
2188          * Creates a path from the encoded and decoded strings.
2189          *
2190          * @param encoded part string
2191          * @param decoded part string
2192          */
from(String encoded, String decoded)2193         static PathPart from(String encoded, String decoded) {
2194             if (encoded == null) {
2195                 return NULL;
2196             }
2197 
2198             if (encoded.length() == 0) {
2199                 return EMPTY;
2200             }
2201 
2202             return new PathPart(encoded, decoded);
2203         }
2204 
2205         /**
2206          * Prepends path values with "/" if they're present, not empty, and
2207          * they don't already start with "/".
2208          */
makeAbsolute(PathPart oldPart)2209         static PathPart makeAbsolute(PathPart oldPart) {
2210             @SuppressWarnings("StringEquality")
2211             boolean encodedCached = oldPart.encoded != NOT_CACHED;
2212 
2213             // We don't care which version we use, and we don't want to force
2214             // unneccessary encoding/decoding.
2215             String oldPath = encodedCached ? oldPart.encoded : oldPart.decoded;
2216 
2217             if (oldPath == null || oldPath.length() == 0
2218                     || oldPath.startsWith("/")) {
2219                 return oldPart;
2220             }
2221 
2222             // Prepend encoded string if present.
2223             String newEncoded = encodedCached
2224                     ? "/" + oldPart.encoded : NOT_CACHED;
2225 
2226             // Prepend decoded string if present.
2227             @SuppressWarnings("StringEquality")
2228             boolean decodedCached = oldPart.decoded != NOT_CACHED;
2229             String newDecoded = decodedCached
2230                     ? "/" + oldPart.decoded
2231                     : NOT_CACHED;
2232 
2233             return new PathPart(newEncoded, newDecoded);
2234         }
2235     }
2236 
2237     /**
2238      * Creates a new Uri by appending an already-encoded path segment to a
2239      * base Uri.
2240      *
2241      * @param baseUri Uri to append path segment to
2242      * @param pathSegment encoded path segment to append
2243      * @return a new Uri based on baseUri with the given segment appended to
2244      *  the path
2245      * @throws NullPointerException if baseUri is null
2246      */
withAppendedPath(Uri baseUri, String pathSegment)2247     public static Uri withAppendedPath(Uri baseUri, String pathSegment) {
2248         Builder builder = baseUri.buildUpon();
2249         builder = builder.appendEncodedPath(pathSegment);
2250         return builder.build();
2251     }
2252 }
2253