• 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 = 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 
getPort()1049         public int getPort() {
1050             return port == NOT_CALCULATED
1051                     ? port = parsePort()
1052                     : port;
1053         }
1054 
parsePort()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 
HierarchicalUri(String scheme, Part authority, PathPart path, Part query, Part fragment)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 
readFrom(Parcel parcel)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 
describeContents()1113         public int describeContents() {
1114             return 0;
1115         }
1116 
writeToParcel(Parcel parcel, int flags)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 
isHierarchical()1126         public boolean isHierarchical() {
1127             return true;
1128         }
1129 
isRelative()1130         public boolean isRelative() {
1131             return scheme == null;
1132         }
1133 
getScheme()1134         public String getScheme() {
1135             return scheme;
1136         }
1137 
1138         private Part ssp;
1139 
getSsp()1140         private Part getSsp() {
1141             return ssp == null
1142                     ? ssp = Part.fromEncoded(makeSchemeSpecificPart()) : ssp;
1143         }
1144 
getEncodedSchemeSpecificPart()1145         public String getEncodedSchemeSpecificPart() {
1146             return getSsp().getEncoded();
1147         }
1148 
getSchemeSpecificPart()1149         public String getSchemeSpecificPart() {
1150             return getSsp().getDecoded();
1151         }
1152 
1153         /**
1154          * Creates the encoded scheme-specific part from its sub parts.
1155          */
makeSchemeSpecificPart()1156         private String makeSchemeSpecificPart() {
1157             StringBuilder builder = new StringBuilder();
1158             appendSspTo(builder);
1159             return builder.toString();
1160         }
1161 
appendSspTo(StringBuilder builder)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 
getAuthority()1179         public String getAuthority() {
1180             return this.authority.getDecoded();
1181         }
1182 
getEncodedAuthority()1183         public String getEncodedAuthority() {
1184             return this.authority.getEncoded();
1185         }
1186 
getEncodedPath()1187         public String getEncodedPath() {
1188             return this.path.getEncoded();
1189         }
1190 
getPath()1191         public String getPath() {
1192             return this.path.getDecoded();
1193         }
1194 
getQuery()1195         public String getQuery() {
1196             return this.query.getDecoded();
1197         }
1198 
getEncodedQuery()1199         public String getEncodedQuery() {
1200             return this.query.getEncoded();
1201         }
1202 
getFragment()1203         public String getFragment() {
1204             return this.fragment.getDecoded();
1205         }
1206 
getEncodedFragment()1207         public String getEncodedFragment() {
1208             return this.fragment.getEncoded();
1209         }
1210 
getPathSegments()1211         public List<String> getPathSegments() {
1212             return this.path.getPathSegments();
1213         }
1214 
1215         private volatile String uriString = NOT_CACHED;
1216 
1217         @Override
toString()1218         public String toString() {
1219             @SuppressWarnings("StringEquality")
1220             boolean cached = (uriString != NOT_CACHED);
1221             return cached ? uriString
1222                     : (uriString = makeUriString());
1223         }
1224 
makeUriString()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 
buildUpon()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 &lt;scheme&gt;://&lt;authority&gt;&lt;absolute path&gt;?&lt;query&gt;#&lt;fragment&gt;}
1257      *
1258      * <p>Relative URI references (which are always hierarchical) follow one
1259      * of two patterns: {@code &lt;relative or absolute path&gt;?&lt;query&gt;#&lt;fragment&gt;}
1260      * or {@code //&lt;authority&gt;&lt;absolute path&gt;?&lt;query&gt;#&lt;fragment&gt;}
1261      *
1262      * <p>An opaque URI follows this pattern:
1263      * {@code &lt;scheme&gt;:&lt;opaque part&gt;#&lt;fragment&gt;}
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          */
Builder()1277         public Builder() {}
1278 
1279         /**
1280          * Sets the scheme.
1281          *
1282          * @param scheme name or {@code null} if this is a relative Uri
1283          */
scheme(String scheme)1284         public Builder scheme(String scheme) {
1285             this.scheme = scheme;
1286             return this;
1287         }
1288 
opaquePart(Part opaquePart)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          */
opaquePart(String opaquePart)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          */
encodedOpaquePart(String opaquePart)1308         public Builder encodedOpaquePart(String opaquePart) {
1309             return opaquePart(Part.fromEncoded(opaquePart));
1310         }
1311 
authority(Part authority)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          */
authority(String authority)1323         public Builder authority(String authority) {
1324             return authority(Part.fromDecoded(authority));
1325         }
1326 
1327         /**
1328          * Sets the previously encoded authority.
1329          */
encodedAuthority(String authority)1330         public Builder encodedAuthority(String authority) {
1331             return authority(Part.fromEncoded(authority));
1332         }
1333 
path(PathPart path)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          */
path(String path)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          */
encodedPath(String path)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          */
appendPath(String newSegment)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          */
appendEncodedPath(String newSegment)1375         public Builder appendEncodedPath(String newSegment) {
1376             return path(PathPart.appendEncodedSegment(path, newSegment));
1377         }
1378 
query(Part query)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          */
query(String query)1390         public Builder query(String query) {
1391             return query(Part.fromDecoded(query));
1392         }
1393 
1394         /**
1395          * Sets the previously encoded query.
1396          */
encodedQuery(String query)1397         public Builder encodedQuery(String query) {
1398             return query(Part.fromEncoded(query));
1399         }
1400 
fragment(Part fragment)1401         Builder fragment(Part fragment) {
1402             this.fragment = fragment;
1403             return this;
1404         }
1405 
1406         /**
1407          * Encodes and sets the fragment.
1408          */
fragment(String fragment)1409         public Builder fragment(String fragment) {
1410             return fragment(Part.fromDecoded(fragment));
1411         }
1412 
1413         /**
1414          * Sets the previously encoded fragment.
1415          */
encodedFragment(String fragment)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          */
appendQueryParameter(String key, String value)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          */
build()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 
hasSchemeOrAuthority()1481         private boolean hasSchemeOrAuthority() {
1482             return scheme != null
1483                     || (authority != null && authority != Part.NULL);
1484 
1485         }
1486 
1487         @Override
toString()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      */
getQueryParameters(String key)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      */
getQueryParameter(String key)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     /** Identifies a null parcelled Uri. */
1612     private static final int NULL_TYPE_ID = 0;
1613 
1614     /**
1615      * Reads Uris from Parcels.
1616      */
1617     public static final Parcelable.Creator<Uri> CREATOR
1618             = new Parcelable.Creator<Uri>() {
1619         public Uri createFromParcel(Parcel in) {
1620             int type = in.readInt();
1621             switch (type) {
1622                 case NULL_TYPE_ID: return null;
1623                 case StringUri.TYPE_ID: return StringUri.readFrom(in);
1624                 case OpaqueUri.TYPE_ID: return OpaqueUri.readFrom(in);
1625                 case HierarchicalUri.TYPE_ID:
1626                     return HierarchicalUri.readFrom(in);
1627             }
1628 
1629             throw new AssertionError("Unknown URI type: " + type);
1630         }
1631 
1632         public Uri[] newArray(int size) {
1633             return new Uri[size];
1634         }
1635     };
1636 
1637     /**
1638      * Writes a Uri to a Parcel.
1639      *
1640      * @param out parcel to write to
1641      * @param uri to write, can be null
1642      */
writeToParcel(Parcel out, Uri uri)1643     public static void writeToParcel(Parcel out, Uri uri) {
1644         if (uri == null) {
1645             out.writeInt(NULL_TYPE_ID);
1646         } else {
1647             uri.writeToParcel(out, 0);
1648         }
1649     }
1650 
1651     private static final char[] HEX_DIGITS = "0123456789ABCDEF".toCharArray();
1652 
1653     /**
1654      * Encodes characters in the given string as '%'-escaped octets
1655      * using the UTF-8 scheme. Leaves letters ("A-Z", "a-z"), numbers
1656      * ("0-9"), and unreserved characters ("_-!.~'()*") intact. Encodes
1657      * all other characters.
1658      *
1659      * @param s string to encode
1660      * @return an encoded version of s suitable for use as a URI component,
1661      *  or null if s is null
1662      */
encode(String s)1663     public static String encode(String s) {
1664         return encode(s, null);
1665     }
1666 
1667     /**
1668      * Encodes characters in the given string as '%'-escaped octets
1669      * using the UTF-8 scheme. Leaves letters ("A-Z", "a-z"), numbers
1670      * ("0-9"), and unreserved characters ("_-!.~'()*") intact. Encodes
1671      * all other characters with the exception of those specified in the
1672      * allow argument.
1673      *
1674      * @param s string to encode
1675      * @param allow set of additional characters to allow in the encoded form,
1676      *  null if no characters should be skipped
1677      * @return an encoded version of s suitable for use as a URI component,
1678      *  or null if s is null
1679      */
encode(String s, String allow)1680     public static String encode(String s, String allow) {
1681         if (s == null) {
1682             return null;
1683         }
1684 
1685         // Lazily-initialized buffers.
1686         StringBuilder encoded = null;
1687 
1688         int oldLength = s.length();
1689 
1690         // This loop alternates between copying over allowed characters and
1691         // encoding in chunks. This results in fewer method calls and
1692         // allocations than encoding one character at a time.
1693         int current = 0;
1694         while (current < oldLength) {
1695             // Start in "copying" mode where we copy over allowed chars.
1696 
1697             // Find the next character which needs to be encoded.
1698             int nextToEncode = current;
1699             while (nextToEncode < oldLength
1700                     && isAllowed(s.charAt(nextToEncode), allow)) {
1701                 nextToEncode++;
1702             }
1703 
1704             // If there's nothing more to encode...
1705             if (nextToEncode == oldLength) {
1706                 if (current == 0) {
1707                     // We didn't need to encode anything!
1708                     return s;
1709                 } else {
1710                     // Presumably, we've already done some encoding.
1711                     encoded.append(s, current, oldLength);
1712                     return encoded.toString();
1713                 }
1714             }
1715 
1716             if (encoded == null) {
1717                 encoded = new StringBuilder();
1718             }
1719 
1720             if (nextToEncode > current) {
1721                 // Append allowed characters leading up to this point.
1722                 encoded.append(s, current, nextToEncode);
1723             } else {
1724                 // assert nextToEncode == current
1725             }
1726 
1727             // Switch to "encoding" mode.
1728 
1729             // Find the next allowed character.
1730             current = nextToEncode;
1731             int nextAllowed = current + 1;
1732             while (nextAllowed < oldLength
1733                     && !isAllowed(s.charAt(nextAllowed), allow)) {
1734                 nextAllowed++;
1735             }
1736 
1737             // Convert the substring to bytes and encode the bytes as
1738             // '%'-escaped octets.
1739             String toEncode = s.substring(current, nextAllowed);
1740             try {
1741                 byte[] bytes = toEncode.getBytes(DEFAULT_ENCODING);
1742                 int bytesLength = bytes.length;
1743                 for (int i = 0; i < bytesLength; i++) {
1744                     encoded.append('%');
1745                     encoded.append(HEX_DIGITS[(bytes[i] & 0xf0) >> 4]);
1746                     encoded.append(HEX_DIGITS[bytes[i] & 0xf]);
1747                 }
1748             } catch (UnsupportedEncodingException e) {
1749                 throw new AssertionError(e);
1750             }
1751 
1752             current = nextAllowed;
1753         }
1754 
1755         // Encoded could still be null at this point if s is empty.
1756         return encoded == null ? s : encoded.toString();
1757     }
1758 
1759     /**
1760      * Returns true if the given character is allowed.
1761      *
1762      * @param c character to check
1763      * @param allow characters to allow
1764      * @return true if the character is allowed or false if it should be
1765      *  encoded
1766      */
isAllowed(char c, String allow)1767     private static boolean isAllowed(char c, String allow) {
1768         return (c >= 'A' && c <= 'Z')
1769                 || (c >= 'a' && c <= 'z')
1770                 || (c >= '0' && c <= '9')
1771                 || "_-!.~'()*".indexOf(c) != NOT_FOUND
1772                 || (allow != null && allow.indexOf(c) != NOT_FOUND);
1773     }
1774 
1775     /** Unicode replacement character: \\uFFFD. */
1776     private static final byte[] REPLACEMENT = { (byte) 0xFF, (byte) 0xFD };
1777 
1778     /**
1779      * Decodes '%'-escaped octets in the given string using the UTF-8 scheme.
1780      * Replaces invalid octets with the unicode replacement character
1781      * ("\\uFFFD").
1782      *
1783      * @param s encoded string to decode
1784      * @return the given string with escaped octets decoded, or null if
1785      *  s is null
1786      */
decode(String s)1787     public static String decode(String s) {
1788         /*
1789         Compared to java.net.URLEncoderDecoder.decode(), this method decodes a
1790         chunk at a time instead of one character at a time, and it doesn't
1791         throw exceptions. It also only allocates memory when necessary--if
1792         there's nothing to decode, this method won't do much.
1793         */
1794 
1795         if (s == null) {
1796             return null;
1797         }
1798 
1799         // Lazily-initialized buffers.
1800         StringBuilder decoded = null;
1801         ByteArrayOutputStream out = null;
1802 
1803         int oldLength = s.length();
1804 
1805         // This loop alternates between copying over normal characters and
1806         // escaping in chunks. This results in fewer method calls and
1807         // allocations than decoding one character at a time.
1808         int current = 0;
1809         while (current < oldLength) {
1810             // Start in "copying" mode where we copy over normal characters.
1811 
1812             // Find the next escape sequence.
1813             int nextEscape = s.indexOf('%', current);
1814 
1815             if (nextEscape == NOT_FOUND) {
1816                 if (decoded == null) {
1817                     // We didn't actually decode anything.
1818                     return s;
1819                 } else {
1820                     // Append the remainder and return the decoded string.
1821                     decoded.append(s, current, oldLength);
1822                     return decoded.toString();
1823                 }
1824             }
1825 
1826             // Prepare buffers.
1827             if (decoded == null) {
1828                 // Looks like we're going to need the buffers...
1829                 // We know the new string will be shorter. Using the old length
1830                 // may overshoot a bit, but it will save us from resizing the
1831                 // buffer.
1832                 decoded = new StringBuilder(oldLength);
1833                 out = new ByteArrayOutputStream(4);
1834             } else {
1835                 // Clear decoding buffer.
1836                 out.reset();
1837             }
1838 
1839             // Append characters leading up to the escape.
1840             if (nextEscape > current) {
1841                 decoded.append(s, current, nextEscape);
1842 
1843                 current = nextEscape;
1844             } else {
1845                 // assert current == nextEscape
1846             }
1847 
1848             // Switch to "decoding" mode where we decode a string of escape
1849             // sequences.
1850 
1851             // Decode and append escape sequences. Escape sequences look like
1852             // "%ab" where % is literal and a and b are hex digits.
1853             try {
1854                 do {
1855                     if (current + 2 >= oldLength) {
1856                         // Truncated escape sequence.
1857                         out.write(REPLACEMENT);
1858                     } else {
1859                         int a = Character.digit(s.charAt(current + 1), 16);
1860                         int b = Character.digit(s.charAt(current + 2), 16);
1861 
1862                         if (a == -1 || b == -1) {
1863                             // Non hex digits.
1864                             out.write(REPLACEMENT);
1865                         } else {
1866                             // Combine the hex digits into one byte and write.
1867                             out.write((a << 4) + b);
1868                         }
1869                     }
1870 
1871                     // Move passed the escape sequence.
1872                     current += 3;
1873                 } while (current < oldLength && s.charAt(current) == '%');
1874 
1875                 // Decode UTF-8 bytes into a string and append it.
1876                 decoded.append(out.toString(DEFAULT_ENCODING));
1877             } catch (UnsupportedEncodingException e) {
1878                 throw new AssertionError(e);
1879             } catch (IOException e) {
1880                 throw new AssertionError(e);
1881             }
1882         }
1883 
1884         // If we don't have a buffer, we didn't have to decode anything.
1885         return decoded == null ? s : decoded.toString();
1886     }
1887 
1888     /**
1889      * Support for part implementations.
1890      */
1891     static abstract class AbstractPart {
1892 
1893         /**
1894          * Enum which indicates which representation of a given part we have.
1895          */
1896         static class Representation {
1897             static final int BOTH = 0;
1898             static final int ENCODED = 1;
1899             static final int DECODED = 2;
1900         }
1901 
1902         volatile String encoded;
1903         volatile String decoded;
1904 
AbstractPart(String encoded, String decoded)1905         AbstractPart(String encoded, String decoded) {
1906             this.encoded = encoded;
1907             this.decoded = decoded;
1908         }
1909 
getEncoded()1910         abstract String getEncoded();
1911 
getDecoded()1912         final String getDecoded() {
1913             @SuppressWarnings("StringEquality")
1914             boolean hasDecoded = decoded != NOT_CACHED;
1915             return hasDecoded ? decoded : (decoded = decode(encoded));
1916         }
1917 
writeTo(Parcel parcel)1918         final void writeTo(Parcel parcel) {
1919             @SuppressWarnings("StringEquality")
1920             boolean hasEncoded = encoded != NOT_CACHED;
1921 
1922             @SuppressWarnings("StringEquality")
1923             boolean hasDecoded = decoded != NOT_CACHED;
1924 
1925             if (hasEncoded && hasDecoded) {
1926                 parcel.writeInt(Representation.BOTH);
1927                 parcel.writeString(encoded);
1928                 parcel.writeString(decoded);
1929             } else if (hasEncoded) {
1930                 parcel.writeInt(Representation.ENCODED);
1931                 parcel.writeString(encoded);
1932             } else if (hasDecoded) {
1933                 parcel.writeInt(Representation.DECODED);
1934                 parcel.writeString(decoded);
1935             } else {
1936                 throw new AssertionError();
1937             }
1938         }
1939     }
1940 
1941     /**
1942      * Immutable wrapper of encoded and decoded versions of a URI part. Lazily
1943      * creates the encoded or decoded version from the other.
1944      */
1945     static class Part extends AbstractPart {
1946 
1947         /** A part with null values. */
1948         static final Part NULL = new EmptyPart(null);
1949 
1950         /** A part with empty strings for values. */
1951         static final Part EMPTY = new EmptyPart("");
1952 
Part(String encoded, String decoded)1953         private Part(String encoded, String decoded) {
1954             super(encoded, decoded);
1955         }
1956 
isEmpty()1957         boolean isEmpty() {
1958             return false;
1959         }
1960 
getEncoded()1961         String getEncoded() {
1962             @SuppressWarnings("StringEquality")
1963             boolean hasEncoded = encoded != NOT_CACHED;
1964             return hasEncoded ? encoded : (encoded = encode(decoded));
1965         }
1966 
readFrom(Parcel parcel)1967         static Part readFrom(Parcel parcel) {
1968             int representation = parcel.readInt();
1969             switch (representation) {
1970                 case Representation.BOTH:
1971                     return from(parcel.readString(), parcel.readString());
1972                 case Representation.ENCODED:
1973                     return fromEncoded(parcel.readString());
1974                 case Representation.DECODED:
1975                     return fromDecoded(parcel.readString());
1976                 default:
1977                     throw new AssertionError();
1978             }
1979         }
1980 
1981         /**
1982          * Returns given part or {@link #NULL} if the given part is null.
1983          */
nonNull(Part part)1984         static Part nonNull(Part part) {
1985             return part == null ? NULL : part;
1986         }
1987 
1988         /**
1989          * Creates a part from the encoded string.
1990          *
1991          * @param encoded part string
1992          */
fromEncoded(String encoded)1993         static Part fromEncoded(String encoded) {
1994             return from(encoded, NOT_CACHED);
1995         }
1996 
1997         /**
1998          * Creates a part from the decoded string.
1999          *
2000          * @param decoded part string
2001          */
fromDecoded(String decoded)2002         static Part fromDecoded(String decoded) {
2003             return from(NOT_CACHED, decoded);
2004         }
2005 
2006         /**
2007          * Creates a part from the encoded and decoded strings.
2008          *
2009          * @param encoded part string
2010          * @param decoded part string
2011          */
from(String encoded, String decoded)2012         static Part from(String encoded, String decoded) {
2013             // We have to check both encoded and decoded in case one is
2014             // NOT_CACHED.
2015 
2016             if (encoded == null) {
2017                 return NULL;
2018             }
2019             if (encoded.length() == 0) {
2020                 return EMPTY;
2021             }
2022 
2023             if (decoded == null) {
2024                 return NULL;
2025             }
2026             if (decoded .length() == 0) {
2027                 return EMPTY;
2028             }
2029 
2030             return new Part(encoded, decoded);
2031         }
2032 
2033         private static class EmptyPart extends Part {
EmptyPart(String value)2034             public EmptyPart(String value) {
2035                 super(value, value);
2036             }
2037 
2038             @Override
isEmpty()2039             boolean isEmpty() {
2040                 return true;
2041             }
2042         }
2043     }
2044 
2045     /**
2046      * Immutable wrapper of encoded and decoded versions of a path part. Lazily
2047      * creates the encoded or decoded version from the other.
2048      */
2049     static class PathPart extends AbstractPart {
2050 
2051         /** A part with null values. */
2052         static final PathPart NULL = new PathPart(null, null);
2053 
2054         /** A part with empty strings for values. */
2055         static final PathPart EMPTY = new PathPart("", "");
2056 
PathPart(String encoded, String decoded)2057         private PathPart(String encoded, String decoded) {
2058             super(encoded, decoded);
2059         }
2060 
getEncoded()2061         String getEncoded() {
2062             @SuppressWarnings("StringEquality")
2063             boolean hasEncoded = encoded != NOT_CACHED;
2064 
2065             // Don't encode '/'.
2066             return hasEncoded ? encoded : (encoded = encode(decoded, "/"));
2067         }
2068 
2069         /**
2070          * Cached path segments. This doesn't need to be volatile--we don't
2071          * care if other threads see the result.
2072          */
2073         private PathSegments pathSegments;
2074 
2075         /**
2076          * Gets the individual path segments. Parses them if necessary.
2077          *
2078          * @return parsed path segments or null if this isn't a hierarchical
2079          *  URI
2080          */
getPathSegments()2081         PathSegments getPathSegments() {
2082             if (pathSegments != null) {
2083                 return pathSegments;
2084             }
2085 
2086             String path = getEncoded();
2087             if (path == null) {
2088                 return pathSegments = PathSegments.EMPTY;
2089             }
2090 
2091             PathSegmentsBuilder segmentBuilder = new PathSegmentsBuilder();
2092 
2093             int previous = 0;
2094             int current;
2095             while ((current = path.indexOf('/', previous)) > -1) {
2096                 // This check keeps us from adding a segment if the path starts
2097                 // '/' and an empty segment for "//".
2098                 if (previous < current) {
2099                     String decodedSegment
2100                             = decode(path.substring(previous, current));
2101                     segmentBuilder.add(decodedSegment);
2102                 }
2103                 previous = current + 1;
2104             }
2105 
2106             // Add in the final path segment.
2107             if (previous < path.length()) {
2108                 segmentBuilder.add(decode(path.substring(previous)));
2109             }
2110 
2111             return pathSegments = segmentBuilder.build();
2112         }
2113 
appendEncodedSegment(PathPart oldPart, String newSegment)2114         static PathPart appendEncodedSegment(PathPart oldPart,
2115                 String newSegment) {
2116             // If there is no old path, should we make the new path relative
2117             // or absolute? I pick absolute.
2118 
2119             if (oldPart == null) {
2120                 // No old path.
2121                 return fromEncoded("/" + newSegment);
2122             }
2123 
2124             String oldPath = oldPart.getEncoded();
2125 
2126             if (oldPath == null) {
2127                 oldPath = "";
2128             }
2129 
2130             int oldPathLength = oldPath.length();
2131             String newPath;
2132             if (oldPathLength == 0) {
2133                 // No old path.
2134                 newPath = "/" + newSegment;
2135             } else if (oldPath.charAt(oldPathLength - 1) == '/') {
2136                 newPath = oldPath + newSegment;
2137             } else {
2138                 newPath = oldPath + "/" + newSegment;
2139             }
2140 
2141             return fromEncoded(newPath);
2142         }
2143 
appendDecodedSegment(PathPart oldPart, String decoded)2144         static PathPart appendDecodedSegment(PathPart oldPart, String decoded) {
2145             String encoded = encode(decoded);
2146 
2147             // TODO: Should we reuse old PathSegments? Probably not.
2148             return appendEncodedSegment(oldPart, encoded);
2149         }
2150 
readFrom(Parcel parcel)2151         static PathPart readFrom(Parcel parcel) {
2152             int representation = parcel.readInt();
2153             switch (representation) {
2154                 case Representation.BOTH:
2155                     return from(parcel.readString(), parcel.readString());
2156                 case Representation.ENCODED:
2157                     return fromEncoded(parcel.readString());
2158                 case Representation.DECODED:
2159                     return fromDecoded(parcel.readString());
2160                 default:
2161                     throw new AssertionError();
2162             }
2163         }
2164 
2165         /**
2166          * Creates a path from the encoded string.
2167          *
2168          * @param encoded part string
2169          */
fromEncoded(String encoded)2170         static PathPart fromEncoded(String encoded) {
2171             return from(encoded, NOT_CACHED);
2172         }
2173 
2174         /**
2175          * Creates a path from the decoded string.
2176          *
2177          * @param decoded part string
2178          */
fromDecoded(String decoded)2179         static PathPart fromDecoded(String decoded) {
2180             return from(NOT_CACHED, decoded);
2181         }
2182 
2183         /**
2184          * Creates a path from the encoded and decoded strings.
2185          *
2186          * @param encoded part string
2187          * @param decoded part string
2188          */
from(String encoded, String decoded)2189         static PathPart from(String encoded, String decoded) {
2190             if (encoded == null) {
2191                 return NULL;
2192             }
2193 
2194             if (encoded.length() == 0) {
2195                 return EMPTY;
2196             }
2197 
2198             return new PathPart(encoded, decoded);
2199         }
2200 
2201         /**
2202          * Prepends path values with "/" if they're present, not empty, and
2203          * they don't already start with "/".
2204          */
makeAbsolute(PathPart oldPart)2205         static PathPart makeAbsolute(PathPart oldPart) {
2206             @SuppressWarnings("StringEquality")
2207             boolean encodedCached = oldPart.encoded != NOT_CACHED;
2208 
2209             // We don't care which version we use, and we don't want to force
2210             // unneccessary encoding/decoding.
2211             String oldPath = encodedCached ? oldPart.encoded : oldPart.decoded;
2212 
2213             if (oldPath == null || oldPath.length() == 0
2214                     || oldPath.startsWith("/")) {
2215                 return oldPart;
2216             }
2217 
2218             // Prepend encoded string if present.
2219             String newEncoded = encodedCached
2220                     ? "/" + oldPart.encoded : NOT_CACHED;
2221 
2222             // Prepend decoded string if present.
2223             @SuppressWarnings("StringEquality")
2224             boolean decodedCached = oldPart.decoded != NOT_CACHED;
2225             String newDecoded = decodedCached
2226                     ? "/" + oldPart.decoded
2227                     : NOT_CACHED;
2228 
2229             return new PathPart(newEncoded, newDecoded);
2230         }
2231     }
2232 
2233     /**
2234      * Creates a new Uri by appending an already-encoded path segment to a
2235      * base Uri.
2236      *
2237      * @param baseUri Uri to append path segment to
2238      * @param pathSegment encoded path segment to append
2239      * @return a new Uri based on baseUri with the given segment appended to
2240      *  the path
2241      * @throws NullPointerException if baseUri is null
2242      */
withAppendedPath(Uri baseUri, String pathSegment)2243     public static Uri withAppendedPath(Uri baseUri, String pathSegment) {
2244         Builder builder = baseUri.buildUpon();
2245         builder = builder.appendEncodedPath(pathSegment);
2246         return builder.build();
2247     }
2248 }
2249