/// <summary>
        /// Parses the string representation of a {@code URL} into a
        /// {@code URL} object.
        /// <para>
        /// If there is any inherited context, then it has already been
        /// copied into the {@code URL} argument.
        /// </para>
        /// <para>
        /// The {@code parseURL} method of {@code URLStreamHandler}
        /// parses the string representation as if it were an
        /// {@code http} specification. Most URL protocol families have a
        /// similar parsing. A stream protocol handler for a protocol that has
        /// a different syntax must override this routine.
        ///
        /// </para>
        /// </summary>
        /// <param name="u">       the {@code URL} to receive the result of parsing
        ///                  the spec. </param>
        /// <param name="spec">    the {@code String} representing the URL that
        ///                  must be parsed. </param>
        /// <param name="start">   the character index at which to begin parsing. This is
        ///                  just past the '{@code :}' (if there is one) that
        ///                  specifies the determination of the protocol name. </param>
        /// <param name="limit">   the character position to stop parsing at. This is the
        ///                  end of the string or the position of the
        ///                  "{@code #}" character, if present. All information
        ///                  after the sharp sign indicates an anchor. </param>
        protected internal virtual void ParseURL(URL u, String spec, int start, int limit)
        {
            // These fields may receive context content if this was relative URL
            String protocol  = u.Protocol;
            String authority = u.Authority;
            String userInfo  = u.UserInfo;
            String host      = u.Host;
            int    port      = u.Port;
            String path      = u.Path;
            String query     = u.Query;

            // This field has already been parsed
            String @ref = u.Ref;

            bool isRelPath = false;
            bool queryOnly = false;

            // FIX: should not assume query if opaque
            // Strip off the query part
            if (start < limit)
            {
                int queryStart = spec.IndexOf('?');
                queryOnly = queryStart == start;
                if ((queryStart != -1) && (queryStart < limit))
                {
                    query = StringHelperClass.SubstringSpecial(spec, queryStart + 1, limit);
                    if (limit > queryStart)
                    {
                        limit = queryStart;
                    }
                    spec = spec.Substring(0, queryStart);
                }
            }

            int i = 0;
            // Parse the authority part if any
            bool isUNCName = (start <= limit - 4) && (spec.CharAt(start) == '/') && (spec.CharAt(start + 1) == '/') && (spec.CharAt(start + 2) == '/') && (spec.CharAt(start + 3) == '/');

            if (!isUNCName && (start <= limit - 2) && (spec.CharAt(start) == '/') && (spec.CharAt(start + 1) == '/'))
            {
                start += 2;
                i      = spec.IndexOf('/', start);
                if (i < 0)
                {
                    i = spec.IndexOf('?', start);
                    if (i < 0)
                    {
                        i = limit;
                    }
                }

                host = authority = spec.Substring(start, i - start);

                int ind = authority.IndexOf('@');
                if (ind != -1)
                {
                    userInfo = authority.Substring(0, ind);
                    host     = authority.Substring(ind + 1);
                }
                else
                {
                    userInfo = null;
                }
                if (host != null)
                {
                    // If the host is surrounded by [ and ] then its an IPv6
                    // literal address as specified in RFC2732
                    if (host.Length() > 0 && (host.CharAt(0) == '['))
                    {
                        if ((ind = host.IndexOf(']')) > 2)
                        {
                            String nhost = host;
                            host = nhost.Substring(0, ind + 1);
                            if (!IPAddressUtil.isIPv6LiteralAddress(host.Substring(1, ind - 1)))
                            {
                                throw new IllegalArgumentException("Invalid host: " + host);
                            }

                            port = -1;
                            if (nhost.Length() > ind + 1)
                            {
                                if (nhost.CharAt(ind + 1) == ':')
                                {
                                    ++ind;
                                    // port can be null according to RFC2396
                                    if (nhost.Length() > (ind + 1))
                                    {
                                        port = Convert.ToInt32(nhost.Substring(ind + 1));
                                    }
                                }
                                else
                                {
                                    throw new IllegalArgumentException("Invalid authority field: " + authority);
                                }
                            }
                        }
                        else
                        {
                            throw new IllegalArgumentException("Invalid authority field: " + authority);
                        }
                    }
                    else
                    {
                        ind  = host.IndexOf(':');
                        port = -1;
                        if (ind >= 0)
                        {
                            // port can be null according to RFC2396
                            if (host.Length() > (ind + 1))
                            {
                                port = Convert.ToInt32(host.Substring(ind + 1));
                            }
                            host = host.Substring(0, ind);
                        }
                    }
                }
                else
                {
                    host = "";
                }
                if (port < -1)
                {
                    throw new IllegalArgumentException("Invalid port number :" + port);
                }
                start = i;
                // If the authority is defined then the path is defined by the
                // spec only; See RFC 2396 Section 5.2.4.
                if (authority != null && authority.Length() > 0)
                {
                    path = "";
                }
            }

            if (host == null)
            {
                host = "";
            }

            // Parse the file path if any
            if (start < limit)
            {
                if (spec.CharAt(start) == '/')
                {
                    path = spec.Substring(start, limit - start);
                }
                else if (path != null && path.Length() > 0)
                {
                    isRelPath = true;
                    int    ind       = path.LastIndexOf('/');
                    String seperator = "";
                    if (ind == -1 && authority != null)
                    {
                        seperator = "/";
                    }
                    path = path.Substring(0, ind + 1) + seperator + spec.Substring(start, limit - start);
                }
                else
                {
                    String seperator = (authority != null) ? "/" : "";
                    path = seperator + spec.Substring(start, limit - start);
                }
            }
            else if (queryOnly && path != null)
            {
                int ind = path.LastIndexOf('/');
                if (ind < 0)
                {
                    ind = 0;
                }
                path = path.Substring(0, ind) + "/";
            }
            if (path == null)
            {
                path = "";
            }

            if (isRelPath)
            {
                // Remove embedded /./
                while ((i = path.IndexOf("/./")) >= 0)
                {
                    path = path.Substring(0, i) + path.Substring(i + 2);
                }
                // Remove embedded /../ if possible
                i = 0;
                while ((i = path.IndexOf("/../", i)) >= 0)
                {
                    /*
                     * A "/../" will cancel the previous segment and itself,
                     * unless that segment is a "/../" itself
                     * i.e. "/a/b/../c" becomes "/a/c"
                     * but "/../../a" should stay unchanged
                     */
                    if (i > 0 && (limit = path.LastIndexOf('/', i - 1)) >= 0 && (path.IndexOf("/../", limit) != 0))
                    {
                        path = path.Substring(0, limit) + path.Substring(i + 3);
                        i    = 0;
                    }
                    else
                    {
                        i = i + 3;
                    }
                }
                // Remove trailing .. if possible
                while (path.EndsWith("/.."))
                {
                    i = path.IndexOf("/..");
                    if ((limit = path.LastIndexOf('/', i - 1)) >= 0)
                    {
                        path = path.Substring(0, limit + 1);
                    }
                    else
                    {
                        break;
                    }
                }
                // Remove starting .
                if (path.StartsWith("./") && path.Length() > 2)
                {
                    path = path.Substring(2);
                }

                // Remove trailing .
                if (path.EndsWith("/."))
                {
                    path = path.Substring(0, path.Length() - 1);
                }
            }

            SetURL(u, protocol, host, port, authority, userInfo, path, query, @ref);
        }