From a63557cdd9d5dbd418607f092f1676cb981cefd7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=CC=88rg=20Prante?= Date: Thu, 17 Jan 2019 21:30:38 +0100 Subject: [PATCH] clean up --- .../java/org/xbib/net/PercentEncoders.java | 1 - .../java/org/xbib/net/ProtocolVersion.java | 2 +- net-url/src/main/java/org/xbib/net/URL.java | 129 +++++++++++------- .../org/xbib/net/matcher/CharMatcher.java | 3 +- .../java/org/xbib/net/scheme/HttpScheme.java | 4 +- .../org/xbib/net/scheme/SchemeRegistry.java | 5 +- .../src/test/java/org/xbib/net/IRITest.java | 4 +- .../test/java/org/xbib/net/URLParserTest.java | 21 ++- 8 files changed, 102 insertions(+), 67 deletions(-) diff --git a/net-url/src/main/java/org/xbib/net/PercentEncoders.java b/net-url/src/main/java/org/xbib/net/PercentEncoders.java index 2e53b6f..f3613a8 100644 --- a/net-url/src/main/java/org/xbib/net/PercentEncoders.java +++ b/net-url/src/main/java/org/xbib/net/PercentEncoders.java @@ -2,7 +2,6 @@ package org.xbib.net; import java.nio.charset.Charset; import java.util.BitSet; - import static java.nio.charset.CodingErrorAction.REPORT; /** diff --git a/net-url/src/main/java/org/xbib/net/ProtocolVersion.java b/net-url/src/main/java/org/xbib/net/ProtocolVersion.java index 2f4f96e..2762664 100644 --- a/net-url/src/main/java/org/xbib/net/ProtocolVersion.java +++ b/net-url/src/main/java/org/xbib/net/ProtocolVersion.java @@ -5,5 +5,5 @@ package org.xbib.net; */ public enum ProtocolVersion { - IPV4, IPV6, IPV46, NONE + IPV4, IPV6, NONE } diff --git a/net-url/src/main/java/org/xbib/net/URL.java b/net-url/src/main/java/org/xbib/net/URL.java index 39fe543..302c602 100755 --- a/net-url/src/main/java/org/xbib/net/URL.java +++ b/net-url/src/main/java/org/xbib/net/URL.java @@ -63,6 +63,10 @@ public class URL implements Comparable { private static final char AT_CHAR = '@'; + private static final char LEFT_BRACKET_CHAR = '['; + + private static final char RIGHT_BRACKET_CHAR = ']'; + private static final String DOUBLE_SLASH = "//"; private static final String EMPTY = ""; @@ -71,10 +75,20 @@ public class URL implements Comparable { private final transient Builder builder; - private final transient Charset charset; - private final transient Scheme scheme; + private final transient PercentEncoder queryParamEncoder; + + private final transient PercentEncoder regNameEncoder; + + private final transient PercentEncoder pathEncoder; + + private final transient PercentEncoder matrixEncoder; + + private final transient PercentEncoder queryEncoder; + + private final transient PercentEncoder fragmentEncoder; + private final String hostinfo; private final String path; @@ -89,19 +103,19 @@ public class URL implements Comparable { private URL(Builder builder) { this.builder = builder; - this.charset = builder.charset; this.scheme = SchemeRegistry.getInstance().getScheme(builder.scheme); + this.queryParamEncoder = PercentEncoders.getQueryParamEncoder(builder.charset); + this.regNameEncoder = PercentEncoders.getRegNameEncoder(builder.charset); + this.pathEncoder = PercentEncoders.getPathEncoder(builder.charset); + this.matrixEncoder = PercentEncoders.getMatrixEncoder(builder.charset); + this.queryEncoder = PercentEncoders.getQueryEncoder(builder.charset); + this.fragmentEncoder = PercentEncoders.getFragmentEncoder(builder.charset); this.hostinfo = encodeHostInfo(); this.path = encodePath(); this.query = encodeQuery(); this.fragment = encodeFragment(); } - /** - * A special, scheme-less URL denoting the fact that this URL should be considered as invalid. - */ - private static final URL INVALID = URL.builder().build(); - public static Builder file() { return new Builder().scheme(Scheme.FILE); } @@ -234,8 +248,14 @@ public class URL implements Comparable { return new Resolver(URL.create(base)); } - public static URL getInvalid() { - return INVALID; + private static final URL NULL_URL = URL.builder().build(); + + /** + * Return a special URL denoting the fact that this URL should be considered as invalid. + * The URL has a null scheme. + */ + public static URL nullUrl() { + return NULL_URL; } public static URL from(String input) { @@ -331,6 +351,10 @@ public class URL implements Comparable { return builder.userInfo; } + /** + * Get the user of the user info. + * @return the user + */ public String getUser() { if (builder.userInfo == null) { return null; @@ -355,6 +379,10 @@ public class URL implements Comparable { return builder.host; } + /** + * Get the decoded host name. + * @return the decoded host name + */ public String getDecodedHost() { return decode(builder.host); } @@ -372,13 +400,16 @@ public class URL implements Comparable { } /** - * Get the encoded path ('/path/to/my/file.html') of the {@code URL} if it exists. - * @return the encoded path + * Get the path ('/path/to/my/file.html') of the {@code URL} if it exists. + * @return the path */ public String getPath() { return path; } + /** + * Get the percent-decoded path of the {@code URL} if it exists. + */ public String getDecodedPath() { return decode(path); } @@ -458,18 +489,16 @@ public class URL implements Comparable { private String toInternalForm(boolean withFragment) { StringBuilder sb = new StringBuilder(); if (!isNullOrEmpty(builder.scheme)) { - sb.append(builder.scheme).append(':'); + sb.append(builder.scheme).append(COLON_CHAR); } if (isOpaque()) { sb.append(builder.schemeSpecificPart); } else { appendHostInfo(sb, false, true); appendPath(sb, false); - if (!isNullOrEmpty(query)) { - sb.append(QUESTION_CHAR).append(query); - } - if (!isNullOrEmpty(fragment) && withFragment) { - sb.append(NUMBER_SIGN_CHAR).append(fragment); + appendQuery(sb, false, true); + if (withFragment) { + appendFragment(sb, false, true); } } return sb.toString(); @@ -478,7 +507,7 @@ public class URL implements Comparable { private String writeExternalForm() { StringBuilder sb = new StringBuilder(); if (!isNullOrEmpty(builder.scheme)) { - sb.append(builder.scheme).append(':'); + sb.append(builder.scheme).append(COLON_CHAR); } if (isOpaque()) { sb.append(builder.schemeSpecificPart); @@ -521,17 +550,16 @@ public class URL implements Comparable { if (s != null && !s.equals(builder.hostAddress)) { sb.append(s); } else if (builder.hostAddress != null) { - sb.append("[").append(builder.hostAddress).append("]"); + sb.append(LEFT_BRACKET_CHAR).append(builder.hostAddress).append(RIGHT_BRACKET_CHAR); } break; case IPV4: - case IPV46: sb.append(builder.host); break; default: if (encoded) { try { - String encodedHostName = PercentEncoders.getRegNameEncoder(charset).encode(builder.host); + String encodedHostName = regNameEncoder.encode(builder.host); validateHostnameCharacters(encodedHostName); sb.append(encodedHostName); } catch (CharacterCodingException e) { @@ -545,7 +573,7 @@ public class URL implements Comparable { } else { if (encoded) { try { - String encodedHostName = PercentEncoders.getRegNameEncoder(charset).encode(builder.host); + String encodedHostName = regNameEncoder.encode(builder.host); validateHostnameCharacters(encodedHostName); sb.append(encodedHostName); } catch (CharacterCodingException e) { @@ -556,7 +584,7 @@ public class URL implements Comparable { } } if (scheme != null && builder.port != null && builder.port != scheme.getDefaultPort()) { - sb.append(':'); + sb.append(COLON_CHAR); if (builder.port != -1) { sb.append(builder.port); } @@ -587,8 +615,6 @@ public class URL implements Comparable { } private void appendPath(StringBuilder sb, boolean encoded) { - PercentEncoder pathEncoder = PercentEncoders.getPathEncoder(charset); - PercentEncoder matrixEncoder = PercentEncoders.getMatrixEncoder(charset); Iterator it = builder.pathSegments.iterator(); while (it.hasNext()) { PathSegment pathSegment = it.next(); @@ -623,7 +649,6 @@ public class URL implements Comparable { sb.append(QUESTION_CHAR); } Iterator> it = builder.queryParams.iterator(); - PercentEncoder queryParamEncoder = PercentEncoders.getQueryParamEncoder(charset); while (it.hasNext()) { QueryParameters.Pair queryParam = it.next(); try { @@ -645,7 +670,7 @@ public class URL implements Comparable { } if (encoded) { try { - sb.append(PercentEncoders.getQueryEncoder(charset).encode(builder.query)); + sb.append(queryEncoder.encode(builder.query)); } catch (CharacterCodingException e) { throw new IllegalArgumentException(e); } @@ -668,7 +693,7 @@ public class URL implements Comparable { } if (encoded) { try { - sb.append(PercentEncoders.getFragmentEncoder(charset).encode(builder.fragment)); + sb.append(fragmentEncoder.encode(builder.fragment)); } catch (CharacterCodingException e) { throw new IllegalArgumentException(e); } @@ -713,10 +738,13 @@ public class URL implements Comparable { } /** - * The URL Builder embedded class is for building an URL by fluent API methods. + * The URL builder class is required for building an URL. It uses fluent API methods + * and pre-processes paralameter accordingly. */ public static class Builder { + private PercentEncoder regNameEncoder; + private final PercentDecoder percentDecoder; private final QueryParameters queryParams; @@ -746,20 +774,26 @@ public class URL implements Comparable { private boolean fatalResolveErrorsEnabled; private Builder() { + charset(StandardCharsets.UTF_8); this.percentDecoder = new PercentDecoder(); this.queryParams = new QueryParameters(); this.pathSegments = new ArrayList<>(); - this.charset = StandardCharsets.UTF_8; } + /** + * Set the character set of the URL. Default is UTF-8. + * @param charset the chaarcter set + * @return this builder + */ public Builder charset(Charset charset) { this.charset = charset; + this.regNameEncoder = PercentEncoders.getRegNameEncoder(charset); return this; } public Builder scheme(String scheme) { if (!isNullOrEmpty(scheme)) { - validateSchemeCharacters(scheme.toLowerCase()); + validateSchemeCharacters(scheme.toLowerCase(Locale.ROOT)); this.scheme = scheme; } return this; @@ -777,8 +811,8 @@ public class URL implements Comparable { public Builder userInfo(String user, String pass) { try { - this.userInfo = PercentEncoders.getRegNameEncoder(charset).encode(user) + ':' + - PercentEncoders.getRegNameEncoder(charset).encode(pass); + // allow colons in usernames and passwords by percent-encoding them here + this.userInfo = regNameEncoder.encode(user) + COLON_CHAR + regNameEncoder.encode(pass); } catch (MalformedInputException | UnmappableCharacterException e) { throw new IllegalArgumentException(e); } @@ -827,8 +861,8 @@ public class URL implements Comparable { } logger.log(Level.WARNING, e.getMessage(), e); if (e.getMessage() != null && !e.getMessage().endsWith("invalid IPv6 address") && - hostname.charAt(0) != '[' && - hostname.charAt(hostname.length() - 1) != ']') { + hostname.charAt(0) != LEFT_BRACKET_CHAR && + hostname.charAt(hostname.length() - 1) != RIGHT_BRACKET_CHAR) { try { String idna = IDN.toASCII(percentDecoder.decode(hostname)); host(idna, ProtocolVersion.NONE); @@ -969,7 +1003,7 @@ public class URL implements Comparable { } /** - * + * A URL parser class. */ public static class Parser { @@ -987,13 +1021,13 @@ public class URL implements Comparable { public URL parse(String input, boolean resolve) throws URLSyntaxException, MalformedInputException, UnmappableCharacterException { if (isNullOrEmpty(input)) { - return INVALID; + return NULL_URL; } if (input.indexOf('\n') >= 0) { - return INVALID; + return NULL_URL; } if (input.indexOf('\t') >= 0) { - return INVALID; + return NULL_URL; } String remaining = parseScheme(builder, input); if (remaining != null) { @@ -1011,7 +1045,7 @@ public class URL implements Comparable { String host = (pos >= 0 ? remaining.substring(0, pos) : remaining); parseHostAndPort(builder, parseUserInfo(builder, host), resolve); if (builder.host == null) { - return INVALID; + return NULL_URL; } remaining = pos >= 0 ? remaining.substring(pos) : EMPTY; } @@ -1053,14 +1087,14 @@ public class URL implements Comparable { private void parseHostAndPort(Builder builder, String rawHost, boolean resolve) throws URLSyntaxException { String host = rawHost; - if (host.indexOf('[') == 0) { - int i = host.lastIndexOf(']'); + if (host.indexOf(LEFT_BRACKET_CHAR) == 0) { + int i = host.lastIndexOf(RIGHT_BRACKET_CHAR); if (i >= 0) { builder.port(parsePort(host.substring(i + 1))); host = host.substring(1, i); } } else { - int i = host.indexOf(':'); + int i = host.indexOf(COLON_CHAR); if (i >= 0) { builder.port(parsePort(host.substring(i))); host = host.substring(0, i); @@ -1077,7 +1111,7 @@ public class URL implements Comparable { if (portStr == null || portStr.isEmpty()) { return null; } - int i = portStr.indexOf(":"); + int i = portStr.indexOf(COLON_CHAR); if (i >= 0) { portStr = portStr.substring(i + 1); if (portStr.isEmpty()) { @@ -1179,8 +1213,7 @@ public class URL implements Comparable { } /** - * The URL resolver class is an embedded class for resolving a relative URL specification to - * a base URL. + * The URL resolver class is a class for resolving a relative URL specification to a base URL. */ public static class Resolver { @@ -1204,7 +1237,7 @@ public class URL implements Comparable { public URL resolve(URL relative) throws URLSyntaxException { - if (relative == null || relative == INVALID) { + if (relative == null || relative.equals(NULL_URL)) { throw new URLSyntaxException("relative URL is invalid"); } if (!base.isAbsolute()) { diff --git a/net-url/src/main/java/org/xbib/net/matcher/CharMatcher.java b/net-url/src/main/java/org/xbib/net/matcher/CharMatcher.java index 8a6f7ee..7e48e7f 100644 --- a/net-url/src/main/java/org/xbib/net/matcher/CharMatcher.java +++ b/net-url/src/main/java/org/xbib/net/matcher/CharMatcher.java @@ -4,7 +4,8 @@ import java.util.Arrays; import java.util.BitSet; /** - * + * The character matcher class is a fast table-based matcher class, able to match whitespace, control, + * literals, persent or hexdigit character groups. */ public abstract class CharMatcher { diff --git a/net-url/src/main/java/org/xbib/net/scheme/HttpScheme.java b/net-url/src/main/java/org/xbib/net/scheme/HttpScheme.java index 42320a8..4fe4fc6 100644 --- a/net-url/src/main/java/org/xbib/net/scheme/HttpScheme.java +++ b/net-url/src/main/java/org/xbib/net/scheme/HttpScheme.java @@ -3,6 +3,8 @@ package org.xbib.net.scheme; import org.xbib.net.URL; import org.xbib.net.path.PathNormalizer; +import java.util.Locale; + /** * HTTP scheme. */ @@ -20,7 +22,7 @@ class HttpScheme extends AbstractScheme { public URL normalize(URL url) { String host = url.getHost(); if (host != null) { - host = host.toLowerCase(); + host = host.toLowerCase(Locale.ROOT); } return URL.builder() .scheme(url.getScheme()) diff --git a/net-url/src/main/java/org/xbib/net/scheme/SchemeRegistry.java b/net-url/src/main/java/org/xbib/net/scheme/SchemeRegistry.java index 903e395..d536745 100644 --- a/net-url/src/main/java/org/xbib/net/scheme/SchemeRegistry.java +++ b/net-url/src/main/java/org/xbib/net/scheme/SchemeRegistry.java @@ -1,6 +1,7 @@ package org.xbib.net.scheme; import java.util.HashMap; +import java.util.Locale; import java.util.Map; import java.util.ServiceLoader; @@ -62,7 +63,7 @@ public final class SchemeRegistry { return false; } if (!schemes.containsKey(name)) { - schemes.put(name.toLowerCase(), scheme); + schemes.put(name.toLowerCase(Locale.ROOT), scheme); return true; } else { return false; @@ -73,7 +74,7 @@ public final class SchemeRegistry { if (scheme == null) { return null; } - Scheme s = schemes.get(scheme.toLowerCase()); + Scheme s = schemes.get(scheme.toLowerCase(Locale.ROOT)); return s != null ? s : new DefaultScheme(scheme); } } diff --git a/net-url/src/test/java/org/xbib/net/IRITest.java b/net-url/src/test/java/org/xbib/net/IRITest.java index 14554ee..3bf4546 100644 --- a/net-url/src/test/java/org/xbib/net/IRITest.java +++ b/net-url/src/test/java/org/xbib/net/IRITest.java @@ -22,14 +22,14 @@ public class IRITest { @Test public void testIpv6() { URL iri = URL.from("http://[2001:0db8:85a3:08d3:1319:8a2e:0370:7344]"); - assertTrue(iri.getProtocolVersion().equals(ProtocolVersion.IPV6)); + assertEquals(iri.getProtocolVersion(), ProtocolVersion.IPV6); assertEquals("http://[2001:db8:85a3:8d3:1319:8a2e:370:7344]", iri.toString()); } @Test public void testIpv6Invalid() { URL iri = URL.from("http://[2001:0db8:85a3:08d3:1319:8a2e:0370:734o]"); - assertEquals(URL.getInvalid(), iri); + assertEquals(URL.nullUrl(), iri); } @Test diff --git a/net-url/src/test/java/org/xbib/net/URLParserTest.java b/net-url/src/test/java/org/xbib/net/URLParserTest.java index 6522fc2..ebfd6c4 100644 --- a/net-url/src/test/java/org/xbib/net/URLParserTest.java +++ b/net-url/src/test/java/org/xbib/net/URLParserTest.java @@ -14,22 +14,22 @@ public class URLParserTest { @Test public void testNull() { - assertEquals(URL.getInvalid(), URL.from(null)); + assertEquals(URL.nullUrl(), URL.from(null)); } @Test public void testEmpty() { - assertEquals(URL.getInvalid(), URL.from("")); + assertEquals(URL.nullUrl(), URL.from("")); } @Test public void testNewline() { - assertEquals(URL.getInvalid(), URL.from("\n")); + assertEquals(URL.nullUrl(), URL.from("\n")); } @Test(expected = IllegalArgumentException.class) - public void testInvalidScheme() throws Exception { - URL url = URL.from("/:23"); + public void testInvalidScheme() { + URL.from("/:23"); } @Test @@ -62,7 +62,7 @@ public class URLParserTest { } @Test - public void testGopher() throws Exception { + public void testGopher() { URL url = URL.from("gopher:/example.com/"); assertEquals("gopher:/example.com/", url.toExternalForm()); } @@ -76,7 +76,7 @@ public class URLParserTest { } @Test - public void testSlashAfterScheme() throws Exception { + public void testSlashAfterScheme() { URL url = URL.from("http:/example.com/"); assertEquals("http:/example.com/", url.toExternalForm()); } @@ -96,7 +96,7 @@ public class URLParserTest { } @Test - public void testNetworkLocation() throws Exception { + public void testNetworkLocation() { URL url = URL.from("//foo.bar"); assertEquals("//foo.bar", url.toExternalForm()); assertEquals("//foo.bar", url.toString()); @@ -131,7 +131,7 @@ public class URLParserTest { } @Test - public void testBackslash() throws Exception { + public void testBackslash() { URL url = URL.from("http://foo.com/\\@"); assertEquals("http://foo.com/@", url.toExternalForm()); } @@ -153,7 +153,6 @@ public class URLParserTest { @Test public void testReservedChar() throws Exception { URL url = URL.from("http://www.google.com/ig/calculator?q=1USD=?EUR"); - //assertEquals("http://www.google.com/ig/calculator?q=1USD=?EUR", url.toString()); if ("false".equals(System.getProperty("java.net.preferIPv6Addresses"))) { assertEquals("http://www.google.com/ig/calculator?q=1USD%3D?EUR", url.toString()); } @@ -163,7 +162,7 @@ public class URLParserTest { @Test public void testPlus() throws Exception { URL url = URL.from("http://foobar:8080/test/print?value=%EA%B0%80+%EB%82%98"); - assertEquals("http://foobar:8080/test/print?value=%EA%B0%80%2B%EB%82%98", url.toString()); + assertEquals("http://foobar:8080/test/print?value=%EA%B0%80%2B%EB%82%98", url.toExternalForm()); assertRoundTrip(url.toExternalForm()); }