View Javadoc
1   /*
2    *  Licensed to the Apache Software Foundation (ASF) under one or more
3    *  contributor license agreements.  See the NOTICE file distributed with
4    *  this work for additional information regarding copyright ownership.
5    *  The ASF licenses this file to You under the Apache License, Version 2.0
6    *  (the "License"); you may not use this file except in compliance with
7    *  the License.  You may obtain a copy of the License at
8    *
9    *     http://www.apache.org/licenses/LICENSE-2.0
10   *
11   *  Unless required by applicable law or agreed to in writing, software
12   *  distributed under the License is distributed on an "AS IS" BASIS,
13   *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14   *  See the License for the specific language governing permissions and
15   *  limitations under the License.
16   */
17  
18  package org.apache.river.api.net;
19  
20  import java.io.File;
21  import java.io.UnsupportedEncodingException;
22  import java.net.MalformedURLException;
23  import java.net.URI;
24  import java.net.URISyntaxException;
25  import java.net.URL;
26  import org.apache.river.impl.Messages;
27  
28  
29  /**
30   * This class represents an immutable instance of a URI as defined by RFC 3986.
31   * 
32   * This class replaces java.net.URI functionality.
33   * 
34   * Unlike java.net.URI this class is not Serializable and hashCode and 
35   * equality is governed by strict RFC3986 normalisation. In addition "other"
36   * characters allowed in java.net.URI as specified by javadoc, not specifically 
37   * allowed by RFC3986 are illegal and must be escaped.  This strict adherence
38   * is essential to eliminate false negative or positive matches.
39   * 
40   * In addition to RFC3896 normalisation, on OS platforms with a \ file separator
41   * the path is converted to UPPER CASE for comparison for file: schema, during
42   * equals and hashCode calls.
43   * 
44   * IPv6 and IPvFuture host addresses must be enclosed in square brackets as per 
45   * RFC3986.
46   * @since 3.0.0
47   */
48  public final class Uri implements Comparable<Uri> {
49  
50      /* Class Implementation */
51      
52      /* Legacy java.net.URI RFC 2396 syntax*/
53      static final String unreserved = "_-!.~\'()*"; //$NON-NLS-1$
54      static final String punct = ",;:$&+="; //$NON-NLS-1$
55      static final String reserved = punct + "?/[]@"; //$NON-NLS-1$
56      // String someLegal = unreserved + punct;
57      // String queryLegal = unreserved + reserved + "\\\""; //$NON-NLS-1$
58      // String allLegal = unreserved + reserved;
59      
60      static final String someLegal = unreserved + punct;
61      
62      static final String queryLegal = unreserved + reserved + "\\\""; //$NON-NLS-1$
63      
64  //    static final String allLegal = unreserved + reserved;
65      
66      /* RFC 3986 */
67  //    private static final char [] latin = new char[256];
68  //    private static final String [] latinEsc = new String[256];
69      
70      /* 2.1.  Percent-Encoding
71       * 
72       * A percent-encoding mechanism is used to represent a data octet in a
73       * component when that octet's corresponding character is outside the
74       * allowed set or is being used as a delimiter of, or within, the
75       * component.  A percent-encoded octet is encoded as a character
76       * triplet, consisting of the percent character "%" followed by the two
77       * hexadecimal digits representing that octet's numeric value.  For
78       * example, "%20" is the percent-encoding for the binary octet
79       * "00100000" (ABNF: %x20), which in US-ASCII corresponds to the space
80       * character (SP).  Section 2.4 describes when percent-encoding and
81       * decoding is applied.
82       * 
83       *    pct-encoded = "%" HEXDIG HEXDIG
84       * 
85       * The uppercase hexadecimal digits 'A' through 'F' are equivalent to
86       * the lowercase digits 'a' through 'f', respectively.  If two URIs
87       * differ only in the case of hexadecimal digits used in percent-encoded
88       * octets, they are equivalent.  For consistency, URI producers and
89       * normalizers should use uppercase hexadecimal digits for all percent-
90       * encodings.
91       */
92      // Any character that is not part of the reserved and unreserved sets must
93      // be encoded.
94      // Section 2.1 Percent encoding must be converted to upper case during normalisation.
95      private static final char escape = '%';
96       /* RFC3986 obsoletes RFC2396 and RFC2732
97       * 
98       * reserved    = gen-delims / sub-delims
99       * 
100      * gen-delims  = ":" / "/" / "?" / "#" / "[" / "]" / "@"
101      * 
102      * sub-delims  = "!" / "$" / "&" / "'" / "(" / ")"
103      *               / "*" / "+" / "," / ";" / "="
104      */
105     // Section 2.2 Reserved set is protected from normalisation.
106 //    private static final char [] gen_delims = {':', '/', '?', '#', '[', ']', '@'};
107 //    private static final char [] sub_delims = {'!', '$', '&', '\'', '(', ')', '*',
108 //                                                            '+', ',', ';', '='};
109     /*
110      * For consistency, percent-encoded octets in the ranges of ALPHA
111      * (%41-%5A and %61-%7A), DIGIT (%30-%39), hyphen (%2D), period (%2E),
112      * underscore (%5F), or tilde (%7E) should not be created by URI
113      * producers and, when found in a URI, should be decoded to their
114      * corresponding unreserved characters by URI normalizers.
115      */
116     // Section 2.3 Unreserved characters (Allowed) must be decoded during normalisation if % encoded.
117 //    private static final char [] lowalpha = "abcdefghijklmnopqrstuvwxyz".toCharArray();
118 //    private static final char [] upalpha = "ABCDEFGHIJKLMNOPQRSTUVWXYZ".toCharArray();
119 //    private static final char [] numeric = "0123456789".toCharArray();
120 //    private static final char [] unres_punct =  {'-' , '.' , '_' , '~'};
121     
122     // Section 3.1 Scheme
123 //    private static final char [] schemeEx = "+-.".toCharArray(); // + ALPHA and numeric.
124     
125     // To be unescaped during normalisation, unmodifiable and safely published.
126 //    final static Map<String, Character> unReserved; 
127 //    final static Map<String, Character> schemeUnreserved;
128     
129     /* Explicit legal String fields follow, ALPHA and DIGIT are implicitly legal */
130     
131     /* All characters that are legal URI syntax */
132     static final String allLegalUnescaped = ":/?#[]@!$&'()*+,;=-._~";
133     static final String allLegal = "%:/?#[]@!$&'()*+,;=-._~";
134     /*
135      *  Syntax Summary
136      * 
137      *  URI         = scheme ":" hier-part [ "?" query ] [ "#" fragment ]
138      * 
139      *  hier-part   = "//" authority path-abempty
140      *              / path-absolute
141      *              / path-rootless
142      *              / path-empty
143      *
144      *  scheme      = ALPHA *( ALPHA / DIGIT / "+" / "-" / "." )
145      */
146     static final String schemeLegal = "+-.";
147     /* 
148      *  authority   = [ userinfo "@" ] host [ ":" port ]
149      *  userinfo    = *( unreserved / pct-encoded / sub-delims / ":" )
150      */ 
151     static final String userinfoLegal = "-._~!$&'()*+,;=:";
152     static final String authorityLegal = userinfoLegal + "@[]";
153     /*  host        = IP-literal / IPv4address / reg-name
154      *  IP-literal = "[" ( IPv6address / IPvFuture  ) "]"
155      *  IPvFuture  = "v" 1*HEXDIG "." 1*( unreserved / sub-delims / ":" )
156      */ 
157     static final String iPvFuture = "-._~!$&'()*+,;=:";
158     /*  IPv6address =                            6( h16 ":" ) ls32
159      *              /                       "::" 5( h16 ":" ) ls32
160      *              / [               h16 ] "::" 4( h16 ":" ) ls32
161      *              / [ *1( h16 ":" ) h16 ] "::" 3( h16 ":" ) ls32
162      *              / [ *2( h16 ":" ) h16 ] "::" 2( h16 ":" ) ls32
163      *              / [ *3( h16 ":" ) h16 ] "::"    h16 ":"   ls32
164      *              / [ *4( h16 ":" ) h16 ] "::"              ls32
165      *              / [ *5( h16 ":" ) h16 ] "::"              h16
166      *              / [ *6( h16 ":" ) h16 ] "::"
167      * 
168      *  ls32        = ( h16 ":" h16 ) / IPv4address
169      *              ; least-significant 32 bits of address
170      * 
171      *  h16         = 1*4HEXDIG
172      *              ; 16 bits of address represented in hexadecimal
173      * 
174      *  IPv4address = dec-octet "." dec-octet "." dec-octet "." dec-octet
175      * 
176      *  dec-octet   = DIGIT                 ; 0-9
177      *              / %x31-39 DIGIT         ; 10-99
178      *              / "1" 2DIGIT            ; 100-199
179      *              / "2" %x30-34 DIGIT     ; 200-249
180      *              / "25" %x30-35          ; 250-255
181      *  reg-name    = *( unreserved / pct-encoded / sub-delims )
182      */
183     static final String hostRegNameLegal = "-._~!$&'()*+,;=";
184     /*  port        = *DIGIT
185      * 
186      *  path        = path-abempty    ; begins with "/" or is empty
187      *              / path-absolute   ; begins with "/" but not "//"
188      *              / path-noscheme   ; begins with a non-colon segment
189      *              / path-rootless   ; begins with a segment
190      *              / path-empty      ; zero characters
191      * 
192      *  path-abempty  = *( "/" segment )
193      *  path-absolute = "/" [ segment-nz *( "/" segment ) ]
194      *  path-noscheme = segment-nz-nc *( "/" segment )
195      *  path-rootless = segment-nz *( "/" segment )
196      *  path-empty    = 0<pchar>
197      *  
198      *  segment       = *pchar
199      *  segment-nz    = 1*pchar
200      *  segment-nz-nc = 1*( unreserved / pct-encoded / sub-delims / "@" ) ; non-zero-length segment without any colon ":"
201      * 
202      *  pchar         = unreserved / pct-encoded / sub-delims / ":" / "@"
203      */ 
204     static final String pcharLegal = "-._~!$&'()*+,;=:@";
205     static final String segmentNzNcLegal = "-._~!$&'()*+,;=@";
206     static final String segmentLegal = pcharLegal;
207     static final String pathLegal = segmentLegal + "/";
208             
209     /*  query       = *( pchar / "/" / "?" )
210      * 
211      *  fragment    = *( pchar / "/" / "?" )
212      */
213     static final String queryFragLegal = pcharLegal + "/?";
214   
215     private final static char a = 'a';
216     private final static char z = 'z';
217     private final static char A = 'A';
218     private final static char Z = 'Z';
219     private final static char upperCaseBitwiseMask = 0xdf;
220     private final static char lowerCaseBitwiseMask = 0x20;
221     
222     static String toAsciiUpperCase(String s){
223         return new String(toAsciiUpperCase(s.toCharArray()));
224     }
225     
226     static char [] toAsciiUpperCase(char [] array){
227         int length = array.length;
228         for (int i = 0; i < length ; i++){
229             if (array[i] >= a && array[i] <= z) {
230                 array[i] = toAsciiUpperCase(array[i]);
231             } 
232         }
233         return array;
234     }
235     
236     static char toAsciiUpperCase(char c){
237         return (char) (c & upperCaseBitwiseMask);
238     }
239     
240     static String toAsciiLowerCase(String s){
241         return new String(toAsciiLowerCase(s.toCharArray()));
242     }
243     
244     static char[] toAsciiLowerCase(char [] array){
245         int length = array.length;
246         for (int i = 0; i < length ; i++){
247             if (array[i] >= A && array[i] <= Z) {
248                 array[i] = toAsciiLowerCase(array[i]);
249             }
250         }
251         return array;
252     }
253     
254     static char toAsciiLowerCase(char c){
255         return (char) (c | lowerCaseBitwiseMask);
256     }
257     
258     static boolean charArraysEqual( char [] a , char [] b){
259         int alen = a.length;
260         int blen = b.length;
261         if (alen != blen) return false;
262         for (int i = 0; i < alen; i++){
263             if (a[i] !=  b[i]) return false;
264         }
265         return true;
266     }
267     
268     static boolean asciiStringsUpperCaseEqual(String a, String b){
269         char [] ac = a.toCharArray();
270         toAsciiUpperCase(ac);
271         char [] bc = b.toCharArray();
272         toAsciiUpperCase(bc);
273         return charArraysEqual(ac, bc);
274     }
275     
276     static boolean asciiStringsLowerCaseEqual(String a, String b){
277         char [] ac = a.toCharArray();
278         toAsciiLowerCase(ac);
279         char [] bc = b.toCharArray();
280         toAsciiLowerCase(bc);
281         return charArraysEqual(ac, bc);
282     }
283     /** Fixes windows file URI string by converting back slashes to forward
284      * slashes and inserting a forward slash before the drive letter if it is
285      * missing.  No normalisation or modification of case is performed.
286      * @param uri String representation of URI
287      * @return fixed URI String
288      */
289     public static String fixWindowsURI(String uri) {
290         if (uri == null) return null;
291         if (File.separatorChar != '\\') return uri;
292         if ( uri.startsWith("file:") || uri.startsWith("FILE:")){
293             char [] u = uri.toCharArray();
294             int l = u.length; 
295             StringBuilder sb = new StringBuilder(uri.length()+1);
296             for (int i=0; i<l; i++){
297                 // Ensure we use forward slashes
298                 if (u[i] == File.separatorChar) {
299                     sb.append('/');
300                     continue;
301                 }
302                 if (i == 5 && uri.startsWith(":", 6 )) {
303                     // Windows drive letter without leading slashes doesn't comply
304                     // with URI spec, fix it here
305                     sb.append("/");
306                 }
307                 sb.append(u[i]);
308             }
309             return sb.toString();
310         }
311         return uri;
312     }
313     
314     public static URI uriToURI(Uri uri){
315         return URI.create(uri.toString());
316     }
317     
318     public static Uri urlToUri(URL url) throws URISyntaxException{
319         return Uri.parseAndCreate(fixWindowsURI(url.toString()));
320     }
321     
322     public static File uriToFile(Uri uri){
323         return new File(uriToURI(uri));
324     }
325    
326     public static Uri fileToUri(File file) throws URISyntaxException{
327         String path = file.getAbsolutePath();
328         if (File.separatorChar == '\\') {
329             path = path.replace(File.separatorChar, '/');
330         }
331         path = fixWindowsURI("file:" + path);
332         return Uri.escapeAndCreate(path); //$NON-NLS-1$
333     }
334     
335     public static Uri filePathToUri(String path) throws URISyntaxException{
336         String forwardSlash = "/";
337         if (path == null || path.length() == 0) {
338             // codebase is "file:"
339             path = "*";
340         }
341         // Ensure compatibility with URLClassLoader, when directory
342         // character is dropped by File.
343         boolean directory = false;
344         if (path.endsWith(forwardSlash)) directory = true;
345         path = new File(path).getAbsolutePath();
346         if (directory) {
347             if (!(path.endsWith(File.separator))){
348                 path = path + File.separator;
349             }
350         }
351         if (File.separatorChar == '\\') {
352             path = path.replace(File.separatorChar, '/');
353         }
354         path = fixWindowsURI("file:" + path);
355         return Uri.escapeAndCreate(path); //$NON-NLS-1$
356     }
357     
358     /* Begin Object Implementation */
359 
360     private final String string;
361     private final String scheme;
362     private final String schemespecificpart;
363     private final String authority;
364     private final String userinfo;
365     private final String host;
366     private final int port;
367     private final String path;
368     private final String query;
369     private final String fragment;
370     private final boolean opaque;
371     private final boolean absolute;
372     private final boolean serverAuthority;
373     private final String hashString;
374     private final int hash;
375     private final boolean fileSchemeCaseInsensitiveOS;
376   
377     /**
378      * 
379      * @param string
380      * @param scheme
381      * @param schemespecificpart
382      * @param authority
383      * @param userinfo
384      * @param host
385      * @param port
386      * @param path
387      * @param query
388      * @param fragment
389      * @param opaque
390      * @param absolute
391      * @param serverAuthority
392      * @param hash 
393      */
394     private Uri(String string,
395             String scheme,
396             String schemespecificpart,
397             String authority,
398             String userinfo,
399             String host,
400             int port,
401             String path,
402             String query,
403             String fragment,
404             boolean opaque,
405             boolean absolute,
406             boolean serverAuthority,
407             int hash, 
408             boolean fileSchemeCaseInsensitiveOS)
409     {
410         super();
411         this.scheme = scheme;
412         this.schemespecificpart = schemespecificpart;
413         this.authority = authority;
414         this.userinfo = userinfo;
415         this.host = host;
416         this.port = port;
417         this.path = path;
418         this.query = query;
419         this.fragment = fragment;
420         this.opaque = opaque;
421         this.absolute = absolute;
422         this.serverAuthority = serverAuthority;
423         if (string == null) {
424             StringBuilder result = new StringBuilder();
425             if (scheme != null) {
426                 result.append(scheme);
427                 result.append(':');
428             }
429             if (opaque) {
430                 result.append(schemespecificpart);
431             } else {
432                 if (authority != null) {
433                     result.append("//"); //$NON-NLS-1$
434                     result.append(authority);
435                 }
436 
437                 if (path != null) {
438                     result.append(path);
439                 }
440 
441                 if (query != null) {
442                     result.append('?');
443                     result.append(query);
444                 }
445             }
446 
447             if (fragment != null) {
448                 result.append('#');
449                 result.append(fragment);
450             }
451 
452             this.string = result.toString();
453         } else {
454             this.string = string;
455         }
456         this.hashString = getHashString();
457         this.hash = hash == -1 ? hashString.hashCode(): hash;
458         this.fileSchemeCaseInsensitiveOS = fileSchemeCaseInsensitiveOS;
459     }
460     
461     /**
462      * Private constructor that doesn't throw URISyntaxException, all public
463      * constructors are designed to avoid finalizer attacks by calling static 
464      * methods that throw URISyntaxException, just in case we
465      * decide to make this class non final at some point in future.
466      * @param p 
467      */
468     private Uri(UriParser p){
469         this(p.string,
470         p.scheme,
471         p.schemespecificpart,
472         p.authority,
473         p.userinfo,
474         p.host,
475         p.port,
476         p.path,
477         p.query,
478         p.fragment,
479         p.opaque,
480         p.absolute,
481         p.serverAuthority,
482         p.hash, 
483         p.fileSchemeCaseInsensitiveOS);
484     }
485     
486     /**
487      * Creates a new URI instance according to the given string {@code uri}.
488      *
489      * The URI must strictly conform to RFC3986, it doesn't support extended
490      * characters sets like java.net.URI, instead all non ASCII characters
491      * must be escaped.
492      * 
493      * Any encoded unreserved characters are decoded.
494      * 
495      * @param uri
496      *            the textual URI representation to be parsed into a URI object.
497      * @throws URISyntaxException
498      *             if the given string {@code uri} doesn't fit to the
499      *             specification RF3986 or could not be parsed correctly.
500      */
501     public Uri(String uri) throws URISyntaxException {
502         this(constructor1(uri));
503     }
504     
505     private static UriParser constructor1(String uri) throws URISyntaxException {
506         uri = URIEncoderDecoder.decodeUnreserved(uri);
507         UriParser p = new UriParser();
508         p.parseURI(uri, false);
509         return p;
510     }
511 
512     /**
513      * Creates a new URI instance using the given arguments. This constructor
514      * first creates a temporary URI string from the given components. This
515      * string will be parsed later on to create the URI instance.
516      * <p>
517      * {@code [scheme:]scheme-specific-part[#fragment]}
518      *
519      * @param scheme
520      *            the scheme part of the URI.
521      * @param ssp
522      *            the scheme-specific-part of the URI.
523      * @param frag
524      *            the fragment part of the URI.
525      * @throws URISyntaxException
526      *             if the temporary created string doesn't fit to the
527      *             specification RFC2396 or could not be parsed correctly.
528      */
529     public Uri(String scheme, String ssp, String frag) throws URISyntaxException {
530         this(constructor2(scheme, ssp, frag));
531     }
532     
533     private static UriParser constructor2(String scheme, String ssp, String frag) throws URISyntaxException{
534         StringBuilder uri = new StringBuilder();
535         if (scheme != null) {
536             uri.append(scheme);
537             uri.append(':');
538         }
539         if (ssp != null) {
540             // QUOTE ILLEGAL CHARACTERS
541             uri.append(quoteComponent(ssp, allLegalUnescaped));
542         }
543         if (frag != null) {
544             uri.append('#');
545             // QUOTE ILLEGAL CHARACTERS
546             uri.append(quoteComponent(frag, Uri.queryFragLegal));
547         }
548 
549         UriParser p = new UriParser();
550         p.parseURI(uri.toString(), false);
551         return p;
552     }
553 
554     /**
555      * Creates a new URI instance using the given arguments. This constructor
556      * first creates a temporary URI string from the given components. This
557      * string will be parsed later on to create the URI instance.
558      * <p>
559      * {@code [scheme:][user-info@]host[:port][path][?query][#fragment]}
560      *
561      * @param scheme
562      *            the scheme part of the URI.
563      * @param userinfo
564      *            the user information of the URI for authentication and
565      *            authorization.
566      * @param host
567      *            the host name of the URI.
568      * @param port
569      *            the port number of the URI.
570      * @param path
571      *            the path to the resource on the host.
572      * @param query
573      *            the query part of the URI to specify parameters for the
574      *            resource.
575      * @param fragment
576      *            the fragment part of the URI.
577      * @throws URISyntaxException
578      *             if the temporary created string doesn't fit to the
579      *             specification RFC2396 or could not be parsed correctly.
580      */
581     public Uri(String scheme, String userinfo, String host, int port,
582             String path, String query, String fragment)
583             throws URISyntaxException {
584         this(constructor3(scheme, userinfo, host, port, path, query, fragment));
585     }
586     
587     private static UriParser constructor3(String scheme, String userinfo, String host, int port,
588             String path, String query, String fragment) throws URISyntaxException {
589         if (scheme == null && userinfo == null && host == null && path == null
590                 && query == null && fragment == null) {
591             UriParser p = new UriParser();
592             p.path = ""; //$NON-NLS-1$
593             return p;
594         }
595 
596         if (scheme != null && path != null && path.length() > 0
597                 && path.charAt(0) != '/') {
598             throw new URISyntaxException(path, Messages.getString("luni.82")); //$NON-NLS-1$
599         }
600 
601         StringBuilder uri = new StringBuilder();
602         if (scheme != null) {
603             uri.append(scheme);
604             uri.append(':');
605         }
606 
607         if (userinfo != null || host != null || port != -1) {
608             uri.append("//"); //$NON-NLS-1$
609         }
610 
611         if (userinfo != null) {
612             // QUOTE ILLEGAL CHARACTERS in userinfo
613             uri.append(quoteComponent(userinfo, Uri.userinfoLegal));
614             uri.append('@');
615         }
616 
617         if (host != null) {
618             // check for ipv6 addresses that hasn't been enclosed
619             // in square brackets
620             if (host.indexOf(':') != -1 && host.indexOf(']') == -1
621                     && host.indexOf('[') == -1) {
622                 host = "[" + host + "]"; //$NON-NLS-1$ //$NON-NLS-2$
623             }
624             uri.append(host);
625         }
626 
627         if (port != -1) {
628             uri.append(':');
629             uri.append(port);
630         }
631 
632         if (path != null) {
633             // QUOTE ILLEGAL CHARS
634             uri.append(quoteComponent(path, "/@" + Uri.pathLegal)); //$NON-NLS-1$
635         }
636 
637         if (query != null) {
638             uri.append('?');
639             // QUOTE ILLEGAL CHARS
640             uri.append(quoteComponent(query, Uri.queryFragLegal));
641         }
642 
643         if (fragment != null) {
644             // QUOTE ILLEGAL CHARS
645             uri.append('#');
646             uri.append(quoteComponent(fragment, Uri.queryFragLegal));
647         }
648 
649         UriParser p = new UriParser();
650         p.parseURI(uri.toString(), true);
651         return p;
652     }
653 
654     /**
655      * Creates a new URI instance using the given arguments. This constructor
656      * first creates a temporary URI string from the given components. This
657      * string will be parsed later on to create the URI instance.
658      * <p>
659      * {@code [scheme:]host[path][#fragment]}
660      *
661      * @param scheme
662      *            the scheme part of the URI.
663      * @param host
664      *            the host name of the URI.
665      * @param path
666      *            the path to the resource on the host.
667      * @param fragment
668      *            the fragment part of the URI.
669      * @throws URISyntaxException
670      *             if the temporary created string doesn't fit to the
671      *             specification RFC2396 or could not be parsed correctly.
672      */
673     public Uri(String scheme, String host, String path, String fragment)
674             throws URISyntaxException {
675         this(scheme, null, host, -1, path, null, fragment);
676     }
677 
678     /**
679      * Creates a new URI instance using the given arguments. This constructor
680      * first creates a temporary URI string from the given components. This
681      * string will be parsed later on to create the URI instance.
682      * <p>
683      * {@code [scheme:][//authority][path][?query][#fragment]}
684      *
685      * @param scheme
686      *            the scheme part of the URI.
687      * @param authority
688      *            the authority part of the URI.
689      * @param path
690      *            the path to the resource on the host.
691      * @param query
692      *            the query part of the URI to specify parameters for the
693      *            resource.
694      * @param fragment
695      *            the fragment part of the URI.
696      * @throws URISyntaxException
697      *             if the temporary created string doesn't fit to the
698      *             specification RFC2396 or could not be parsed correctly.
699      */
700     public Uri(String scheme, String authority, String path, String query,
701             String fragment) throws URISyntaxException {
702         this(constructor4(scheme, authority, path, query, fragment));
703     }
704 
705     private static UriParser constructor4(String scheme, String authority, String path, String query,
706             String fragment) throws URISyntaxException {
707         if (scheme != null && path != null && path.length() > 0
708                 && path.charAt(0) != '/') {
709             throw new URISyntaxException(path, Messages.getString("luni.82")); //$NON-NLS-1$
710         }
711 
712         StringBuilder uri = new StringBuilder();
713         if (scheme != null) {
714             uri.append(scheme);
715             uri.append(':');
716         }
717         if (authority != null) {
718             uri.append("//"); //$NON-NLS-1$
719             // QUOTE ILLEGAL CHARS
720             uri.append(quoteComponent(authority, "@[]" + Uri.authorityLegal)); //$NON-NLS-1$
721         }
722 
723         if (path != null) {
724             // QUOTE ILLEGAL CHARS
725             uri.append(quoteComponent(path, "/@" + Uri.pathLegal)); //$NON-NLS-1$
726         }
727         if (query != null) {
728             // QUOTE ILLEGAL CHARS
729             uri.append('?');
730             uri.append(quoteComponent(query, Uri.queryFragLegal));
731         }
732         if (fragment != null) {
733             // QUOTE ILLEGAL CHARS
734             uri.append('#');
735             uri.append(quoteComponent(fragment, Uri.queryFragLegal));
736         }
737 
738         UriParser p = new UriParser();
739         p.parseURI(uri.toString(), false);
740         return p;
741     }
742     
743     /*
744      * Quote illegal chars for each component, but not the others
745      * 
746      * @param component java.lang.String the component to be converted @param
747      * legalset java.lang.String the legal character set allowed in the
748      * component s @return java.lang.String the converted string
749      */
750     private static String quoteComponent(String component, String legalset) {
751         try {
752             /*
753              * Use a different encoder than URLEncoder since: 1. chars like "/",
754              * "#", "@" etc needs to be preserved instead of being encoded, 2.
755              * UTF-8 char set needs to be used for encoding instead of default
756              * platform one
757              */
758             return URIEncoderDecoder.quoteIllegal(component, legalset);
759         } catch (UnsupportedEncodingException e) {
760             throw new RuntimeException(e.toString());
761         }
762     }
763 
764     /**
765      * Compares this URI with the given argument {@code uri}. This method will
766      * return a negative value if this URI instance is less than the given
767      * argument and a positive value if this URI instance is greater than the
768      * given argument. The return value {@code 0} indicates that the two
769      * instances represent the same URI. To define the order the single parts of
770      * the URI are compared with each other. String components will be orderer
771      * in the natural case-sensitive way. A hierarchical URI is less than an
772      * opaque URI and if one part is {@code null} the URI with the undefined
773      * part is less than the other one.
774      *
775      * @param uri
776      *            the URI this instance has to compare with.
777      * @return the value representing the order of the two instances.
778      */
779     @Override
780     public int compareTo(Uri uri) {
781         int ret;
782 
783         // compare schemes
784         if (scheme == null && uri.scheme != null) {
785             return -1;
786         } else if (scheme != null && uri.scheme == null) {
787             return 1;
788         } else if (scheme != null && uri.scheme != null) {
789             ret = scheme.compareToIgnoreCase(uri.scheme);
790             if (ret != 0) return ret;
791         }
792 
793         // compare opacities
794         if (!opaque && uri.opaque) {
795             return -1;
796         } else if (opaque && !uri.opaque) {
797             return 1;
798         } else if (opaque && uri.opaque) {
799             ret = schemespecificpart.compareTo(uri.schemespecificpart);
800             if (ret != 0) {
801                 return ret;
802             }
803         } else {
804 
805             // otherwise both must be hierarchical
806 
807             // compare authorities
808             if (authority != null && uri.authority == null) {
809                 return 1;
810             } else if (authority == null && uri.authority != null) {
811                 return -1;
812             } else if (authority != null && uri.authority != null) {
813                 if (host != null && uri.host != null) {
814                     // both are server based, so compare userinfo, host, port
815                     if (userinfo != null && uri.userinfo == null) {
816                         return 1;
817                     } else if (userinfo == null && uri.userinfo != null) {
818                         return -1;
819                     } else if (userinfo != null && uri.userinfo != null) {
820                         ret = userinfo.compareTo(uri.userinfo);
821                         if (ret != 0) {
822                             return ret;
823                         }
824                     }
825 
826                     // userinfo's are the same, compare hostname
827                     ret = host.compareToIgnoreCase(uri.host);
828                     if (ret != 0) {
829                         return ret;
830                     }
831 
832                     // compare port
833                     if (port != uri.port) {
834                         return port - uri.port;
835                     }
836                 } else { // one or both are registry based, compare the whole
837                     // authority
838                     ret = authority.compareTo(uri.authority);
839                     if (ret != 0) {
840                         return ret;
841                     }
842                 }
843             }
844 
845             // authorities are the same
846             // compare paths
847             
848             if (fileSchemeCaseInsensitiveOS){
849                 ret = toAsciiUpperCase(path).compareTo(toAsciiUpperCase(uri.path));
850 //                ret = path.toUpperCase(Locale.ENGLISH).compareTo(uri.path.toUpperCase(Locale.ENGLISH));
851             } else {
852                 ret = path.compareTo(uri.path);
853             }
854             if (ret != 0) {
855                 return ret;
856             }
857 
858             // compare queries
859 
860             if (query != null && uri.query == null) {
861                 return 1;
862             } else if (query == null && uri.query != null) {
863                 return -1;
864             } else if (query != null && uri.query != null) {
865                 ret = query.compareTo(uri.query);
866                 if (ret != 0) {
867                     return ret;
868                 }
869             }
870         }
871 
872         // everything else is identical, so compare fragments
873         if (fragment != null && uri.fragment == null) {
874             return 1;
875         } else if (fragment == null && uri.fragment != null) {
876             return -1;
877         } else if (fragment != null && uri.fragment != null) {
878             ret = fragment.compareTo(uri.fragment);
879             if (ret != 0) {
880                 return ret;
881             }
882         }
883 
884         // identical
885         return 0;
886     }
887 
888     /**
889      * Parses the given argument {@code rfc3986compliantURI} and creates an appropriate URI
890      * instance.
891      * 
892      * The parameter string is checked for compliance, an IllegalArgumentException
893      * is thrown if the string is non compliant.
894      *
895      * @param rfc3986compliantURI
896      *            the string which has to be parsed to create the URI instance.
897      * @return the created instance representing the given URI.
898      */
899     public static Uri create(String rfc3986compliantURI) {
900         Uri result = null;
901         try {
902             result = new Uri(rfc3986compliantURI);
903         } catch (URISyntaxException e) {
904             throw new IllegalArgumentException(e.getMessage());
905         }
906         return result;
907     }
908     
909     /**
910      * The parameter string doesn't contain any existing escape sequences, any
911      * escape character % found is encoded as %25. Illegal characters are 
912      * escaped if possible.
913      * 
914      * The Uri is normalised according to RFC3986.
915      * 
916      * @param unescapedString URI in un-escaped string form
917      * @return an RFC3986 compliant Uri.
918      * @throws java.net.URISyntaxException if string cannot be escaped.
919      */
920     public static Uri escapeAndCreate(String unescapedString) throws URISyntaxException{
921         return new Uri(quoteComponent(unescapedString, allLegalUnescaped));
922     }
923     
924     /**
925      * The parameter string may already contain escaped sequences, any illegal
926      * characters are escaped and any that shouldn't be escaped are un-escaped.
927      * 
928      * The escape character % is not re-encoded.
929      * @param nonCompliantEscapedString URI in string from.
930      * @return an RFC3986 compliant Uri.
931      * @throws java.net.URISyntaxException if string cannot be escaped.
932      */
933     public static Uri parseAndCreate(String nonCompliantEscapedString) throws URISyntaxException{
934         return new Uri(quoteComponent(nonCompliantEscapedString, allLegal));
935     }
936     
937 
938     /*
939      * Takes a string that may contain hex sequences like %F1 or %2b and
940      * converts the hex values following the '%' to uppercase
941      */
942     private String convertHexToUpperCase(String s) {
943         StringBuilder result = new StringBuilder(""); //$NON-NLS-1$
944         if (s.indexOf('%') == -1) {
945             return s;
946         }
947 
948         int index, previndex = 0;
949         while ((index = s.indexOf('%', previndex)) != -1) {
950             result.append(s.substring(previndex, index + 1));
951             // Convert to upper case ascii
952             result.append(toAsciiUpperCase(s.substring(index + 1, index + 3).toCharArray()));
953             index += 3;
954             previndex = index;
955         }
956         return result.toString();
957     }
958 
959     /*
960      * Takes two strings that may contain hex sequences like %F1 or %2b and
961      * compares them, ignoring case for the hex values. Hex values must always
962      * occur in pairs as above
963      */
964     private boolean equalsHexCaseInsensitive(String first, String second) {
965         //Hex will always be upper case.
966         if (first != null) return first.equals(second); 
967         return second == null;
968     }
969 
970     /**
971      * Compares this URI instance with the given argument {@code o} and
972      * determines if both are equal. Two URI instances are equal if all single
973      * parts are identical in their meaning.
974      *
975      * @param o
976      *            the URI this instance has to be compared with.
977      * @return {@code true} if both URI instances point to the same resource,
978      *         {@code false} otherwise.
979      */
980     @Override
981     public boolean equals(Object o) {
982         if (!(o instanceof Uri)) {
983             return false;
984         }
985         if (hash != o.hashCode()) return false;
986         Uri uri = (Uri) o;
987 
988         if (uri.fragment == null && fragment != null || uri.fragment != null
989                 && fragment == null) {
990             return false;
991         } else if (uri.fragment != null && fragment != null) {
992             if (!equalsHexCaseInsensitive(uri.fragment, fragment)) {
993                 return false;
994             }
995         }
996 
997         if (uri.scheme == null && scheme != null || uri.scheme != null
998                 && scheme == null) {
999             return false;
1000         } else if (uri.scheme != null && scheme != null) {
1001             if (!uri.scheme.equalsIgnoreCase(scheme)) {
1002                 return false;
1003             }
1004         }
1005 
1006         if (uri.opaque && opaque) {
1007             return equalsHexCaseInsensitive(uri.schemespecificpart,
1008                     schemespecificpart);
1009         } else if (!uri.opaque && !opaque) {
1010             if ( !(path != null && (path.equals(uri.path) 
1011                     || fileSchemeCaseInsensitiveOS
1012                     // Upper case comparison required for Windows & VMS.
1013                     && asciiStringsUpperCaseEqual(path, uri.path)
1014                     ))) 
1015             {
1016                 return false;
1017             }
1018 
1019             if (uri.query != null && query == null || uri.query == null
1020                     && query != null) {
1021                 return false;
1022             } else if (uri.query != null && query != null) {
1023                 if (!equalsHexCaseInsensitive(uri.query, query)) {
1024                     return false;
1025                 }
1026             }
1027 
1028             if (uri.authority != null && authority == null
1029                     || uri.authority == null && authority != null) {
1030                 return false;
1031             } else if (uri.authority != null && authority != null) {
1032                 if (uri.host != null && host == null || uri.host == null
1033                         && host != null) {
1034                     return false;
1035                 } else if (uri.host == null && host == null) {
1036                     // both are registry based, so compare the whole authority
1037                     return equalsHexCaseInsensitive(uri.authority, authority);
1038                 } else { // uri.host != null && host != null, so server-based
1039                     if (!host.equalsIgnoreCase(uri.host)) {
1040                         return false;
1041                     }
1042 
1043                     if (port != uri.port) {
1044                         return false;
1045                     }
1046 
1047                     if (uri.userinfo != null && userinfo == null
1048                             || uri.userinfo == null && userinfo != null) {
1049                         return false;
1050                     } else if (uri.userinfo != null && userinfo != null) {
1051                         return equalsHexCaseInsensitive(userinfo, uri.userinfo);
1052                     } else {
1053                         return true;
1054                     }
1055                 }
1056             } else {
1057                 // no authority
1058                 return true;
1059             }
1060 
1061         } else {
1062             // one is opaque, the other hierarchical
1063             return false;
1064         }
1065     }
1066     
1067     /** 
1068      * <p>
1069      * Indicates whether the specified Uri is implied by this {@link
1070      * Uri}. Returns {@code true} if all of the following conditions are
1071      * {@code true}, otherwise {@code false}:
1072      * </p>
1073      * <ul>
1074      * <li>this scheme is not {@code null}
1075      * <li>this scheme is equal to {@code implied}'s scheme.
1076      * <li>if this host is not {@code null}, the
1077      * following conditions are checked
1078      * <ul>
1079      * <li>{@code cs}'s host is not {@code null}
1080      * <li>the wildcard or partial wildcard of this {@code Uri}'s
1081      * host matches {@code implied}'s host.
1082      * </ul>
1083      * <li>if this {@code Uri}'s port != -1 the port of {@code
1084      * implied}'s location is equal to this {@code Uri}'s port
1085      * <li>this {@code Uri}'s path matches {@code implied}'s path
1086      * whereas special wildcard matching applies as described below.
1087      * </ul>
1088      * Matching rules for path:
1089      * <ul>
1090      * <li>if this {@code Uri}'s path ends with {@code "/-"},
1091      * then {@code implied}'s path must start with {@code Uri}'s path
1092      * (exclusive the trailing '-')
1093      * <li>if this {@code Uri}'s path ends with {@code "/*"},
1094      * then {@code implied}'s path must start with {@code Uri}'s path
1095      * (exclusive the trailing '*') and must not have any further '/'
1096      * <li>if this {@code Uri}'s path ends with {@code "/"},
1097      * then {@code implied}'s path must start with {@code Uri}'s path
1098      * <li>if this {@code Uri}'s path does not end with {@code
1099      * "/"}, then {@code implied}'s path must start with {@code Uri}'s
1100      * path with the '/' appended to it.
1101      * </ul>
1102      * Examples for Uri that imply the Uri
1103      * "http://harmony.apache.org/milestones/M9/apache-harmony.jar":
1104      *
1105      * <pre>
1106      * http:
1107      * http://&#42;/milestones/M9/*
1108      * http://*.apache.org/milestones/M9/*
1109      * http://harmony.apache.org/milestones/-
1110      * http://harmony.apache.org/milestones/M9/apache-harmony.jar
1111      * </pre>
1112      *
1113      * @param implied
1114      *            the Uri to check.
1115      * @return {@code true} if the argument is implied by this
1116      *         {@code Uri}, otherwise {@code false}.
1117      */
1118     public boolean implies(Uri implied) { // package private for junit
1119         //.This section of code was copied from Apache Harmony's CodeSource
1120         // SVN Revision 929252
1121         //
1122         // Here, javadoc:N refers to the appropriate item in the API spec for 
1123         // the CodeSource.implies()
1124         // The info was taken from the 1.5 final API spec
1125 
1126         // javadoc:1
1127 //        if (cs == null) {
1128 //            return false;
1129 //        }
1130 
1131         
1132         // javadoc:2
1133         // with a comment: the javadoc says only about certificates and does 
1134         // not explicitly mention CodeSigners' certs.
1135         // It seems more convenient to use getCerts() to get the real 
1136         // certificates - with a certificates got form the signers
1137 //        Certificate[] thizCerts = getCertificatesNoClone();
1138 //        if (thizCerts != null) {
1139 //            Certificate[] thatCerts = cs.getCertificatesNoClone();
1140 //            if (thatCerts == null
1141 //                    || !PolicyUtils.matchSubset(thizCerts, thatCerts)) {
1142 //                return false;
1143 //            }
1144 //        }
1145 
1146         // javadoc:3
1147         
1148             
1149             //javadoc:3.1
1150 //            URL otherURL = cs.getLocation();
1151 //            if ( otherURL == null) {
1152 //                return false;
1153 //            }
1154 //            URI otherURI;
1155 //            try {
1156 //                otherURI = otherURL.toURI();
1157 //            } catch (URISyntaxException ex) {
1158 //                return false;
1159 //            }
1160             //javadoc:3.2
1161             if (hash == implied.hash){
1162                 if (this.equals(implied)) {
1163                     return true;
1164                 }
1165             }
1166             //javadoc:3.3
1167             if ( scheme != null){
1168                 if (!scheme.equalsIgnoreCase(implied.scheme)) {
1169                     return false;
1170                 }
1171             }
1172             //javadoc:3.4
1173             if (host != null) {
1174                 if (implied.host == null) {
1175                     return false;
1176                 }
1177 
1178                 // 1. According to the spec, an empty string will be considered 
1179                 // as "localhost" in the SocketPermission
1180                 // 2. 'file://' URLs will have an empty getHost()
1181                 // so, let's make a special processing of localhost-s, I do 
1182                 // believe this'll improve performance of file:// code sources 
1183 
1184                 //
1185                 // Don't have to evaluate both the boolean-s each time.
1186                 // It's better to evaluate them directly under if() statement.
1187                 // 
1188                 // boolean thisIsLocalHost = thisHost.length() == 0 || "localhost".equals(thisHost);
1189                 // boolean thatIsLocalHost = thatHost.length() == 0 || "localhost".equals(thatHost);
1190                 // 
1191                 // if( !(thisIsLocalHost && thatIsLocalHost) &&
1192                 // !thisHost.equals(thatHost)) {
1193 
1194                 if (!((host.length() == 0 || "localhost".equals(host)) && (implied.host //$NON-NLS-1$
1195                         .length() == 0 || "localhost".equals(implied.host))) //$NON-NLS-1$
1196                         && !host.equals(implied.host)) {
1197                     
1198                     // Do wildcard matching here to replace SocketPermission functionality.
1199                     // This section was copied from Apache Harmony SocketPermission
1200                     boolean hostNameMatches = false;
1201                     boolean isPartialWild = (host.charAt(0) == '*');
1202                     if (isPartialWild) {
1203                         boolean isWild = (host.length() == 1);
1204                         if (isWild) {
1205                             hostNameMatches = true;
1206                         } else {
1207                             // Check if thisHost matches the end of thatHost after the wildcard
1208                             int length = host.length() - 1;
1209                             hostNameMatches = implied.host.regionMatches(implied.host.length() - length,
1210                                     host, 1, length);
1211                         }
1212                     }
1213                     if (!hostNameMatches) return false; // else continue.
1214                     
1215                     /* Don't want to try resolving URI with DNS, it either has a
1216                      * matching host or it doesn't.
1217                      * 
1218                      * The following section is for resolving hosts, it is
1219                      * not relevant here, but has been preserved for information
1220                      * purposes only.
1221                      * 
1222                      * Not only is it expensive to perform DNS resolution, hence
1223                      * the creation of Uri, but a CodeSource.implies
1224                      * may also require another SocketPermission which may 
1225                      * cause the policy to get stuck in an endless loop, since it
1226                      * doesn't perform the implies in priviledged mode, it might
1227                      * also allow an attacker to substitute one codebase for
1228                      * another using a dns cache poisioning attack.  In any case
1229                      * the DNS cannot be assumed trustworthy enough to supply
1230                      * the policy with information at this level. The implications
1231                      * are greater than the threat posed by SocketPermission
1232                      * which simply allows a network connection, as this may
1233                      * apply to any Permission, even AllPermission.
1234                      * 
1235                      * Typically the URI of the codebase will be a match for
1236                      * the codebase annotation string that is stored as a URL
1237                      * in CodeSource, then converted to a URI for comparison.
1238                      */
1239 
1240                     // Obvious, but very slow way....
1241                     // 
1242                     // SocketPermission thisPerm = new SocketPermission(
1243                     //          this.location.getHost(), "resolve");
1244                     // SocketPermission thatPerm = new SocketPermission(
1245                     //          cs.location.getHost(), "resolve");
1246                     // if (!thisPerm.implies(thatPerm)) { 
1247                     //      return false;
1248                     // }
1249                     //
1250                     // let's cache it: 
1251 
1252 //                    if (this.sp == null) {
1253 //                        this.sp = new SocketPermission(thisHost, "resolve"); //$NON-NLS-1$
1254 //                    }
1255 //
1256 //                    if (cs.sp == null) {
1257 //                        cs.sp = new SocketPermission(thatHost, "resolve"); //$NON-NLS-1$
1258 //                    } 
1259 //
1260 //                    if (!this.sp.implies(cs.sp)) {
1261 //                        return false;
1262 //                    }
1263                     
1264                 } // if( ! this.location.getHost().equals(cs.location.getHost())
1265             } // if (this.location.getHost() != null)
1266 
1267             //javadoc:3.5
1268             if (port != -1) {
1269                 if (port != implied.port) {
1270                     return false;
1271                 }
1272             }
1273 
1274             //javadoc:3.6
1275             // compatbility with URL.getFile
1276             String thisFile;
1277             String thatFile;
1278             if (fileSchemeCaseInsensitiveOS){
1279                 thisFile = path == null ? null: toAsciiUpperCase(path);
1280                 thatFile = implied.path == null ? null: toAsciiUpperCase(implied.path);
1281             } else {
1282                 thisFile = path;
1283                 thatFile = implied.path;
1284             }
1285             if (thatFile == null || thisFile == null) return false;
1286             if (thisFile.endsWith("/-")) { //javadoc:3.6."/-" //$NON-NLS-1$
1287                 if (!thatFile.startsWith(thisFile.substring(0, thisFile
1288                         .length() - 2))) {
1289                     return false;
1290                 }
1291             } else if (thisFile.endsWith("/*")) { //javadoc:3.6."/*" //$NON-NLS-1$
1292                 if (!thatFile.startsWith(thisFile.substring(0, thisFile
1293                         .length() - 2))) {
1294                     return false;
1295                 }
1296                 // no further separators(s) allowed
1297                 if (thatFile.indexOf("/", thisFile.length() - 1) != -1) { //$NON-NLS-1$
1298                     return false;
1299                 }
1300             } else {
1301                 // javadoc:3.6."/"
1302                 if (!thisFile.equals(thatFile)) {
1303                     if (!thisFile.endsWith("/")) { //$NON-NLS-1$
1304                         if (!thatFile.equals(thisFile + "/")) { //$NON-NLS-1$
1305                             return false;
1306                         }
1307                     } else {
1308                         return false;
1309                     }
1310                 }
1311             }
1312             
1313             // Fragment and path are ignored.
1314             //javadoc:3.7
1315             // A URL Anchor is a URI Fragment.
1316 //            if (thiss.getFragment() != null) {
1317 //                if (!thiss.getFragment().equals(implied.getFragment())) {
1318 //                    return false;
1319 //                }
1320 //            }
1321             // ok, every check was made, and they all were successful. 
1322             // it's ok to return true.
1323         
1324 
1325         // javadoc: a note about CodeSource with null location and null Certs 
1326         // is applicable here 
1327         return true;
1328     }
1329 
1330     /**
1331      * Gets the decoded authority part of this URI.
1332      *
1333      * @return the decoded authority part or {@code null} if undefined.
1334      */
1335     public String getAuthority() {
1336         return decode(authority);
1337     }
1338 
1339     /**
1340      * Gets the decoded fragment part of this URI.
1341      * 
1342      * @return the decoded fragment part or {@code null} if undefined.
1343      */
1344     public String getFragment() {
1345         return decode(fragment);
1346     }
1347 
1348     /**
1349      * Gets the host part of this URI.
1350      * 
1351      * @return the host part or {@code null} if undefined.
1352      */
1353     public String getHost() {
1354         return host;
1355     }
1356 
1357     /**
1358      * Gets the decoded path part of this URI.
1359      * 
1360      * @return the decoded path part or {@code null} if undefined.
1361      */
1362     public String getPath() {
1363         return decode(path);
1364     }
1365 
1366     /**
1367      * Gets the port number of this URI.
1368      * 
1369      * @return the port number or {@code -1} if undefined.
1370      */
1371     public int getPort() {
1372         return port;
1373     }
1374 
1375     /**
1376      * Gets the decoded query part of this URI.
1377      * 
1378      * @return the decoded query part or {@code null} if undefined.
1379      */
1380     public String getQuery() {
1381         return decode(query);
1382     }
1383 
1384     /**
1385      * Gets the authority part of this URI in raw form.
1386      * 
1387      * @return the encoded authority part or {@code null} if undefined.
1388      */
1389     public String getRawAuthority() {
1390         return authority;
1391     }
1392 
1393     /**
1394      * Gets the fragment part of this URI in raw form.
1395      * 
1396      * @return the encoded fragment part or {@code null} if undefined.
1397      */
1398     public String getRawFragment() {
1399         return fragment;
1400     }
1401 
1402     /**
1403      * Gets the path part of this URI in raw form.
1404      * 
1405      * @return the encoded path part or {@code null} if undefined.
1406      */
1407     public String getRawPath() {
1408         return path;
1409     }
1410 
1411     /**
1412      * Gets the query part of this URI in raw form.
1413      * 
1414      * @return the encoded query part or {@code null} if undefined.
1415      */
1416     public String getRawQuery() {
1417         return query;
1418     }
1419 
1420     /**
1421      * Gets the scheme-specific part of this URI in raw form.
1422      * 
1423      * @return the encoded scheme-specific part or {@code null} if undefined.
1424      */
1425     public String getRawSchemeSpecificPart() {
1426         return schemespecificpart;
1427     }
1428 
1429     /**
1430      * Gets the user-info part of this URI in raw form.
1431      * 
1432      * @return the encoded user-info part or {@code null} if undefined.
1433      */
1434     public String getRawUserInfo() {
1435         return userinfo;
1436     }
1437 
1438     /**
1439      * Gets the scheme part of this URI.
1440      * 
1441      * @return the scheme part or {@code null} if undefined.
1442      */
1443     public String getScheme() {
1444         return scheme;
1445     }
1446 
1447     /**
1448      * Gets the decoded scheme-specific part of this URI.
1449      * 
1450      * @return the decoded scheme-specific part or {@code null} if undefined.
1451      */
1452     public String getSchemeSpecificPart() {
1453         return decode(schemespecificpart);
1454     }
1455 
1456     /**
1457      * Gets the decoded user-info part of this URI.
1458      * 
1459      * @return the decoded user-info part or {@code null} if undefined.
1460      */
1461     public String getUserInfo() {
1462         return decode(userinfo);
1463     }
1464 
1465     /**
1466      * Gets the hashcode value of this URI instance.
1467      *
1468      * @return the appropriate hashcode value.
1469      */
1470     @Override
1471     public int hashCode() {
1472         return hash;
1473     }
1474 
1475     /**
1476      * Indicates whether this URI is absolute, which means that a scheme part is
1477      * defined in this URI.
1478      * 
1479      * @return {@code true} if this URI is absolute, {@code false} otherwise.
1480      */
1481     public boolean isAbsolute() {
1482         return absolute;
1483     }
1484 
1485     /**
1486      * Indicates whether this URI is opaque or not. An opaque URI is absolute
1487      * and has a scheme-specific part which does not start with a slash
1488      * character. All parts except scheme, scheme-specific and fragment are
1489      * undefined.
1490      * 
1491      * @return {@code true} if the URI is opaque, {@code false} otherwise.
1492      */
1493     public boolean isOpaque() {
1494         return opaque;
1495     }
1496     
1497     /**
1498      * Normalizes the path part of this URI.
1499      *
1500      * @return an URI object which represents this instance with a normalized
1501      *         path.
1502      */
1503     public Uri normalize() {
1504         if (opaque) {
1505             return this;
1506         }
1507         String normalizedPath = normalize(path);
1508         // if the path is already normalized, return this
1509         if (path.equals(normalizedPath)) {
1510             return this;
1511         }
1512         // get an exact copy of the URI re-calculate the scheme specific part
1513         // since the path of the normalized URI is different from this URI.
1514         return new Uri( null,
1515                         scheme,
1516                         setSchemeSpecificPart(authority, normalizedPath , query),
1517                         authority,
1518                         userinfo,
1519                         host,
1520                         port,
1521                         normalizedPath,
1522                         query,
1523                         fragment,
1524                         opaque,
1525                         absolute,
1526                         serverAuthority,
1527                         hash, 
1528                         fileSchemeCaseInsensitiveOS);
1529     }
1530 
1531 
1532     /*
1533      * normalize path, and return the resulting string
1534      */
1535     private String normalize(String path) {
1536         // count the number of '/'s, to determine number of segments
1537         int index = -1;
1538         int pathlen = path.length();
1539         int size = 0;
1540         if (pathlen > 0 && path.charAt(0) != '/') {
1541             size++;
1542         }
1543         while ((index = path.indexOf('/', index + 1)) != -1) {
1544             if (index + 1 < pathlen && path.charAt(index + 1) != '/') {
1545                 size++;
1546             }
1547         }
1548 
1549         String[] seglist = new String[size];
1550         boolean[] include = new boolean[size];
1551 
1552         // break the path into segments and store in the list
1553         int current = 0;
1554         int index2;
1555         index = (pathlen > 0 && path.charAt(0) == '/') ? 1 : 0;
1556         while ((index2 = path.indexOf('/', index + 1)) != -1) {
1557             seglist[current++] = path.substring(index, index2);
1558             index = index2 + 1;
1559         }
1560 
1561         // if current==size, then the last character was a slash
1562         // and there are no more segments
1563         if (current < size) {
1564             seglist[current] = path.substring(index);
1565         }
1566 
1567         // determine which segments get included in the normalized path
1568         for (int i = 0; i < size; i++) {
1569             include[i] = true;
1570             if (seglist[i].equals("..")) { //$NON-NLS-1$
1571                 int remove = i - 1;
1572                 // search back to find a segment to remove, if possible
1573                 while (remove > -1 && !include[remove]) {
1574                     remove--;
1575                 }
1576                 // if we find a segment to remove, remove it and the ".."
1577                 // segment
1578                 if (remove > -1 && !seglist[remove].equals("..")) { //$NON-NLS-1$
1579                     include[remove] = false;
1580                     include[i] = false;
1581                 }
1582             } else if (seglist[i].equals(".")) { //$NON-NLS-1$
1583                 include[i] = false;
1584             }
1585         }
1586 
1587         // put the path back together
1588         StringBuilder newpath = new StringBuilder();
1589         if (path.startsWith("/")) { //$NON-NLS-1$
1590             newpath.append('/');
1591         }
1592 
1593         for (int i = 0; i < seglist.length; i++) {
1594             if (include[i]) {
1595                 newpath.append(seglist[i]);
1596                 newpath.append('/');
1597             }
1598         }
1599 
1600         // if we used at least one segment and the path previously ended with
1601         // a slash and the last segment is still used, then delete the extra
1602         // trailing '/'
1603         if (!path.endsWith("/") && seglist.length > 0 //$NON-NLS-1$
1604                 && include[seglist.length - 1]) {
1605             newpath.deleteCharAt(newpath.length() - 1);
1606         }
1607 
1608         String result = newpath.toString();
1609 
1610         // check for a ':' in the first segment if one exists,
1611         // prepend "./" to normalize
1612         index = result.indexOf(':');
1613         index2 = result.indexOf('/');
1614         if (index != -1 && (index < index2 || index2 == -1)) {
1615             newpath.insert(0, "./"); //$NON-NLS-1$
1616             result = newpath.toString();
1617         }
1618         return result;
1619     }
1620 
1621     /**
1622      * Tries to parse the authority component of this URI to divide it into the
1623      * host, port, and user-info. If this URI is already determined as a
1624      * ServerAuthority this instance will be returned without changes.
1625      *
1626      * @return this instance with the components of the parsed server authority.
1627      * @throws URISyntaxException
1628      *             if the authority part could not be parsed as a server-based
1629      *             authority.
1630      */
1631     public Uri parseServerAuthority() throws URISyntaxException {
1632         if (!serverAuthority) {
1633             UriParser p = new UriParser();
1634             p.parseURI(this.toString(), false);
1635             p.parseAuthority(true);
1636             return new Uri(p);
1637         }
1638         return this;
1639     }
1640 
1641     /**
1642      * Makes the given URI {@code relative} to a relative URI against the URI
1643      * represented by this instance.
1644      *
1645      * @param relative
1646      *            the URI which has to be relativized against this URI.
1647      * @return the relative URI.
1648      */
1649     public Uri relativize(Uri relative) {
1650         if (relative.opaque || opaque) {
1651             return relative;
1652         }
1653 
1654         if (scheme == null ? relative.scheme != null : !scheme
1655                 .equals(relative.scheme)) {
1656             return relative;
1657         }
1658 
1659         if (authority == null ? relative.authority != null : !authority
1660                 .equals(relative.authority)) {
1661             return relative;
1662         }
1663 
1664         // normalize both paths
1665         String thisPath = normalize(path);
1666         String relativePath = normalize(relative.path);
1667 
1668         /*
1669          * if the paths aren't equal, then we need to determine if this URI's
1670          * path is a parent path (begins with) the relative URI's path
1671          */
1672         if (!thisPath.equals(relativePath)) {
1673             // if this URI's path doesn't end in a '/', add one
1674             if (!thisPath.endsWith("/")) { //$NON-NLS-1$
1675                 thisPath = thisPath + '/';
1676             }
1677             /*
1678              * if the relative URI's path doesn't start with this URI's path,
1679              * then just return the relative URI; the URIs have nothing in
1680              * common
1681              */
1682             if (!relativePath.startsWith(thisPath)) {
1683                 return relative;
1684             }
1685         }
1686 
1687         String qry = relative.query;
1688         // the result URI is the remainder of the relative URI's path
1689         String pth = relativePath.substring(thisPath.length());
1690         return new Uri( null,
1691                         null,
1692                         setSchemeSpecificPart(null, pth, qry),
1693                         null,
1694                         null,
1695                         null,
1696                         -1,
1697                         pth,
1698                         qry,
1699                         relative.fragment,
1700                         false,
1701                         false,
1702                         false,
1703                         -1, 
1704                 fileSchemeCaseInsensitiveOS);
1705     }
1706 
1707     /**
1708      * Resolves the given URI {@code relative} against the URI represented by
1709      * this instance.
1710      *
1711      * @param relative
1712      *            the URI which has to be resolved against this URI.
1713      * @return the resolved URI.
1714      */
1715     public Uri resolve(Uri relative) {
1716         if (relative.absolute || opaque) {
1717             return relative;
1718         }
1719 
1720         if (relative.path.equals("") && relative.scheme == null //$NON-NLS-1$
1721                 && relative.authority == null && relative.query == null
1722                 && relative.fragment != null) {
1723             // if the relative URI only consists of fragment,
1724             // the resolved URI is very similar to this URI,
1725             // except that it has the fragement from the relative URI.
1726             
1727             return new Uri( null,
1728                         scheme,
1729                         schemespecificpart,
1730                         authority,
1731                         userinfo,
1732                         host,
1733                         port,
1734                         path,
1735                         query,
1736                         relative.fragment,
1737                         opaque,
1738                         absolute,
1739                         serverAuthority,
1740                         hash,
1741                     fileSchemeCaseInsensitiveOS);
1742             // no need to re-calculate the scheme specific part,
1743             // since fragment is not part of scheme specific part.
1744            
1745         }
1746 
1747         if (relative.authority != null) {
1748             // if the relative URI has authority,
1749             // the resolved URI is almost the same as the relative URI,
1750             // except that it has the scheme of this URI.
1751             return new Uri( null,
1752                         scheme,
1753                         relative.schemespecificpart,
1754                         relative.authority,
1755                         relative.userinfo,
1756                         relative.host,
1757                         relative.port,
1758                         relative.path,
1759                         relative.query,
1760                         relative.fragment,
1761                         relative.opaque,
1762                         absolute,
1763                         relative.serverAuthority,
1764                         relative.hash, 
1765                     fileSchemeCaseInsensitiveOS);
1766         } else {
1767             // since relative URI has no authority,
1768             // the resolved URI is very similar to this URI,
1769             // except that it has the query and fragment of the relative URI,
1770             // and the path is different.
1771             // re-calculate the scheme specific part since
1772             // query and path of the resolved URI is different from this URI.
1773             int endindex = path.lastIndexOf('/') + 1;
1774             String p = relative.path.startsWith("/")? relative.path: 
1775                         normalize(path.substring(0, endindex) + relative.path);
1776             return new Uri( null,
1777                         scheme,
1778                         setSchemeSpecificPart(authority, p, relative.query),
1779                         authority,
1780                         userinfo,
1781                         host,
1782                         port,
1783                         p,
1784                         relative.query,
1785                         relative.fragment,
1786                         opaque,
1787                         absolute,
1788                         serverAuthority,
1789                         hash, 
1790                     fileSchemeCaseInsensitiveOS);
1791         }
1792     }
1793 
1794     /**
1795      * UriParser method used to re-calculate the scheme specific part of the
1796      * resolved or normalized URIs
1797      */
1798     private String setSchemeSpecificPart(String authority,
1799                                          String path,
1800                                          String query) {
1801         // ssp = [//authority][path][?query]
1802         StringBuilder ssp = new StringBuilder();
1803         if (authority != null) {
1804             ssp.append("//"); //$NON-NLS-1$
1805             ssp.append(authority);
1806         }
1807         if (path != null) {
1808             ssp.append(path);
1809         }
1810         if (query != null) {
1811             ssp.append("?"); //$NON-NLS-1$
1812             ssp.append(query);
1813         }
1814         // reset string, so that it can be re-calculated correctly when asked.
1815         return ssp.toString();
1816     }
1817 
1818     /**
1819      * Creates a new URI instance by parsing the given string {@code relative}
1820      * and resolves the created URI against the URI represented by this
1821      * instance.
1822      *
1823      * @param relative
1824      *            the given string to create the new URI instance which has to
1825      *            be resolved later on.
1826      * @return the created and resolved URI.
1827      */
1828     public Uri resolve(String relative) {
1829         return resolve(create(relative));
1830     }
1831 
1832     /*
1833      * Encode unicode chars that are not part of US-ASCII char set into the
1834      * escaped form
1835      * 
1836      * i.e. The Euro currency symbol is encoded as "%E2%82%AC".
1837      * 
1838      * @param component java.lang.String the component to be converted @param
1839      * legalset java.lang.String the legal character set allowed in the
1840      * component s @return java.lang.String the converted string
1841      */
1842     private String encodeOthers(String s) {
1843         try {
1844             /*
1845              * Use a different encoder than URLEncoder since: 1. chars like "/",
1846              * "#", "@" etc needs to be preserved instead of being encoded, 2.
1847              * UTF-8 char set needs to be used for encoding instead of default
1848              * platform one 3. Only other chars need to be converted
1849              */
1850             return URIEncoderDecoder.encodeOthers(s);
1851         } catch (UnsupportedEncodingException e) {
1852             throw new RuntimeException(e.toString());
1853         }
1854     }
1855 
1856     private String decode(String s) {
1857         if (s == null) {
1858             return s;
1859         }
1860 
1861         try {
1862             return URIEncoderDecoder.decode(s);
1863         } catch (UnsupportedEncodingException e) {
1864             throw new RuntimeException(e.toString());
1865         }
1866     }
1867 
1868     /**
1869      * Returns the textual string representation of this URI instance using the
1870      * US-ASCII encoding.
1871      *
1872      * @return the US-ASCII string representation of this URI.
1873      */
1874     public String toASCIIString() {
1875         return encodeOthers(toString());
1876     }
1877 
1878     /**
1879      * Returns the textual string representation of this URI instance.
1880      *
1881      * @return the textual string representation of this URI.
1882      */
1883     @Override
1884     public String toString() {
1885         return string;
1886     }
1887 
1888     /*
1889      * Form a string from the components of this URI, similarly to the
1890      * toString() method. But this method converts scheme and host to lowercase,
1891      * and converts escaped octets to uppercase.
1892      * 
1893      * Should convert octets to uppercase and follow platform specific 
1894      * normalization rules for file: uri.
1895      */
1896     private String getHashString() {
1897         StringBuilder result = new StringBuilder();
1898         if (scheme != null) {
1899             result.append(toAsciiLowerCase(scheme));
1900             result.append(':');
1901         }
1902         if (opaque) {
1903             result.append(schemespecificpart);
1904         } else {
1905             if (authority != null) {
1906                 result.append("//"); //$NON-NLS-1$
1907                 if (host == null) {
1908                     result.append(authority);
1909                 } else {
1910                     if (userinfo != null) {
1911                         result.append(userinfo); //$NON-NLS-1$
1912                         result.append("@");
1913                     }
1914                     result.append(toAsciiLowerCase(host));
1915                     if (port != -1) {
1916                         result.append(":"); //$NON-NLS-1$
1917                         result.append(port);
1918                     }
1919                 }
1920             }
1921 
1922             if (path != null) {
1923                 if (fileSchemeCaseInsensitiveOS){
1924                     result.append(toAsciiUpperCase(path));
1925                 } else {
1926                     result.append(path);
1927                 }
1928             }
1929 
1930             if (query != null) {
1931                 result.append('?');
1932                 result.append(query);
1933             }
1934         }
1935 
1936         if (fragment != null) {
1937             result.append('#');
1938             result.append(fragment);
1939         }
1940 
1941         return convertHexToUpperCase(result.toString());
1942     }
1943 
1944     /**
1945      * Converts this URI instance to a URL.
1946      *
1947      * @return the created URL representing the same resource as this URI.
1948      * @throws MalformedURLException
1949      *             if an error occurs while creating the URL or no protocol
1950      *             handler could be found.
1951      */
1952     public URL toURL() throws MalformedURLException {
1953         if (!absolute) {
1954             throw new IllegalArgumentException(Messages.getString("luni.91") + ": " //$NON-NLS-1$//$NON-NLS-2$
1955                     + toString());
1956         }
1957         return new URL(toString());
1958     }
1959 }