public URLNormalizer removeDotSegments() {
        String path = toURL().getPath().trim();

        // (Bulleted comments are from RFC3986, section-5.2.4)

        // 1.  The input buffer is initialized with the now-appended path
        //     components and the output buffer is initialized to the empty
        //     string.
        StringBuilder in = new StringBuilder(path);
        StringBuilder out = new StringBuilder();

        // 2.  While the input buffer is not empty, loop as follows:
        while (in.length() > 0) {

            // A.  If the input buffer begins with a prefix of "../" or "./",
            //     then remove that prefix from the input buffer; otherwise,
            if (startsWith(in, "../")) {
                deleteStart(in, "../");
            } else if (startsWith(in, "./")) {
                deleteStart(in, "./");
            }

            // B.  if the input buffer begins with a prefix of "/./" or "/.",
            //     where "." is a complete path segment, then replace that
            //     prefix with "/" in the input buffer; otherwise,
            else if (startsWith(in, "/./")) {
                replaceStart(in, "/./", "/");
            } else if (equalStrings(in, "/.")) {
                replaceStart(in, "/.", "/");
            }

            // C.  if the input buffer begins with a prefix of "/../" or "/..",
            //     where ".." is a complete path segment, then replace that
            //     prefix with "/" in the input buffer and remove the last
            //     segment and its preceding "/" (if any) from the output
            //     buffer; otherwise,
            else if (startsWith(in, "/../")) {
                replaceStart(in, "/../", "/");
                removeLastSegment(out);
            } else if (equalStrings(in, "/..")) {
                replaceStart(in, "/..", "/");
                removeLastSegment(out);
            }

            // D.  if the input buffer consists only of "." or "..", then remove
            //      that from the input buffer; otherwise,
            else if (equalStrings(in, "..")) {
                deleteStart(in, "..");
            } else if (equalStrings(in, ".")) {
                deleteStart(in, ".");
            }

            // E.  move the first path segment in the input buffer to the end of
            //     the output buffer, including the initial "/" character (if
            //     any) and any subsequent characters up to, but not including,
            //     the next "/" character or the end of the input buffer.
            else {
                int nextSlashIndex = in.indexOf("/", 1);
                if (nextSlashIndex > -1) {
                    out.append(in.substring(0, nextSlashIndex));
                    in.delete(0, nextSlashIndex);
                } else {
                    out.append(in);
                    in.setLength(0);
                }
            }
        }

        // 3.  Finally, the output buffer is returned as the result of
        //     remove_dot_segments.
        url = StringUtils.replaceOnce(url, path, out.toString());
        return this;
    }
@Test
    public void testRemoveDotSegments() {
        s = "http://www.example.com/../a/b/../c/./d.html";
        t = "http://www.example.com/a/c/d.html";
        assertEquals(t, n(s).removeDotSegments().toString());
        s = "http://www.example.com/a/../b/../c/./d.html";
        t = "http://www.example.com/c/d.html";
        assertEquals(t, n(s).removeDotSegments().toString());
        // From ticket #173:
        s = "http://www.example.com/a/../../../../b/c/d/e/f.jpg";
        t = "http://www.example.com/b/c/d/e/f.jpg";
        assertEquals(t, n(s).removeDotSegments().toString());

        //--- Tests from http://tools.ietf.org/html/rfc3986#section-5.4 ---
        String urlRoot = "http://a.com";
        Map<String, String> m = new HashMap<>();

        // 5.4.1 Normal Examples
        m.put("/b/c/."             , "/b/c/");
        m.put("/b/c/./"            , "/b/c/");
        m.put("/b/c/.."            , "/b/");
        m.put("/b/c/../"           , "/b/");
        m.put("/b/c/../g"          , "/b/g");
        m.put("/b/c/../.."         , "/");
        m.put("/b/c/../../"        , "/");
        m.put("/b/c/../../g"       , "/g");

        // 5.4.2. Abnormal Examples
        m.put("/b/c/../../../g"    ,  "/g");
        m.put("/b/c/../../../../g" ,  "/g");

        m.put("/./g"               ,  "/g");
        m.put("/../g"              ,  "/g");
        m.put("/b/c/g."            ,  "/b/c/g.");
        m.put("/b/c/.g"            ,  "/b/c/.g");
        m.put("/b/c/g.."           ,  "/b/c/g..");
        m.put("/b/c/..g"           ,  "/b/c/..g");

        m.put("/b/c/./../g"        ,  "/b/g");
        m.put("/b/c/./g/."         ,  "/b/c/g/");
        m.put("/b/c/g/./h"         ,  "/b/c/g/h");
        m.put("/b/c/g/../h"        ,  "/b/c/h");
        m.put("/b/c/g;x=1/./y"     ,  "/b/c/g;x=1/y");
        m.put("/b/c/g;x=1/../y"    ,  "/b/c/y");

        m.put("/b/c/g?y/./x"       ,  "/b/c/g?y/./x");
        m.put("/b/c/g?y/../x"      ,  "/b/c/g?y/../x");
        m.put("/b/c/g#s/./x"       ,  "/b/c/g#s/./x");
        m.put("/b/c/g#s/../x"      ,  "/b/c/g#s/../x");

        for (Map.Entry<String, String> e : m.entrySet()) {
            s = urlRoot + e.getKey();
            t = urlRoot + e.getValue();
            assertEquals(t, n(s).removeDotSegments().toString());
        }
    }