From 2a3d4308fd85bba8af3d418f1f2711bb33962e4f Mon Sep 17 00:00:00 2001 From: "Santiago M. Mola" Date: Tue, 7 Jan 2014 10:05:44 +0100 Subject: [PATCH] Path methods to path/pathSegments. Closes #15. --- src/main/java/io/mola/galimatias/URL.java | 114 ++++++++++-------- .../java/io/mola/galimatias/URLParser.java | 34 +++--- src/main/java/io/mola/galimatias/cli/CLI.java | 4 +- .../java/io/mola/galimatias/URL2Test.java | 50 ++++---- src/test/java/io/mola/galimatias/URLTest.java | 2 +- 5 files changed, 105 insertions(+), 99 deletions(-) diff --git a/src/main/java/io/mola/galimatias/URL.java b/src/main/java/io/mola/galimatias/URL.java index 00bf071..9308cde 100644 --- a/src/main/java/io/mola/galimatias/URL.java +++ b/src/main/java/io/mola/galimatias/URL.java @@ -26,7 +26,9 @@ import java.net.MalformedURLException; import java.net.URI; import java.net.URISyntaxException; +import java.util.ArrayList; import java.util.Arrays; +import java.util.List; /** * A parsed URL. Immutable. @@ -39,24 +41,32 @@ */ public class URL implements Serializable { - private static final String[] EMPTY_PATH = new String[0]; - private final String scheme; private final String schemeData; private final String username; private final String password; private final Host host; private final int port; - private final String[] path; + private final String path; private final String query; private final String fragment; private final boolean isHierarchical; + URL(final String scheme, final String schemeData, + final String username, final String password, + final Host host, final int port, + final Iterable pathSegments, + final String query, final String fragment, + final boolean isHierarchical) { + this(scheme, schemeData, username, password, host, port, pathSegmentsToString(pathSegments), + query, fragment, isHierarchical); + } + URL(final String scheme, final String schemeData, final String username, final String password, final Host host, final int port, - final String[] path, + final String path, final String query, final String fragment, final boolean isHierarchical) { this.scheme = scheme; @@ -65,11 +75,7 @@ public class URL implements Serializable { this.password = password; this.host = host; this.port = port; - if (path != null && path.length > 0 && (path.length > 1 || !path[0].equals(""))) { - this.path = Arrays.copyOf(path, path.length); - } else { - this.path = EMPTY_PATH; - } + this.path = path; this.query = query; this.fragment = fragment; this.isHierarchical = isHierarchical; @@ -140,23 +146,15 @@ public int defaultPort() { return Integer.parseInt(defaultPort); } - public String[] path() { - return Arrays.copyOf(path, path.length); + public String path() { + return path; } - public String pathString() { + public List pathSegments() { if (!isHierarchical) { return null; } - StringBuilder output = new StringBuilder(); - output.append('/'); - if (path.length > 0) { - output.append(path[0]); - for (int i = 1; i < path.length; i++) { - output.append('/').append(path[i]); - } - } - return output.toString(); + return pathStringToSegments(path); } public String query() { @@ -167,18 +165,16 @@ public String fragment() { return fragment; } - protected String file() { - final String pathString = pathString(); - if (pathString == null && query == null && fragment == null) { + public String file() { + if (path == null && query == null) { return ""; } final StringBuilder output = new StringBuilder( - ((pathString != null)? pathString.length() : 0) + - ((query != null)? query.length() + 1 : 0) + - ((fragment != null)? fragment.length() + 1 : 0) + ((path != null)? path.length() : 0) + + ((query != null)? query.length() + 1 : 0) ); - if (pathString != null) { - output.append(pathString); + if (path != null) { + output.append(path); } if (query != null) { output.append('?').append(query); @@ -194,6 +190,37 @@ public boolean isOpaque() { return !isHierarchical; } + private static String pathSegmentsToString(final Iterable segments) { + if (segments == null) { + return null; + } + final StringBuilder output = new StringBuilder(); + for (final String segment : segments) { + output.append('/').append(segment); + } + if (output.length() == 0) { + return "/"; + } + return output.toString(); + } + + private static List pathStringToSegments(String path) { + if (path == null) { + return new ArrayList(); + } + if (path.startsWith("/")) { + path = path.substring(1); + } + final String[] segments = path.split("/", -1); + final List result = new ArrayList(segments.length + 1); + if (segments.length == 0) { + result.add(""); + return result; + } + result.addAll(Arrays.asList(segments)); + return result; + } + /** * Resolves a relative reference to an absolute URL. * @@ -471,12 +498,8 @@ public String toString() { if (port != -1) { output.append(':').append(port); } - output.append('/'); - if (path.length > 0) { - output.append(path[0]); - for (int i = 1; i < path.length; i++) { - output.append('/').append(path[i]); - } + if (path != null) { + output.append(path); } } else { output.append(schemeData); @@ -508,27 +531,12 @@ public boolean equals(final Object obj) { ((password == null)? other.password == null : password.equals(other.password)) && ((host == null)? other.host == null : host.equals(other.host)) && port == other.port && + ((path == null)? other.host == null : path.equals(other.path)) && ((fragment == null)? other.fragment == null : fragment.equals(other.fragment)) && - ((query == null)? other.query == null : query.equals(other.query)) && - arrayEquals(path, other.path) + ((query == null)? other.query == null : query.equals(other.query)) ; } - private static boolean arrayEquals(final String[] one, final String[] other) { - if (one == other) { - return true; - } - if (one.length != other.length) { - return false; - } - for (int i = 0; i < one.length; i++) { - if (!one[i].equals(other[i])) { - return false; - } - } - return true; - } - @Override public int hashCode() { int result = scheme != null ? scheme.hashCode() : 0; @@ -537,7 +545,7 @@ public int hashCode() { result = 31 * result + (password != null ? password.hashCode() : 0); result = 31 * result + (host != null ? host.hashCode() : 0); result = 31 * result + (port != -1 ? port : 0); - result = 31 * result + Arrays.hashCode(path); + result = 31 * result + (path != null? path.hashCode() : 0); result = 31 * result + (query != null ? query.hashCode() : 0); result = 31 * result + (fragment != null ? fragment.hashCode() : 0); result = 31 * result + (isHierarchical ? 1 : 0); diff --git a/src/main/java/io/mola/galimatias/URLParser.java b/src/main/java/io/mola/galimatias/URLParser.java index 9d7a24c..faf872a 100644 --- a/src/main/java/io/mola/galimatias/URLParser.java +++ b/src/main/java/io/mola/galimatias/URLParser.java @@ -23,7 +23,9 @@ package io.mola.galimatias; import java.nio.charset.Charset; +import java.util.ArrayList; import java.util.Arrays; +import java.util.List; import java.util.Locale; import static io.mola.galimatias.URLUtils.*; @@ -158,7 +160,7 @@ public URL parse() throws GalimatiasParseException { boolean relativeFlag = (url != null) && url.isHierarchical(); boolean atFlag = false; // @-flag boolean bracketsFlag = false; // []-flag - String[] path = (url == null || stateOverride == ParseURLState.RELATIVE_PATH_START)? new String[0] : url.path(); + List pathSegments = (url == null || stateOverride == ParseURLState.RELATIVE_PATH_START)? new ArrayList() : url.pathSegments(); StringBuilder query = (url == null || url.query() == null || stateOverride == ParseURLState.QUERY)? null : new StringBuilder(url.query()); StringBuilder fragment = (url == null || url.fragment() == null|| stateOverride == ParseURLState.FRAGMENT)? null : new StringBuilder(url.fragment()); @@ -353,7 +355,7 @@ else if (c == '#') { if (isEOF) { host = (base == null)? null : base.host(); port = (base == null || base.port() == base.defaultPort())? -1 : base.port(); - path = (base == null)? null : base.path(); + pathSegments = (base == null)? null : base.pathSegments(); query = (base == null || base.query() == null)? null : new StringBuilder(base.query()); } else if (c == '/' || c == '\\') { if (c == '\\') { @@ -363,13 +365,13 @@ else if (c == '#') { } else if (c == '?') { host = (base == null)? null : base.host(); port = (base == null || base.port() == base.defaultPort())? -1 : base.port(); - path = (base == null)? null : base.path(); + pathSegments = (base == null)? null : base.pathSegments(); query = new StringBuilder(); state = ParseURLState.QUERY; } else if (c == '#') { host = (base == null)? null : base.host(); port = (base == null || base.port() == base.defaultPort())? -1 : base.port(); - path = (base == null)? null : base.path(); + pathSegments = (base == null)? null : base.pathSegments(); query = (base == null)? null : new StringBuilder(base.query()); fragment = new StringBuilder(); state = ParseURLState.FRAGMENT; @@ -384,10 +386,10 @@ else if (c == '#') { host = (base == null)? null : base.host(); port = (base == null || base.port() == base.defaultPort())? -1 : base.port(); - path = (base == null)? new String[0] : base.path(); + pathSegments = (base == null)? new ArrayList() : base.pathSegments(); // Pop path - if (path.length > 0) { - path = Arrays.copyOf(path, path.length - 1); + if (!pathSegments.isEmpty()) { + pathSegments.remove(pathSegments.size() - 1); } } state = ParseURLState.RELATIVE_PATH; @@ -634,27 +636,23 @@ else if (c == '#') { } if ("..".equals(buffer.toString())) { // Pop path - if (path.length > 0) { - path = Arrays.copyOf(path, path.length - 1); + if (!pathSegments.isEmpty()) { + pathSegments.remove(pathSegments.size() - 1); } if (c != '/' && c != '\\') { - path = Arrays.copyOf(path, path.length + 1); - path[path.length - 1] = ""; + pathSegments.add(""); } - //FIX: It is not clear in the spec how a path should be pop'd } else if (".".equals(buffer.toString()) && c != '/' && c != '\\') { - path = Arrays.copyOf(path, path.length + 1); - path[path.length - 1] = ""; + pathSegments.add(""); } else if (!".".equals(buffer.toString())) { - if ("file".equals(scheme) && path.length == 0 && + if ("file".equals(scheme) && pathSegments.isEmpty() && buffer.length() == 2 && isASCIIAlpha(buffer.charAt(0)) && buffer.charAt(1) == '|') { buffer.setCharAt(1, ':'); } - path = Arrays.copyOf(path, path.length + 1); - path[path.length - 1] = buffer.toString(); + pathSegments.add(buffer.toString()); } buffer.setLength(0); if (c == '?') { @@ -789,7 +787,7 @@ else if (c == '#') { return new URL(scheme, schemeData.toString(), username, password, - host, port, path, + host, port, pathSegments, (query == null)? null : query.toString(), (fragment == null)? null : fragment.toString(), relativeFlag); diff --git a/src/main/java/io/mola/galimatias/cli/CLI.java b/src/main/java/io/mola/galimatias/cli/CLI.java index 77a5e7b..9523945 100644 --- a/src/main/java/io/mola/galimatias/cli/CLI.java +++ b/src/main/java/io/mola/galimatias/cli/CLI.java @@ -59,8 +59,8 @@ private static void printResult(URL url) { if (url.port() != -1) { System.out.println("\t\tPort: " + url.port()); } - if (url.pathString() != null) { - System.out.println("\t\tPath: " + url.pathString()); + if (url.path() != null) { + System.out.println("\t\tPath: " + url.path()); } if (url.query() != null) { System.out.println("\t\tQuery: " + url.query()); diff --git a/src/test/java/io/mola/galimatias/URL2Test.java b/src/test/java/io/mola/galimatias/URL2Test.java index a93a03c..685132a 100644 --- a/src/test/java/io/mola/galimatias/URL2Test.java +++ b/src/test/java/io/mola/galimatias/URL2Test.java @@ -35,7 +35,7 @@ public void testUrlParts() throws Exception { assertEquals(8080, url.port()); assertEquals(80, url.defaultPort()); assertEquals("/directory/file?query", url.file()); - assertEquals("/directory/file", url.pathString()); + assertEquals("/directory/file", url.path()); assertEquals("query", url.query()); assertEquals("ref", url.fragment()); } @@ -145,7 +145,7 @@ public void testOmittedHost() throws Exception { URL url = URL.parse("http:///path"); assertEquals("path", url.host().toString()); // Android will parse this as "" assertEquals("/", url.file()); // Android will parse this as "/path" - assertEquals("/", url.pathString()); + assertEquals("/", url.path()); } public void testNoHost() throws Exception { @@ -157,7 +157,7 @@ public void testNoHost() throws Exception { assertEquals(80, url.port()); assertEquals(80, url.defaultPort()); assertEquals("/", url.file()); // Android will be "/path" - assertEquals("/", url.pathString()); // Android will be "/path" + assertEquals("/", url.path()); // Android will be "/path" assertEquals(null, url.query()); assertEquals(null, url.fragment()); } @@ -166,7 +166,7 @@ public void testNoPath() throws Exception { URL url = URL.parse("http://host"); assertEquals("host", url.host().toString()); assertEquals("/", url.file()); // Android will be "" - assertEquals("/", url.pathString()); // Android will be "" + assertEquals("/", url.path()); // Android will be "" } public void testEmptyHostAndNoPath() throws Exception { @@ -239,68 +239,68 @@ public void testUserPasswordEmptyHostEmptyPort() throws Exception { public void testPathOnly() throws Exception { URL url = URL.parse("http://host/path"); assertEquals("/path", url.file()); - assertEquals("/path", url.pathString()); + assertEquals("/path", url.path()); } public void testQueryOnly() throws Exception { URL url = URL.parse("http://host?query"); assertEquals("/?query", url.file()); - assertEquals("/", url.pathString()); + assertEquals("/", url.path()); assertEquals("query", url.query()); } public void testFragmentOnly() throws Exception { URL url = URL.parse("http://host#fragment"); assertEquals("/", url.file()); - assertEquals("/", url.pathString()); + assertEquals("/", url.path()); assertEquals("fragment", url.fragment()); } public void testAtSignInPath() throws Exception { URL url = URL.parse("http://host/file@foo"); assertEquals("/file@foo", url.file()); - assertEquals("/file@foo", url.pathString()); + assertEquals("/file@foo", url.path()); assertEquals(null, url.userInfo()); } public void testColonInPath() throws Exception { URL url = URL.parse("http://host/file:colon"); assertEquals("/file:colon", url.file()); - assertEquals("/file:colon", url.pathString()); + assertEquals("/file:colon", url.path()); } public void testSlashInQuery() throws Exception { URL url = URL.parse("http://host/file?query/path"); assertEquals("/file?query/path", url.file()); - assertEquals("/file", url.pathString()); + assertEquals("/file", url.path()); assertEquals("query/path", url.query()); } public void testQuestionMarkInQuery() throws Exception { URL url = URL.parse("http://host/file?query?another"); assertEquals("/file?query?another", url.file()); - assertEquals("/file", url.pathString()); + assertEquals("/file", url.path()); assertEquals("query?another", url.query()); } public void testAtSignInQuery() throws Exception { URL url = URL.parse("http://host/file?query@at"); assertEquals("/file?query@at", url.file()); - assertEquals("/file", url.pathString()); + assertEquals("/file", url.path()); assertEquals("query@at", url.query()); } public void testColonInQuery() throws Exception { URL url = URL.parse("http://host/file?query:colon"); assertEquals("/file?query:colon", url.file()); - assertEquals("/file", url.pathString()); + assertEquals("/file", url.path()); assertEquals("query:colon", url.query()); } public void testQuestionMarkInFragment() throws Exception { URL url = URL.parse("http://host/file#fragment?query"); assertEquals("/file", url.file()); - assertEquals("/file", url.pathString()); + assertEquals("/file", url.path()); assertEquals(null, url.query()); assertEquals("fragment?query", url.fragment()); } @@ -308,7 +308,7 @@ public void testQuestionMarkInFragment() throws Exception { public void testColonInFragment() throws Exception { URL url = URL.parse("http://host/file#fragment:80"); assertEquals("/file", url.file()); - assertEquals("/file", url.pathString()); + assertEquals("/file", url.path()); assertEquals(80, url.port()); assertEquals("fragment:80", url.fragment()); } @@ -316,7 +316,7 @@ public void testColonInFragment() throws Exception { public void testSlashInFragment() throws Exception { URL url = URL.parse("http://host/file#fragment/path"); assertEquals("/file", url.file()); - assertEquals("/file", url.pathString()); + assertEquals("/file", url.path()); assertEquals("fragment/path", url.fragment()); } @@ -332,7 +332,7 @@ public void testSlashInFragmentCombiningConstructor() throws Exception { public void testHashInFragment() throws Exception { URL url = URL.parse("http://host/file#fragment#another"); assertEquals("/file", url.file()); - assertEquals("/file", url.pathString()); + assertEquals("/file", url.path()); assertEquals("fragment#another", url.fragment()); } @@ -370,7 +370,7 @@ public void testRelativePathOnQuery() throws Exception { URL url = URL.parse(base, "another"); assertEquals("http://host/another", url.toString()); assertEquals("/another", url.file()); - assertEquals("/another", url.pathString()); + assertEquals("/another", url.path()); assertEquals(null, url.query()); assertEquals(null, url.fragment()); } @@ -380,7 +380,7 @@ public void testRelativeFragmentOnQuery() throws Exception { URL url = URL.parse(base, "#another"); assertEquals("http://host/file?query/x#another", url.toString()); assertEquals("/file?query/x", url.file()); - assertEquals("/file", url.pathString()); + assertEquals("/file", url.path()); assertEquals("query/x", url.query()); assertEquals("another", url.fragment()); } @@ -570,14 +570,14 @@ public void testComposeUrlWithNullHost() throws Exception { public void testFileUrlExtraLeadingSlashes() throws Exception { URL url = URL.parse("file:////foo"); assertEquals(null, url.authority()); // RI and galimatias return null, Android returns "" - assertEquals("//foo", url.pathString()); + assertEquals("//foo", url.path()); assertEquals("file:////foo", url.toString()); } public void testFileUrlWithAuthority() throws Exception { URL url = URL.parse("file://x/foo"); assertEquals("x", url.authority()); - assertEquals("/foo", url.pathString()); + assertEquals("/foo", url.path()); assertEquals("file://x/foo", url.toString()); } @@ -589,14 +589,14 @@ public void testFileUrlWithAuthority() throws Exception { public void testEmptyAuthority() throws Exception { URL url = URL.parse("http:///foo"); assertEquals("foo", url.authority()); // Android will be "" - assertEquals("/", url.pathString()); // Android will be "/foo" + assertEquals("/", url.path()); // Android will be "/foo" assertEquals("http://foo/", url.toString()); // RI drops '//', android will be "http:///foo" } public void testHttpUrlExtraLeadingSlashes() throws Exception { URL url = URL.parse("http:////foo"); assertEquals("foo", url.authority()); // RI returns null, Android "//" - assertEquals("/", url.pathString()); // Android returns "//foo" + assertEquals("/", url.path()); // Android returns "//foo" assertEquals("http://foo/", url.toString()); // Android returns "http:////foo" } @@ -607,7 +607,7 @@ public void testFileUrlRelativePath() throws Exception { public void testFileUrlDottedPath() throws Exception { URL url = URL.parse("file:../a/b"); - assertEquals("/a/b", url.pathString()); // Android will be "../a/b" + assertEquals("/a/b", url.path()); // Android will be "../a/b" assertEquals("file:///a/b", url.toString()); // Android will be "file:../a/b" } @@ -686,7 +686,7 @@ public void testSchemeCaseIsCanonicalized() throws Exception { public void testEmptyAuthorityWithPath() throws Exception { URL url = URL.parse("http:///path"); assertEquals("path", url.authority()); // Android will be "" - assertEquals("/", url.pathString()); // Android will be "/path" + assertEquals("/", url.path()); // Android will be "/path" } public void testEmptyAuthorityWithQuery() throws Exception { diff --git a/src/test/java/io/mola/galimatias/URLTest.java b/src/test/java/io/mola/galimatias/URLTest.java index 9521932..26720f1 100644 --- a/src/test/java/io/mola/galimatias/URLTest.java +++ b/src/test/java/io/mola/galimatias/URLTest.java @@ -192,7 +192,7 @@ public void withPortOpaque(final @TestURLs TestURL testURL) throws GalimatiasPar public void withPath(final @TestURLs TestURL testURL) throws GalimatiasParseException { final URL originalURL = URL.parse(testURL.base(), testURL.original()); assumeTrue(originalURL.isHierarchical()); - assertThat(originalURL.withPath("/foo/bar").pathString()).isEqualTo("/foo/bar"); + assertThat(originalURL.withPath("/foo/bar").path()).isEqualTo("/foo/bar"); } @Theory