better exception method signatures, avoid null URLs as return value, remove internal package with single class
This commit is contained in:
parent
286ab07793
commit
93e201db8b
13 changed files with 149 additions and 231 deletions
|
@ -1,6 +1,6 @@
|
||||||
group = org.xbib
|
group = org.xbib
|
||||||
name = net
|
name = net
|
||||||
version = 1.0.1
|
version = 1.0.2
|
||||||
|
|
||||||
junit.version = 4.12
|
junit.version = 4.12
|
||||||
asciidoclet.version = 1.5.4
|
asciidoclet.version = 1.5.4
|
||||||
|
|
|
@ -1,118 +0,0 @@
|
||||||
package org.xbib.net;
|
|
||||||
|
|
||||||
import java.util.Enumeration;
|
|
||||||
import java.util.HashSet;
|
|
||||||
import java.util.Iterator;
|
|
||||||
import java.util.Locale;
|
|
||||||
import java.util.MissingResourceException;
|
|
||||||
import java.util.ResourceBundle;
|
|
||||||
import java.util.Set;
|
|
||||||
import java.util.SortedMap;
|
|
||||||
import java.util.TreeMap;
|
|
||||||
import java.util.logging.Level;
|
|
||||||
import java.util.logging.Logger;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Contains a simple context for namespaces.
|
|
||||||
*/
|
|
||||||
public class SimpleNamespaceContext {
|
|
||||||
|
|
||||||
private static final Logger logger = Logger.getLogger(SimpleNamespaceContext.class.getName());
|
|
||||||
|
|
||||||
private static final String DEFAULT_RESOURCE =
|
|
||||||
SimpleNamespaceContext.class.getPackage().getName().replace('.', '/') + '/' + "namespace";
|
|
||||||
|
|
||||||
private static final SimpleNamespaceContext DEFAULT_CONTEXT = newDefaultInstance();
|
|
||||||
|
|
||||||
// sort namespace by length in descending order, useful for compacting prefix
|
|
||||||
protected final SortedMap<String, String> namespaces = new TreeMap<>();
|
|
||||||
|
|
||||||
private final SortedMap<String, Set<String>> prefixes = new TreeMap<>();
|
|
||||||
|
|
||||||
protected SimpleNamespaceContext() {
|
|
||||||
}
|
|
||||||
|
|
||||||
protected SimpleNamespaceContext(ResourceBundle bundle) {
|
|
||||||
Enumeration<String> en = bundle.getKeys();
|
|
||||||
while (en.hasMoreElements()) {
|
|
||||||
String prefix = en.nextElement();
|
|
||||||
String namespace = bundle.getString(prefix);
|
|
||||||
addNamespace(prefix, namespace);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public static SimpleNamespaceContext getInstance() {
|
|
||||||
return DEFAULT_CONTEXT;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Empty namespace context.
|
|
||||||
*
|
|
||||||
* @return an XML namespace context
|
|
||||||
*/
|
|
||||||
public static SimpleNamespaceContext newInstance() {
|
|
||||||
return new SimpleNamespaceContext();
|
|
||||||
}
|
|
||||||
|
|
||||||
public static SimpleNamespaceContext newDefaultInstance() {
|
|
||||||
return newInstance(DEFAULT_RESOURCE);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Use thread context class laoder to instantiate a namespace context.
|
|
||||||
* @param bundleName the resource bundle name
|
|
||||||
* @return XML namespace context
|
|
||||||
*/
|
|
||||||
public static SimpleNamespaceContext newInstance(String bundleName) {
|
|
||||||
return newInstance(bundleName, Locale.getDefault(), Thread.currentThread().getContextClassLoader());
|
|
||||||
}
|
|
||||||
|
|
||||||
public static SimpleNamespaceContext newInstance(String bundleName, Locale locale, ClassLoader classLoader) {
|
|
||||||
try {
|
|
||||||
return new SimpleNamespaceContext(ResourceBundle.getBundle(bundleName, locale, classLoader));
|
|
||||||
} catch (MissingResourceException e) {
|
|
||||||
logger.log(Level.WARNING, e.getMessage(), e);
|
|
||||||
return new SimpleNamespaceContext();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void addNamespace(String prefix, String namespace) {
|
|
||||||
namespaces.put(prefix, namespace);
|
|
||||||
if (prefixes.containsKey(namespace)) {
|
|
||||||
prefixes.get(namespace).add(prefix);
|
|
||||||
} else {
|
|
||||||
Set<String> set = new HashSet<>();
|
|
||||||
set.add(prefix);
|
|
||||||
prefixes.put(namespace, set);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public SortedMap<String, String> getNamespaces() {
|
|
||||||
return namespaces;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getNamespaceURI(String prefix) {
|
|
||||||
if (prefix == null) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
return namespaces.getOrDefault(prefix, null);
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getPrefix(String namespaceURI) {
|
|
||||||
Iterator<String> it = getPrefixes(namespaceURI);
|
|
||||||
return it != null && it.hasNext() ? it.next() : null;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Iterator<String> getPrefixes(String namespace) {
|
|
||||||
if (namespace == null) {
|
|
||||||
throw new IllegalArgumentException("namespace URI cannot be null");
|
|
||||||
}
|
|
||||||
return prefixes.containsKey(namespace) ?
|
|
||||||
prefixes.get(namespace).iterator() : null;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String toString() {
|
|
||||||
return namespaces.toString();
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -22,9 +22,21 @@ import java.util.logging.Level;
|
||||||
import java.util.logging.Logger;
|
import java.util.logging.Logger;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* {@link URL} is a Java implementation of the Uniform Resource Identifier ({@code RFC 3986})
|
*
|
||||||
|
* A Uniform Resource Locator (URL) is a compact representation of the
|
||||||
|
* location and access method for a resource available via the Internet.
|
||||||
|
*
|
||||||
|
* Historically, there are many different forms of internet resource representations, for example,
|
||||||
|
* the URL (RFC 1738 as of 1994), the URI (RFC 2396 as of 1998), and IRI (RFC 3987 as of 2005),
|
||||||
|
* and most of them have updated specifications.
|
||||||
|
*
|
||||||
|
* {@link URL} is a Java implementation that serves as a universal point of handling all
|
||||||
|
* different forms. It follows the syntax of the Uniform Resource Identifier ({@code RFC 3986})
|
||||||
* in accordance with the link:https://url.spec.whatwg.org/[{@code WHATWG} URL standard].
|
* in accordance with the link:https://url.spec.whatwg.org/[{@code WHATWG} URL standard].
|
||||||
*
|
*
|
||||||
|
* The reason for the name {@code URL} is merely because of the popularity of the name, which
|
||||||
|
* overweighs the URI or IRI popularity.
|
||||||
|
*
|
||||||
* [source,java]
|
* [source,java]
|
||||||
* --
|
* --
|
||||||
* URL url = URL.http().resolveFromHost("google.com").build();
|
* URL url = URL.http().resolveFromHost("google.com").build();
|
||||||
|
@ -87,6 +99,9 @@ public class URL implements Serializable {
|
||||||
this.fragment = encodeFragment();
|
this.fragment = encodeFragment();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A special, scheme-less URL denoting the fact that this URL should be considered as invalid.
|
||||||
|
*/
|
||||||
public static final URL INVALID = URL.builder().build();
|
public static final URL INVALID = URL.builder().build();
|
||||||
|
|
||||||
public static Builder file() {
|
public static Builder file() {
|
||||||
|
@ -224,7 +239,7 @@ public class URL implements Serializable {
|
||||||
public static URL from(String input) {
|
public static URL from(String input) {
|
||||||
try {
|
try {
|
||||||
return parser().parse(input, true);
|
return parser().parse(input, true);
|
||||||
} catch (CharacterCodingException e) {
|
} catch (URLSyntaxException e) {
|
||||||
throw new IllegalArgumentException(e);
|
throw new IllegalArgumentException(e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -232,13 +247,25 @@ public class URL implements Serializable {
|
||||||
public static URL create(String input) {
|
public static URL create(String input) {
|
||||||
try {
|
try {
|
||||||
return parser().parse(input, false);
|
return parser().parse(input, false);
|
||||||
} catch (CharacterCodingException e) {
|
} catch (URLSyntaxException e) {
|
||||||
throw new IllegalArgumentException(e);
|
throw new IllegalArgumentException(e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public URL resolve(String spec) {
|
public URL resolve(String spec) {
|
||||||
return new Resolver(this).resolve(spec);
|
try {
|
||||||
|
return new Resolver(this).resolve(spec);
|
||||||
|
} catch (URLSyntaxException e) {
|
||||||
|
throw new IllegalArgumentException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public URL resolve(URL spec) {
|
||||||
|
try {
|
||||||
|
return new Resolver(this).resolve(spec);
|
||||||
|
} catch (URLSyntaxException e) {
|
||||||
|
throw new IllegalArgumentException(e);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public String decode(String input) {
|
public String decode(String input) {
|
||||||
|
@ -627,7 +654,7 @@ public class URL implements Serializable {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
* The URL Builder embedded class is for building an URL by fluent API methods.
|
||||||
*/
|
*/
|
||||||
public static class Builder {
|
public static class Builder {
|
||||||
|
|
||||||
|
@ -706,7 +733,7 @@ public class URL implements Serializable {
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Builder resolveFromHost(String hostname) throws CharacterCodingException {
|
public Builder resolveFromHost(String hostname) {
|
||||||
if (hostname == null) {
|
if (hostname == null) {
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
@ -729,8 +756,12 @@ public class URL implements Serializable {
|
||||||
if (e.getMessage() != null && !e.getMessage().endsWith("invalid IPv6 address") &&
|
if (e.getMessage() != null && !e.getMessage().endsWith("invalid IPv6 address") &&
|
||||||
hostname.charAt(0) != '[' &&
|
hostname.charAt(0) != '[' &&
|
||||||
hostname.charAt(hostname.length() - 1) != ']') {
|
hostname.charAt(hostname.length() - 1) != ']') {
|
||||||
String idna = IDN.toASCII(percentDecoder.decode(hostname));
|
try {
|
||||||
host(idna, ProtocolVersion.NONE);
|
String idna = IDN.toASCII(percentDecoder.decode(hostname));
|
||||||
|
host(idna, ProtocolVersion.NONE);
|
||||||
|
} catch (CharacterCodingException e2) {
|
||||||
|
throw new IllegalArgumentException(e2);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return this;
|
return this;
|
||||||
|
@ -863,27 +894,26 @@ public class URL implements Serializable {
|
||||||
*/
|
*/
|
||||||
public static class Parser {
|
public static class Parser {
|
||||||
|
|
||||||
private final PercentDecoder percentDecoder;
|
private final Builder builder;
|
||||||
|
|
||||||
private Parser() {
|
private Parser() {
|
||||||
percentDecoder = new PercentDecoder();
|
builder = new Builder();
|
||||||
}
|
}
|
||||||
|
|
||||||
public URL parse(String input) throws CharacterCodingException {
|
public URL parse(String input) throws URLSyntaxException {
|
||||||
return parse(input, true);
|
return parse(input, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
public URL parse(String input, boolean resolve) throws CharacterCodingException {
|
public URL parse(String input, boolean resolve) throws URLSyntaxException {
|
||||||
if (isNullOrEmpty(input)) {
|
if (isNullOrEmpty(input)) {
|
||||||
return null;
|
return INVALID;
|
||||||
}
|
}
|
||||||
if (input.indexOf('\n') >= 0) {
|
if (input.indexOf('\n') >= 0) {
|
||||||
return null;
|
return INVALID;
|
||||||
}
|
}
|
||||||
if (input.indexOf('\t') >= 0) {
|
if (input.indexOf('\t') >= 0) {
|
||||||
return null;
|
return INVALID;
|
||||||
}
|
}
|
||||||
Builder builder = new Builder();
|
|
||||||
String remaining = parseScheme(builder, input);
|
String remaining = parseScheme(builder, input);
|
||||||
if (remaining != null) {
|
if (remaining != null) {
|
||||||
remaining = remaining.replace('\\', SEPARATOR_CHAR);
|
remaining = remaining.replace('\\', SEPARATOR_CHAR);
|
||||||
|
@ -906,7 +936,11 @@ public class URL implements Serializable {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (!isNullOrEmpty(remaining)) {
|
if (!isNullOrEmpty(remaining)) {
|
||||||
parsePathWithQueryAndFragment(builder, remaining);
|
try {
|
||||||
|
parsePathWithQueryAndFragment(builder, remaining);
|
||||||
|
} catch (CharacterCodingException e) {
|
||||||
|
throw new URLSyntaxException(e);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return builder.build();
|
return builder.build();
|
||||||
|
@ -933,7 +967,7 @@ public class URL implements Serializable {
|
||||||
return remaining;
|
return remaining;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void parseHostAndPort(Builder builder, String host, boolean resolve) throws CharacterCodingException {
|
private void parseHostAndPort(Builder builder, String host, boolean resolve) throws URLSyntaxException {
|
||||||
if (host.indexOf('[') == 0) {
|
if (host.indexOf('[') == 0) {
|
||||||
int i = host.lastIndexOf(']');
|
int i = host.lastIndexOf(']');
|
||||||
if (i >= 0) {
|
if (i >= 0) {
|
||||||
|
@ -954,7 +988,7 @@ public class URL implements Serializable {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private Integer parsePort(String portStr) {
|
private Integer parsePort(String portStr) throws URLSyntaxException {
|
||||||
if (portStr == null || portStr.isEmpty()) {
|
if (portStr == null || portStr.isEmpty()) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
@ -970,20 +1004,21 @@ public class URL implements Serializable {
|
||||||
if (port > 0 && port < 65536) {
|
if (port > 0 && port < 65536) {
|
||||||
return port;
|
return port;
|
||||||
} else {
|
} else {
|
||||||
throw new IllegalArgumentException("invalid port");
|
throw new URLSyntaxException("invalid port");
|
||||||
}
|
}
|
||||||
} catch (NumberFormatException e) {
|
} catch (NumberFormatException e) {
|
||||||
throw new IllegalArgumentException("no numeric port: " + portStr);
|
throw new URLSyntaxException("no numeric port: " + portStr);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void parsePathWithQueryAndFragment(Builder builder, String input) throws CharacterCodingException {
|
private void parsePathWithQueryAndFragment(Builder builder, String input)
|
||||||
|
throws MalformedInputException, UnmappableCharacterException {
|
||||||
if (input == null) {
|
if (input == null) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
int i = input.lastIndexOf(NUMBER_SIGN_CHAR);
|
int i = input.lastIndexOf(NUMBER_SIGN_CHAR);
|
||||||
if (i >= 0) {
|
if (i >= 0) {
|
||||||
builder.fragment(percentDecoder.decode(input.substring(i + 1)));
|
builder.fragment(builder.percentDecoder.decode(input.substring(i + 1)));
|
||||||
input = input.substring(0, i);
|
input = input.substring(0, i);
|
||||||
}
|
}
|
||||||
i = input.indexOf(QUESTION_CHAR);
|
i = input.indexOf(QUESTION_CHAR);
|
||||||
|
@ -1006,7 +1041,8 @@ public class URL implements Serializable {
|
||||||
Pair<String, String> pathWithMatrixElem = indexOf(SEMICOLON_CHAR, t);
|
Pair<String, String> pathWithMatrixElem = indexOf(SEMICOLON_CHAR, t);
|
||||||
String matrixElem = pathWithMatrixElem.getFirst();
|
String matrixElem = pathWithMatrixElem.getFirst();
|
||||||
Pair<String, String> p = indexOf(EQUAL_CHAR, matrixElem);
|
Pair<String, String> p = indexOf(EQUAL_CHAR, matrixElem);
|
||||||
builder.matrixParam(percentDecoder.decode(p.getFirst()), percentDecoder.decode(p.getSecond()));
|
builder.matrixParam(builder.percentDecoder.decode(p.getFirst()),
|
||||||
|
builder.percentDecoder.decode(p.getSecond()));
|
||||||
t = pathWithMatrixElem.getSecond();
|
t = pathWithMatrixElem.getSecond();
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
@ -1016,10 +1052,11 @@ public class URL implements Serializable {
|
||||||
Pair<String, String> pathWithMatrixElem = indexOf(SEMICOLON_CHAR, t);
|
Pair<String, String> pathWithMatrixElem = indexOf(SEMICOLON_CHAR, t);
|
||||||
String segment = pathWithMatrixElem.getFirst();
|
String segment = pathWithMatrixElem.getFirst();
|
||||||
if (i == 0) {
|
if (i == 0) {
|
||||||
builder.pathSegment(percentDecoder.decode(segment));
|
builder.pathSegment(builder.percentDecoder.decode(segment));
|
||||||
} else {
|
} else {
|
||||||
Pair<String, String> p = indexOf(EQUAL_CHAR, segment);
|
Pair<String, String> p = indexOf(EQUAL_CHAR, segment);
|
||||||
builder.matrixParam(percentDecoder.decode(p.getFirst()), percentDecoder.decode(p.getSecond()));
|
builder.matrixParam(builder.percentDecoder.decode(p.getFirst()),
|
||||||
|
builder.percentDecoder.decode(p.getSecond()));
|
||||||
}
|
}
|
||||||
t = pathWithMatrixElem.getSecond();
|
t = pathWithMatrixElem.getSecond();
|
||||||
i++;
|
i++;
|
||||||
|
@ -1033,7 +1070,8 @@ public class URL implements Serializable {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void parseQuery(Builder builder, String query) throws CharacterCodingException {
|
private void parseQuery(Builder builder, String query)
|
||||||
|
throws MalformedInputException, UnmappableCharacterException {
|
||||||
if (query == null) {
|
if (query == null) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -1042,18 +1080,19 @@ public class URL implements Serializable {
|
||||||
Pair<String, String> p = indexOf(AMPERSAND_CHAR, s);
|
Pair<String, String> p = indexOf(AMPERSAND_CHAR, s);
|
||||||
Pair<String, String> param = indexOf(EQUAL_CHAR, p.getFirst());
|
Pair<String, String> param = indexOf(EQUAL_CHAR, p.getFirst());
|
||||||
if (!isNullOrEmpty(param.getFirst())) {
|
if (!isNullOrEmpty(param.getFirst())) {
|
||||||
builder.queryParam(percentDecoder.decode(param.getFirst()), percentDecoder.decode(param.getSecond()));
|
builder.queryParam(builder.percentDecoder.decode(param.getFirst()),
|
||||||
|
builder.percentDecoder.decode(param.getSecond()));
|
||||||
}
|
}
|
||||||
s = p.getSecond();
|
s = p.getSecond();
|
||||||
}
|
}
|
||||||
if (builder.queryParams.isEmpty()) {
|
if (builder.queryParams.isEmpty()) {
|
||||||
builder.query(percentDecoder.decode(query));
|
builder.query(builder.percentDecoder.decode(query));
|
||||||
} else {
|
} else {
|
||||||
builder.query(query);
|
builder.query(query);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Pair<String, String> indexOf(char ch, String input) {
|
private Pair<String, String> indexOf(char ch, String input) {
|
||||||
int i = input.indexOf(ch);
|
int i = input.indexOf(ch);
|
||||||
String k = i >= 0 ? input.substring(0, i) : input;
|
String k = i >= 0 ? input.substring(0, i) : input;
|
||||||
String v = i >= 0 ? input.substring(i + 1) : null;
|
String v = i >= 0 ? input.substring(i + 1) : null;
|
||||||
|
@ -1062,7 +1101,8 @@ public class URL implements Serializable {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
* The URL resolver class is an embedded class for resolving a relative URL specification to
|
||||||
|
* a base URL.
|
||||||
*/
|
*/
|
||||||
public static class Resolver {
|
public static class Resolver {
|
||||||
|
|
||||||
|
@ -1072,27 +1112,23 @@ public class URL implements Serializable {
|
||||||
this.base = base;
|
this.base = base;
|
||||||
}
|
}
|
||||||
|
|
||||||
public URL resolve(String relative) {
|
public URL resolve(String relative) throws URLSyntaxException {
|
||||||
if (relative == null) {
|
if (relative == null) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
if (relative.isEmpty()) {
|
if (relative.isEmpty()) {
|
||||||
return base;
|
return base;
|
||||||
}
|
}
|
||||||
try {
|
URL url = parser().parse(relative);
|
||||||
URL url = parser().parse(relative);
|
return resolve(url);
|
||||||
return url != null ? resolve(url) : null;
|
|
||||||
} catch (CharacterCodingException e) {
|
|
||||||
throw new IllegalArgumentException(e);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public URL resolve(URL relative) throws CharacterCodingException {
|
public URL resolve(URL relative) throws URLSyntaxException {
|
||||||
if (relative == null) {
|
if (relative == null || relative == INVALID) {
|
||||||
return null;
|
throw new URLSyntaxException("relative URL is invalid");
|
||||||
}
|
}
|
||||||
if (!base.isAbsolute()) {
|
if (!base.isAbsolute()) {
|
||||||
throw new IllegalArgumentException("base is not absolute");
|
throw new URLSyntaxException("base URL is not absolute");
|
||||||
}
|
}
|
||||||
Builder builder = new Builder();
|
Builder builder = new Builder();
|
||||||
if (relative.isOpaque()) {
|
if (relative.isOpaque()) {
|
||||||
|
|
|
@ -3,15 +3,15 @@ package org.xbib.net;
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
public class IRISyntaxException extends RuntimeException {
|
public class URLSyntaxException extends Exception {
|
||||||
|
|
||||||
private static final long serialVersionUID = 1813084470937980392L;
|
private static final long serialVersionUID = 1813084470937980392L;
|
||||||
|
|
||||||
IRISyntaxException(String message) {
|
URLSyntaxException(String message) {
|
||||||
super(message);
|
super(message);
|
||||||
}
|
}
|
||||||
|
|
||||||
IRISyntaxException(Throwable cause) {
|
URLSyntaxException(Throwable cause) {
|
||||||
super(cause);
|
super(cause);
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,26 +0,0 @@
|
||||||
package org.xbib.net.internal;
|
|
||||||
|
|
||||||
import java.util.LinkedHashMap;
|
|
||||||
import java.util.Map;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A simple LRU cache, based on a {@link LinkedHashMap}.
|
|
||||||
*
|
|
||||||
* @param <K> the key type parameter
|
|
||||||
* @param <V> the vale type parameter
|
|
||||||
*/
|
|
||||||
public class LRUCache<K, V> extends LinkedHashMap<K, V> {
|
|
||||||
|
|
||||||
private static final long serialVersionUID = -2795566703268944901L;
|
|
||||||
|
|
||||||
private final int cacheSize;
|
|
||||||
|
|
||||||
public LRUCache(int cacheSize) {
|
|
||||||
super(16, 0.75f, true);
|
|
||||||
this.cacheSize = cacheSize;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected boolean removeEldestEntry(Map.Entry<K, V> eldest) {
|
|
||||||
return size() >= cacheSize;
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,4 +0,0 @@
|
||||||
/**
|
|
||||||
* Classes for internal use in the {@code org.xbib.net} package.
|
|
||||||
*/
|
|
||||||
package org.xbib.net.internal;
|
|
|
@ -1,11 +1,11 @@
|
||||||
package org.xbib.net.path;
|
package org.xbib.net.path;
|
||||||
|
|
||||||
import org.xbib.net.QueryParameters;
|
import org.xbib.net.QueryParameters;
|
||||||
import org.xbib.net.internal.LRUCache;
|
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.Comparator;
|
import java.util.Comparator;
|
||||||
|
import java.util.LinkedHashMap;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.StringTokenizer;
|
import java.util.StringTokenizer;
|
||||||
|
@ -16,9 +16,11 @@ public class PathMatcher {
|
||||||
|
|
||||||
private static final String DEFAULT_PATH_SEPARATOR = "/";
|
private static final String DEFAULT_PATH_SEPARATOR = "/";
|
||||||
|
|
||||||
private final Map<String, List<String>> tokenizedPatternCache = Collections.synchronizedMap(new LRUCache<>(1024));
|
private final Map<String, List<String>> tokenizedPatternCache =
|
||||||
|
Collections.synchronizedMap(new LRUCache<>(1024));
|
||||||
|
|
||||||
private final Map<String, PathStringMatcher> stringMatcherCache = Collections.synchronizedMap(new LRUCache<>(1024));
|
private final Map<String, PathStringMatcher> stringMatcherCache =
|
||||||
|
Collections.synchronizedMap(new LRUCache<>(1024));
|
||||||
|
|
||||||
private String pathSeparator;
|
private String pathSeparator;
|
||||||
|
|
||||||
|
@ -28,8 +30,6 @@ public class PathMatcher {
|
||||||
|
|
||||||
private boolean trimTokens = true;
|
private boolean trimTokens = true;
|
||||||
|
|
||||||
private volatile boolean cachePatterns = true;
|
|
||||||
|
|
||||||
public PathMatcher() {
|
public PathMatcher() {
|
||||||
this(DEFAULT_PATH_SEPARATOR);
|
this(DEFAULT_PATH_SEPARATOR);
|
||||||
}
|
}
|
||||||
|
@ -60,10 +60,6 @@ public class PathMatcher {
|
||||||
return queryParameters;
|
return queryParameters;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setCachePatterns(boolean cachePatterns) {
|
|
||||||
this.cachePatterns = cachePatterns;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Map<String, PathStringMatcher> stringMatcherCache() {
|
public Map<String, PathStringMatcher> stringMatcherCache() {
|
||||||
return stringMatcherCache;
|
return stringMatcherCache;
|
||||||
}
|
}
|
||||||
|
@ -274,9 +270,7 @@ public class PathMatcher {
|
||||||
}
|
}
|
||||||
|
|
||||||
private List<String> tokenizePattern(String pattern) {
|
private List<String> tokenizePattern(String pattern) {
|
||||||
return cachePatterns ?
|
return tokenizedPatternCache.computeIfAbsent(pattern, this::tokenizePath);
|
||||||
tokenizedPatternCache.computeIfAbsent(pattern, this::tokenizePath) :
|
|
||||||
tokenizePath(pattern);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private List<String> tokenizePath(String path) {
|
private List<String> tokenizePath(String path) {
|
||||||
|
@ -306,8 +300,28 @@ public class PathMatcher {
|
||||||
}
|
}
|
||||||
|
|
||||||
private PathStringMatcher getStringMatcher(String pattern) {
|
private PathStringMatcher getStringMatcher(String pattern) {
|
||||||
return cachePatterns ?
|
return stringMatcherCache.computeIfAbsent(pattern, p -> new PathStringMatcher(p, this.caseSensitive));
|
||||||
stringMatcherCache.computeIfAbsent(pattern, p -> new PathStringMatcher(p, this.caseSensitive)) :
|
}
|
||||||
new PathStringMatcher(pattern, this.caseSensitive);
|
|
||||||
|
/**
|
||||||
|
* A simple LRU cache, based on a {@link LinkedHashMap}.
|
||||||
|
*
|
||||||
|
* @param <K> the key type parameter
|
||||||
|
* @param <V> the vale type parameter
|
||||||
|
*/
|
||||||
|
private static class LRUCache<K, V> extends LinkedHashMap<K, V> {
|
||||||
|
|
||||||
|
private static final long serialVersionUID = -2795566703268944901L;
|
||||||
|
|
||||||
|
private final int cacheSize;
|
||||||
|
|
||||||
|
LRUCache(int cacheSize) {
|
||||||
|
super(16, 0.75f, true);
|
||||||
|
this.cacheSize = cacheSize;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected boolean removeEldestEntry(Map.Entry<K, V> eldest) {
|
||||||
|
return size() >= cacheSize;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,7 +3,6 @@ package org.xbib.net.scheme;
|
||||||
/**
|
/**
|
||||||
* The mailto scheme.
|
* The mailto scheme.
|
||||||
*
|
*
|
||||||
* @see <a href="https://tools.ietf.org/html/rfc2368">mailto RFC</a>
|
|
||||||
*/
|
*/
|
||||||
public class MailtoScheme extends AbstractScheme {
|
public class MailtoScheme extends AbstractScheme {
|
||||||
|
|
||||||
|
|
|
@ -261,6 +261,13 @@ public class URLBuilderTest {
|
||||||
assertEquals("http://foo.com?q%23%2B", s);
|
assertEquals("http://foo.com?q%23%2B", s);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testNewBuilder() {
|
||||||
|
URL.Builder builder = URL.from("http://google.com:8008/foobar").newBuilder();
|
||||||
|
builder.scheme("https");
|
||||||
|
assertEquals("https://google.com:8008/foobar", builder.build().toString());
|
||||||
|
}
|
||||||
|
|
||||||
private void assertUrl(String urlString, String expected) throws Exception {
|
private void assertUrl(String urlString, String expected) throws Exception {
|
||||||
assertEquals(expected, urlString);
|
assertEquals(expected, urlString);
|
||||||
assertEquals(expected, URL.from(urlString).toExternalForm());
|
assertEquals(expected, URL.from(urlString).toExternalForm());
|
||||||
|
|
|
@ -15,17 +15,17 @@ public class URLParserTest {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testNull() {
|
public void testNull() {
|
||||||
assertNull(URL.from(null));
|
assertEquals(URL.INVALID, URL.from(null));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testEmpty() {
|
public void testEmpty() {
|
||||||
assertNull(URL.from(""));
|
assertEquals(URL.INVALID, URL.from(""));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testNewline() {
|
public void testNewline() {
|
||||||
assertNull(URL.from("\n"));
|
assertEquals(URL.INVALID, URL.from("\n"));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test(expected = IllegalArgumentException.class)
|
@Test(expected = IllegalArgumentException.class)
|
||||||
|
|
|
@ -4,17 +4,34 @@ import org.junit.Test;
|
||||||
|
|
||||||
import static org.junit.Assert.assertEquals;
|
import static org.junit.Assert.assertEquals;
|
||||||
|
|
||||||
|
import java.net.URI;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*/
|
*/
|
||||||
public class URLResolverTest {
|
public class URLResolverTest {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testResolve() throws Exception {
|
public void testResolveURI() throws Exception {
|
||||||
URL base = URL.create("http://example.org/foo/");
|
URI base = URI.create("http://example.org/foo");
|
||||||
assertEquals("http://example.org/", base.resolve("/").toString());
|
assertEquals("http://example.org/", base.resolve("/").toString());
|
||||||
resolve("http://foo.bar", "foobar", "http://foo.bar/foobar");
|
assertEquals("http://example.org/foobar", base.resolve("/foobar").toString());
|
||||||
resolve("http://foo.bar/", "foobar", "http://foo.bar/foobar");
|
assertEquals("http://example.org/foobar", base.resolve("foobar").toString());
|
||||||
resolve("http://foo.bar/foobar", "foobar", "http://foo.bar/foobar");
|
base = URI.create("http://example.org/foo/");
|
||||||
|
assertEquals("http://example.org/", base.resolve("/").toString());
|
||||||
|
assertEquals("http://example.org/foobar", base.resolve("/foobar").toString());
|
||||||
|
assertEquals("http://example.org/foo/foobar", base.resolve("foobar").toString());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testResolveURL() throws Exception {
|
||||||
|
URL base = URL.create("http://example.org/foo");
|
||||||
|
assertEquals("http://example.org/", base.resolve("/").toString());
|
||||||
|
assertEquals("http://example.org/foobar", base.resolve("/foobar").toString());
|
||||||
|
assertEquals("http://example.org/foobar", base.resolve("foobar").toString());
|
||||||
|
base = URL.create("http://example.org/foo/");
|
||||||
|
assertEquals("http://example.org/", base.resolve("/").toString());
|
||||||
|
assertEquals("http://example.org/foobar", base.resolve("/foobar").toString());
|
||||||
|
assertEquals("http://example.org/foo/foobar", base.resolve("foobar").toString());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
@ -69,7 +86,7 @@ public class URLResolverTest {
|
||||||
resolve("http://a/b/c/d;p?q", "http:", "http:");
|
resolve("http://a/b/c/d;p?q", "http:", "http:");
|
||||||
}
|
}
|
||||||
|
|
||||||
private void resolve(String inputBase, String relative, String expected) {
|
private void resolve(String inputBase, String relative, String expected) throws URLSyntaxException {
|
||||||
assertEquals(expected, URL.base(inputBase).resolve(relative).toExternalForm());
|
assertEquals(expected, URL.base(inputBase).resolve(relative).toExternalForm());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -37,8 +37,8 @@ public class URLTest {
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if (base != null && input != null) {
|
if (base != null && input != null) {
|
||||||
URL url = URL.base(base).resolve(input);
|
try {
|
||||||
if (url != null) {
|
URL url = URL.base(base).resolve(input);
|
||||||
System.err.println("resolved: " + url.toString());
|
System.err.println("resolved: " + url.toString());
|
||||||
if (test.protocol != null) {
|
if (test.protocol != null) {
|
||||||
assertEquals(test.protocol, url.getScheme() + ":");
|
assertEquals(test.protocol, url.getScheme() + ":");
|
||||||
|
@ -54,8 +54,8 @@ public class URLTest {
|
||||||
// assertEquals(test.pathname, url.getPath());
|
// assertEquals(test.pathname, url.getPath());
|
||||||
//}
|
//}
|
||||||
System.err.println("passed: " + base + " " + input);
|
System.err.println("passed: " + base + " " + input);
|
||||||
} else {
|
} catch (URLSyntaxException e) {
|
||||||
System.err.println("unable to resolve: " + base + " " + input);
|
System.err.println("unable to resolve: " + base + " " + input + " reason: " + e.getMessage());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -453,7 +453,7 @@ public class PathMatcherTest {
|
||||||
@Test
|
@Test
|
||||||
public void patternComparatorSort() {
|
public void patternComparatorSort() {
|
||||||
Comparator<String> comparator = pathMatcher.getPatternComparator("/hotels/new");
|
Comparator<String> comparator = pathMatcher.getPatternComparator("/hotels/new");
|
||||||
List<String> paths = new ArrayList<String>(3);
|
List<String> paths = new ArrayList<>(3);
|
||||||
paths.add(null);
|
paths.add(null);
|
||||||
paths.add("/hotels/new");
|
paths.add("/hotels/new");
|
||||||
paths.sort(comparator);
|
paths.sort(comparator);
|
||||||
|
@ -561,13 +561,6 @@ public class PathMatcherTest {
|
||||||
assertTrue(pathMatcher.match("/Group/{groupName}/Members", "/group/Sales/members"));
|
assertTrue(pathMatcher.match("/Group/{groupName}/Members", "/group/Sales/members"));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
|
||||||
public void cachePatternsSetToFalse() {
|
|
||||||
pathMatcher.setCachePatterns(false);
|
|
||||||
match();
|
|
||||||
assertTrue(pathMatcher.stringMatcherCache().isEmpty());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void extensionMappingWithDotPathSeparator() {
|
public void extensionMappingWithDotPathSeparator() {
|
||||||
pathMatcher.setPathSeparator(".");
|
pathMatcher.setPathSeparator(".");
|
||||||
|
|
Loading…
Reference in a new issue