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