URI xref

View Javadoc

    1   /*
    2    * $HeadURL: https://svn.apache.org/repos/asf/jakarta/httpcomponents/oac.hc3x/tags/HTTPCLIENT_3_1/src/java/org/apache/commons/httpclient/URI.java $
    3    * $Revision: 564973 $
    4    * $Date: 2007-08-11 22:51:47 +0200 (Sat, 11 Aug 2007) $
    5    *
    6    * ====================================================================
    7    *
    8    *  Licensed to the Apache Software Foundation (ASF) under one or more
    9    *  contributor license agreements.  See the NOTICE file distributed with
    10   *  this work for additional information regarding copyright ownership.
    11   *  The ASF licenses this file to You under the Apache License, Version 2.0
    12   *  (the "License"); you may not use this file except in compliance with
    13   *  the License.  You may obtain a copy of the License at
    14   *
    15   *      http://www.apache.org/licenses/LICENSE-2.0
    16   *
    17   *  Unless required by applicable law or agreed to in writing, software
    18   *  distributed under the License is distributed on an "AS IS" BASIS,
    19   *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    20   *  See the License for the specific language governing permissions and
    21   *  limitations under the License.
    22   * ====================================================================
    23   *
    24   * This software consists of voluntary contributions made by many
    25   * individuals on behalf of the Apache Software Foundation.  For more
    26   * information on the Apache Software Foundation, please see
    27   * <http://www.apache.org/>.
    28   *
    29   */
    30
    31  package org.apache.commons.httpclient;
    32
    33  import java.io.IOException;
    34  import java.io.ObjectInputStream;
    35  import java.io.ObjectOutputStream;
    36  import java.io.Serializable;
    37  import java.util.Arrays;
    38  import java.util.Locale;
    39  import java.util.BitSet;
    40  import java.util.Hashtable;
    41
    42  import org.apache.commons.codec.DecoderException;
    43  import org.apache.commons.codec.net.URLCodec;
    44  import org.apache.commons.httpclient.util.EncodingUtil;
    45
    46  /**
    47   * The interface for the URI(Uniform Resource Identifiers) version of RFC 2396.
    48   * This class has the purpose of supportting of parsing a URI reference to
    49   * extend any specific protocols, the character encoding of the protocol to
    50   * be transported and the charset of the document.
    51   * <p>
    52   * A URI is always in an "escaped" form, since escaping or unescaping a
    53   * completed URI might change its semantics.
    54   * <p>
    55   * Implementers should be careful not to escape or unescape the same string
    56   * more than once, since unescaping an already unescaped string might lead to
    57   * misinterpreting a percent data character as another escaped character,
    58   * or vice versa in the case of escaping an already escaped string.
    59   * <p>
    60   * In order to avoid these problems, data types used as follows:
    61   * <p><blockquote><pre>
    62   *   URI character sequence: char
    63   *   octet sequence: byte
    64   *   original character sequence: String
    65   * </pre></blockquote><p>
    66   *
    67   * So, a URI is a sequence of characters as an array of a char type, which
    68   * is not always represented as a sequence of octets as an array of byte.
    69   * <p>
    70   *
    71   * URI Syntactic Components
    72   * <p><blockquote><pre>
    73   * - In general, written as follows:
    74   *   Absolute URI = &lt;scheme&gt:&lt;scheme-specific-part&gt;
    75   *   Generic URI = &lt;scheme&gt;://&lt;authority&gt;&lt;path&gt;?&lt;query&gt;
    76   *
    77   * - Syntax
    78   *   absoluteURI   = scheme ":" ( hier_part | opaque_part )
    79   *   hier_part     = ( net_path | abs_path ) [ "?" query ]
    80   *   net_path      = "//" authority [ abs_path ]
    81   *   abs_path      = "/"  path_segments
    82   * </pre></blockquote><p>
    83   *
    84   * The following examples illustrate URI that are in common use.
    85   * <pre>
    86   * ftp://ftp.is.co.za/rfc/rfc1808.txt
    87   *    -- ftp scheme for File Transfer Protocol services
    88   * gopher://spinaltap.micro.umn.edu/00/Weather/California/Los%20Angeles
    89   *    -- gopher scheme for Gopher and Gopher+ Protocol services
    90   * http://www.math.uio.no/faq/compression-faq/part1.html
    91   *    -- http scheme for Hypertext Transfer Protocol services
    92   * mailto:mduerst@ifi.unizh.ch
    93   *    -- mailto scheme for electronic mail addresses
    94   * news:comp.infosystems.www.servers.unix
    95   *    -- news scheme for USENET news groups and articles
    96   * telnet://melvyl.ucop.edu/
    97   *    -- telnet scheme for interactive services via the TELNET Protocol
    98   * </pre>
    99   * Please, notice that there are many modifications from URL(RFC 1738) and
    100  * relative URL(RFC 1808).
    101  * <p>
    102  * <b>The expressions for a URI</b>
    103  * <p><pre>
    104  * For escaped URI forms
    105  *  - URI(char[]) // constructor
    106  *  - char[] getRawXxx() // method
    107  *  - String getEscapedXxx() // method
    108  *  - String toString() // method
    109  * <p>
    110  * For unescaped URI forms
    111  *  - URI(String) // constructor
    112  *  - String getXXX() // method
    113  * </pre><p>
    114  *
    115  * @author <a href="mailto:jericho@apache.org">Sung-Gu</a>
    116  * @author <a href="mailto:mbowler@GargoyleSoftware.com">Mike Bowler</a>
    117  * @version $Revision: 564973 $ $Date: 2002/03/14 15:14:01
    118  */
    119 public class URI implements Cloneable, Comparable, Serializable {
    120
    121
    122     // ----------------------------------------------------------- Constructors
    123
    124     /** Create an instance as an internal use */
    125     protected URI() {
    126     }
    127
    128     /**
    129      * Construct a URI from a string with the given charset. The input string can
    130      * be either in escaped or unescaped form.
    131      *
    132      * @param s URI character sequence
    133      * @param escaped <tt>true</tt> if URI character sequence is in escaped form.
    134      *                <tt>false</tt> otherwise.
    135      * @param charset the charset string to do escape encoding, if required
    136      *
    137      * @throws URIException If the URI cannot be created.
    138      * @throws NullPointerException if input string is <code>null</code>
    139      *
    140      * @see #getProtocolCharset
    141      *
    142      * @since 3.0
    143      */
    144     public URI(String s, boolean escaped, String charset)
    145         throws URIException, NullPointerException {
    146         protocolCharset = charset;
    147         parseUriReference(s, escaped);
    148     }
    149
    150     /**
    151      * Construct a URI from a string with the given charset. The input string can
    152      * be either in escaped or unescaped form.
    153      *
    154      * @param s URI character sequence
    155      * @param escaped <tt>true</tt> if URI character sequence is in escaped form.
    156      *                <tt>false</tt> otherwise.
    157      *
    158      * @throws URIException If the URI cannot be created.
    159      * @throws NullPointerException if input string is <code>null</code>
    160      *
    161      * @see #getProtocolCharset
    162      *
    163      * @since 3.0
    164      */
    165     public URI(String s, boolean escaped)
    166         throws URIException, NullPointerException {
    167         parseUriReference(s, escaped);
    168     }
    169
    170     /**
    171      * Construct a URI as an escaped form of a character array with the given
    172      * charset.
    173      *
    174      * @param escaped the URI character sequence
    175      * @param charset the charset string to do escape encoding
    176      * @throws URIException If the URI cannot be created.
    177      * @throws NullPointerException if <code>escaped</code> is <code>null</code>
    178      * @see #getProtocolCharset
    179      *
    180      * @deprecated Use #URI(String, boolean, String)
    181      */
    182     public URI(char[] escaped, String charset)
    183         throws URIException, NullPointerException {
    184         protocolCharset = charset;
    185         parseUriReference(new String(escaped), true);
    186     }
    187
    188
    189     /**
    190      * Construct a URI as an escaped form of a character array.
    191      * An URI can be placed within double-quotes or angle brackets like
    192      * "http://test.com/" and &lt;http://test.com/&gt;
    193      *
    194      * @param escaped the URI character sequence
    195      * @throws URIException If the URI cannot be created.
    196      * @throws NullPointerException if <code>escaped</code> is <code>null</code>
    197      * @see #getDefaultProtocolCharset
    198      *
    199      * @deprecated Use #URI(String, boolean)
    200      */
    201     public URI(char[] escaped)
    202         throws URIException, NullPointerException {
    203         parseUriReference(new String(escaped), true);
    204     }
    205
    206
    207     /**
    208      * Construct a URI from the given string with the given charset.
    209      *
    210      * @param original the string to be represented to URI character sequence
    211      * It is one of absoluteURI and relativeURI.
    212      * @param charset the charset string to do escape encoding
    213      * @throws URIException If the URI cannot be created.
    214      * @see #getProtocolCharset
    215      *
    216      * @deprecated Use #URI(String, boolean, String)
    217      */
    218     public URI(String original, String charset) throws URIException {
    219         protocolCharset = charset;
    220         parseUriReference(original, false);
    221     }
    222
    223
    224     /**
    225      * Construct a URI from the given string.
    226      * <p><blockquote><pre>
    227      *   URI-reference = [ absoluteURI | relativeURI ] [ "#" fragment ]
    228      * </pre></blockquote><p>
    229      * An URI can be placed within double-quotes or angle brackets like
    230      * "http://test.com/" and &lt;http://test.com/&gt;
    231      *
    232      * @param original the string to be represented to URI character sequence
    233      * It is one of absoluteURI and relativeURI.
    234      * @throws URIException If the URI cannot be created.
    235      * @see #getDefaultProtocolCharset
    236      *
    237      * @deprecated Use #URI(String, boolean)
    238      */
    239     public URI(String original) throws URIException {
    240         parseUriReference(original, false);
    241     }
    242
    243
    244     /**
    245      * Construct a general URI from the given components.
    246      * <p><blockquote><pre>
    247      *   URI-reference = [ absoluteURI | relativeURI ] [ "#" fragment ]
    248      *   absoluteURI   = scheme ":" ( hier_part | opaque_part )
    249      *   opaque_part   = uric_no_slash *uric
    250      * </pre></blockquote><p>
    251      * It's for absolute URI = &lt;scheme&gt;:&lt;scheme-specific-part&gt;#
    252      * &lt;fragment&gt;.
    253      *
    254      * @param scheme the scheme string
    255      * @param schemeSpecificPart scheme_specific_part
    256      * @param fragment the fragment string
    257      * @throws URIException If the URI cannot be created.
    258      * @see #getDefaultProtocolCharset
    259      */
    260     public URI(String scheme, String schemeSpecificPart, String fragment)
    261         throws URIException {
    262
    263         // validate and contruct the URI character sequence
    264         if (scheme == null) {
    265            throw new URIException(URIException.PARSING, "scheme required");
    266         }
    267         char[] s = scheme.toLowerCase().toCharArray();
    268         if (validate(s, URI.scheme)) {
    269             _scheme = s; // is_absoluteURI
    270         } else {
    271             throw new URIException(URIException.PARSING, "incorrect scheme");
    272         }
    273         _opaque = encode(schemeSpecificPart, allowed_opaque_part,
    274                 getProtocolCharset());
    275         // Set flag
    276         _is_opaque_part = true;
    277         _fragment = fragment == null ? null : fragment.toCharArray();
    278         setURI();
    279     }
    280
    281
    282     /**
    283      * Construct a general URI from the given components.
    284      * <p><blockquote><pre>
    285      *   URI-reference = [ absoluteURI | relativeURI ] [ "#" fragment ]
    286      *   absoluteURI   = scheme ":" ( hier_part | opaque_part )
    287      *   relativeURI   = ( net_path | abs_path | rel_path ) [ "?" query ]
    288      *   hier_part     = ( net_path | abs_path ) [ "?" query ]
    289      * </pre></blockquote><p>
    290      * It's for absolute URI = &lt;scheme&gt;:&lt;path&gt;?&lt;query&gt;#&lt;
    291      * fragment&gt; and relative URI = &lt;path&gt;?&lt;query&gt;#&lt;fragment
    292      * &gt;.
    293      *
    294      * @param scheme the scheme string
    295      * @param authority the authority string
    296      * @param path the path string
    297      * @param query the query string
    298      * @param fragment the fragment string
    299      * @throws URIException If the new URI cannot be created.
    300      * @see #getDefaultProtocolCharset
    301      */
    302     public URI(String scheme, String authority, String path, String query,
    303                String fragment) throws URIException {
    304
    305         // validate and contruct the URI character sequence
    306         StringBuffer buff = new StringBuffer();
    307         if (scheme != null) {
    308             buff.append(scheme);
    309             buff.append(':');
    310         }
    311         if (authority != null) {
    312             buff.append("//");
    313             buff.append(authority);
    314         }
    315         if (path != null) {  // accept empty path
    316             if ((scheme != null || authority != null)
    317                     && !path.startsWith("/")) {
    318                 throw new URIException(URIException.PARSING,
    319                         "abs_path requested");
    320             }
    321             buff.append(path);
    322         }
    323         if (query != null) {
    324             buff.append('?');
    325             buff.append(query);
    326         }
    327         if (fragment != null) {
    328             buff.append('#');
    329             buff.append(fragment);
    330         }
    331         parseUriReference(buff.toString(), false);
    332     }
    333
    334
    335     /**
    336      * Construct a general URI from the given components.
    337      *
    338      * @param scheme the scheme string
    339      * @param userinfo the userinfo string
    340      * @param host the host string
    341      * @param port the port number
    342      * @throws URIException If the new URI cannot be created.
    343      * @see #getDefaultProtocolCharset
    344      */
    345     public URI(String scheme, String userinfo, String host, int port)
    346         throws URIException {
    347
    348         this(scheme, userinfo, host, port, null, null, null);
    349     }
    350
    351
    352     /**
    353      * Construct a general URI from the given components.
    354      *
    355      * @param scheme the scheme string
    356      * @param userinfo the userinfo string
    357      * @param host the host string
    358      * @param port the port number
    359      * @param path the path string
    360      * @throws URIException If the new URI cannot be created.
    361      * @see #getDefaultProtocolCharset
    362      */
    363     public URI(String scheme, String userinfo, String host, int port,
    364             String path) throws URIException {
    365
    366         this(scheme, userinfo, host, port, path, null, null);
    367     }
    368
    369
    370     /**
    371      * Construct a general URI from the given components.
    372      *
    373      * @param scheme the scheme string
    374      * @param userinfo the userinfo string
    375      * @param host the host string
    376      * @param port the port number
    377      * @param path the path string
    378      * @param query the query string
    379      * @throws URIException If the new URI cannot be created.
    380      * @see #getDefaultProtocolCharset
    381      */
    382     public URI(String scheme, String userinfo, String host, int port,
    383             String path, String query) throws URIException {
    384
    385         this(scheme, userinfo, host, port, path, query, null);
    386     }
    387
    388
    389     /**
    390      * Construct a general URI from the given components.
    391      *
    392      * @param scheme the scheme string
    393      * @param userinfo the userinfo string
    394      * @param host the host string
    395      * @param port the port number
    396      * @param path the path string
    397      * @param query the query string
    398      * @param fragment the fragment string
    399      * @throws URIException If the new URI cannot be created.
    400      * @see #getDefaultProtocolCharset
    401      */
    402     public URI(String scheme, String userinfo, String host, int port,
    403             String path, String query, String fragment) throws URIException {
    404
    405         this(scheme, (host == null) ? null
    406             : ((userinfo != null) ? userinfo + '@' : "") + host
    407                 + ((port != -1) ? ":" + port : ""), path, query, fragment);
    408     }
    409
    410
    411     /**
    412      * Construct a general URI from the given components.
    413      *
    414      * @param scheme the scheme string
    415      * @param host the host string
    416      * @param path the path string
    417      * @param fragment the fragment string
    418      * @throws URIException If the new URI cannot be created.
    419      * @see #getDefaultProtocolCharset
    420      */
    421     public URI(String scheme, String host, String path, String fragment)
    422         throws URIException {
    423
    424         this(scheme, host, path, null, fragment);
    425     }
    426
    427
    428     /**
    429      * Construct a general URI with the given relative URI string.
    430      *
    431      * @param base the base URI
    432      * @param relative the relative URI string
    433      * @throws URIException If the new URI cannot be created.
    434      *
    435      * @deprecated Use #URI(URI, String, boolean)
    436      */
    437     public URI(URI base, String relative) throws URIException {
    438         this(base, new URI(relative));
    439     }
    440
    441
    442     /**
    443      * Construct a general URI with the given relative URI string.
    444      *
    445      * @param base the base URI
    446      * @param relative the relative URI string
    447      * @param escaped <tt>true</tt> if URI character sequence is in escaped form.
    448      *                <tt>false</tt> otherwise.
    449      *
    450      * @throws URIException If the new URI cannot be created.
    451      *
    452      * @since 3.0
    453      */
    454     public URI(URI base, String relative, boolean escaped) throws URIException {
    455         this(base, new URI(relative, escaped));
    456     }
    457
    458
    459     /**
    460      * Construct a general URI with the given relative URI.
    461      * <p><blockquote><pre>
    462      *   URI-reference = [ absoluteURI | relativeURI ] [ "#" fragment ]
    463      *   relativeURI   = ( net_path | abs_path | rel_path ) [ "?" query ]
    464      * </pre></blockquote><p>
    465      * Resolving Relative References to Absolute Form.
    466      *
    467      * <strong>Examples of Resolving Relative URI References</strong>
    468      *
    469      * Within an object with a well-defined base URI of
    470      * <p><blockquote><pre>
    471      *   http://a/b/c/d;p?q
    472      * </pre></blockquote><p>
    473      * the relative URI would be resolved as follows:
    474      *
    475      * Normal Examples
    476      *
    477      * <p><blockquote><pre>
    478      *   g:h           =  g:h
    479      *   g             =  http://a/b/c/g
    480      *   ./g           =  http://a/b/c/g
    481      *   g/            =  http://a/b/c/g/
    482      *   /g            =  http://a/g
    483      *   //g           =  http://g
    484      *   ?y            =  http://a/b/c/?y
    485      *   g?y           =  http://a/b/c/g?y
    486      *   #s            =  (current document)#s
    487      *   g#s           =  http://a/b/c/g#s
    488      *   g?y#s         =  http://a/b/c/g?y#s
    489      *   ;x            =  http://a/b/c/;x
    490      *   g;x           =  http://a/b/c/g;x
    491      *   g;x?y#s       =  http://a/b/c/g;x?y#s
    492      *   .             =  http://a/b/c/
    493      *   ./            =  http://a/b/c/
    494      *   ..            =  http://a/b/
    495      *   ../           =  http://a/b/
    496      *   ../g          =  http://a/b/g
    497      *   ../..         =  http://a/
    498      *   ../../        =  http://a/
    499      *   ../../g       =  http://a/g
    500      * </pre></blockquote><p>
    501      *
    502      * Some URI schemes do not allow a hierarchical syntax matching the
    503      * <hier_part> syntax, and thus cannot use relative references.
    504      *
    505      * @param base the base URI
    506      * @param relative the relative URI
    507      * @throws URIException If the new URI cannot be created.
    508      */
    509     public URI(URI base, URI relative) throws URIException {
    510
    511         if (base._scheme == null) {
    512             throw new URIException(URIException.PARSING, "base URI required");
    513         }
    514         if (base._scheme != null) {
    515             this._scheme = base._scheme;
    516             this._authority = base._authority;
    517             this._is_net_path = base._is_net_path;
    518         }
    519         if (base._is_opaque_part || relative._is_opaque_part) {
    520             this._scheme = base._scheme;
    521             this._is_opaque_part = base._is_opaque_part
    522                 || relative._is_opaque_part;
    523             this._opaque = relative._opaque;
    524             this._fragment = relative._fragment;
    525             this.setURI();
    526             return;
    527         }
    528         boolean schemesEqual = Arrays.equals(base._scheme,relative._scheme);
    529         if (relative._scheme != null
    530                 && (!schemesEqual  || relative._authority != null)) {
    531             this._scheme = relative._scheme;
    532             this._is_net_path = relative._is_net_path;
    533             this._authority = relative._authority;
    534             if (relative._is_server) {
    535                 this._is_server = relative._is_server;
    536                 this._userinfo = relative._userinfo;
    537                 this._host = relative._host;
    538                 this._port = relative._port;
    539             } else if (relative._is_reg_name) {
    540                 this._is_reg_name = relative._is_reg_name;
    541             }
    542             this._is_abs_path = relative._is_abs_path;
    543             this._is_rel_path = relative._is_rel_path;
    544             this._path = relative._path;
    545         } else if (base._authority != null && relative._scheme == null) {
    546             this._is_net_path = base._is_net_path;
    547             this._authority = base._authority;
    548             if (base._is_server) {
    549                 this._is_server = base._is_server;
    550                 this._userinfo = base._userinfo;
    551                 this._host = base._host;
    552                 this._port = base._port;
    553             } else if (base._is_reg_name) {
    554                 this._is_reg_name = base._is_reg_name;
    555             }
    556         }
    557         if (relative._authority != null) {
    558             this._is_net_path = relative._is_net_path;
    559             this._authority = relative._authority;
    560             if (relative._is_server) {
    561                 this._is_server = relative._is_server;
    562                 this._userinfo = relative._userinfo;
    563                 this._host = relative._host;
    564                 this._port = relative._port;
    565             } else if (relative._is_reg_name) {
    566                 this._is_reg_name = relative._is_reg_name;
    567             }
    568             this._is_abs_path = relative._is_abs_path;
    569             this._is_rel_path = relative._is_rel_path;
    570             this._path = relative._path;
    571         }
    572         // resolve the path and query if necessary
    573         if (relative._authority == null
    574             && (relative._scheme == null || schemesEqual)) {
    575             if ((relative._path == null || relative._path.length == 0)
    576                 && relative._query == null) {
    577                 // handle a reference to the current document, see RFC 2396
    578                 // section 5.2 step 2
    579                 this._path = base._path;
    580                 this._query = base._query;
    581             } else {
    582                 this._path = resolvePath(base._path, relative._path);
    583             }
    584         }
    585         // base._query removed
    586         if (relative._query != null) {
    587             this._query = relative._query;
    588         }
    589         // base._fragment removed
    590         if (relative._fragment != null) {
    591             this._fragment = relative._fragment;
    592         }
    593         this.setURI();
    594         // reparse the newly built URI, this will ensure that all flags are set correctly.
    595         // TODO there must be a better way to do this
    596         parseUriReference(new String(_uri), true);
    597     }
    598
    599     // --------------------------------------------------- Instance Variables
    600
    601     /** Version ID for serialization */
    602     static final long serialVersionUID = 604752400577948726L;
    603
    604
    605     /**
    606      * Cache the hash code for this URI.
    607      */
    608     protected int hash = 0;
    609
    610
    611     /**
    612      * This Uniform Resource Identifier (URI).
    613      * The URI is always in an "escaped" form, since escaping or unescaping
    614      * a completed URI might change its semantics.
    615      */
    616     protected char[] _uri = null;
    617
    618
    619     /**
    620      * The charset of the protocol used by this URI instance.
    621      */
    622     protected String protocolCharset = null;
    623
    624
    625     /**
    626      * The default charset of the protocol.  RFC 2277, 2396
    627      */
    628     protected static String defaultProtocolCharset = "UTF-8";
    629
    630
    631     /**
    632      * The default charset of the document.  RFC 2277, 2396
    633      * The platform's charset is used for the document by default.
    634      */
    635     protected static String defaultDocumentCharset = null;
    636     protected static String defaultDocumentCharsetByLocale = null;
    637     protected static String defaultDocumentCharsetByPlatform = null;
    638     // Static initializer for defaultDocumentCharset
    639     static {
    640         Locale locale = Locale.getDefault();
    641         // in order to support backward compatiblity
    642         if (locale != null) {
    643             defaultDocumentCharsetByLocale =
    644                 LocaleToCharsetMap.getCharset(locale);
    645             // set the default document charset
    646             defaultDocumentCharset = defaultDocumentCharsetByLocale;
    647         }
    648         // in order to support platform encoding
    649         try {
    650             defaultDocumentCharsetByPlatform = System.getProperty("file.encoding");
    651         } catch (SecurityException ignore) {
    652         }
    653         if (defaultDocumentCharset == null) {
    654             // set the default document charset
    655             defaultDocumentCharset = defaultDocumentCharsetByPlatform;
    656         }
    657     }
    658
    659
    660     /**
    661      * The scheme.
    662      */
    663     protected char[] _scheme = null;
    664
    665
    666     /**
    667      * The opaque.
    668      */
    669     protected char[] _opaque = null;
    670
    671
    672     /**
    673      * The authority.
    674      */
    675     protected char[] _authority = null;
    676
    677
    678     /**
    679      * The userinfo.
    680      */
    681     protected char[] _userinfo = null;
    682
    683
    684     /**
    685      * The host.
    686      */
    687     protected char[] _host = null;
    688
    689
    690     /**
    691      * The port.
    692      */
    693     protected int _port = -1;
    694
    695
    696     /**
    697      * The path.
    698      */
    699     protected char[] _path = null;
    700
    701
    702     /**
    703      * The query.
    704      */
    705     protected char[] _query = null;
    706
    707
    708     /**
    709      * The fragment.
    710      */
    711     protected char[] _fragment = null;
    712
    713
    714     /**
    715      * The root path.
    716      */
    717     protected static final char[] rootPath = { '/' };
    718
    719     // ---------------------- Generous characters for each component validation
    720
    721     /**
    722      * The percent "%" character always has the reserved purpose of being the
    723      * escape indicator, it must be escaped as "%25" in order to be used as
    724      * data within a URI.
    725      */
    726     protected static final BitSet percent = new BitSet(256);
    727     // Static initializer for percent
    728     static {
    729         percent.set('%');
    730     }
    731
    732
    733     /**
    734      * BitSet for digit.
    735      * <p><blockquote><pre>
    736      * digit    = "0" | "1" | "2" | "3" | "4" | "5" | "6" | "7" |
    737      *            "8" | "9"
    738      * </pre></blockquote><p>
    739      */
    740     protected static final BitSet digit = new BitSet(256);
    741     // Static initializer for digit
    742     static {
    743         for (int i = '0'; i <= '9'; i++) {
    744             digit.set(i);
    745         }
    746     }
    747
    748
    749     /**
    750      * BitSet for alpha.
    751      * <p><blockquote><pre>
    752      * alpha         = lowalpha | upalpha
    753      * </pre></blockquote><p>
    754      */
    755     protected static final BitSet alpha = new BitSet(256);
    756     // Static initializer for alpha
    757     static {
    758         for (int i = 'a'; i <= 'z'; i++) {
    759             alpha.set(i);
    760         }
    761         for (int i = 'A'; i <= 'Z'; i++) {
    762             alpha.set(i);
    763         }
    764     }
    765
    766
    767     /**
    768      * BitSet for alphanum (join of alpha &amp; digit).
    769      * <p><blockquote><pre>
    770      *  alphanum      = alpha | digit
    771      * </pre></blockquote><p>
    772      */
    773     protected static final BitSet alphanum = new BitSet(256);
    774     // Static initializer for alphanum
    775     static {
    776         alphanum.or(alpha);
    777         alphanum.or(digit);
    778     }
    779
    780
    781     /**
    782      * BitSet for hex.
    783      * <p><blockquote><pre>
    784      * hex           = digit | "A" | "B" | "C" | "D" | "E" | "F" |
    785      *                         "a" | "b" | "c" | "d" | "e" | "f"
    786      * </pre></blockquote><p>
    787      */
    788     protected static final BitSet hex = new BitSet(256);
    789     // Static initializer for hex
    790     static {
    791         hex.or(digit);
    792         for (int i = 'a'; i <= 'f'; i++) {
    793             hex.set(i);
    794         }
    795         for (int i = 'A'; i <= 'F'; i++) {
    796             hex.set(i);
    797         }
    798     }
    799
    800
    801     /**
    802      * BitSet for escaped.
    803      * <p><blockquote><pre>
    804      * escaped       = "%" hex hex
    805      * </pre></blockquote><p>
    806      */
    807     protected static final BitSet escaped = new BitSet(256);
    808     // Static initializer for escaped
    809     static {
    810         escaped.or(percent);
    811         escaped.or(hex);
    812     }
    813
    814
    815     /**
    816      * BitSet for mark.
    817      * <p><blockquote><pre>
    818      * mark          = "-" | "_" | "." | "!" | "~" | "*" | "'" |
    819      *                 "(" | ")"
    820      * </pre></blockquote><p>
    821      */
    822     protected static final BitSet mark = new BitSet(256);
    823     // Static initializer for mark
    824     static {
    825         mark.set('-');
    826         mark.set('_');
    827         mark.set('.');
    828         mark.set('!');
    829         mark.set('~');
    830         mark.set('*');
    831         mark.set('\'');
    832         mark.set('(');
    833         mark.set(')');
    834     }
    835
    836
    837     /**
    838      * Data characters that are allowed in a URI but do not have a reserved
    839      * purpose are called unreserved.
    840      * <p><blockquote><pre>
    841      * unreserved    = alphanum | mark
    842      * </pre></blockquote><p>
    843      */
    844     protected static final BitSet unreserved = new BitSet(256);
    845     // Static initializer for unreserved
    846     static {
    847         unreserved.or(alphanum);
    848         unreserved.or(mark);
    849     }
    850
    851
    852     /**
    853      * BitSet for reserved.
    854      * <p><blockquote><pre>
    855      * reserved      = ";" | "/" | "?" | ":" | "@" | "&amp;" | "=" | "+" |
    856      *                 "$" | ","
    857      * </pre></blockquote><p>
    858      */
    859     protected static final BitSet reserved = new BitSet(256);
    860     // Static initializer for reserved
    861     static {
    862         reserved.set(';');
    863         reserved.set('/');
    864         reserved.set('?');
    865         reserved.set(':');
    866         reserved.set('@');
    867         reserved.set('&');
    868         reserved.set('=');
    869         reserved.set('+');
    870         reserved.set('$');
    871         reserved.set(',');
    872     }
    873
    874
    875     /**
    876      * BitSet for uric.
    877      * <p><blockquote><pre>
    878      * uric          = reserved | unreserved | escaped
    879      * </pre></blockquote><p>
    880      */
    881     protected static final BitSet uric = new BitSet(256);
    882     // Static initializer for uric
    883     static {
    884         uric.or(reserved);
    885         uric.or(unreserved);
    886         uric.or(escaped);
    887     }
    888
    889
    890     /**
    891      * BitSet for fragment (alias for uric).
    892      * <p><blockquote><pre>
    893      * fragment      = *uric
    894      * </pre></blockquote><p>
    895      */
    896     protected static final BitSet fragment = uric;
    897
    898
    899     /**
    900      * BitSet for query (alias for uric).
    901      * <p><blockquote><pre>
    902      * query         = *uric
    903      * </pre></blockquote><p>
    904      */
    905     protected static final BitSet query = uric;
    906
    907
    908     /**
    909      * BitSet for pchar.
    910      * <p><blockquote><pre>
    911      * pchar         = unreserved | escaped |
    912      *                 ":" | "@" | "&amp;" | "=" | "+" | "$" | ","
    913      * </pre></blockquote><p>
    914      */
    915     protected static final BitSet pchar = new BitSet(256);
    916     // Static initializer for pchar
    917     static {
    918         pchar.or(unreserved);
    919         pchar.or(escaped);
    920         pchar.set(':');
    921         pchar.set('@');
    922         pchar.set('&');
    923         pchar.set('=');
    924         pchar.set('+');
    925         pchar.set('$');
    926         pchar.set(',');
    927     }
    928
    929
    930     /**
    931      * BitSet for param (alias for pchar).
    932      * <p><blockquote><pre>
    933      * param         = *pchar
    934      * </pre></blockquote><p>
    935      */
    936     protected static final BitSet param = pchar;
    937
    938
    939     /**
    940      * BitSet for segment.
    941      * <p><blockquote><pre>
    942      * segment       = *pchar *( ";" param )
    943      * </pre></blockquote><p>
    944      */
    945     protected static final BitSet segment = new BitSet(256);
    946     // Static initializer for segment
    947     static {
    948         segment.or(pchar);
    949         segment.set(';');
    950         segment.or(param);
    951     }
    952
    953
    954     /**
    955      * BitSet for path segments.
    956      * <p><blockquote><pre>
    957      * path_segments = segment *( "/" segment )
    958      * </pre></blockquote><p>
    959      */
    960     protected static final BitSet path_segments = new BitSet(256);
    961     // Static initializer for path_segments
    962     static {
    963         path_segments.set('/');
    964         path_segments.or(segment);
    965     }
    966
    967
    968     /**
    969      * URI absolute path.
    970      * <p><blockquote><pre>
    971      * abs_path      = "/"  path_segments
    972      * </pre></blockquote><p>
    973      */
    974     protected static final BitSet abs_path = new BitSet(256);
    975     // Static initializer for abs_path
    976     static {
    977         abs_path.set('/');
    978         abs_path.or(path_segments);
    979     }
    980
    981
    982     /**
    983      * URI bitset for encoding typical non-slash characters.
    984      * <p><blockquote><pre>
    985      * uric_no_slash = unreserved | escaped | ";" | "?" | ":" | "@" |
    986      *                 "&amp;" | "=" | "+" | "$" | ","
    987      * </pre></blockquote><p>
    988      */
    989     protected static final BitSet uric_no_slash = new BitSet(256);
    990     // Static initializer for uric_no_slash
    991     static {
    992         uric_no_slash.or(unreserved);
    993         uric_no_slash.or(escaped);
    994         uric_no_slash.set(';');
    995         uric_no_slash.set('?');
    996         uric_no_slash.set(';');
    997         uric_no_slash.set('@');
    998         uric_no_slash.set('&');
    999         uric_no_slash.set('=');
    1000         uric_no_slash.set('+');
    1001         uric_no_slash.set('$');
    1002         uric_no_slash.set(',');
    1003     }
    1004
    1005
    1006     /**
    1007      * URI bitset that combines uric_no_slash and uric.
    1008      * <p><blockquote><pre>
    1009      * opaque_part   = uric_no_slash *uric
    1010      * </pre></blockquote><p>
    1011      */
    1012     protected static final BitSet opaque_part = new BitSet(256);
    1013     // Static initializer for opaque_part
    1014     static {
    1015         // it's generous. because first character must not include a slash
    1016         opaque_part.or(uric_no_slash);
    1017         opaque_part.or(uric);
    1018     }
    1019
    1020
    1021     /**
    1022      * URI bitset that combines absolute path and opaque part.
    1023      * <p><blockquote><pre>
    1024      * path          = [ abs_path | opaque_part ]
    1025      * </pre></blockquote><p>
    1026      */
    1027     protected static final BitSet path = new BitSet(256);
    1028     // Static initializer for path
    1029     static {
    1030         path.or(abs_path);
    1031         path.or(opaque_part);
    1032     }
    1033
    1034
    1035     /**
    1036      * Port, a logical alias for digit.
    1037      */
    1038     protected static final BitSet port = digit;
    1039
    1040
    1041     /**
    1042      * Bitset that combines digit and dot fo IPv$address.
    1043      * <p><blockquote><pre>
    1044      * IPv4address   = 1*digit "." 1*digit "." 1*digit "." 1*digit
    1045      * </pre></blockquote><p>
    1046      */
    1047     protected static final BitSet IPv4address = new BitSet(256);
    1048     // Static initializer for IPv4address
    1049     static {
    1050         IPv4address.or(digit);
    1051         IPv4address.set('.');
    1052     }
    1053
    1054
    1055     /**
    1056      * RFC 2373.
    1057      * <p><blockquote><pre>
    1058      * IPv6address = hexpart [ ":" IPv4address ]
    1059      * </pre></blockquote><p>
    1060      */
    1061     protected static final BitSet IPv6address = new BitSet(256);
    1062     // Static initializer for IPv6address reference
    1063     static {
    1064         IPv6address.or(hex); // hexpart
    1065         IPv6address.set(':');
    1066         IPv6address.or(IPv4address);
    1067     }
    1068
    1069
    1070     /**
    1071      * RFC 2732, 2373.
    1072      * <p><blockquote><pre>
    1073      * IPv6reference   = "[" IPv6address "]"
    1074      * </pre></blockquote><p>
    1075      */
    1076     protected static final BitSet IPv6reference = new BitSet(256);
    1077     // Static initializer for IPv6reference
    1078     static {
    1079         IPv6reference.set('[');
    1080         IPv6reference.or(IPv6address);
    1081         IPv6reference.set(']');
    1082     }
    1083
    1084
    1085     /**
    1086      * BitSet for toplabel.
    1087      * <p><blockquote><pre>
    1088      * toplabel      = alpha | alpha *( alphanum | "-" ) alphanum
    1089      * </pre></blockquote><p>
    1090      */
    1091     protected static final BitSet toplabel = new BitSet(256);
    1092     // Static initializer for toplabel
    1093     static {
    1094         toplabel.or(alphanum);
    1095         toplabel.set('-');
    1096     }
    1097
    1098
    1099     /**
    1100      * BitSet for domainlabel.
    1101      * <p><blockquote><pre>
    1102      * domainlabel   = alphanum | alphanum *( alphanum | "-" ) alphanum
    1103      * </pre></blockquote><p>
    1104      */
    1105     protected static final BitSet domainlabel = toplabel;
    1106
    1107
    1108     /**
    1109      * BitSet for hostname.
    1110      * <p><blockquote><pre>
    1111      * hostname      = *( domainlabel "." ) toplabel [ "." ]
    1112      * </pre></blockquote><p>
    1113      */
    1114     protected static final BitSet hostname = new BitSet(256);
    1115     // Static initializer for hostname
    1116     static {
    1117         hostname.or(toplabel);
    1118         // hostname.or(domainlabel);
    1119         hostname.set('.');
    1120     }
    1121
    1122
    1123     /**
    1124      * BitSet for host.
    1125      * <p><blockquote><pre>
    1126      * host          = hostname | IPv4address | IPv6reference
    1127      * </pre></blockquote><p>
    1128      */
    1129     protected static final BitSet host = new BitSet(256);
    1130     // Static initializer for host
    1131     static {
    1132         host.or(hostname);
    1133         // host.or(IPv4address);
    1134         host.or(IPv6reference); // IPv4address
    1135     }
    1136
    1137
    1138     /**
    1139      * BitSet for hostport.
    1140      * <p><blockquote><pre>
    1141      * hostport      = host [ ":" port ]
    1142      * </pre></blockquote><p>
    1143      */
    1144     protected static final BitSet hostport = new BitSet(256);
    1145     // Static initializer for hostport
    1146     static {
    1147         hostport.or(host);
    1148         hostport.set(':');
    1149         hostport.or(port);
    1150     }
    1151
    1152
    1153     /**
    1154      * Bitset for userinfo.
    1155      * <p><blockquote><pre>
    1156      * userinfo      = *( unreserved | escaped |
    1157      *                    ";" | ":" | "&amp;" | "=" | "+" | "$" | "," )
    1158      * </pre></blockquote><p>
    1159      */
    1160     protected static final BitSet userinfo = new BitSet(256);
    1161     // Static initializer for userinfo
    1162     static {
    1163         userinfo.or(unreserved);
    1164         userinfo.or(escaped);
    1165         userinfo.set(';');
    1166         userinfo.set(':');
    1167         userinfo.set('&');
    1168         userinfo.set('=');
    1169         userinfo.set('+');
    1170         userinfo.set('$');
    1171         userinfo.set(',');
    1172     }
    1173
    1174
    1175     /**
    1176      * BitSet for within the userinfo component like user and password.
    1177      */
    1178     public static final BitSet within_userinfo = new BitSet(256);
    1179     // Static initializer for within_userinfo
    1180     static {
    1181         within_userinfo.or(userinfo);
    1182         within_userinfo.clear(';'); // reserved within authority
    1183         within_userinfo.clear(':');
    1184         within_userinfo.clear('@');
    1185         within_userinfo.clear('?');
    1186         within_userinfo.clear('/');
    1187     }
    1188
    1189
    1190     /**
    1191      * Bitset for server.
    1192      * <p><blockquote><pre>
    1193      * server        = [ [ userinfo "@" ] hostport ]
    1194      * </pre></blockquote><p>
    1195      */
    1196     protected static final BitSet server = new BitSet(256);
    1197     // Static initializer for server
    1198     static {
    1199         server.or(userinfo);
    1200         server.set('@');
    1201         server.or(hostport);
    1202     }
    1203
    1204
    1205     /**
    1206      * BitSet for reg_name.
    1207      * <p><blockquote><pre>
    1208      * reg_name      = 1*( unreserved | escaped | "$" | "," |
    1209      *                     ";" | ":" | "@" | "&amp;" | "=" | "+" )
    1210      * </pre></blockquote><p>
    1211      */
    1212     protected static final BitSet reg_name = new BitSet(256);
    1213     // Static initializer for reg_name
    1214     static {
    1215         reg_name.or(unreserved);
    1216         reg_name.or(escaped);
    1217         reg_name.set('$');
    1218         reg_name.set(',');
    1219         reg_name.set(';');
    1220         reg_name.set(':');
    1221         reg_name.set('@');
    1222         reg_name.set('&');
    1223         reg_name.set('=');
    1224         reg_name.set('+');
    1225     }
    1226
    1227
    1228     /**
    1229      * BitSet for authority.
    1230      * <p><blockquote><pre>
    1231      * authority     = server | reg_name
    1232      * </pre></blockquote><p>
    1233      */
    1234     protected static final BitSet authority = new BitSet(256);
    1235     // Static initializer for authority
    1236     static {
    1237         authority.or(server);
    1238         authority.or(reg_name);
    1239     }
    1240
    1241
    1242     /**
    1243      * BitSet for scheme.
    1244      * <p><blockquote><pre>
    1245      * scheme        = alpha *( alpha | digit | "+" | "-" | "." )
    1246      * </pre></blockquote><p>
    1247      */
    1248     protected static final BitSet scheme = new BitSet(256);
    1249     // Static initializer for scheme
    1250     static {
    1251         scheme.or(alpha);
    1252         scheme.or(digit);
    1253         scheme.set('+');
    1254         scheme.set('-');
    1255         scheme.set('.');
    1256     }
    1257
    1258
    1259     /**
    1260      * BitSet for rel_segment.
    1261      * <p><blockquote><pre>
    1262      * rel_segment   = 1*( unreserved | escaped |
    1263      *                     ";" | "@" | "&amp;" | "=" | "+" | "$" | "," )
    1264      * </pre></blockquote><p>
    1265      */
    1266     protected static final BitSet rel_segment = new BitSet(256);
    1267     // Static initializer for rel_segment
    1268     static {
    1269         rel_segment.or(unreserved);
    1270         rel_segment.or(escaped);
    1271         rel_segment.set(';');
    1272         rel_segment.set('@');
    1273         rel_segment.set('&');
    1274         rel_segment.set('=');
    1275         rel_segment.set('+');
    1276         rel_segment.set('$');
    1277         rel_segment.set(',');
    1278     }
    1279
    1280
    1281     /**
    1282      * BitSet for rel_path.
    1283      * <p><blockquote><pre>
    1284      * rel_path      = rel_segment [ abs_path ]
    1285      * </pre></blockquote><p>
    1286      */
    1287     protected static final BitSet rel_path = new BitSet(256);
    1288     // Static initializer for rel_path
    1289     static {
    1290         rel_path.or(rel_segment);
    1291         rel_path.or(abs_path);
    1292     }
    1293
    1294
    1295     /**
    1296      * BitSet for net_path.
    1297      * <p><blockquote><pre>
    1298      * net_path      = "//" authority [ abs_path ]
    1299      * </pre></blockquote><p>
    1300      */
    1301     protected static final BitSet net_path = new BitSet(256);
    1302     // Static initializer for net_path
    1303     static {
    1304         net_path.set('/');
    1305         net_path.or(authority);
    1306         net_path.or(abs_path);
    1307     }
    1308
    1309
    1310     /**
    1311      * BitSet for hier_part.
    1312      * <p><blockquote><pre>
    1313      * hier_part     = ( net_path | abs_path ) [ "?" query ]
    1314      * </pre></blockquote><p>
    1315      */
    1316     protected static final BitSet hier_part = new BitSet(256);
    1317     // Static initializer for hier_part
    1318     static {
    1319         hier_part.or(net_path);
    1320         hier_part.or(abs_path);
    1321         // hier_part.set('?'); aleady included
    1322         hier_part.or(query);
    1323     }
    1324
    1325
    1326     /**
    1327      * BitSet for relativeURI.
    1328      * <p><blockquote><pre>
    1329      * relativeURI   = ( net_path | abs_path | rel_path ) [ "?" query ]
    1330      * </pre></blockquote><p>
    1331      */
    1332     protected static final BitSet relativeURI = new BitSet(256);
    1333     // Static initializer for relativeURI
    1334     static {
    1335         relativeURI.or(net_path);
    1336         relativeURI.or(abs_path);
    1337         relativeURI.or(rel_path);
    1338         // relativeURI.set('?'); aleady included
    1339         relativeURI.or(query);
    1340     }
    1341
    1342
    1343     /**
    1344      * BitSet for absoluteURI.
    1345      * <p><blockquote><pre>
    1346      * absoluteURI   = scheme ":" ( hier_part | opaque_part )
    1347      * </pre></blockquote><p>
    1348      */
    1349     protected static final BitSet absoluteURI = new BitSet(256);
    1350     // Static initializer for absoluteURI
    1351     static {
    1352         absoluteURI.or(scheme);
    1353         absoluteURI.set(':');
    1354         absoluteURI.or(hier_part);
    1355         absoluteURI.or(opaque_part);
    1356     }
    1357
    1358
    1359     /**
    1360      * BitSet for URI-reference.
    1361      * <p><blockquote><pre>
    1362      * URI-reference = [ absoluteURI | relativeURI ] [ "#" fragment ]
    1363      * </pre></blockquote><p>
    1364      */
    1365     protected static final BitSet URI_reference = new BitSet(256);
    1366     // Static initializer for URI_reference
    1367     static {
    1368         URI_reference.or(absoluteURI);
    1369         URI_reference.or(relativeURI);
    1370         URI_reference.set('#');
    1371         URI_reference.or(fragment);
    1372     }
    1373
    1374     // ---------------------------- Characters disallowed within the URI syntax
    1375     // Excluded US-ASCII Characters are like control, space, delims and unwise
    1376
    1377     /**
    1378      * BitSet for control.
    1379      */
    1380     public static final BitSet control = new BitSet(256);
    1381     // Static initializer for control
    1382     static {
    1383         for (int i = 0; i <= 0x1F; i++) {
    1384             control.set(i);
    1385         }
    1386         control.set(0x7F);
    1387     }
    1388
    1389     /**
    1390      * BitSet for space.
    1391      */
    1392     public static final BitSet space = new BitSet(256);
    1393     // Static initializer for space
    1394     static {
    1395         space.set(0x20);
    1396     }
    1397
    1398
    1399     /**
    1400      * BitSet for delims.
    1401      */
    1402     public static final BitSet delims = new BitSet(256);
    1403     // Static initializer for delims
    1404     static {
    1405         delims.set('<');
    1406         delims.set('>');
    1407         delims.set('#');
    1408         delims.set('%');
    1409         delims.set('"');
    1410     }
    1411
    1412
    1413     /**
    1414      * BitSet for unwise.
    1415      */
    1416     public static final BitSet unwise = new BitSet(256);
    1417     // Static initializer for unwise
    1418     static {
    1419         unwise.set('{');
    1420         unwise.set('}');
    1421         unwise.set('|');
    1422         unwise.set('\\');
    1423         unwise.set('^');
    1424         unwise.set('[');
    1425         unwise.set(']');
    1426         unwise.set('`');
    1427     }
    1428
    1429
    1430     /**
    1431      * Disallowed rel_path before escaping.
    1432      */
    1433     public static final BitSet disallowed_rel_path = new BitSet(256);
    1434     // Static initializer for disallowed_rel_path
    1435     static {
    1436         disallowed_rel_path.or(uric);
    1437         disallowed_rel_path.andNot(rel_path);
    1438     }
    1439
    1440
    1441     /**
    1442      * Disallowed opaque_part before escaping.
    1443      */
    1444     public static final BitSet disallowed_opaque_part = new BitSet(256);
    1445     // Static initializer for disallowed_opaque_part
    1446     static {
    1447         disallowed_opaque_part.or(uric);
    1448         disallowed_opaque_part.andNot(opaque_part);
    1449     }
    1450
    1451     // ----------------------- Characters allowed within and for each component
    1452
    1453     /**
    1454      * Those characters that are allowed for the authority component.
    1455      */
    1456     public static final BitSet allowed_authority = new BitSet(256);
    1457     // Static initializer for allowed_authority
    1458     static {
    1459         allowed_authority.or(authority);
    1460         allowed_authority.clear('%');
    1461     }
    1462
    1463
    1464     /**
    1465      * Those characters that are allowed for the opaque_part.
    1466      */
    1467     public static final BitSet allowed_opaque_part = new BitSet(256);
    1468     // Static initializer for allowed_opaque_part
    1469     static {
    1470         allowed_opaque_part.or(opaque_part);
    1471         allowed_opaque_part.clear('%');
    1472     }
    1473
    1474
    1475     /**
    1476      * Those characters that are allowed for the reg_name.
    1477      */
    1478     public static final BitSet allowed_reg_name = new BitSet(256);
    1479     // Static initializer for allowed_reg_name
    1480     static {
    1481         allowed_reg_name.or(reg_name);
    1482         // allowed_reg_name.andNot(percent);
    1483         allowed_reg_name.clear('%');
    1484     }
    1485
    1486
    1487     /**
    1488      * Those characters that are allowed for the userinfo component.
    1489      */
    1490     public static final BitSet allowed_userinfo = new BitSet(256);
    1491     // Static initializer for allowed_userinfo
    1492     static {
    1493         allowed_userinfo.or(userinfo);
    1494         // allowed_userinfo.andNot(percent);
    1495         allowed_userinfo.clear('%');
    1496     }
    1497
    1498
    1499     /**
    1500      * Those characters that are allowed for within the userinfo component.
    1501      */
    1502     public static final BitSet allowed_within_userinfo = new BitSet(256);
    1503     // Static initializer for allowed_within_userinfo
    1504     static {
    1505         allowed_within_userinfo.or(within_userinfo);
    1506         allowed_within_userinfo.clear('%');
    1507     }
    1508
    1509
    1510     /**
    1511      * Those characters that are allowed for the IPv6reference component.
    1512      * The characters '[', ']' in IPv6reference should be excluded.
    1513      */
    1514     public static final BitSet allowed_IPv6reference = new BitSet(256);
    1515     // Static initializer for allowed_IPv6reference
    1516     static {
    1517         allowed_IPv6reference.or(IPv6reference);
    1518         // allowed_IPv6reference.andNot(unwise);
    1519         allowed_IPv6reference.clear('[');
    1520         allowed_IPv6reference.clear(']');
    1521     }
    1522
    1523
    1524     /**
    1525      * Those characters that are allowed for the host component.
    1526      * The characters '[', ']' in IPv6reference should be excluded.
    1527      */
    1528     public static final BitSet allowed_host = new BitSet(256);
    1529     // Static initializer for allowed_host
    1530     static {
    1531         allowed_host.or(hostname);
    1532         allowed_host.or(allowed_IPv6reference);
    1533     }
    1534
    1535
    1536     /**
    1537      * Those characters that are allowed for the authority component.
    1538      */
    1539     public static final BitSet allowed_within_authority = new BitSet(256);
    1540     // Static initializer for allowed_within_authority
    1541     static {
    1542         allowed_within_authority.or(server);
    1543         allowed_within_authority.or(reg_name);
    1544         allowed_within_authority.clear(';');
    1545         allowed_within_authority.clear(':');
    1546         allowed_within_authority.clear('@');
    1547         allowed_within_authority.clear('?');
    1548         allowed_within_authority.clear('/');
    1549     }
    1550
    1551
    1552     /**
    1553      * Those characters that are allowed for the abs_path.
    1554      */
    1555     public static final BitSet allowed_abs_path = new BitSet(256);
    1556     // Static initializer for allowed_abs_path
    1557     static {
    1558         allowed_abs_path.or(abs_path);
    1559         // allowed_abs_path.set('/');  // aleady included
    1560         allowed_abs_path.andNot(percent);
    1561         allowed_abs_path.clear('+');
    1562     }
    1563
    1564
    1565     /**
    1566      * Those characters that are allowed for the rel_path.
    1567      */
    1568     public static final BitSet allowed_rel_path = new BitSet(256);
    1569     // Static initializer for allowed_rel_path
    1570     static {
    1571         allowed_rel_path.or(rel_path);
    1572         allowed_rel_path.clear('%');
    1573         allowed_rel_path.clear('+');
    1574     }
    1575
    1576
    1577     /**
    1578      * Those characters that are allowed within the path.
    1579      */
    1580     public static final BitSet allowed_within_path = new BitSet(256);
    1581     // Static initializer for allowed_within_path
    1582     static {
    1583         allowed_within_path.or(abs_path);
    1584         allowed_within_path.clear('/');
    1585         allowed_within_path.clear(';');
    1586         allowed_within_path.clear('=');
    1587         allowed_within_path.clear('?');
    1588     }
    1589
    1590
    1591     /**
    1592      * Those characters that are allowed for the query component.
    1593      */
    1594     public static final BitSet allowed_query = new BitSet(256);
    1595     // Static initializer for allowed_query
    1596     static {
    1597         allowed_query.or(uric);
    1598         allowed_query.clear('%');
    1599     }
    1600
    1601
    1602     /**
    1603      * Those characters that are allowed within the query component.
    1604      */
    1605     public static final BitSet allowed_within_query = new BitSet(256);
    1606     // Static initializer for allowed_within_query
    1607     static {
    1608         allowed_within_query.or(allowed_query);
    1609         allowed_within_query.andNot(reserved); // excluded 'reserved'
    1610     }
    1611
    1612
    1613     /**
    1614      * Those characters that are allowed for the fragment component.
    1615      */
    1616     public static final BitSet allowed_fragment = new BitSet(256);
    1617     // Static initializer for allowed_fragment
    1618     static {
    1619         allowed_fragment.or(uric);
    1620         allowed_fragment.clear('%');
    1621     }
    1622
    1623     // ------------------------------------------- Flags for this URI-reference
    1624
    1625     // TODO: Figure out what all these variables are for and provide javadoc
    1626
    1627     // URI-reference = [ absoluteURI | relativeURI ] [ "#" fragment ]
    1628     // absoluteURI   = scheme ":" ( hier_part | opaque_part )
    1629     protected boolean _is_hier_part;
    1630     protected boolean _is_opaque_part;
    1631     // relativeURI   = ( net_path | abs_path | rel_path ) [ "?" query ]
    1632     // hier_part     = ( net_path | abs_path ) [ "?" query ]
    1633     protected boolean _is_net_path;
    1634     protected boolean _is_abs_path;
    1635     protected boolean _is_rel_path;
    1636     // net_path      = "//" authority [ abs_path ]
    1637     // authority     = server | reg_name
    1638     protected boolean _is_reg_name;
    1639     protected boolean _is_server;  // = _has_server
    1640     // server        = [ [ userinfo "@" ] hostport ]
    1641     // host          = hostname | IPv4address | IPv6reference
    1642     protected boolean _is_hostname;
    1643     protected boolean _is_IPv4address;
    1644     protected boolean _is_IPv6reference;
    1645
    1646     // ------------------------------------------ Character and escape encoding
    1647
    1648     /**
    1649      * Encodes URI string.
    1650      *
    1651      * This is a two mapping, one from original characters to octets, and
    1652      * subsequently a second from octets to URI characters:
    1653      * <p><blockquote><pre>
    1654      *   original character sequence->octet sequence->URI character sequence
    1655      * </pre></blockquote><p>
    1656      *
    1657      * An escaped octet is encoded as a character triplet, consisting of the
    1658      * percent character "%" followed by the two hexadecimal digits
    1659      * representing the octet code. For example, "%20" is the escaped
    1660      * encoding for the US-ASCII space character.
    1661      * <p>
    1662      * Conversion from the local filesystem character set to UTF-8 will
    1663      * normally involve a two step process. First convert the local character
    1664      * set to the UCS; then convert the UCS to UTF-8.
    1665      * The first step in the process can be performed by maintaining a mapping
    1666      * table that includes the local character set code and the corresponding
    1667      * UCS code.
    1668      * The next step is to convert the UCS character code to the UTF-8 encoding.
    1669      * <p>
    1670      * Mapping between vendor codepages can be done in a very similar manner
    1671      * as described above.
    1672      * <p>
    1673      * The only time escape encodings can allowedly be made is when a URI is
    1674      * being created from its component parts.  The escape and validate methods
    1675      * are internally performed within this method.
    1676      *
    1677      * @param original the original character sequence
    1678      * @param allowed those characters that are allowed within a component
    1679      * @param charset the protocol charset
    1680      * @return URI character sequence
    1681      * @throws URIException null component or unsupported character encoding
    1682      */
    1683
    1684     protected static char[] encode(String original, BitSet allowed,
    1685             String charset) throws URIException {
    1686         if (original == null) {
    1687             throw new IllegalArgumentException("Original string may not be null");
    1688         }
    1689         if (allowed == null) {
    1690             throw new IllegalArgumentException("Allowed bitset may not be null");
    1691         }
    1692         byte[] rawdata = URLCodec.encodeUrl(allowed, EncodingUtil.getBytes(original, charset));
    1693         return EncodingUtil.getAsciiString(rawdata).toCharArray();
    1694     }
    1695
    1696     /**
    1697      * Decodes URI encoded string.
    1698      *
    1699      * This is a two mapping, one from URI characters to octets, and
    1700      * subsequently a second from octets to original characters:
    1701      * <p><blockquote><pre>
    1702      *   URI character sequence->octet sequence->original character sequence
    1703      * </pre></blockquote><p>
    1704      *
    1705      * A URI must be separated into its components before the escaped
    1706      * characters within those components can be allowedly decoded.
    1707      * <p>
    1708      * Notice that there is a chance that URI characters that are non UTF-8
    1709      * may be parsed as valid UTF-8.  A recent non-scientific analysis found
    1710      * that EUC encoded Japanese words had a 2.7% false reading; SJIS had a
    1711      * 0.0005% false reading; other encoding such as ASCII or KOI-8 have a 0%
    1712      * false reading.
    1713      * <p>
    1714      * The percent "%" character always has the reserved purpose of being
    1715      * the escape indicator, it must be escaped as "%25" in order to be used
    1716      * as data within a URI.
    1717      * <p>
    1718      * The unescape method is internally performed within this method.
    1719      *
    1720      * @param component the URI character sequence
    1721      * @param charset the protocol charset
    1722      * @return original character sequence
    1723      * @throws URIException incomplete trailing escape pattern or unsupported
    1724      * character encoding
    1725      */
    1726     protected static String decode(char[] component, String charset)
    1727         throws URIException {
    1728         if (component == null) {
    1729             throw new IllegalArgumentException("Component array of chars may not be null");
    1730         }
    1731         return decode(new String(component), charset);
    1732     }
    1733
    1734     /**
    1735      * Decodes URI encoded string.
    1736      *
    1737      * This is a two mapping, one from URI characters to octets, and
    1738      * subsequently a second from octets to original characters:
    1739      * <p><blockquote><pre>
    1740      *   URI character sequence->octet sequence->original character sequence
    1741      * </pre></blockquote><p>
    1742      *
    1743      * A URI must be separated into its components before the escaped
    1744      * characters within those components can be allowedly decoded.
    1745      * <p>
    1746      * Notice that there is a chance that URI characters that are non UTF-8
    1747      * may be parsed as valid UTF-8.  A recent non-scientific analysis found
    1748      * that EUC encoded Japanese words had a 2.7% false reading; SJIS had a
    1749      * 0.0005% false reading; other encoding such as ASCII or KOI-8 have a 0%
    1750      * false reading.
    1751      * <p>
    1752      * The percent "%" character always has the reserved purpose of being
    1753      * the escape indicator, it must be escaped as "%25" in order to be used
    1754      * as data within a URI.
    1755      * <p>
    1756      * The unescape method is internally performed within this method.
    1757      *
    1758      * @param component the URI character sequence
    1759      * @param charset the protocol charset
    1760      * @return original character sequence
    1761      * @throws URIException incomplete trailing escape pattern or unsupported
    1762      * character encoding
    1763      *
    1764      * @since 3.0
    1765      */
    1766     protected static String decode(String component, String charset)
    1767         throws URIException {
    1768         if (component == null) {
    1769             throw new IllegalArgumentException("Component array of chars may not be null");
    1770         }
    1771         byte[] rawdata = null;
    1772         try {
    1773             rawdata = URLCodec.decodeUrl(EncodingUtil.getAsciiBytes(component));
    1774         } catch (DecoderException e) {
    1775             throw new URIException(e.getMessage());
    1776         }
    1777         return EncodingUtil.getString(rawdata, charset);
    1778     }
    1779     /**
    1780      * Pre-validate the unescaped URI string within a specific component.
    1781      *
    1782      * @param component the component string within the component
    1783      * @param disallowed those characters disallowed within the component
    1784      * @return if true, it doesn't have the disallowed characters
    1785      * if false, the component is undefined or an incorrect one
    1786      */
    1787     protected boolean prevalidate(String component, BitSet disallowed) {
    1788         // prevalidate the given component by disallowed characters
    1789         if (component == null) {
    1790             return false; // undefined
    1791         }
    1792         char[] target = component.toCharArray();
    1793         for (int i = 0; i < target.length; i++) {
    1794             if (disallowed.get(target[i])) {
    1795                 return false;
    1796             }
    1797         }
    1798         return true;
    1799     }
    1800
    1801
    1802     /**
    1803      * Validate the URI characters within a specific component.
    1804      * The component must be performed after escape encoding. Or it doesn't
    1805      * include escaped characters.
    1806      *
    1807      * @param component the characters sequence within the component
    1808      * @param generous those characters that are allowed within a component
    1809      * @return if true, it's the correct URI character sequence
    1810      */
    1811     protected boolean validate(char[] component, BitSet generous) {
    1812         // validate each component by generous characters
    1813         return validate(component, 0, -1, generous);
    1814     }
    1815
    1816
    1817     /**
    1818      * Validate the URI characters within a specific component.
    1819      * The component must be performed after escape encoding. Or it doesn't
    1820      * include escaped characters.
    1821      * <p>
    1822      * It's not that much strict, generous.  The strict validation might be
    1823      * performed before being called this method.
    1824      *
    1825      * @param component the characters sequence within the component
    1826      * @param soffset the starting offset of the given component
    1827      * @param eoffset the ending offset of the given component
    1828      * if -1, it means the length of the component
    1829      * @param generous those characters that are allowed within a component
    1830      * @return if true, it's the correct URI character sequence
    1831      */
    1832     protected boolean validate(char[] component, int soffset, int eoffset,
    1833             BitSet generous) {
    1834         // validate each component by generous characters
    1835         if (eoffset == -1) {
    1836             eoffset = component.length - 1;
    1837         }
    1838         for (int i = soffset; i <= eoffset; i++) {
    1839             if (!generous.get(component[i])) {
    1840                 return false;
    1841             }
    1842         }
    1843         return true;
    1844     }
    1845
    1846
    1847     /**
    1848      * In order to avoid any possilbity of conflict with non-ASCII characters,
    1849      * Parse a URI reference as a <code>String</code> with the character
    1850      * encoding of the local system or the document.
    1851      * <p>
    1852      * The following line is the regular expression for breaking-down a URI
    1853      * reference into its components.
    1854      * <p><blockquote><pre>
    1855      *   ^(([^:/?#]+):)?(//([^/?#]*))?([^?#]*)(\?([^#]*))?(#(.*))?
    1856      *    12            3  4          5       6  7        8 9
    1857      * </pre></blockquote><p>
    1858      * For example, matching the above expression to
    1859      *   http://jakarta.apache.org/ietf/uri/#Related
    1860      * results in the following subexpression matches:
    1861      * <p><blockquote><pre>
    1862      *               $1 = http:
    1863      *  scheme    =  $2 = http
    1864      *               $3 = //jakarta.apache.org
    1865      *  authority =  $4 = jakarta.apache.org
    1866      *  path      =  $5 = /ietf/uri/
    1867      *               $6 = <undefined>
    1868      *  query     =  $7 = <undefined>
    1869      *               $8 = #Related
    1870      *  fragment  =  $9 = Related
    1871      * </pre></blockquote><p>
    1872      *
    1873      * @param original the original character sequence
    1874      * @param escaped <code>true</code> if <code>original</code> is escaped
    1875      * @throws URIException If an error occurs.
    1876      */
    1877     protected void parseUriReference(String original, boolean escaped)
    1878         throws URIException {
    1879
    1880         // validate and contruct the URI character sequence
    1881         if (original == null) {
    1882             throw new URIException("URI-Reference required");
    1883         }
    1884
    1885         /* @
    1886          *  ^(([^:/?#]+):)?(//([^/?#]*))?([^?#]*)(\?([^#]*))?(#(.*))?
    1887          */
    1888         String tmp = original.trim();
    1889
    1890         /*
    1891          * The length of the string sequence of characters.
    1892          * It may not be equal to the length of the byte array.
    1893          */
    1894         int length = tmp.length();
    1895
    1896         /*
    1897          * Remove the delimiters like angle brackets around an URI.
    1898          */
    1899         if (length > 0) {
    1900             char[] firstDelimiter = { tmp.charAt(0) };
    1901             if (validate(firstDelimiter, delims)) {
    1902                 if (length >= 2) {
    1903                     char[] lastDelimiter = { tmp.charAt(length - 1) };
    1904                     if (validate(lastDelimiter, delims)) {
    1905                         tmp = tmp.substring(1, length - 1);
    1906                         length = length - 2;
    1907                     }
    1908                 }
    1909             }
    1910         }
    1911
    1912         /*
    1913          * The starting index
    1914          */
    1915         int from = 0;
    1916
    1917         /*
    1918          * The test flag whether the URI is started from the path component.
    1919          */
    1920         boolean isStartedFromPath = false;
    1921         int atColon = tmp.indexOf(':');
    1922         int atSlash = tmp.indexOf('/');
    1923         if ((atColon <= 0 && !tmp.startsWith("//"))
    1924             || (atSlash >= 0 && atSlash < atColon)) {
    1925             isStartedFromPath = true;
    1926         }
    1927
    1928         /*
    1929          * <p><blockquote><pre>
    1930          *     @@@@@@@@
    1931          *  ^(([^:/?#]+):)?(//([^/?#]*))?([^?#]*)(\?([^#]*))?(#(.*))?
    1932          * </pre></blockquote><p>
    1933          */
    1934         int at = indexFirstOf(tmp, isStartedFromPath ? "/?#" : ":/?#", from);
    1935         if (at == -1) {
    1936             at = 0;
    1937         }
    1938
    1939         /*
    1940          * Parse the scheme.
    1941          * <p><blockquote><pre>
    1942          *  scheme    =  $2 = http
    1943          *              @
    1944          *  ^(([^:/?#]+):)?(//([^/?#]*))?([^?#]*)(\?([^#]*))?(#(.*))?
    1945          * </pre></blockquote><p>
    1946          */
    1947         if (at > 0 && at < length && tmp.charAt(at) == ':') {
    1948             char[] target = tmp.substring(0, at).toLowerCase().toCharArray();
    1949             if (validate(target, scheme)) {
    1950                 _scheme = target;
    1951             } else {
    1952                 throw new URIException("incorrect scheme");
    1953             }
    1954             from = ++at;
    1955         }
    1956
    1957         /*
    1958          * Parse the authority component.
    1959          * <p><blockquote><pre>
    1960          *  authority =  $4 = jakarta.apache.org
    1961          *                  @@
    1962          *  ^(([^:/?#]+):)?(//([^/?#]*))?([^?#]*)(\?([^#]*))?(#(.*))?
    1963          * </pre></blockquote><p>
    1964          */
    1965         // Reset flags
    1966         _is_net_path = _is_abs_path = _is_rel_path = _is_hier_part = false;
    1967         if (0 <= at && at < length && tmp.charAt(at) == '/') {
    1968             // Set flag
    1969             _is_hier_part = true;
    1970             if (at + 2 < length && tmp.charAt(at + 1) == '/'
    1971                 && !isStartedFromPath) {
    1972                 // the temporary index to start the search from
    1973                 int next = indexFirstOf(tmp, "/?#", at + 2);
    1974                 if (next == -1) {
    1975                     next = (tmp.substring(at + 2).length() == 0) ? at + 2
    1976                         : tmp.length();
    1977                 }
    1978                 parseAuthority(tmp.substring(at + 2, next), escaped);
    1979                 from = at = next;
    1980                 // Set flag
    1981                 _is_net_path = true;
    1982             }
    1983             if (from == at) {
    1984                 // Set flag
    1985                 _is_abs_path = true;
    1986             }
    1987         }
    1988
    1989         /*
    1990          * Parse the path component.
    1991          * <p><blockquote><pre>
    1992          *  path      =  $5 = /ietf/uri/
    1993          *                                @@@@@@
    1994          *  ^(([^:/?#]+):)?(//([^/?#]*))?([^?#]*)(\?([^#]*))?(#(.*))?
    1995          * </pre></blockquote><p>
    1996          */
    1997         if (from < length) {
    1998             // rel_path = rel_segment [ abs_path ]
    1999             int next = indexFirstOf(tmp, "?#", from);
    2000             if (next == -1) {
    2001                 next = tmp.length();
    2002             }
    2003             if (!_is_abs_path) {
    2004                 if (!escaped
    2005                     && prevalidate(tmp.substring(from, next), disallowed_rel_path)
    2006                     || escaped
    2007                     && validate(tmp.substring(from, next).toCharArray(), rel_path)) {
    2008                     // Set flag
    2009                     _is_rel_path = true;
    2010                 } else if (!escaped
    2011                     && prevalidate(tmp.substring(from, next), disallowed_opaque_part)
    2012                     || escaped
    2013                     && validate(tmp.substring(from, next).toCharArray(), opaque_part)) {
    2014                     // Set flag
    2015                     _is_opaque_part = true;
    2016                 } else {
    2017                     // the path component may be empty
    2018                     _path = null;
    2019                 }
    2020             }
    2021             String s = tmp.substring(from, next);
    2022             if (escaped) {
    2023                 setRawPath(s.toCharArray());
    2024             } else {
    2025                 setPath(s);
    2026             }
    2027             at = next;
    2028         }
    2029
    2030         // set the charset to do escape encoding
    2031         String charset = getProtocolCharset();
    2032
    2033         /*
    2034          * Parse the query component.
    2035          * <p><blockquote><pre>
    2036          *  query     =  $7 = <undefined>
    2037          *                                        @@@@@@@@@
    2038          *  ^(([^:/?#]+):)?(//([^/?#]*))?([^?#]*)(\?([^#]*))?(#(.*))?
    2039          * </pre></blockquote><p>
    2040          */
    2041         if (0 <= at && at + 1 < length && tmp.charAt(at) == '?') {
    2042             int next = tmp.indexOf('#', at + 1);
    2043             if (next == -1) {
    2044                 next = tmp.length();
    2045             }
    2046             if (escaped) {
    2047                 _query = tmp.substring(at + 1, next).toCharArray();
    2048                 if (!validate(_query, uric)) {
    2049                     throw new URIException("Invalid query");
    2050                 }
    2051             } else {
    2052                 _query = encode(tmp.substring(at + 1, next), allowed_query, charset);
    2053             }
    2054             at = next;
    2055         }
    2056
    2057         /*
    2058          * Parse the fragment component.
    2059          * <p><blockquote><pre>
    2060          *  fragment  =  $9 = Related
    2061          *                                                   @@@@@@@@
    2062          *  ^(([^:/?#]+):)?(//([^/?#]*))?([^?#]*)(\?([^#]*))?(#(.*))?
    2063          * </pre></blockquote><p>
    2064          */
    2065         if (0 <= at && at + 1 <= length && tmp.charAt(at) == '#') {
    2066             if (at + 1 == length) { // empty fragment
    2067                 _fragment = "".toCharArray();
    2068             } else {
    2069                 _fragment = (escaped) ? tmp.substring(at + 1).toCharArray()
    2070                     : encode(tmp.substring(at + 1), allowed_fragment, charset);
    2071             }
    2072         }
    2073
    2074         // set this URI.
    2075         setURI();
    2076     }
    2077
    2078
    2079     /**
    2080      * Get the earlier index that to be searched for the first occurrance in
    2081      * one of any of the given string.
    2082      *
    2083      * @param s the string to be indexed
    2084      * @param delims the delimiters used to index
    2085      * @return the earlier index if there are delimiters
    2086      */
    2087     protected int indexFirstOf(String s, String delims) {
    2088         return indexFirstOf(s, delims, -1);
    2089     }
    2090
    2091
    2092     /**
    2093      * Get the earlier index that to be searched for the first occurrance in
    2094      * one of any of the given string.
    2095      *
    2096      * @param s the string to be indexed
    2097      * @param delims the delimiters used to index
    2098      * @param offset the from index
    2099      * @return the earlier index if there are delimiters
    2100      */
    2101     protected int indexFirstOf(String s, String delims, int offset) {
    2102         if (s == null || s.length() == 0) {
    2103             return -1;
    2104         }
    2105         if (delims == null || delims.length() == 0) {
    2106             return -1;
    2107         }
    2108         // check boundaries
    2109         if (offset < 0) {
    2110             offset = 0;
    2111         } else if (offset > s.length()) {
    2112             return -1;
    2113         }
    2114         // s is never null
    2115         int min = s.length();
    2116         char[] delim = delims.toCharArray();
    2117         for (int i = 0; i < delim.length; i++) {
    2118             int at = s.indexOf(delim[i], offset);
    2119             if (at >= 0 && at < min) {
    2120                 min = at;
    2121             }
    2122         }
    2123         return (min == s.length()) ? -1 : min;
    2124     }
    2125
    2126
    2127     /**
    2128      * Get the earlier index that to be searched for the first occurrance in
    2129      * one of any of the given array.
    2130      *
    2131      * @param s the character array to be indexed
    2132      * @param delim the delimiter used to index
    2133      * @return the ealier index if there are a delimiter
    2134      */
    2135     protected int indexFirstOf(char[] s, char delim) {
    2136         return indexFirstOf(s, delim, 0);
    2137     }
    2138
    2139
    2140     /**
    2141      * Get the earlier index that to be searched for the first occurrance in
    2142      * one of any of the given array.
    2143      *
    2144      * @param s the character array to be indexed
    2145      * @param delim the delimiter used to index
    2146      * @param offset The offset.
    2147      * @return the ealier index if there is a delimiter
    2148      */
    2149     protected int indexFirstOf(char[] s, char delim, int offset) {
    2150         if (s == null || s.length == 0) {
    2151             return -1;
    2152         }
    2153         // check boundaries
    2154         if (offset < 0) {
    2155             offset = 0;
    2156         } else if (offset > s.length) {
    2157             return -1;
    2158         }
    2159         for (int i = offset; i < s.length; i++) {
    2160             if (s[i] == delim) {
    2161                 return i;
    2162             }
    2163         }
    2164         return -1;
    2165     }
    2166
    2167
    2168     /**
    2169      * Parse the authority component.
    2170      *
    2171      * @param original the original character sequence of authority component
    2172      * @param escaped <code>true</code> if <code>original</code> is escaped
    2173      * @throws URIException If an error occurs.
    2174      */
    2175     protected void parseAuthority(String original, boolean escaped)
    2176         throws URIException {
    2177
    2178         // Reset flags
    2179         _is_reg_name = _is_server =
    2180         _is_hostname = _is_IPv4address = _is_IPv6reference = false;
    2181
    2182         // set the charset to do escape encoding
    2183         String charset = getProtocolCharset();
    2184
    2185         boolean hasPort = true;
    2186         int from = 0;
    2187         int next = original.indexOf('@');
    2188         if (next != -1) { // neither -1 and 0
    2189             // each protocol extented from URI supports the specific userinfo
    2190             _userinfo = (escaped) ? original.substring(0, next).toCharArray()
    2191                 : encode(original.substring(0, next), allowed_userinfo,
    2192                         charset);
    2193             from = next + 1;
    2194         }
    2195         next = original.indexOf('[', from);
    2196         if (next >= from) {
    2197             next = original.indexOf(']', from);
    2198             if (next == -1) {
    2199                 throw new URIException(URIException.PARSING, "IPv6reference");
    2200             } else {
    2201                 next++;
    2202             }
    2203             // In IPv6reference, '[', ']' should be excluded
    2204             _host = (escaped) ? original.substring(from, next).toCharArray()
    2205                 : encode(original.substring(from, next), allowed_IPv6reference,
    2206                         charset);
    2207             // Set flag
    2208             _is_IPv6reference = true;
    2209         } else { // only for !_is_IPv6reference
    2210             next = original.indexOf(':', from);
    2211             if (next == -1) {
    2212                 next = original.length();
    2213                 hasPort = false;
    2214             }
    2215             // REMINDME: it doesn't need the pre-validation
    2216             _host = original.substring(from, next).toCharArray();
    2217             if (validate(_host, IPv4address)) {
    2218                 // Set flag
    2219                 _is_IPv4address = true;
    2220             } else if (validate(_host, hostname)) {
    2221                 // Set flag
    2222                 _is_hostname = true;
    2223             } else {
    2224                 // Set flag
    2225                 _is_reg_name = true;
    2226             }
    2227         }
    2228         if (_is_reg_name) {
    2229             // Reset flags for a server-based naming authority
    2230             _is_server = _is_hostname = _is_IPv4address =
    2231             _is_IPv6reference = false;
    2232             // set a registry-based naming authority
    2233             if (escaped) {
    2234                 _authority = original.toCharArray();
    2235                 if (!validate(_authority, reg_name)) {
    2236                     throw new URIException("Invalid authority");
    2237                 }
    2238             } else {
    2239                 _authority = encode(original, allowed_reg_name, charset);
    2240             }
    2241         } else {
    2242             if (original.length() - 1 > next && hasPort
    2243                 && original.charAt(next) == ':') { // not empty
    2244                 from = next + 1;
    2245                 try {
    2246                     _port = Integer.parseInt(original.substring(from));
    2247                 } catch (NumberFormatException error) {
    2248                     throw new URIException(URIException.PARSING,
    2249                             "invalid port number");
    2250                 }
    2251             }
    2252             // set a server-based naming authority
    2253             StringBuffer buf = new StringBuffer();
    2254             if (_userinfo != null) { // has_userinfo
    2255                 buf.append(_userinfo);
    2256                 buf.append('@');
    2257             }
    2258             if (_host != null) {
    2259                 buf.append(_host);
    2260                 if (_port != -1) {
    2261                     buf.append(':');
    2262                     buf.append(_port);
    2263                 }
    2264             }
    2265             _authority = buf.toString().toCharArray();
    2266             // Set flag
    2267             _is_server = true;
    2268         }
    2269     }
    2270
    2271
    2272     /**
    2273      * Once it's parsed successfully, set this URI.
    2274      *
    2275      * @see #getRawURI
    2276      */
    2277     protected void setURI() {
    2278         // set _uri
    2279         StringBuffer buf = new StringBuffer();
    2280         // ^(([^:/?#]+):)?(//([^/?#]*))?([^?#]*)(\?([^#]*))?(#(.*))?
    2281         if (_scheme != null) {
    2282             buf.append(_scheme);
    2283             buf.append(':');
    2284         }
    2285         if (_is_net_path) {
    2286             buf.append("//");
    2287             if (_authority != null) { // has_authority
    2288                 buf.append(_authority);
    2289             }
    2290         }
    2291         if (_opaque != null && _is_opaque_part) {
    2292             buf.append(_opaque);
    2293         } else if (_path != null) {
    2294             // _is_hier_part or _is_relativeURI
    2295             if (_path.length != 0) {
    2296                 buf.append(_path);
    2297             }
    2298         }
    2299         if (_query != null) { // has_query
    2300             buf.append('?');
    2301             buf.append(_query);
    2302         }
    2303         // ignore the fragment identifier
    2304         _uri = buf.toString().toCharArray();
    2305         hash = 0;
    2306     }
    2307
    2308     // ----------------------------------------------------------- Test methods
    2309
    2310
    2311     /**
    2312      * Tell whether or not this URI is absolute.
    2313      *
    2314      * @return true iif this URI is absoluteURI
    2315      */
    2316     public boolean isAbsoluteURI() {
    2317         return (_scheme != null);
    2318     }
    2319
    2320
    2321     /**
    2322      * Tell whether or not this URI is relative.
    2323      *
    2324      * @return true iif this URI is relativeURI
    2325      */
    2326     public boolean isRelativeURI() {
    2327         return (_scheme == null);
    2328     }
    2329
    2330
    2331     /**
    2332      * Tell whether or not the absoluteURI of this URI is hier_part.
    2333      *
    2334      * @return true iif the absoluteURI is hier_part
    2335      */
    2336     public boolean isHierPart() {
    2337         return _is_hier_part;
    2338     }
    2339
    2340
    2341     /**
    2342      * Tell whether or not the absoluteURI of this URI is opaque_part.
    2343      *
    2344      * @return true iif the absoluteURI is opaque_part
    2345      */
    2346     public boolean isOpaquePart() {
    2347         return _is_opaque_part;
    2348     }
    2349
    2350
    2351     /**
    2352      * Tell whether or not the relativeURI or heir_part of this URI is net_path.
    2353      * It's the same function as the has_authority() method.
    2354      *
    2355      * @return true iif the relativeURI or heir_part is net_path
    2356      * @see #hasAuthority
    2357      */
    2358     public boolean isNetPath() {
    2359         return _is_net_path || (_authority != null);
    2360     }
    2361
    2362
    2363     /**
    2364      * Tell whether or not the relativeURI or hier_part of this URI is abs_path.
    2365      *
    2366      * @return true iif the relativeURI or hier_part is abs_path
    2367      */
    2368     public boolean isAbsPath() {
    2369         return _is_abs_path;
    2370     }
    2371
    2372
    2373     /**
    2374      * Tell whether or not the relativeURI of this URI is rel_path.
    2375      *
    2376      * @return true iif the relativeURI is rel_path
    2377      */
    2378     public boolean isRelPath() {
    2379         return _is_rel_path;
    2380     }
    2381
    2382
    2383     /**
    2384      * Tell whether or not this URI has authority.
    2385      * It's the same function as the is_net_path() method.
    2386      *
    2387      * @return true iif this URI has authority
    2388      * @see #isNetPath
    2389      */
    2390     public boolean hasAuthority() {
    2391         return (_authority != null) || _is_net_path;
    2392     }
    2393
    2394     /**
    2395      * Tell whether or not the authority component of this URI is reg_name.
    2396      *
    2397      * @return true iif the authority component is reg_name
    2398      */
    2399     public boolean isRegName() {
    2400         return _is_reg_name;
    2401     }
    2402
    2403
    2404     /**
    2405      * Tell whether or not the authority component of this URI is server.
    2406      *
    2407      * @return true iif the authority component is server
    2408      */
    2409     public boolean isServer() {
    2410         return _is_server;
    2411     }
    2412
    2413
    2414     /**
    2415      * Tell whether or not this URI has userinfo.
    2416      *
    2417      * @return true iif this URI has userinfo
    2418      */
    2419     public boolean hasUserinfo() {
    2420         return (_userinfo != null);
    2421     }
    2422
    2423
    2424     /**
    2425      * Tell whether or not the host part of this URI is hostname.
    2426      *
    2427      * @return true iif the host part is hostname
    2428      */
    2429     public boolean isHostname() {
    2430         return _is_hostname;
    2431     }
    2432
    2433
    2434     /**
    2435      * Tell whether or not the host part of this URI is IPv4address.
    2436      *
    2437      * @return true iif the host part is IPv4address
    2438      */
    2439     public boolean isIPv4address() {
    2440         return _is_IPv4address;
    2441     }
    2442
    2443
    2444     /**
    2445      * Tell whether or not the host part of this URI is IPv6reference.
    2446      *
    2447      * @return true iif the host part is IPv6reference
    2448      */
    2449     public boolean isIPv6reference() {
    2450         return _is_IPv6reference;
    2451     }
    2452
    2453
    2454     /**
    2455      * Tell whether or not this URI has query.
    2456      *
    2457      * @return true iif this URI has query
    2458      */
    2459     public boolean hasQuery() {
    2460         return (_query != null);
    2461     }
    2462
    2463
    2464     /**
    2465      * Tell whether or not this URI has fragment.
    2466      *
    2467      * @return true iif this URI has fragment
    2468      */
    2469     public boolean hasFragment() {
    2470         return (_fragment != null);
    2471     }
    2472
    2473
    2474     // ---------------------------------------------------------------- Charset
    2475
    2476
    2477     /**
    2478      * Set the default charset of the protocol.
    2479      * <p>
    2480      * The character set used to store files SHALL remain a local decision and
    2481      * MAY depend on the capability of local operating systems. Prior to the
    2482      * exchange of URIs they SHOULD be converted into a ISO/IEC 10646 format
    2483      * and UTF-8 encoded. This approach, while allowing international exchange
    2484      * of URIs, will still allow backward compatibility with older systems
    2485      * because the code set positions for ASCII characters are identical to the
    2486      * one byte sequence in UTF-8.
    2487      * <p>
    2488      * An individual URI scheme may require a single charset, define a default
    2489      * charset, or provide a way to indicate the charset used.
    2490      *
    2491      * <p>
    2492      * Always all the time, the setter method is always succeeded and throws
    2493      * <code>DefaultCharsetChanged</code> exception.
    2494      *
    2495      * So API programmer must follow the following way:
    2496      * <code><pre>
    2497      *  import org.apache.util.URI$DefaultCharsetChanged;
    2498      *      .
    2499      *      .
    2500      *      .
    2501      *  try {
    2502      *      URI.setDefaultProtocolCharset("UTF-8");
    2503      *  } catch (DefaultCharsetChanged cc) {
    2504      *      // CASE 1: the exception could be ignored, when it is set by user
    2505      *      if (cc.getReasonCode() == DefaultCharsetChanged.PROTOCOL_CHARSET) {
    2506      *      // CASE 2: let user know the default protocol charset changed
    2507      *      } else {
    2508      *      // CASE 2: let user know the default document charset changed
    2509      *      }
    2510      *  }
    2511      *  </pre></code>
    2512      *
    2513      * The API programmer is responsible to set the correct charset.
    2514      * And each application should remember its own charset to support.
    2515      *
    2516      * @param charset the default charset for each protocol
    2517      * @throws DefaultCharsetChanged default charset changed
    2518      */
    2519     public static void setDefaultProtocolCharset(String charset)
    2520         throws DefaultCharsetChanged {
    2521
    2522         defaultProtocolCharset = charset;
    2523         throw new DefaultCharsetChanged(DefaultCharsetChanged.PROTOCOL_CHARSET,
    2524                 "the default protocol charset changed");
    2525     }
    2526
    2527
    2528     /**
    2529      * Get the default charset of the protocol.
    2530      * <p>
    2531      * An individual URI scheme may require a single charset, define a default
    2532      * charset, or provide a way to indicate the charset used.
    2533      * <p>
    2534      * To work globally either requires support of a number of character sets
    2535      * and to be able to convert between them, or the use of a single preferred
    2536      * character set.
    2537      * For support of global compatibility it is STRONGLY RECOMMENDED that
    2538      * clients and servers use UTF-8 encoding when exchanging URIs.
    2539      *
    2540      * @return the default charset string
    2541      */
    2542     public static String getDefaultProtocolCharset() {
    2543         return defaultProtocolCharset;
    2544     }
    2545
    2546
    2547     /**
    2548      * Get the protocol charset used by this current URI instance.
    2549      * It was set by the constructor for this instance. If it was not set by
    2550      * contructor, it will return the default protocol charset.
    2551      *
    2552      * @return the protocol charset string
    2553      * @see #getDefaultProtocolCharset
    2554      */
    2555     public String getProtocolCharset() {
    2556         return (protocolCharset != null)
    2557             ? protocolCharset
    2558             : defaultProtocolCharset;
    2559     }
    2560
    2561
    2562     /**
    2563      * Set the default charset of the document.
    2564      * <p>
    2565      * Notice that it will be possible to contain mixed characters (e.g.
    2566      * ftp://host/KoreanNamespace/ChineseResource). To handle the Bi-directional
    2567      * display of these character sets, the protocol charset could be simply
    2568      * used again. Because it's not yet implemented that the insertion of BIDI
    2569      * control characters at different points during composition is extracted.
    2570      * <p>
    2571      *
    2572      * Always all the time, the setter method is always succeeded and throws
    2573      * <code>DefaultCharsetChanged</code> exception.
    2574      *
    2575      * So API programmer must follow the following way:
    2576      * <code><pre>
    2577      *  import org.apache.util.URI$DefaultCharsetChanged;
    2578      *      .
    2579      *      .
    2580      *      .
    2581      *  try {
    2582      *      URI.setDefaultDocumentCharset("EUC-KR");
    2583      *  } catch (DefaultCharsetChanged cc) {
    2584      *      // CASE 1: the exception could be ignored, when it is set by user
    2585      *      if (cc.getReasonCode() == DefaultCharsetChanged.DOCUMENT_CHARSET) {
    2586      *      // CASE 2: let user know the default document charset changed
    2587      *      } else {
    2588      *      // CASE 2: let user know the default protocol charset changed
    2589      *      }
    2590      *  }
    2591      *  </pre></code>
    2592      *
    2593      * The API programmer is responsible to set the correct charset.
    2594      * And each application should remember its own charset to support.
    2595      *
    2596      * @param charset the default charset for the document
    2597      * @throws DefaultCharsetChanged default charset changed
    2598      */
    2599     public static void setDefaultDocumentCharset(String charset)
    2600         throws DefaultCharsetChanged {
    2601
    2602         defaultDocumentCharset = charset;
    2603         throw new DefaultCharsetChanged(DefaultCharsetChanged.DOCUMENT_CHARSET,
    2604                 "the default document charset changed");
    2605     }
    2606
    2607
    2608     /**
    2609      * Get the recommended default charset of the document.
    2610      *
    2611      * @return the default charset string
    2612      */
    2613     public static String getDefaultDocumentCharset() {
    2614         return defaultDocumentCharset;
    2615     }
    2616
    2617
    2618     /**
    2619      * Get the default charset of the document by locale.
    2620      *
    2621      * @return the default charset string by locale
    2622      */
    2623     public static String getDefaultDocumentCharsetByLocale() {
    2624         return defaultDocumentCharsetByLocale;
    2625     }
    2626
    2627
    2628     /**
    2629      * Get the default charset of the document by platform.
    2630      *
    2631      * @return the default charset string by platform
    2632      */
    2633     public static String getDefaultDocumentCharsetByPlatform() {
    2634         return defaultDocumentCharsetByPlatform;
    2635     }
    2636
    2637     // ------------------------------------------------------------- The scheme
    2638
    2639     /**
    2640      * Get the scheme.
    2641      *
    2642      * @return the scheme
    2643      */
    2644     public char[] getRawScheme() {
    2645         return _scheme;
    2646     }
    2647
    2648
    2649     /**
    2650      * Get the scheme.
    2651      *
    2652      * @return the scheme
    2653      * null if undefined scheme
    2654      */
    2655     public String getScheme() {
    2656         return (_scheme == null) ? null : new String(_scheme);
    2657     }
    2658
    2659     // ---------------------------------------------------------- The authority
    2660
    2661     /**
    2662      * Set the authority.  It can be one type of server, hostport, hostname,
    2663      * IPv4address, IPv6reference and reg_name.
    2664      * <p><blockquote><pre>
    2665      *   authority     = server | reg_name
    2666      * </pre></blockquote><p>
    2667      *
    2668      * @param escapedAuthority the raw escaped authority
    2669      * @throws URIException If {@link
    2670      * #parseAuthority(java.lang.String,boolean)} fails
    2671      * @throws NullPointerException null authority
    2672      */
    2673     public void setRawAuthority(char[] escapedAuthority)
    2674         throws URIException, NullPointerException {
    2675
    2676         parseAuthority(new String(escapedAuthority), true);
    2677         setURI();
    2678     }
    2679
    2680
    2681     /**
    2682      * Set the authority.  It can be one type of server, hostport, hostname,
    2683      * IPv4address, IPv6reference and reg_name.
    2684      * Note that there is no setAuthority method by the escape encoding reason.
    2685      *
    2686      * @param escapedAuthority the escaped authority string
    2687      * @throws URIException If {@link
    2688      * #parseAuthority(java.lang.String,boolean)} fails
    2689      */
    2690     public void setEscapedAuthority(String escapedAuthority)
    2691         throws URIException {
    2692
    2693         parseAuthority(escapedAuthority, true);
    2694         setURI();
    2695     }
    2696
    2697
    2698     /**
    2699      * Get the raw-escaped authority.
    2700      *
    2701      * @return the raw-escaped authority
    2702      */
    2703     public char[] getRawAuthority() {
    2704         return _authority;
    2705     }
    2706
    2707
    2708     /**
    2709      * Get the escaped authority.
    2710      *
    2711      * @return the escaped authority
    2712      */
    2713     public String getEscapedAuthority() {
    2714         return (_authority == null) ? null : new String(_authority);
    2715     }
    2716
    2717
    2718     /**
    2719      * Get the authority.
    2720      *
    2721      * @return the authority
    2722      * @throws URIException If {@link #decode} fails
    2723      */
    2724     public String getAuthority() throws URIException {
    2725         return (_authority == null) ? null : decode(_authority,
    2726                 getProtocolCharset());
    2727     }
    2728
    2729     // ----------------------------------------------------------- The userinfo
    2730
    2731     /**
    2732      * Get the raw-escaped userinfo.
    2733      *
    2734      * @return the raw-escaped userinfo
    2735      * @see #getAuthority
    2736      */
    2737     public char[] getRawUserinfo() {
    2738         return _userinfo;
    2739     }
    2740
    2741
    2742     /**
    2743      * Get the escaped userinfo.
    2744      *
    2745      * @return the escaped userinfo
    2746      * @see #getAuthority
    2747      */
    2748     public String getEscapedUserinfo() {
    2749         return (_userinfo == null) ? null : new String(_userinfo);
    2750     }
    2751
    2752
    2753     /**
    2754      * Get the userinfo.
    2755      *
    2756      * @return the userinfo
    2757      * @throws URIException If {@link #decode} fails
    2758      * @see #getAuthority
    2759      */
    2760     public String getUserinfo() throws URIException {
    2761         return (_userinfo == null) ? null : decode(_userinfo,
    2762                 getProtocolCharset());
    2763     }
    2764
    2765     // --------------------------------------------------------------- The host
    2766
    2767     /**
    2768      * Get the host.
    2769      * <p><blockquote><pre>
    2770      *   host          = hostname | IPv4address | IPv6reference
    2771      * </pre></blockquote><p>
    2772      *
    2773      * @return the host
    2774      * @see #getAuthority
    2775      */
    2776     public char[] getRawHost() {
    2777         return _host;
    2778     }
    2779
    2780
    2781     /**
    2782      * Get the host.
    2783      * <p><blockquote><pre>
    2784      *   host          = hostname | IPv4address | IPv6reference
    2785      * </pre></blockquote><p>
    2786      *
    2787      * @return the host
    2788      * @throws URIException If {@link #decode} fails
    2789      * @see #getAuthority
    2790      */
    2791     public String getHost() throws URIException {
    2792         if (_host != null) {
    2793             return decode(_host, getProtocolCharset());
    2794         } else {
    2795             return null;
    2796         }
    2797     }
    2798
    2799     // --------------------------------------------------------------- The port
    2800
    2801     /**
    2802      * Get the port.  In order to get the specfic default port, the specific
    2803      * protocol-supported class extended from the URI class should be used.
    2804      * It has the server-based naming authority.
    2805      *
    2806      * @return the port
    2807      * if -1, it has the default port for the scheme or the server-based
    2808      * naming authority is not supported in the specific URI.
    2809      */
    2810     public int getPort() {
    2811         return _port;
    2812     }
    2813
    2814     // --------------------------------------------------------------- The path
    2815
    2816     /**
    2817      * Set the raw-escaped path.
    2818      *
    2819      * @param escapedPath the path character sequence
    2820      * @throws URIException encoding error or not proper for initial instance
    2821      * @see #encode
    2822      */
    2823     public void setRawPath(char[] escapedPath) throws URIException {
    2824         if (escapedPath == null || escapedPath.length == 0) {
    2825             _path = _opaque = escapedPath;
    2826             setURI();
    2827             return;
    2828         }
    2829         // remove the fragment identifier
    2830         escapedPath = removeFragmentIdentifier(escapedPath);
    2831         if (_is_net_path || _is_abs_path) {
    2832             if (escapedPath[0] != '/') {
    2833                 throw new URIException(URIException.PARSING,
    2834                         "not absolute path");
    2835             }
    2836             if (!validate(escapedPath, abs_path)) {
    2837                 throw new URIException(URIException.ESCAPING,
    2838                         "escaped absolute path not valid");
    2839             }
    2840             _path = escapedPath;
    2841         } else if (_is_rel_path) {
    2842             int at = indexFirstOf(escapedPath, '/');
    2843             if (at == 0) {
    2844                 throw new URIException(URIException.PARSING, "incorrect path");
    2845             }
    2846             if (at > 0 && !validate(escapedPath, 0, at - 1, rel_segment)
    2847                 && !validate(escapedPath, at, -1, abs_path)
    2848                 || at < 0 && !validate(escapedPath, 0, -1, rel_segment)) {
    2849
    2850                 throw new URIException(URIException.ESCAPING,
    2851                         "escaped relative path not valid");
    2852             }
    2853             _path = escapedPath;
    2854         } else if (_is_opaque_part) {
    2855             if (!uric_no_slash.get(escapedPath[0])
    2856                 && !validate(escapedPath, 1, -1, uric)) {
    2857                 throw new URIException(URIException.ESCAPING,
    2858                     "escaped opaque part not valid");
    2859             }
    2860             _opaque = escapedPath;
    2861         } else {
    2862             throw new URIException(URIException.PARSING, "incorrect path");
    2863         }
    2864         setURI();
    2865     }
    2866
    2867
    2868     /**
    2869      * Set the escaped path.
    2870      *
    2871      * @param escapedPath the escaped path string
    2872      * @throws URIException encoding error or not proper for initial instance
    2873      * @see #encode
    2874      */
    2875     public void setEscapedPath(String escapedPath) throws URIException {
    2876         if (escapedPath == null) {
    2877             _path = _opaque = null;
    2878             setURI();
    2879             return;
    2880         }
    2881         setRawPath(escapedPath.toCharArray());
    2882     }
    2883
    2884
    2885     /**
    2886      * Set the path.
    2887      *
    2888      * @param path the path string
    2889      * @throws URIException set incorrectly or fragment only
    2890      * @see #encode
    2891      */
    2892     public void setPath(String path) throws URIException {
    2893
    2894         if (path == null || path.length() == 0) {
    2895             _path = _opaque = (path == null) ? null : path.toCharArray();
    2896             setURI();
    2897             return;
    2898         }
    2899         // set the charset to do escape encoding
    2900         String charset = getProtocolCharset();
    2901
    2902         if (_is_net_path || _is_abs_path) {
    2903             _path = encode(path, allowed_abs_path, charset);
    2904         } else if (_is_rel_path) {
    2905             StringBuffer buff = new StringBuffer(path.length());
    2906             int at = path.indexOf('/');
    2907             if (at == 0) { // never 0
    2908                 throw new URIException(URIException.PARSING,
    2909                         "incorrect relative path");
    2910             }
    2911             if (at > 0) {
    2912                 buff.append(encode(path.substring(0, at), allowed_rel_path,
    2913                             charset));
    2914                 buff.append(encode(path.substring(at), allowed_abs_path,
    2915                             charset));
    2916             } else {
    2917                 buff.append(encode(path, allowed_rel_path, charset));
    2918             }
    2919             _path = buff.toString().toCharArray();
    2920         } else if (_is_opaque_part) {
    2921             StringBuffer buf = new StringBuffer();
    2922             buf.insert(0, encode(path.substring(0, 1), uric_no_slash, charset));
    2923             buf.insert(1, encode(path.substring(1), uric, charset));
    2924             _opaque = buf.toString().toCharArray();
    2925         } else {
    2926             throw new URIException(URIException.PARSING, "incorrect path");
    2927         }
    2928         setURI();
    2929     }
    2930
    2931
    2932     /**
    2933      * Resolve the base and relative path.
    2934      *
    2935      * @param basePath a character array of the basePath
    2936      * @param relPath a character array of the relPath
    2937      * @return the resolved path
    2938      * @throws URIException no more higher path level to be resolved
    2939      */
    2940     protected char[] resolvePath(char[] basePath, char[] relPath)
    2941         throws URIException {
    2942
    2943         // REMINDME: paths are never null
    2944         String base = (basePath == null) ? "" : new String(basePath);
    2945
    2946         // _path could be empty
    2947         if (relPath == null || relPath.length == 0) {
    2948             return normalize(basePath);
    2949         } else if (relPath[0] == '/') {
    2950             return normalize(relPath);
    2951         } else {
    2952             int at = base.lastIndexOf('/');
    2953             if (at != -1) {
    2954                 basePath = base.substring(0, at + 1).toCharArray();
    2955             }
    2956             StringBuffer buff = new StringBuffer(base.length()
    2957                 + relPath.length);
    2958             buff.append((at != -1) ? base.substring(0, at + 1) : "/");
    2959             buff.append(relPath);
    2960             return normalize(buff.toString().toCharArray());
    2961         }
    2962     }
    2963
    2964
    2965     /**
    2966      * Get the raw-escaped current hierarchy level in the given path.
    2967      * If the last namespace is a collection, the slash mark ('/') should be
    2968      * ended with at the last character of the path string.
    2969      *
    2970      * @param path the path
    2971      * @return the current hierarchy level
    2972      * @throws URIException no hierarchy level
    2973      */
    2974     protected char[] getRawCurrentHierPath(char[] path) throws URIException {
    2975
    2976         if (_is_opaque_part) {
    2977             throw new URIException(URIException.PARSING, "no hierarchy level");
    2978         }
    2979         if (path == null) {
    2980             throw new URIException(URIException.PARSING, "empty path");
    2981         }
    2982         String buff = new String(path);
    2983         int first = buff.indexOf('/');
    2984         int last = buff.lastIndexOf('/');
    2985         if (last == 0) {
    2986             return rootPath;
    2987         } else if (first != last && last != -1) {
    2988             return buff.substring(0, last).toCharArray();
    2989         }
    2990         // FIXME: it could be a document on the server side
    2991         return path;
    2992     }
    2993
    2994
    2995     /**
    2996      * Get the raw-escaped current hierarchy level.
    2997      *
    2998      * @return the raw-escaped current hierarchy level
    2999      * @throws URIException If {@link #getRawCurrentHierPath(char[])} fails.
    3000      */
    3001     public char[] getRawCurrentHierPath() throws URIException {
    3002         return (_path == null) ? null : getRawCurrentHierPath(_path);
    3003     }
    3004
    3005
    3006     /**
    3007      * Get the escaped current hierarchy level.
    3008      *
    3009      * @return the escaped current hierarchy level
    3010      * @throws URIException If {@link #getRawCurrentHierPath(char[])} fails.
    3011      */
    3012     public String getEscapedCurrentHierPath() throws URIException {
    3013         char[] path = getRawCurrentHierPath();
    3014         return (path == null) ? null : new String(path);
    3015     }
    3016
    3017
    3018     /**
    3019      * Get the current hierarchy level.
    3020      *
    3021      * @return the current hierarchy level
    3022      * @throws URIException If {@link #getRawCurrentHierPath(char[])} fails.
    3023      * @see #decode
    3024      */
    3025     public String getCurrentHierPath() throws URIException {
    3026         char[] path = getRawCurrentHierPath();
    3027         return (path == null) ? null : decode(path, getProtocolCharset());
    3028     }
    3029
    3030
    3031     /**
    3032      * Get the level above the this hierarchy level.
    3033      *
    3034      * @return the raw above hierarchy level
    3035      * @throws URIException If {@link #getRawCurrentHierPath(char[])} fails.
    3036      */
    3037     public char[] getRawAboveHierPath() throws URIException {
    3038         char[] path = getRawCurrentHierPath();
    3039         return (path == null) ? null : getRawCurrentHierPath(path);
    3040     }
    3041
    3042
    3043     /**
    3044      * Get the level above the this hierarchy level.
    3045      *
    3046      * @return the raw above hierarchy level
    3047      * @throws URIException If {@link #getRawCurrentHierPath(char[])} fails.
    3048      */
    3049     public String getEscapedAboveHierPath() throws URIException {
    3050         char[] path = getRawAboveHierPath();
    3051         return (path == null) ? null : new String(path);
    3052     }
    3053
    3054
    3055     /**
    3056      * Get the level above the this hierarchy level.
    3057      *
    3058      * @return the above hierarchy level
    3059      * @throws URIException If {@link #getRawCurrentHierPath(char[])} fails.
    3060      * @see #decode
    3061      */
    3062     public String getAboveHierPath() throws URIException {
    3063         char[] path = getRawAboveHierPath();
    3064         return (path == null) ? null : decode(path, getProtocolCharset());
    3065     }
    3066
    3067
    3068     /**
    3069      * Get the raw-escaped path.
    3070      * <p><blockquote><pre>
    3071      *   path          = [ abs_path | opaque_part ]
    3072      * </pre></blockquote><p>
    3073      *
    3074      * @return the raw-escaped path
    3075      */
    3076     public char[] getRawPath() {
    3077         return _is_opaque_part ? _opaque : _path;
    3078     }
    3079
    3080
    3081     /**
    3082      * Get the escaped path.
    3083      * <p><blockquote><pre>
    3084      *   path          = [ abs_path | opaque_part ]
    3085      *   abs_path      = "/"  path_segments
    3086      *   opaque_part   = uric_no_slash *uric
    3087      * </pre></blockquote><p>
    3088      *
    3089      * @return the escaped path string
    3090      */
    3091     public String getEscapedPath() {
    3092         char[] path = getRawPath();
    3093         return (path == null) ? null : new String(path);
    3094     }
    3095
    3096
    3097     /**
    3098      * Get the path.
    3099      * <p><blockquote><pre>
    3100      *   path          = [ abs_path | opaque_part ]
    3101      * </pre></blockquote><p>
    3102      * @return the path string
    3103      * @throws URIException If {@link #decode} fails.
    3104      * @see #decode
    3105      */
    3106     public String getPath() throws URIException {
    3107         char[] path =  getRawPath();
    3108         return (path == null) ? null : decode(path, getProtocolCharset());
    3109     }
    3110
    3111
    3112     /**
    3113      * Get the raw-escaped basename of the path.
    3114      *
    3115      * @return the raw-escaped basename
    3116      */
    3117     public char[] getRawName() {
    3118         if (_path == null) {
    3119             return null;
    3120         }
    3121
    3122         int at = 0;
    3123         for (int i = _path.length - 1; i >= 0; i--) {
    3124             if (_path[i] == '/') {
    3125                 at = i + 1;
    3126                 break;
    3127             }
    3128         }
    3129         int len = _path.length - at;
    3130         char[] basename =  new char[len];
    3131         System.arraycopy(_path, at, basename, 0, len);
    3132         return basename;
    3133     }
    3134
    3135
    3136     /**
    3137      * Get the escaped basename of the path.
    3138      *
    3139      * @return the escaped basename string
    3140      */
    3141     public String getEscapedName() {
    3142         char[] basename = getRawName();
    3143         return (basename == null) ? null : new String(basename);
    3144     }
    3145
    3146
    3147     /**
    3148      * Get the basename of the path.
    3149      *
    3150      * @return the basename string
    3151      * @throws URIException incomplete trailing escape pattern or unsupported
    3152      * character encoding
    3153      * @see #decode
    3154      */
    3155     public String getName() throws URIException {
    3156         char[] basename = getRawName();
    3157         return (basename == null) ? null : decode(getRawName(),
    3158                 getProtocolCharset());
    3159     }
    3160
    3161     // ----------------------------------------------------- The path and query
    3162
    3163     /**
    3164      * Get the raw-escaped path and query.
    3165      *
    3166      * @return the raw-escaped path and query
    3167      */
    3168     public char[] getRawPathQuery() {
    3169
    3170         if (_path == null && _query == null) {
    3171             return null;
    3172         }
    3173         StringBuffer buff = new StringBuffer();
    3174         if (_path != null) {
    3175             buff.append(_path);
    3176         }
    3177         if (_query != null) {
    3178             buff.append('?');
    3179             buff.append(_query);
    3180         }
    3181         return buff.toString().toCharArray();
    3182     }
    3183
    3184
    3185     /**
    3186      * Get the escaped query.
    3187      *
    3188      * @return the escaped path and query string
    3189      */
    3190     public String getEscapedPathQuery() {
    3191         char[] rawPathQuery = getRawPathQuery();
    3192         return (rawPathQuery == null) ? null : new String(rawPathQuery);
    3193     }
    3194
    3195
    3196     /**
    3197      * Get the path and query.
    3198      *
    3199      * @return the path and query string.
    3200      * @throws URIException incomplete trailing escape pattern or unsupported
    3201      * character encoding
    3202      * @see #decode
    3203      */
    3204     public String getPathQuery() throws URIException {
    3205         char[] rawPathQuery = getRawPathQuery();
    3206         return (rawPathQuery == null) ? null : decode(rawPathQuery,
    3207                 getProtocolCharset());
    3208     }
    3209
    3210     // -------------------------------------------------------------- The query
    3211
    3212     /**
    3213      * Set the raw-escaped query.
    3214      *
    3215      * @param escapedQuery the raw-escaped query
    3216      * @throws URIException escaped query not valid
    3217      */
    3218     public void setRawQuery(char[] escapedQuery) throws URIException {
    3219         if (escapedQuery == null || escapedQuery.length == 0) {
    3220             _query = escapedQuery;
    3221             setURI();
    3222             return;
    3223         }
    3224         // remove the fragment identifier
    3225         escapedQuery = removeFragmentIdentifier(escapedQuery);
    3226         if (!validate(escapedQuery, query)) {
    3227             throw new URIException(URIException.ESCAPING,
    3228                     "escaped query not valid");
    3229         }
    3230         _query = escapedQuery;
    3231         setURI();
    3232     }
    3233
    3234
    3235     /**
    3236      * Set the escaped query string.
    3237      *
    3238      * @param escapedQuery the escaped query string
    3239      * @throws URIException escaped query not valid
    3240      */
    3241     public void setEscapedQuery(String escapedQuery) throws URIException {
    3242         if (escapedQuery == null) {
    3243             _query = null;
    3244             setURI();
    3245             return;
    3246         }
    3247         setRawQuery(escapedQuery.toCharArray());
    3248     }
    3249
    3250
    3251     /**
    3252      * Set the query.
    3253      * <p>
    3254      * When a query string is not misunderstood the reserved special characters
    3255      * ("&amp;", "=", "+", ",", and "$") within a query component, it is
    3256      * recommended to use in encoding the whole query with this method.
    3257      * <p>
    3258      * The additional APIs for the special purpose using by the reserved
    3259      * special characters used in each protocol are implemented in each protocol
    3260      * classes inherited from <code>URI</code>.  So refer to the same-named APIs
    3261      * implemented in each specific protocol instance.
    3262      *
    3263      * @param query the query string.
    3264      * @throws URIException incomplete trailing escape pattern or unsupported
    3265      * character encoding
    3266      * @see #encode
    3267      */
    3268     public void setQuery(String query) throws URIException {
    3269         if (query == null || query.length() == 0) {
    3270             _query = (query == null) ? null : query.toCharArray();
    3271             setURI();
    3272             return;
    3273         }
    3274         setRawQuery(encode(query, allowed_query, getProtocolCharset()));
    3275     }
    3276
    3277
    3278     /**
    3279      * Get the raw-escaped query.
    3280      *
    3281      * @return the raw-escaped query
    3282      */
    3283     public char[] getRawQuery() {
    3284         return _query;
    3285     }
    3286
    3287
    3288     /**
    3289      * Get the escaped query.
    3290      *
    3291      * @return the escaped query string
    3292      */
    3293     public String getEscapedQuery() {
    3294         return (_query == null) ? null : new String(_query);
    3295     }
    3296
    3297
    3298     /**
    3299      * Get the query.
    3300      *
    3301      * @return the query string.
    3302      * @throws URIException incomplete trailing escape pattern or unsupported
    3303      * character encoding
    3304      * @see #decode
    3305      */
    3306     public String getQuery() throws URIException {
    3307         return (_query == null) ? null : decode(_query, getProtocolCharset());
    3308     }
    3309
    3310     // ----------------------------------------------------------- The fragment
    3311
    3312     /**
    3313      * Set the raw-escaped fragment.
    3314      *
    3315      * @param escapedFragment the raw-escaped fragment
    3316      * @throws URIException escaped fragment not valid
    3317      */
    3318     public void setRawFragment(char[] escapedFragment) throws URIException {
    3319         if (escapedFragment == null || escapedFragment.length == 0) {
    3320             _fragment = escapedFragment;
    3321             hash = 0;
    3322             return;
    3323         }
    3324         if (!validate(escapedFragment, fragment)) {
    3325             throw new URIException(URIException.ESCAPING,
    3326                     "escaped fragment not valid");
    3327         }
    3328         _fragment = escapedFragment;
    3329         hash = 0;
    3330     }
    3331
    3332
    3333     /**
    3334      * Set the escaped fragment string.
    3335      *
    3336      * @param escapedFragment the escaped fragment string
    3337      * @throws URIException escaped fragment not valid
    3338      */
    3339     public void setEscapedFragment(String escapedFragment) throws URIException {
    3340         if (escapedFragment == null) {
    3341             _fragment = null;
    3342             hash = 0;
    3343             return;
    3344         }
    3345         setRawFragment(escapedFragment.toCharArray());
    3346     }
    3347
    3348
    3349     /**
    3350      * Set the fragment.
    3351      *
    3352      * @param fragment the fragment string.
    3353      * @throws URIException If an error occurs.
    3354      */
    3355     public void setFragment(String fragment) throws URIException {
    3356         if (fragment == null || fragment.length() == 0) {
    3357             _fragment = (fragment == null) ? null : fragment.toCharArray();
    3358             hash = 0;
    3359             return;
    3360         }
    3361         _fragment = encode(fragment, allowed_fragment, getProtocolCharset());
    3362         hash = 0;
    3363     }
    3364
    3365
    3366     /**
    3367      * Get the raw-escaped fragment.
    3368      * <p>
    3369      * The optional fragment identifier is not part of a URI, but is often used
    3370      * in conjunction with a URI.
    3371      * <p>
    3372      * The format and interpretation of fragment identifiers is dependent on
    3373      * the media type [RFC2046] of the retrieval result.
    3374      * <p>
    3375      * A fragment identifier is only meaningful when a URI reference is
    3376      * intended for retrieval and the result of that retrieval is a document
    3377      * for which the identified fragment is consistently defined.
    3378      *
    3379      * @return the raw-escaped fragment
    3380      */
    3381     public char[] getRawFragment() {
    3382         return _fragment;
    3383     }
    3384
    3385
    3386     /**
    3387      * Get the escaped fragment.
    3388      *
    3389      * @return the escaped fragment string
    3390      */
    3391     public String getEscapedFragment() {
    3392         return (_fragment == null) ? null : new String(_fragment);
    3393     }
    3394
    3395
    3396     /**
    3397      * Get the fragment.
    3398      *
    3399      * @return the fragment string
    3400      * @throws URIException incomplete trailing escape pattern or unsupported
    3401      * character encoding
    3402      * @see #decode
    3403      */
    3404     public String getFragment() throws URIException {
    3405         return (_fragment == null) ? null : decode(_fragment,
    3406                 getProtocolCharset());
    3407     }
    3408
    3409     // ------------------------------------------------------------- Utilities
    3410
    3411     /**
    3412      * Remove the fragment identifier of the given component.
    3413      *
    3414      * @param component the component that a fragment may be included
    3415      * @return the component that the fragment identifier is removed
    3416      */
    3417     protected char[] removeFragmentIdentifier(char[] component) {
    3418         if (component == null) {
    3419             return null;
    3420         }
    3421         int lastIndex = new String(component).indexOf('#');
    3422         if (lastIndex != -1) {
    3423             component = new String(component).substring(0,
    3424                     lastIndex).toCharArray();
    3425         }
    3426         return component;
    3427     }
    3428
    3429
    3430     /**
    3431      * Normalize the given hier path part.
    3432      *
    3433      * <p>Algorithm taken from URI reference parser at
    3434      * http://www.apache.org/~fielding/uri/rev-2002/issues.html.
    3435      *
    3436      * @param path the path to normalize
    3437      * @return the normalized path
    3438      * @throws URIException no more higher path level to be normalized
    3439      */
    3440     protected char[] normalize(char[] path) throws URIException {
    3441
    3442         if (path == null) {
    3443             return null;
    3444         }
    3445
    3446         String normalized = new String(path);
    3447
    3448         // If the buffer begins with "./" or "../", the "." or ".." is removed.
    3449         if (normalized.startsWith("./")) {
    3450             normalized = normalized.substring(1);
    3451         } else if (normalized.startsWith("../")) {
    3452             normalized = normalized.substring(2);
    3453         } else if (normalized.startsWith("..")) {
    3454             normalized = normalized.substring(2);
    3455         }
    3456
    3457         // All occurrences of "/./" in the buffer are replaced with "/"
    3458         int index = -1;
    3459         while ((index = normalized.indexOf("/./")) != -1) {
    3460             normalized = normalized.substring(0, index) + normalized.substring(index + 2);
    3461         }
    3462
    3463         // If the buffer ends with "/.", the "." is removed.
    3464         if (normalized.endsWith("/.")) {
    3465             normalized = normalized.substring(0, normalized.length() - 1);
    3466         }
    3467
    3468         int startIndex = 0;
    3469
    3470         // All occurrences of "/<segment>/../" in the buffer, where ".."
    3471         // and <segment> are complete path segments, are iteratively replaced
    3472         // with "/" in order from left to right until no matching pattern remains.
    3473         // If the buffer ends with "/<segment>/..", that is also replaced
    3474         // with "/".  Note that <segment> may be empty.
    3475         while ((index = normalized.indexOf("/../", startIndex)) != -1) {
    3476             int slashIndex = normalized.lastIndexOf('/', index - 1);
    3477             if (slashIndex >= 0) {
    3478                 normalized = normalized.substring(0, slashIndex) + normalized.substring(index + 3);
    3479             } else {
    3480                 startIndex = index + 3;
    3481             }
    3482         }
    3483         if (normalized.endsWith("/..")) {
    3484             int slashIndex = normalized.lastIndexOf('/', normalized.length() - 4);
    3485             if (slashIndex >= 0) {
    3486                 normalized = normalized.substring(0, slashIndex + 1);
    3487             }
    3488         }
    3489
    3490         // All prefixes of "<segment>/../" in the buffer, where ".."
    3491         // and <segment> are complete path segments, are iteratively replaced
    3492         // with "/" in order from left to right until no matching pattern remains.
    3493         // If the buffer ends with "<segment>/..", that is also replaced
    3494         // with "/".  Note that <segment> may be empty.
    3495         while ((index = normalized.indexOf("/../")) != -1) {
    3496             int slashIndex = normalized.lastIndexOf('/', index - 1);
    3497             if (slashIndex >= 0) {
    3498                 break;
    3499             } else {
    3500                 normalized = normalized.substring(index + 3);
    3501             }
    3502         }
    3503         if (normalized.endsWith("/..")) {
    3504             int slashIndex = normalized.lastIndexOf('/', normalized.length() - 4);
    3505             if (slashIndex < 0) {
    3506                 normalized = "/";
    3507             }
    3508         }
    3509
    3510         return normalized.toCharArray();
    3511     }
    3512
    3513
    3514     /**
    3515      * Normalizes the path part of this URI.  Normalization is only meant to be performed on
    3516      * URIs with an absolute path.  Calling this method on a relative path URI will have no
    3517      * effect.
    3518      *
    3519      * @throws URIException no more higher path level to be normalized
    3520      *
    3521      * @see #isAbsPath()
    3522      */
    3523     public void normalize() throws URIException {
    3524         if (isAbsPath()) {
    3525             _path = normalize(_path);
    3526             setURI();
    3527         }
    3528     }
    3529
    3530
    3531     /**
    3532      * Test if the first array is equal to the second array.
    3533      *
    3534      * @param first the first character array
    3535      * @param second the second character array
    3536      * @return true if they're equal
    3537      */
    3538     protected boolean equals(char[] first, char[] second) {
    3539
    3540         if (first == null && second == null) {
    3541             return true;
    3542         }
    3543         if (first == null || second == null) {
    3544             return false;
    3545         }
    3546         if (first.length != second.length) {
    3547             return false;
    3548         }
    3549         for (int i = 0; i < first.length; i++) {
    3550             if (first[i] != second[i]) {
    3551                 return false;
    3552             }
    3553         }
    3554         return true;
    3555     }
    3556
    3557
    3558     /**
    3559      * Test an object if this URI is equal to another.
    3560      *
    3561      * @param obj an object to compare
    3562      * @return true if two URI objects are equal
    3563      */
    3564     public boolean equals(Object obj) {
    3565
    3566         // normalize and test each components
    3567         if (obj == this) {
    3568             return true;
    3569         }
    3570         if (!(obj instanceof URI)) {
    3571             return false;
    3572         }
    3573         URI another = (URI) obj;
    3574         // scheme
    3575         if (!equals(_scheme, another._scheme)) {
    3576             return false;
    3577         }
    3578         // is_opaque_part or is_hier_part?  and opaque
    3579         if (!equals(_opaque, another._opaque)) {
    3580             return false;
    3581         }
    3582         // is_hier_part
    3583         // has_authority
    3584         if (!equals(_authority, another._authority)) {
    3585             return false;
    3586         }
    3587         // path
    3588         if (!equals(_path, another._path)) {
    3589             return false;
    3590         }
    3591         // has_query
    3592         if (!equals(_query, another._query)) {
    3593             return false;
    3594         }
    3595         // has_fragment?  should be careful of the only fragment case.
    3596         if (!equals(_fragment, another._fragment)) {
    3597             return false;
    3598         }
    3599         return true;
    3600     }
    3601
    3602     // ---------------------------------------------------------- Serialization
    3603
    3604     /**
    3605      * Write the content of this URI.
    3606      *
    3607      * @param oos the object-output stream
    3608      * @throws IOException If an IO problem occurs.
    3609      */
    3610     private void writeObject(ObjectOutputStream oos)
    3611         throws IOException {
    3612
    3613         oos.defaultWriteObject();
    3614     }
    3615
    3616
    3617     /**
    3618      * Read a URI.
    3619      *
    3620      * @param ois the object-input stream
    3621      * @throws ClassNotFoundException If one of the classes specified in the
    3622      * input stream cannot be found.
    3623      * @throws IOException If an IO problem occurs.
    3624      */
    3625     private void readObject(ObjectInputStream ois)
    3626         throws ClassNotFoundException, IOException {
    3627
    3628         ois.defaultReadObject();
    3629     }
    3630
    3631     // -------------------------------------------------------------- Hash code
    3632
    3633     /**
    3634      * Return a hash code for this URI.
    3635      *
    3636      * @return a has code value for this URI
    3637      */
    3638     public int hashCode() {
    3639         if (hash == 0) {
    3640             char[] c = _uri;
    3641             if (c != null) {
    3642                 for (int i = 0, len = c.length; i < len; i++) {
    3643                     hash = 31 * hash + c[i];
    3644                 }
    3645             }
    3646             c = _fragment;
    3647             if (c != null) {
    3648                 for (int i = 0, len = c.length; i < len; i++) {
    3649                     hash = 31 * hash + c[i];
    3650                 }
    3651             }
    3652         }
    3653         return hash;
    3654     }
    3655
    3656     // ------------------------------------------------------------- Comparison
    3657
    3658     /**
    3659      * Compare this URI to another object.
    3660      *
    3661      * @param obj the object to be compared.
    3662      * @return 0, if it's same,
    3663      * -1, if failed, first being compared with in the authority component
    3664      * @throws ClassCastException not URI argument
    3665      */
    3666     public int compareTo(Object obj) throws ClassCastException {
    3667
    3668         URI another = (URI) obj;
    3669         if (!equals(_authority, another.getRawAuthority())) {
    3670             return -1;
    3671         }
    3672         return toString().compareTo(another.toString());
    3673     }
    3674
    3675     // ------------------------------------------------------------------ Clone
    3676
    3677     /**
    3678      * Create and return a copy of this object, the URI-reference containing
    3679      * the userinfo component.  Notice that the whole URI-reference including
    3680      * the userinfo component counld not be gotten as a <code>String</code>.
    3681      * <p>
    3682      * To copy the identical <code>URI</code> object including the userinfo
    3683      * component, it should be used.
    3684      *
    3685      * @return a clone of this instance
    3686      */
    3687     public synchronized Object clone() throws CloneNotSupportedException {
    3688
    3689         URI instance = (URI) super.clone();
    3690
    3691         instance._uri = _uri;
    3692         instance._scheme = _scheme;
    3693         instance._opaque = _opaque;
    3694         instance._authority = _authority;
    3695         instance._userinfo = _userinfo;
    3696         instance._host = _host;
    3697         instance._port = _port;
    3698         instance._path = _path;
    3699         instance._query = _query;
    3700         instance._fragment = _fragment;
    3701         // the charset to do escape encoding for this instance
    3702         instance.protocolCharset = protocolCharset;
    3703         // flags
    3704         instance._is_hier_part = _is_hier_part;
    3705         instance._is_opaque_part = _is_opaque_part;
    3706         instance._is_net_path = _is_net_path;
    3707         instance._is_abs_path = _is_abs_path;
    3708         instance._is_rel_path = _is_rel_path;
    3709         instance._is_reg_name = _is_reg_name;
    3710         instance._is_server = _is_server;
    3711         instance._is_hostname = _is_hostname;
    3712         instance._is_IPv4address = _is_IPv4address;
    3713         instance._is_IPv6reference = _is_IPv6reference;
    3714
    3715         return instance;
    3716     }
    3717
    3718     // ------------------------------------------------------------ Get the URI
    3719
    3720     /**
    3721      * It can be gotten the URI character sequence. It's raw-escaped.
    3722      * For the purpose of the protocol to be transported, it will be useful.
    3723      * <p>
    3724      * It is clearly unwise to use a URL that contains a password which is
    3725      * intended to be secret. In particular, the use of a password within
    3726      * the 'userinfo' component of a URL is strongly disrecommended except
    3727      * in those rare cases where the 'password' parameter is intended to be
    3728      * public.
    3729      * <p>
    3730      * When you want to get each part of the userinfo, you need to use the
    3731      * specific methods in the specific URL. It depends on the specific URL.
    3732      *
    3733      * @return the URI character sequence
    3734      */
    3735     public char[] getRawURI() {
    3736         return _uri;
    3737     }
    3738
    3739
    3740     /**
    3741      * It can be gotten the URI character sequence. It's escaped.
    3742      * For the purpose of the protocol to be transported, it will be useful.
    3743      *
    3744      * @return the escaped URI string
    3745      */
    3746     public String getEscapedURI() {
    3747         return (_uri == null) ? null : new String(_uri);
    3748     }
    3749
    3750
    3751     /**
    3752      * It can be gotten the URI character sequence.
    3753      *
    3754      * @return the original URI string
    3755      * @throws URIException incomplete trailing escape pattern or unsupported
    3756      * character encoding
    3757      * @see #decode
    3758      */
    3759     public String getURI() throws URIException {
    3760         return (_uri == null) ? null : decode(_uri, getProtocolCharset());
    3761     }
    3762
    3763
    3764     /**
    3765      * Get the URI reference character sequence.
    3766      *
    3767      * @return the URI reference character sequence
    3768      */
    3769     public char[] getRawURIReference() {
    3770         if (_fragment == null) {
    3771             return _uri;
    3772         }
    3773         if (_uri == null) {
    3774             return _fragment;
    3775         }
    3776         // if _uri != null &&  _fragment != null
    3777         String uriReference = new String(_uri) + "#" + new String(_fragment);
    3778         return uriReference.toCharArray();
    3779     }
    3780
    3781
    3782     /**
    3783      * Get the escaped URI reference string.
    3784      *
    3785      * @return the escaped URI reference string
    3786      */
    3787     public String getEscapedURIReference() {
    3788         char[] uriReference = getRawURIReference();
    3789         return (uriReference == null) ? null : new String(uriReference);
    3790     }
    3791
    3792
    3793     /**
    3794      * Get the original URI reference string.
    3795      *
    3796      * @return the original URI reference string
    3797      * @throws URIException If {@link #decode} fails.
    3798      */
    3799     public String getURIReference() throws URIException {
    3800         char[] uriReference = getRawURIReference();
    3801         return (uriReference == null) ? null : decode(uriReference,
    3802                 getProtocolCharset());
    3803     }
    3804
    3805
    3806     /**
    3807      * Get the escaped URI string.
    3808      * <p>
    3809      * On the document, the URI-reference form is only used without the userinfo
    3810      * component like http://jakarta.apache.org/ by the security reason.
    3811      * But the URI-reference form with the userinfo component could be parsed.
    3812      * <p>
    3813      * In other words, this URI and any its subclasses must not expose the
    3814      * URI-reference expression with the userinfo component like
    3815      * http://user:password@hostport/restricted_zone.<br>
    3816      * It means that the API client programmer should extract each user and
    3817      * password to access manually.  Probably it will be supported in the each
    3818      * subclass, however, not a whole URI-reference expression.
    3819      *
    3820      * @return the escaped URI string
    3821      * @see #clone()
    3822      */
    3823     public String toString() {
    3824         return getEscapedURI();
    3825     }
    3826
    3827
    3828     // ------------------------------------------------------------ Inner class
    3829
    3830     /**
    3831      * The charset-changed normal operation to represent to be required to
    3832      * alert to user the fact the default charset is changed.
    3833      */
    3834     public static class DefaultCharsetChanged extends RuntimeException {
    3835
    3836         // ------------------------------------------------------- constructors
    3837
    3838         /**
    3839          * The constructor with a reason string and its code arguments.
    3840          *
    3841          * @param reasonCode the reason code
    3842          * @param reason the reason
    3843          */
    3844         public DefaultCharsetChanged(int reasonCode, String reason) {
    3845             super(reason);
    3846             this.reason = reason;
    3847             this.reasonCode = reasonCode;
    3848         }
    3849
    3850         // ---------------------------------------------------------- constants
    3851
    3852         /** No specified reason code. */
    3853         public static final int UNKNOWN = 0;
    3854
    3855         /** Protocol charset changed. */
    3856         public static final int PROTOCOL_CHARSET = 1;
    3857
    3858         /** Document charset changed. */
    3859         public static final int DOCUMENT_CHARSET = 2;
    3860
    3861         // ------------------------------------------------- instance variables
    3862
    3863         /** The reason code. */
    3864         private int reasonCode;
    3865
    3866         /** The reason message. */
    3867         private String reason;
    3868
    3869         // ------------------------------------------------------------ methods
    3870
    3871         /**
    3872          * Get the reason code.
    3873          *
    3874          * @return the reason code
    3875          */
    3876         public int getReasonCode() {
    3877             return reasonCode;
    3878         }
    3879
    3880         /**
    3881          * Get the reason message.
    3882          *
    3883          * @return the reason message
    3884          */
    3885         public String getReason() {
    3886             return reason;
    3887         }
    3888
    3889     }
    3890
    3891
    3892     /**
    3893      * A mapping to determine the (somewhat arbitrarily) preferred charset for a
    3894      * given locale.  Supports all locales recognized in JDK 1.1.
    3895      * <p>
    3896      * The distribution of this class is Servlets.com.    It was originally
    3897      * written by Jason Hunter [jhunter at acm.org] and used by with permission.
    3898      */
    3899     public static class LocaleToCharsetMap {
    3900
    3901         /** A mapping of language code to charset */
    3902         private static final Hashtable LOCALE_TO_CHARSET_MAP;
    3903         static {
    3904             LOCALE_TO_CHARSET_MAP = new Hashtable();
    3905             LOCALE_TO_CHARSET_MAP.put("ar", "ISO-8859-6");
    3906             LOCALE_TO_CHARSET_MAP.put("be", "ISO-8859-5");
    3907             LOCALE_TO_CHARSET_MAP.put("bg", "ISO-8859-5");
    3908             LOCALE_TO_CHARSET_MAP.put("ca", "ISO-8859-1");
    3909             LOCALE_TO_CHARSET_MAP.put("cs", "ISO-8859-2");
    3910             LOCALE_TO_CHARSET_MAP.put("da", "ISO-8859-1");
    3911             LOCALE_TO_CHARSET_MAP.put("de", "ISO-8859-1");
    3912             LOCALE_TO_CHARSET_MAP.put("el", "ISO-8859-7");
    3913             LOCALE_TO_CHARSET_MAP.put("en", "ISO-8859-1");
    3914             LOCALE_TO_CHARSET_MAP.put("es", "ISO-8859-1");
    3915             LOCALE_TO_CHARSET_MAP.put("et", "ISO-8859-1");
    3916             LOCALE_TO_CHARSET_MAP.put("fi", "ISO-8859-1");
    3917             LOCALE_TO_CHARSET_MAP.put("fr", "ISO-8859-1");
    3918             LOCALE_TO_CHARSET_MAP.put("hr", "ISO-8859-2");
    3919             LOCALE_TO_CHARSET_MAP.put("hu", "ISO-8859-2");
    3920             LOCALE_TO_CHARSET_MAP.put("is", "ISO-8859-1");
    3921             LOCALE_TO_CHARSET_MAP.put("it", "ISO-8859-1");
    3922             LOCALE_TO_CHARSET_MAP.put("iw", "ISO-8859-8");
    3923             LOCALE_TO_CHARSET_MAP.put("ja", "Shift_JIS");
    3924             LOCALE_TO_CHARSET_MAP.put("ko", "EUC-KR");
    3925             LOCALE_TO_CHARSET_MAP.put("lt", "ISO-8859-2");
    3926             LOCALE_TO_CHARSET_MAP.put("lv", "ISO-8859-2");
    3927             LOCALE_TO_CHARSET_MAP.put("mk", "ISO-8859-5");
    3928             LOCALE_TO_CHARSET_MAP.put("nl", "ISO-8859-1");
    3929             LOCALE_TO_CHARSET_MAP.put("no", "ISO-8859-1");
    3930             LOCALE_TO_CHARSET_MAP.put("pl", "ISO-8859-2");
    3931             LOCALE_TO_CHARSET_MAP.put("pt", "ISO-8859-1");
    3932             LOCALE_TO_CHARSET_MAP.put("ro", "ISO-8859-2");
    3933             LOCALE_TO_CHARSET_MAP.put("ru", "ISO-8859-5");
    3934             LOCALE_TO_CHARSET_MAP.put("sh", "ISO-8859-5");
    3935             LOCALE_TO_CHARSET_MAP.put("sk", "ISO-8859-2");
    3936             LOCALE_TO_CHARSET_MAP.put("sl", "ISO-8859-2");
    3937             LOCALE_TO_CHARSET_MAP.put("sq", "ISO-8859-2");
    3938             LOCALE_TO_CHARSET_MAP.put("sr", "ISO-8859-5");
    3939             LOCALE_TO_CHARSET_MAP.put("sv", "ISO-8859-1");
    3940             LOCALE_TO_CHARSET_MAP.put("tr", "ISO-8859-9");
    3941             LOCALE_TO_CHARSET_MAP.put("uk", "ISO-8859-5");
    3942             LOCALE_TO_CHARSET_MAP.put("zh", "GB2312");
    3943             LOCALE_TO_CHARSET_MAP.put("zh_TW", "Big5");
    3944         }
    3945
    3946         /**
    3947          * Get the preferred charset for the given locale.
    3948          *
    3949          * @param locale the locale
    3950          * @return the preferred charset or null if the locale is not
    3951          * recognized.
    3952          */
    3953         public static String getCharset(Locale locale) {
    3954             // try for an full name match (may include country)
    3955             String charset =
    3956                 (String) LOCALE_TO_CHARSET_MAP.get(locale.toString());
    3957             if (charset != null) {
    3958                 return charset;
    3959             }
    3960
    3961             // if a full name didn't match, try just the language
    3962             charset = (String) LOCALE_TO_CHARSET_MAP.get(locale.getLanguage());
    3963             return charset;  // may be null
    3964         }
    3965
    3966     }
    3967
    3968 }
    3969