add IRI
This commit is contained in:
parent
da6f3145be
commit
7ef0e83364
14 changed files with 2294 additions and 0 deletions
|
@ -19,3 +19,9 @@ The org.xbib.net.buffer "DataBuffer" classes are taken from Spring Framework, Co
|
|||
https://github.com/spring-projects/spring-framework/tree/main/spring-core/src/main/java/org/springframework/core/io/buffer
|
||||
|
||||
License: Apacche 2.0
|
||||
|
||||
The IRI class is a modified version taken from org.apache.abdera.i18n.text
|
||||
|
||||
https://abdera.apache.org
|
||||
|
||||
License: Apacche 2.0
|
||||
|
|
803
net/src/main/java/org/xbib/net/IRI.java
Normal file
803
net/src/main/java/org/xbib/net/IRI.java
Normal file
|
@ -0,0 +1,803 @@
|
|||
package org.xbib.net;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.IDN;
|
||||
import java.net.MalformedURLException;
|
||||
import java.net.URI;
|
||||
import java.net.URISyntaxException;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
import org.xbib.net.scheme.Scheme;
|
||||
import org.xbib.net.scheme.SchemeRegistry;
|
||||
import org.xbib.net.util.CharUtils;
|
||||
import org.xbib.net.util.InvalidCharacterException;
|
||||
import org.xbib.net.util.Profile;
|
||||
|
||||
public class IRI implements Comparable<IRI> {
|
||||
|
||||
private static final Pattern IRIPATTERN =
|
||||
Pattern.compile("^(?:([^:/?#]+):)?(?://([^/?#]*))?([^?#]*)(?:\\?([^#]*))?(?:#(.*))?");
|
||||
|
||||
private final IRIBuilder builder;
|
||||
|
||||
IRI(IRIBuilder builder) {
|
||||
this.builder = builder;
|
||||
}
|
||||
|
||||
public static IRIBuilder builder() {
|
||||
return new IRIBuilder();
|
||||
}
|
||||
|
||||
public static IRI create(String iri) {
|
||||
return IRI.builder().from(iri).build();
|
||||
}
|
||||
|
||||
public String getScheme() {
|
||||
return builder.scheme;
|
||||
}
|
||||
|
||||
public String getAuthority() {
|
||||
return (builder.authority != null && builder.authority.length() > 0) ? builder.authority : null;
|
||||
}
|
||||
|
||||
public String getFragment() {
|
||||
return builder.fragment;
|
||||
}
|
||||
|
||||
public String getHost() {
|
||||
return (builder.host != null && builder.host.length() > 0) ? builder.host : null;
|
||||
}
|
||||
|
||||
public String getPath() {
|
||||
return builder.path;
|
||||
}
|
||||
|
||||
public int getPort() {
|
||||
return builder.port;
|
||||
}
|
||||
|
||||
public String getQuery() {
|
||||
return builder.query;
|
||||
}
|
||||
|
||||
public String getSchemeSpecificPart() {
|
||||
return builder.schemeSpecificPart;
|
||||
}
|
||||
|
||||
public String getUserInfo() {
|
||||
return builder.userinfo;
|
||||
}
|
||||
|
||||
public boolean isAbsolute() {
|
||||
return builder.scheme != null;
|
||||
}
|
||||
|
||||
public boolean isOpaque() {
|
||||
return builder.path == null;
|
||||
}
|
||||
|
||||
public boolean isPathAbsolute() {
|
||||
String s = getPath();
|
||||
return s != null && s.length() > 0 && s.charAt(0) == '/';
|
||||
}
|
||||
|
||||
public boolean isSameDocumentReference() {
|
||||
return builder.scheme == null && builder.authority == null
|
||||
&& (builder.path == null || builder.path.length() == 0 || ".".equals(builder.path))
|
||||
&& builder.query == null;
|
||||
}
|
||||
|
||||
|
||||
public String getASCIIHost() {
|
||||
return builder.getASCIIHost();
|
||||
}
|
||||
|
||||
public String getASCIIAuthority() {
|
||||
return builder.getASCIIAuthority();
|
||||
}
|
||||
|
||||
public String getASCIIFragment() {
|
||||
return builder.getASCIIFragment();
|
||||
}
|
||||
|
||||
public String getASCIIPath() {
|
||||
return builder.getASCIIPath();
|
||||
}
|
||||
|
||||
public String getASCIIQuery() {
|
||||
return builder.getASCIIQuery();
|
||||
}
|
||||
|
||||
public String getASCIIUserInfo() {
|
||||
return builder.getASCIIUserInfo();
|
||||
}
|
||||
|
||||
public String getASCIISchemeSpecificPart() {
|
||||
return builder.getASCIISchemeSpecificPart();
|
||||
}
|
||||
|
||||
public IRI resolve(IRI iri) {
|
||||
return resolve(this, iri);
|
||||
}
|
||||
|
||||
public IRI resolve(String iri) {
|
||||
return resolve(this, IRI.builder().from(iri).build());
|
||||
}
|
||||
|
||||
public static IRI resolve(IRI b, IRI c) {
|
||||
if (c == null) {
|
||||
return null;
|
||||
}
|
||||
if ("".equals(c.toString()) || "#".equals(c.toString())
|
||||
|| ".".equals(c.toString())
|
||||
|| "./".equals(c.toString())) {
|
||||
return b;
|
||||
}
|
||||
if (b == null) {
|
||||
return c;
|
||||
}
|
||||
if (c.isOpaque() || b.isOpaque()) {
|
||||
return c;
|
||||
}
|
||||
if (c.isSameDocumentReference()) {
|
||||
String cfragment = c.getFragment();
|
||||
String bfragment = b.getFragment();
|
||||
if ((cfragment == null && bfragment == null) || (cfragment != null && cfragment.equals(bfragment))) {
|
||||
return b;
|
||||
} else {
|
||||
return IRI.builder()
|
||||
.scheme(b.builder.scheme)
|
||||
.authority(b.builder.authority)
|
||||
.userinfo(b.builder.userinfo)
|
||||
.host(b.builder.host)
|
||||
.port(b.builder.port)
|
||||
.path(normalizePath(b.builder.path))
|
||||
.query(b.builder.query)
|
||||
.fragment(cfragment)
|
||||
.build();
|
||||
}
|
||||
}
|
||||
if (c.isAbsolute()) {
|
||||
return c;
|
||||
}
|
||||
String scheme = b.builder.scheme;
|
||||
String query = c.getQuery();
|
||||
String fragment = c.getFragment();
|
||||
String userinfo;
|
||||
String authority;
|
||||
String host;
|
||||
int port;
|
||||
String path;
|
||||
if (c.getAuthority() == null) {
|
||||
authority = b.getAuthority();
|
||||
userinfo = b.getUserInfo();
|
||||
host = b.getHost();
|
||||
port = b.getPort();
|
||||
path = c.isPathAbsolute() ? normalizePath(c.getPath()) : resolve(b.getPath(), c.getPath());
|
||||
} else {
|
||||
authority = c.getAuthority();
|
||||
userinfo = c.getUserInfo();
|
||||
host = c.getHost();
|
||||
port = c.getPort();
|
||||
path = normalizePath(c.getPath());
|
||||
}
|
||||
return IRI.builder()
|
||||
.scheme(scheme)
|
||||
.authority(authority)
|
||||
.userinfo(userinfo)
|
||||
.host(host)
|
||||
.port(port)
|
||||
.path(path)
|
||||
.query(query)
|
||||
.fragment(fragment)
|
||||
.build();
|
||||
}
|
||||
|
||||
public static IRI relativize(IRI b, IRI c) {
|
||||
if (c.isOpaque() || b.isOpaque()) {
|
||||
return c;
|
||||
}
|
||||
if ((b.builder.scheme == null && c.builder.scheme != null) || (b.builder.scheme != null && c.builder.scheme == null)
|
||||
|| (b.builder.scheme != null && !b.builder.scheme.equalsIgnoreCase(c.builder.scheme))) {
|
||||
return c;
|
||||
}
|
||||
String bpath = normalizePath(b.getPath());
|
||||
String cpath = normalizePath(c.getPath());
|
||||
if (!bpath.equals(cpath)) {
|
||||
if (bpath.charAt(bpath.length() - 1) != '/') {
|
||||
bpath += "/";
|
||||
}
|
||||
if (!cpath.startsWith(bpath)) {
|
||||
return c;
|
||||
}
|
||||
}
|
||||
return IRI.builder()
|
||||
.scheme(null)
|
||||
.authority(null)
|
||||
.userinfo(null)
|
||||
.host(null)
|
||||
.port(-1)
|
||||
.path(normalizePath(cpath.substring(bpath.length())))
|
||||
.query(c.getQuery())
|
||||
.fragment(c.getFragment())
|
||||
.build();
|
||||
}
|
||||
|
||||
private static String normalizePath(String path) {
|
||||
if (path == null || path.length() == 0) {
|
||||
return "/";
|
||||
}
|
||||
String[] segments = path.split("/");
|
||||
if (segments.length < 2) {
|
||||
return path;
|
||||
}
|
||||
StringBuilder buf = new StringBuilder("/");
|
||||
for (int n = 0; n < segments.length; n++) {
|
||||
String segment = segments[n].intern();
|
||||
if (".".equals(segment)) {
|
||||
segments[n] = null;
|
||||
}
|
||||
}
|
||||
PercentDecoder percentDecoder = new PercentDecoder();
|
||||
for (String segment : segments) {
|
||||
if (segment != null) {
|
||||
if (buf.length() > 1) {
|
||||
buf.append('/');
|
||||
}
|
||||
try {
|
||||
buf.append(PercentEncoders.getMatrixEncoder(StandardCharsets.UTF_8).encode(percentDecoder.decode(segment)));
|
||||
} catch (IOException e) {
|
||||
//logger.log(Level.FINE, e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (path.endsWith("/") || path.endsWith("/.")) {
|
||||
buf.append('/');
|
||||
}
|
||||
return buf.toString();
|
||||
}
|
||||
|
||||
private static String resolve(String bpath, String cpath) {
|
||||
if (bpath == null && cpath == null) {
|
||||
return null;
|
||||
}
|
||||
if (bpath == null) {
|
||||
return (!cpath.startsWith("/")) ? "/" + cpath : cpath;
|
||||
}
|
||||
if (cpath == null) {
|
||||
return bpath;
|
||||
}
|
||||
StringBuilder buf = new StringBuilder("");
|
||||
int n = bpath.lastIndexOf('/');
|
||||
if (n > -1) {
|
||||
buf.append(bpath, 0, n + 1);
|
||||
}
|
||||
if (cpath.length() != 0) {
|
||||
buf.append(cpath);
|
||||
}
|
||||
if (buf.charAt(0) != '/') {
|
||||
buf.insert(0, '/');
|
||||
}
|
||||
return normalizePath(buf.toString());
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
StringBuilder buf = new StringBuilder();
|
||||
String s = getScheme();
|
||||
if (s != null && !s.isEmpty()) {
|
||||
buf.append(s).append(':');
|
||||
}
|
||||
buf.append(getSchemeSpecificPart());
|
||||
return buf.toString();
|
||||
}
|
||||
|
||||
public String toEncodedString() throws IOException {
|
||||
return PercentEncoders.getUnreservedEncoder(StandardCharsets.UTF_8).encode(toString());
|
||||
}
|
||||
|
||||
public String toASCIIString() {
|
||||
StringBuilder buf = new StringBuilder();
|
||||
String s = getScheme();
|
||||
if (s != null && !s.isEmpty()) {
|
||||
buf.append(s).append(':');
|
||||
}
|
||||
buf.append(getASCIISchemeSpecificPart());
|
||||
return buf.toString();
|
||||
}
|
||||
|
||||
public String toBIDIString() {
|
||||
return CharUtils.wrapBidi(toString(), CharUtils.LRE);
|
||||
}
|
||||
|
||||
public URI toURI() throws URISyntaxException {
|
||||
return new URI(toASCIIString());
|
||||
}
|
||||
|
||||
public java.net.URL toURL() throws MalformedURLException, URISyntaxException {
|
||||
return toURI().toURL();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
final int p = 31;
|
||||
int result = 1;
|
||||
result = p * result + ((builder.authority == null) ? 0 : builder.authority.hashCode());
|
||||
result = p * result + ((builder.fragment == null) ? 0 : builder.fragment.hashCode());
|
||||
result = p * result + ((builder.host == null) ? 0 : builder.host.hashCode());
|
||||
result = p * result + ((builder.path == null) ? 0 : builder.path.hashCode());
|
||||
result = p * result + builder.port;
|
||||
result = p * result + ((builder.query == null) ? 0 : builder.query.hashCode());
|
||||
result = p * result + ((builder.scheme == null) ? 0 : builder.scheme.hashCode());
|
||||
result = p * result + ((builder.userinfo == null) ? 0 : builder.userinfo.hashCode());
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
if (this == obj) {
|
||||
return true;
|
||||
}
|
||||
if (obj == null) {
|
||||
return false;
|
||||
}
|
||||
if (getClass() != obj.getClass()) {
|
||||
return false;
|
||||
}
|
||||
final IRI other = (IRI) obj;
|
||||
if (builder.authority == null) {
|
||||
if (other.builder.authority != null) {
|
||||
return false;
|
||||
}
|
||||
} else if (!builder.authority.equals(other.builder.authority)) {
|
||||
return false;
|
||||
}
|
||||
if (builder.fragment == null) {
|
||||
if (other.builder.fragment != null) {
|
||||
return false;
|
||||
}
|
||||
} else if (!builder.fragment.equals(other.builder.fragment)) {
|
||||
return false;
|
||||
}
|
||||
if (builder.host == null) {
|
||||
if (other.builder.host != null) {
|
||||
return false;
|
||||
}
|
||||
} else if (!builder.host.equals(other.builder.host)) {
|
||||
return false;
|
||||
}
|
||||
if (builder.path == null) {
|
||||
if (other.builder.path != null) {
|
||||
return false;
|
||||
}
|
||||
} else if (!builder.path.equals(other.builder.path)) {
|
||||
return false;
|
||||
}
|
||||
if (builder.port != other.builder.port) {
|
||||
return false;
|
||||
}
|
||||
if (builder.query == null) {
|
||||
if (other.builder.query != null) {
|
||||
return false;
|
||||
}
|
||||
} else if (!builder.query.equals(other.builder.query)) {
|
||||
return false;
|
||||
}
|
||||
if (builder.scheme == null) {
|
||||
if (other.builder.scheme != null) {
|
||||
return false;
|
||||
}
|
||||
} else if (!builder.scheme.equals(other.builder.scheme)) {
|
||||
return false;
|
||||
}
|
||||
if (builder.userinfo == null) {
|
||||
return other.builder.userinfo == null;
|
||||
} else {
|
||||
return builder.userinfo.equals(other.builder.userinfo);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public int compareTo(IRI that) {
|
||||
int c;
|
||||
if ((c = compareIgnoringCase(builder.scheme, that.builder.scheme)) != 0) {
|
||||
return c;
|
||||
}
|
||||
if (isOpaque()) {
|
||||
if (that.isOpaque()) {
|
||||
// Both opaque
|
||||
if ((c = compare(builder.schemeSpecificPart, that.builder.schemeSpecificPart)) != 0) {
|
||||
return c;
|
||||
}
|
||||
return compare(builder.fragment, that.builder.fragment);
|
||||
}
|
||||
return +1;
|
||||
} else if (that.isOpaque()) {
|
||||
return -1;
|
||||
}
|
||||
// Hierarchical
|
||||
if ((builder.host != null) && (that.builder.host != null)) {
|
||||
// Both server-based
|
||||
if ((c = compare(builder.userinfo, that.builder.userinfo)) != 0) {
|
||||
return c;
|
||||
}
|
||||
if ((c = compareIgnoringCase(builder.host, that.builder.host)) != 0) {
|
||||
return c;
|
||||
}
|
||||
if ((c = builder.port - that.builder.port) != 0) {
|
||||
return c;
|
||||
}
|
||||
} else {
|
||||
if ((c = compare(builder.authority, that.builder.authority)) != 0) {
|
||||
return c;
|
||||
}
|
||||
}
|
||||
if ((c = compare(builder.path, that.builder.path)) != 0) {
|
||||
return c;
|
||||
}
|
||||
if ((c = compare(builder.query, that.builder.query)) != 0) {
|
||||
return c;
|
||||
}
|
||||
return compare(builder.fragment, that.builder.fragment);
|
||||
}
|
||||
|
||||
private int compare(String s, String t) {
|
||||
if (s != null) {
|
||||
if (s.equals(t)) {
|
||||
return 0;
|
||||
}
|
||||
if (t != null) {
|
||||
return s.compareTo(t);
|
||||
} else {
|
||||
return +1;
|
||||
}
|
||||
} else {
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
private int compareIgnoringCase(String s, String t) {
|
||||
if (s != null) {
|
||||
if (s.equals(t)) {
|
||||
return 0;
|
||||
}
|
||||
if (t != null) {
|
||||
int sn = s.length();
|
||||
int tn = t.length();
|
||||
int n = Math.min(sn, tn);
|
||||
for (int i = 0; i < n; i++) {
|
||||
int c = toLower(s.charAt(i)) - toLower(t.charAt(i));
|
||||
if (c != 0) {
|
||||
return c;
|
||||
}
|
||||
}
|
||||
return sn - tn;
|
||||
}
|
||||
return +1;
|
||||
} else {
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
private int toLower(char c) {
|
||||
if ((c >= 'A') && (c <= 'Z')) {
|
||||
return c + ('a' - 'A');
|
||||
}
|
||||
return c;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
public static class IRIBuilder {
|
||||
|
||||
final SchemeRegistry reg = SchemeRegistry.getInstance();
|
||||
|
||||
Scheme schemeClass;
|
||||
|
||||
String scheme;
|
||||
|
||||
String schemeSpecificPart;
|
||||
|
||||
String authority;
|
||||
|
||||
String userinfo;
|
||||
|
||||
String host;
|
||||
|
||||
int port = -1;
|
||||
|
||||
String path;
|
||||
|
||||
String query;
|
||||
|
||||
String fragment;
|
||||
|
||||
private String asciiHost;
|
||||
|
||||
private String asciiAuthority;
|
||||
|
||||
private String asciiUserinfo;
|
||||
|
||||
private String asciiSchemeSpecificPart;
|
||||
|
||||
private String asciiPath;
|
||||
|
||||
private String asciiQuery;
|
||||
|
||||
private String asciiFragment;
|
||||
|
||||
private IRIBuilder() {
|
||||
}
|
||||
|
||||
public IRIBuilder from(String string) {
|
||||
parse(CharUtils.stripBidi(string));
|
||||
authorityAndSchemeSpecificPart();
|
||||
return this;
|
||||
}
|
||||
|
||||
public IRIBuilder from(URI uri) {
|
||||
scheme = uri.getScheme();
|
||||
schemeClass = reg.getScheme(scheme);
|
||||
authority = uri.getAuthority();
|
||||
path = uri.getPath();
|
||||
query = uri.getQuery();
|
||||
fragment = uri.getFragment();
|
||||
parseAuthority();
|
||||
authorityAndSchemeSpecificPart();
|
||||
return this;
|
||||
}
|
||||
|
||||
public IRIBuilder from(IRI uri) {
|
||||
scheme = uri.getScheme();
|
||||
schemeClass = reg.getScheme(scheme);
|
||||
authority = uri.getAuthority();
|
||||
path = uri.getPath();
|
||||
query = uri.getQuery();
|
||||
fragment = uri.getFragment();
|
||||
parseAuthority();
|
||||
authorityAndSchemeSpecificPart();
|
||||
return this;
|
||||
}
|
||||
|
||||
public IRIBuilder from(String scheme, String schemeSpecificPart, String fragment) {
|
||||
this.scheme = scheme.toLowerCase();
|
||||
this.schemeSpecificPart = schemeSpecificPart;
|
||||
this.fragment = fragment;
|
||||
authorityAndSchemeSpecificPart();
|
||||
return this;
|
||||
}
|
||||
|
||||
public IRIBuilder scheme(String scheme) {
|
||||
this.scheme = scheme;
|
||||
this.schemeClass = reg.getScheme(scheme);
|
||||
return this;
|
||||
}
|
||||
|
||||
public IRIBuilder schemeSpecificPart(String schemeSpecificPart) {
|
||||
this.schemeSpecificPart = schemeSpecificPart;
|
||||
return this;
|
||||
}
|
||||
|
||||
public IRIBuilder curie(String prefix, String path) {
|
||||
this.scheme = prefix;
|
||||
this.path = path;
|
||||
return this;
|
||||
}
|
||||
|
||||
public IRIBuilder curie(String schemeAndPath) {
|
||||
int pos = schemeAndPath.indexOf(':');
|
||||
this.scheme = pos > 0 ? schemeAndPath.substring(0, pos) : null;
|
||||
this.path = pos > 0 ? schemeAndPath.substring(pos + 1) : schemeAndPath;
|
||||
return this;
|
||||
}
|
||||
|
||||
public IRIBuilder authority(String authority) {
|
||||
this.authority = authority;
|
||||
return this;
|
||||
}
|
||||
|
||||
public IRIBuilder userinfo(String userinfo) {
|
||||
this.userinfo = userinfo;
|
||||
return this;
|
||||
}
|
||||
|
||||
public IRIBuilder host(String host) {
|
||||
this.host = host;
|
||||
return this;
|
||||
}
|
||||
|
||||
public IRIBuilder port(int port) {
|
||||
this.port = port;
|
||||
return this;
|
||||
}
|
||||
|
||||
public IRIBuilder path(String path) {
|
||||
this.path = path;
|
||||
return this;
|
||||
}
|
||||
|
||||
public IRIBuilder query(String query) {
|
||||
this.query = query;
|
||||
return this;
|
||||
}
|
||||
|
||||
public IRIBuilder fragment(String fragment) {
|
||||
this.fragment = fragment;
|
||||
return this;
|
||||
}
|
||||
|
||||
public IRI build() {
|
||||
return new IRI(this);
|
||||
}
|
||||
|
||||
private void parse(String iri) {
|
||||
try {
|
||||
Matcher irim = IRIPATTERN.matcher(iri);
|
||||
if (irim.find()) {
|
||||
scheme = irim.group(1);
|
||||
schemeClass = reg.getScheme(scheme);
|
||||
authority = irim.group(2);
|
||||
path = irim.group(3);
|
||||
query = irim.group(4);
|
||||
fragment = irim.group(5);
|
||||
parseAuthority();
|
||||
try {
|
||||
CharUtils.verify(scheme, Profile.SCHEME);
|
||||
CharUtils.verify(path, Profile.IPATH);
|
||||
CharUtils.verify(query, Profile.IQUERY);
|
||||
CharUtils.verify(fragment, Profile.IFRAGMENT);
|
||||
} catch (InvalidCharacterException e) {
|
||||
throw new IRISyntaxException(e);
|
||||
}
|
||||
} else {
|
||||
throw new IRISyntaxException("invalid Syntax");
|
||||
}
|
||||
} catch (IRISyntaxException e) {
|
||||
throw e;
|
||||
} catch (Exception e) {
|
||||
throw new IRISyntaxException(e);
|
||||
}
|
||||
}
|
||||
|
||||
private void parseAuthority() {
|
||||
if (authority != null) {
|
||||
// [ <userinfo> '@' ] <host> [ ':' <port> ]
|
||||
int pos = authority.lastIndexOf('@');
|
||||
userinfo = pos >= 0 ? authority.substring(0, pos) : null;
|
||||
String s = pos >= 0 ? authority.substring(pos + 1) : authority;
|
||||
pos = s.indexOf(':');
|
||||
host = pos >= 0 ? s.substring(0, pos) : s;
|
||||
port = pos >= 0 ? Integer.parseInt(s.substring(pos + 1)) : -1;
|
||||
try {
|
||||
CharUtils.verify(userinfo, Profile.IUSERINFO);
|
||||
CharUtils.verify(host, Profile.IHOST);
|
||||
} catch (InvalidCharacterException e) {
|
||||
throw new IRISyntaxException(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void authorityAndSchemeSpecificPart() {
|
||||
if (authority == null && (userinfo != null || host != null)) {
|
||||
StringBuilder buf = new StringBuilder();
|
||||
buildAuthority(buf, userinfo, host, port);
|
||||
authority = (buf.length() != 0) ? buf.toString() : null;
|
||||
}
|
||||
StringBuilder buf = new StringBuilder();
|
||||
buildSchemeSpecificPart(buf, authority, path, query, fragment);
|
||||
schemeSpecificPart = buf.toString();
|
||||
}
|
||||
|
||||
private static void buildSchemeSpecificPart(StringBuilder buf, String authority, String path, String query,
|
||||
String fragment) {
|
||||
if (authority != null) {
|
||||
buf.append("//");
|
||||
buf.append(authority);
|
||||
}
|
||||
if (path != null && path.length() > 0) {
|
||||
buf.append(path);
|
||||
}
|
||||
if (query != null) {
|
||||
buf.append('?');
|
||||
buf.append(query);
|
||||
}
|
||||
if (fragment != null) {
|
||||
buf.append('#');
|
||||
buf.append(fragment);
|
||||
}
|
||||
}
|
||||
|
||||
public String getASCIIHost() {
|
||||
if (host != null && asciiHost == null) {
|
||||
if (host.startsWith("[")) {
|
||||
asciiHost = host;
|
||||
} else {
|
||||
asciiHost = IDN.toASCII(host);
|
||||
}
|
||||
}
|
||||
return (asciiHost != null && asciiHost.length() > 0) ? asciiHost : null;
|
||||
}
|
||||
|
||||
private String getASCIIAuthority() {
|
||||
if (authority != null && asciiAuthority == null) {
|
||||
asciiAuthority = buildASCIIAuthority();
|
||||
}
|
||||
return asciiAuthority != null && asciiAuthority.length() > 0 ? asciiAuthority : null;
|
||||
}
|
||||
|
||||
private String buildASCIIAuthority() {
|
||||
StringBuilder buf = new StringBuilder();
|
||||
buildAuthority(buf, getASCIIUserInfo(), getASCIIHost(), port);
|
||||
return buf.toString();
|
||||
}
|
||||
|
||||
private static void buildAuthority(StringBuilder buf, String aui, String ah, int port) {
|
||||
if (aui != null && aui.length() != 0) {
|
||||
buf.append(aui);
|
||||
buf.append('@');
|
||||
}
|
||||
if (ah != null && ah.length() != 0) {
|
||||
buf.append(ah);
|
||||
}
|
||||
if (port != -1) {
|
||||
buf.append(':');
|
||||
buf.append(port);
|
||||
}
|
||||
}
|
||||
|
||||
private String getASCIIFragment() {
|
||||
if (fragment != null && asciiFragment == null) {
|
||||
try {
|
||||
asciiFragment = PercentEncoders.getFragmentEncoder(StandardCharsets.UTF_8).encode(fragment);
|
||||
} catch (IOException e) {
|
||||
//logger.log(Level.FINE, e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
return asciiFragment;
|
||||
}
|
||||
|
||||
private String getASCIIPath() {
|
||||
if (path != null && asciiPath == null) {
|
||||
try {
|
||||
asciiPath = PercentEncoders.getPathEncoder(StandardCharsets.UTF_8).encode(path);
|
||||
} catch (IOException e) {
|
||||
//logger.log(Level.FINE, e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
return asciiPath;
|
||||
}
|
||||
|
||||
public String getASCIIQuery() {
|
||||
if (query != null && asciiQuery == null) {
|
||||
try {
|
||||
asciiQuery = PercentEncoders.getQueryEncoder(StandardCharsets.UTF_8).encode(query);
|
||||
} catch (IOException e) {
|
||||
//logger.log(Level.FINE, e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
return asciiQuery;
|
||||
}
|
||||
|
||||
public String getASCIIUserInfo() {
|
||||
if (userinfo != null && asciiUserinfo == null) {
|
||||
try {
|
||||
asciiUserinfo = PercentEncoders.getUnreservedEncoder(StandardCharsets.UTF_8).encode(userinfo);
|
||||
} catch (IOException e) {
|
||||
//logger.log(Level.FINE, e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
return asciiUserinfo;
|
||||
}
|
||||
|
||||
public String getASCIISchemeSpecificPart() {
|
||||
if (asciiSchemeSpecificPart == null) {
|
||||
StringBuilder buf = new StringBuilder();
|
||||
buildSchemeSpecificPart(buf, getASCIIAuthority(), getASCIIPath(), getASCIIQuery(), getASCIIFragment());
|
||||
asciiSchemeSpecificPart = buf.toString();
|
||||
}
|
||||
return asciiSchemeSpecificPart;
|
||||
}
|
||||
}
|
||||
}
|
17
net/src/main/java/org/xbib/net/IRISyntaxException.java
Normal file
17
net/src/main/java/org/xbib/net/IRISyntaxException.java
Normal file
|
@ -0,0 +1,17 @@
|
|||
package org.xbib.net;
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
@SuppressWarnings("serial")
|
||||
public class IRISyntaxException extends RuntimeException {
|
||||
|
||||
IRISyntaxException(String message) {
|
||||
super(message);
|
||||
}
|
||||
|
||||
IRISyntaxException(Throwable cause) {
|
||||
super(cause);
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,28 @@
|
|||
package org.xbib.net.util;
|
||||
|
||||
class CharArrayCodepointIterator extends CodepointIterator {
|
||||
protected char[] buffer;
|
||||
|
||||
CharArrayCodepointIterator(char[] buffer) {
|
||||
this(buffer, 0, buffer.length);
|
||||
}
|
||||
|
||||
CharArrayCodepointIterator(char[] buffer, int n, int e) {
|
||||
this.buffer = buffer;
|
||||
this.position = n;
|
||||
this.limit = Math.min(buffer.length - n, e);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected char get() {
|
||||
return (position < limit) ? buffer[position++] : (char) -1;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected char get(int index) {
|
||||
if (index < 0 || index >= limit) {
|
||||
throw new ArrayIndexOutOfBoundsException(index);
|
||||
}
|
||||
return buffer[index];
|
||||
}
|
||||
}
|
|
@ -0,0 +1,25 @@
|
|||
package org.xbib.net.util;
|
||||
|
||||
class CharSequenceCodepointIterator extends CodepointIterator {
|
||||
private final CharSequence buffer;
|
||||
|
||||
CharSequenceCodepointIterator(CharSequence buffer) {
|
||||
this(buffer, 0, buffer.length());
|
||||
}
|
||||
|
||||
CharSequenceCodepointIterator(CharSequence buffer, int n, int e) {
|
||||
this.buffer = buffer;
|
||||
this.position = n;
|
||||
this.limit = Math.min(buffer.length() - n, e);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected char get() {
|
||||
return buffer.charAt(position++);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected char get(int index) {
|
||||
return buffer.charAt(index);
|
||||
}
|
||||
}
|
597
net/src/main/java/org/xbib/net/util/CharUtils.java
Normal file
597
net/src/main/java/org/xbib/net/util/CharUtils.java
Normal file
|
@ -0,0 +1,597 @@
|
|||
package org.xbib.net.util;
|
||||
|
||||
/**
|
||||
* General utilities for dealing with Unicode characters.
|
||||
*/
|
||||
public final class CharUtils {
|
||||
|
||||
public static final char LRE = 0x202A;
|
||||
public static final char RLE = 0x202B;
|
||||
public static final char LRO = 0x202D;
|
||||
public static final char RLO = 0x202E;
|
||||
public static final char LRM = 0x200E;
|
||||
public static final char RLM = 0x200F;
|
||||
public static final char PDF = 0x202C;
|
||||
|
||||
private CharUtils() {
|
||||
}
|
||||
|
||||
/**
|
||||
* True if the character is a valid unicode codepoint.
|
||||
* @param c char
|
||||
* @return true if the character is a valid unicode codepoint
|
||||
*/
|
||||
public static boolean isValid(int c) {
|
||||
return c >= 0x000000 && c <= 0x10ffff;
|
||||
}
|
||||
|
||||
/**
|
||||
* True if the character is a valid unicode codepoint.
|
||||
* @param c code point
|
||||
* @return true if the character is a valid unicode codepoint
|
||||
*/
|
||||
public static boolean isValid(Codepoint c) {
|
||||
return isValid(c.getValue());
|
||||
}
|
||||
|
||||
/**
|
||||
* True if all the characters in chars are within the set [low,high].
|
||||
* @param chars chars
|
||||
* @param low low
|
||||
* @param high high
|
||||
* @return true if all the characters in chars are within the set [low,high]
|
||||
*/
|
||||
public static boolean inRange(char[] chars, char low, char high) {
|
||||
for (char aChar : chars) {
|
||||
if (aChar < low || aChar > high) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* True if all the characters in chars are within the set [low,high].
|
||||
* @param chars chars
|
||||
* @param low low
|
||||
* @param high high
|
||||
* @return true if all the characters in chars are within the set [low,high]
|
||||
*/
|
||||
public static boolean inRange(char[] chars, int low, int high) {
|
||||
for (int i = 0; i < chars.length; i++) {
|
||||
char n = chars[i];
|
||||
Codepoint cp =
|
||||
(isHighSurrogate(n) && i + 1 < chars.length && isLowSurrogate(chars[i + 1]))
|
||||
? toSupplementary(n, chars[i++]) : new Codepoint(n);
|
||||
int c = cp.getValue();
|
||||
if (c < low || c > high) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* True if the codepoint is within the set [low,high].
|
||||
* @param codepoint code point
|
||||
* @param low low
|
||||
* @param high high
|
||||
* @return true if the codepoint is within the set [low,high]
|
||||
*/
|
||||
public static boolean inRange(int codepoint, int low, int high) {
|
||||
return codepoint >= low && codepoint <= high;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the high surrogate for a particular unicode codepoint.
|
||||
* @param c char
|
||||
* @return high surrugate
|
||||
*/
|
||||
public static char getHighSurrogate(int c) {
|
||||
return c >= 0x10000 ? (char) ((0xD800 - (0x10000 >> 10)) + (c >> 10)) : 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the low surrogate for a particular unicode codepoint.
|
||||
* @param c char
|
||||
* @return low surrogate
|
||||
*/
|
||||
public static char getLowSurrogate(int c) {
|
||||
return c >= 0x10000 ? (char) (0xDC00 + (c & 0x3FF)) : (char) c;
|
||||
}
|
||||
|
||||
/**
|
||||
* True if the specified char is a high surrogate.
|
||||
* @param c char
|
||||
* @return true if the specified char is a high surrogate
|
||||
*/
|
||||
public static boolean isHighSurrogate(char c) {
|
||||
return c <= '\uDBFF' && c >= '\uD800';
|
||||
}
|
||||
|
||||
/**
|
||||
* True if the specified char is a low surrogate.
|
||||
* @param c char
|
||||
* @return true if the specified char is a low surrogate
|
||||
*/
|
||||
public static boolean isLowSurrogate(char c) {
|
||||
return c <= '\uDFFF' && c >= '\uDC00';
|
||||
}
|
||||
|
||||
/**
|
||||
* True if the specified character is supplemental.
|
||||
* @param c char
|
||||
* @return true if the specified character is supplemental
|
||||
*/
|
||||
public static boolean isSupplementary(int c) {
|
||||
return c <= 0x10ffff && c >= 0x010000;
|
||||
}
|
||||
|
||||
/**
|
||||
* True if the two chars represent a surrogate pair.
|
||||
* @param high high char
|
||||
* @param low low char
|
||||
* @return true if the two chars represent a surrogate pair
|
||||
*/
|
||||
public static boolean isSurrogatePair(char high, char low) {
|
||||
return isHighSurrogate(high) && isLowSurrogate(low);
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts the high and low surrogate into a supplementary codepoint.
|
||||
* @param high high char
|
||||
* @param low low char
|
||||
* @return code point
|
||||
*/
|
||||
public static Codepoint toSupplementary(char high, char low) {
|
||||
if (!isHighSurrogate(high)) {
|
||||
throw new IllegalArgumentException("Invalid High Surrogate");
|
||||
}
|
||||
if (!isLowSurrogate(low)) {
|
||||
throw new IllegalArgumentException("Invalid Low Surrogate");
|
||||
}
|
||||
return new Codepoint(((high - '\uD800') << 10) + (low - '\uDC00') + 0x010000);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the codepoint at the given location, automatically dealing with surrogate pairs.
|
||||
* @param s string
|
||||
* @param i location
|
||||
* @return code point
|
||||
*/
|
||||
public static Codepoint codepointAt(String s, int i) {
|
||||
char c = s.charAt(i);
|
||||
if (c < 0xD800 || c > 0xDFFF) {
|
||||
return new Codepoint(c);
|
||||
}
|
||||
if (isHighSurrogate(c) && s.length() != i) {
|
||||
char low = s.charAt(i + 1);
|
||||
if (isLowSurrogate(low)) {
|
||||
return toSupplementary(c, low);
|
||||
}
|
||||
} else if (isLowSurrogate(c) && i >= 1) {
|
||||
char high = s.charAt(i - 1);
|
||||
if (isHighSurrogate(high)) {
|
||||
return toSupplementary(high, c);
|
||||
}
|
||||
}
|
||||
return new Codepoint(c);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the number of characters used to represent the codepoint (will return 1 or 2).
|
||||
* @param c code point
|
||||
* @return the number of characters used to represent the codepoint
|
||||
*/
|
||||
public static int length(Codepoint c) {
|
||||
return c.getCharCount();
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the number of characters used to represent the codepoint (will return 1 or 2).
|
||||
* @param c code point
|
||||
* @return the number of characters used to represent the codepoint
|
||||
*/
|
||||
public static int length(int c) {
|
||||
return new Codepoint(c).getCharCount();
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the total number of codepoints in the buffer. Each surrogate pair counts as a single codepoint.
|
||||
* @param c code point
|
||||
* @return the total number of codepoints in the buffer
|
||||
*/
|
||||
public static int length(CharSequence c) {
|
||||
return length(CodepointIterator.forCharSequence(c));
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the total number of codepoints in the buffer. Each surrogate pair counts as a single codepoint.
|
||||
* @param c chars
|
||||
* @return the total number of codepoints in the buffer
|
||||
*/
|
||||
public static int length(char[] c) {
|
||||
return length(CodepointIterator.forCharArray(c));
|
||||
}
|
||||
|
||||
private static int length(CodepointIterator ci) {
|
||||
int n = 0;
|
||||
while (ci.hasNext()) {
|
||||
ci.next();
|
||||
n++;
|
||||
}
|
||||
return n;
|
||||
}
|
||||
|
||||
private static String supplementaryToString(int c) {
|
||||
return String.valueOf(getHighSurrogate(c)) + getLowSurrogate(c);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the String representation of the codepoint, automatically dealing with surrogate pairs.
|
||||
* @param c char
|
||||
* @return string representation of the codepoint
|
||||
*/
|
||||
public static String toString(int c) {
|
||||
return isSupplementary(c) ? supplementaryToString(c) : String.valueOf((char) c);
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes leading and trailing bidi controls from the string.
|
||||
* @param string string
|
||||
* @return string without bidi controls
|
||||
*/
|
||||
public static String stripBidi(String string) {
|
||||
String s = string;
|
||||
if (s == null || s.length() <= 1) {
|
||||
return s;
|
||||
}
|
||||
if (isBidi(s.charAt(0))) {
|
||||
s = s.substring(1);
|
||||
}
|
||||
if (isBidi(s.charAt(s.length() - 1))) {
|
||||
s = s.substring(0, s.length() - 1);
|
||||
}
|
||||
return s;
|
||||
}
|
||||
|
||||
private static String wrap(String s, char c1, char c2) {
|
||||
StringBuilder buf = new StringBuilder(s);
|
||||
if (buf.length() > 1) {
|
||||
if (buf.charAt(0) != c1) {
|
||||
buf.insert(0, c1);
|
||||
}
|
||||
if (buf.charAt(buf.length() - 1) != c2) {
|
||||
buf.append(c2);
|
||||
}
|
||||
}
|
||||
return buf.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Wrap the string with the specified bidi control.
|
||||
* @param s string
|
||||
* @param c char
|
||||
* @return string with specified bidi control
|
||||
*/
|
||||
public static String wrapBidi(String s, char c) {
|
||||
switch (c) {
|
||||
case RLE:
|
||||
return wrap(s, RLE, PDF);
|
||||
case RLO:
|
||||
return wrap(s, RLO, PDF);
|
||||
case LRE:
|
||||
return wrap(s, LRE, PDF);
|
||||
case LRO:
|
||||
return wrap(s, LRO, PDF);
|
||||
case RLM:
|
||||
return wrap(s, RLM, RLM);
|
||||
case LRM:
|
||||
return wrap(s, LRM, LRM);
|
||||
default:
|
||||
return s;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* True if the codepoint is a digit.
|
||||
* @param codepoint code point
|
||||
* @return true if the codepoint is a digit
|
||||
*/
|
||||
public static boolean isDigit(int codepoint) {
|
||||
return inRange(codepoint, '0', '9');
|
||||
}
|
||||
|
||||
/**
|
||||
* True if the codepoint is part of the ASCII alphabet (a-z, A-Z).
|
||||
* @param codepoint code point
|
||||
* @return true if the codepoint is a digit
|
||||
*/
|
||||
public static boolean isAlpha(int codepoint) {
|
||||
return inRange(codepoint, 'A', 'Z') || inRange(codepoint, 'a', 'z');
|
||||
}
|
||||
|
||||
/**
|
||||
* True if isAlpha and isDigit both return true.
|
||||
* @param codepoint code point
|
||||
* @return true if isAlpha and isDigit both return true
|
||||
*/
|
||||
public static boolean isAlphaDigit(int codepoint) {
|
||||
return isDigit(codepoint) || isAlpha(codepoint);
|
||||
}
|
||||
|
||||
public static boolean isHex(int codepoint) {
|
||||
return isDigit(codepoint) || inRange(codepoint, 'a', 'f') || inRange(codepoint, 'A', 'F');
|
||||
}
|
||||
|
||||
/**
|
||||
* True if the codepoint is a bidi control character.
|
||||
* @param codepoint code point
|
||||
* @return true if the codepoint is a bidi control character
|
||||
*/
|
||||
public static boolean isBidi(int codepoint) {
|
||||
return codepoint == LRM ||
|
||||
codepoint == RLM ||
|
||||
codepoint == LRE ||
|
||||
codepoint == RLE ||
|
||||
codepoint == LRO ||
|
||||
codepoint == RLO ||
|
||||
codepoint == PDF;
|
||||
}
|
||||
|
||||
public static boolean isPctEnc(int codepoint) {
|
||||
return codepoint == '%' || isDigit(codepoint) ||
|
||||
inRange(codepoint, 'A', 'F') ||
|
||||
inRange(codepoint, 'a', 'f');
|
||||
}
|
||||
|
||||
public static boolean isMark(int codepoint) {
|
||||
return codepoint == '-' ||
|
||||
codepoint == '_' ||
|
||||
codepoint == '.' ||
|
||||
codepoint == '!' ||
|
||||
codepoint == '~' ||
|
||||
codepoint == '*' ||
|
||||
codepoint == '\\' ||
|
||||
codepoint == '\'' ||
|
||||
codepoint == '(' ||
|
||||
codepoint == ')';
|
||||
}
|
||||
|
||||
public static boolean isUnreserved(int codepoint) {
|
||||
return isAlphaDigit(codepoint) ||
|
||||
codepoint == '-' ||
|
||||
codepoint == '.' ||
|
||||
codepoint == '_' ||
|
||||
codepoint == '~';
|
||||
}
|
||||
|
||||
public static boolean isReserved(int codepoint) {
|
||||
return codepoint == '$' ||
|
||||
codepoint == '&' ||
|
||||
codepoint == '+' ||
|
||||
codepoint == ',' ||
|
||||
codepoint == '/' ||
|
||||
codepoint == ':' ||
|
||||
codepoint == ';' ||
|
||||
codepoint == '=' ||
|
||||
codepoint == '?' ||
|
||||
codepoint == '@' ||
|
||||
codepoint == '[' ||
|
||||
codepoint == ']';
|
||||
}
|
||||
|
||||
public static boolean isGenDelim(int codepoint) {
|
||||
return codepoint == '#' || codepoint == '/'
|
||||
|| codepoint == ':'
|
||||
|| codepoint == '?'
|
||||
|| codepoint == '@'
|
||||
|| codepoint == '['
|
||||
|| codepoint == ']';
|
||||
}
|
||||
|
||||
public static boolean isSubDelim(int codepoint) {
|
||||
return codepoint == '!' ||
|
||||
codepoint == '$' ||
|
||||
codepoint == '&' ||
|
||||
codepoint == '\'' ||
|
||||
codepoint == '(' ||
|
||||
codepoint == ')' ||
|
||||
codepoint == '*' ||
|
||||
codepoint == '+' ||
|
||||
codepoint == ',' ||
|
||||
codepoint == ';' ||
|
||||
codepoint == '=' ||
|
||||
codepoint == '\\';
|
||||
}
|
||||
|
||||
public static boolean isPchar(int codepoint) {
|
||||
return isUnreserved(codepoint) || codepoint == ':'
|
||||
|| codepoint == '@'
|
||||
|| codepoint == '&'
|
||||
|| codepoint == '='
|
||||
|| codepoint == '+'
|
||||
|| codepoint == '$'
|
||||
|| codepoint == ',';
|
||||
}
|
||||
|
||||
public static boolean isPath(int codepoint) {
|
||||
return isPchar(codepoint) || codepoint == ';' || codepoint == '/' || codepoint == '%' || codepoint == ',';
|
||||
}
|
||||
|
||||
public static boolean isPathNoDelims(int codepoint) {
|
||||
return isPath(codepoint) && !isGenDelim(codepoint);
|
||||
}
|
||||
|
||||
public static boolean isScheme(int codepoint) {
|
||||
return isAlphaDigit(codepoint) || codepoint == '+' || codepoint == '-' || codepoint == '.';
|
||||
}
|
||||
|
||||
public static boolean isUserInfo(int codepoint) {
|
||||
return isUnreserved(codepoint) || isSubDelim(codepoint) || isPctEnc(codepoint);
|
||||
}
|
||||
|
||||
public static boolean isQuery(int codepoint) {
|
||||
return isPchar(codepoint) || codepoint == ';' || codepoint == '/' || codepoint == '?' || codepoint == '%';
|
||||
}
|
||||
|
||||
public static boolean isFragment(int codepoint) {
|
||||
return isPchar(codepoint) || codepoint == '/' || codepoint == '?' || codepoint == '%';
|
||||
}
|
||||
|
||||
public static boolean isUcsChar(int codepoint) {
|
||||
return inRange(codepoint, '\u00A0', '\uD7FF') ||
|
||||
inRange(codepoint, '\uF900', '\uFDCF') ||
|
||||
inRange(codepoint, '\uFDF0', '\uFFEF') ||
|
||||
inRange(codepoint, 0x10000, 0x1FFFD) ||
|
||||
inRange(codepoint, 0x20000, 0x2FFFD) ||
|
||||
inRange(codepoint, 0x30000, 0x3FFFD) ||
|
||||
inRange(codepoint, 0x40000, 0x4FFFD) ||
|
||||
inRange(codepoint, 0x50000, 0x5FFFD) ||
|
||||
inRange(codepoint, 0x60000, 0x6FFFD) ||
|
||||
inRange(codepoint, 0x70000, 0x7FFFD) ||
|
||||
inRange(codepoint, 0x80000, 0x8FFFD) ||
|
||||
inRange(codepoint, 0x90000, 0x9FFFD) ||
|
||||
inRange(codepoint, 0xA0000, 0xAFFFD) ||
|
||||
inRange(codepoint, 0xB0000, 0xBFFFD) ||
|
||||
inRange(codepoint, 0xC0000, 0xCFFFD) ||
|
||||
inRange(codepoint, 0xD0000, 0xDFFFD) ||
|
||||
inRange(codepoint, 0xE1000, 0xEFFFD);
|
||||
}
|
||||
|
||||
public static boolean isIprivate(int codepoint) {
|
||||
return inRange(codepoint, '\uE000', '\uF8FF') ||
|
||||
inRange(codepoint, 0xF0000, 0xFFFFD) ||
|
||||
inRange(codepoint, 0x100000, 0x10FFFD);
|
||||
}
|
||||
|
||||
public static boolean isIunreserved(int codepoint) {
|
||||
return isAlphaDigit(codepoint) || isMark(codepoint) || isUcsChar(codepoint);
|
||||
}
|
||||
|
||||
public static boolean isIpchar(int codepoint) {
|
||||
return isIunreserved(codepoint) ||
|
||||
isSubDelim(codepoint) ||
|
||||
codepoint == ':' ||
|
||||
codepoint == '@' ||
|
||||
codepoint == '&' ||
|
||||
codepoint == '=' ||
|
||||
codepoint == '+' ||
|
||||
codepoint == '$';
|
||||
}
|
||||
|
||||
public static boolean isIpath(int codepoint) {
|
||||
return isIpchar(codepoint) ||
|
||||
codepoint == ';' ||
|
||||
codepoint == '/' ||
|
||||
codepoint == '%' ||
|
||||
codepoint == ',';
|
||||
}
|
||||
|
||||
public static boolean isIpathnodelims(int codepoint) {
|
||||
return isIpath(codepoint) && !isGenDelim(codepoint);
|
||||
}
|
||||
|
||||
public static boolean isIquery(int codepoint) {
|
||||
return isIpchar(codepoint) ||
|
||||
isIprivate(codepoint) ||
|
||||
codepoint == ';' ||
|
||||
codepoint == '/' ||
|
||||
codepoint == '?' ||
|
||||
codepoint == '%';
|
||||
}
|
||||
|
||||
public static boolean isIfragment(int codepoint) {
|
||||
return isIpchar(codepoint) || isIprivate(codepoint)
|
||||
|| codepoint == '/'
|
||||
|| codepoint == '?'
|
||||
|| codepoint == '%';
|
||||
}
|
||||
|
||||
public static boolean isIregname(int codepoint) {
|
||||
return isIunreserved(codepoint) || codepoint == '!'
|
||||
|| codepoint == '$'
|
||||
|| codepoint == '&'
|
||||
|| codepoint == '\''
|
||||
|| codepoint == '('
|
||||
|| codepoint == ')'
|
||||
|| codepoint == '*'
|
||||
|| codepoint == '+'
|
||||
|| codepoint == ','
|
||||
|| codepoint == ';'
|
||||
|| codepoint == '='
|
||||
|| codepoint == '"';
|
||||
}
|
||||
|
||||
public static boolean isIpliteral(int codepoint) {
|
||||
return isHex(codepoint) || codepoint == ':'
|
||||
|| codepoint == '['
|
||||
|| codepoint == ']';
|
||||
}
|
||||
|
||||
public static boolean isIhost(int codepoint) {
|
||||
return isIregname(codepoint) || isIpliteral(codepoint);
|
||||
}
|
||||
|
||||
public static boolean isRegname(int codepoint) {
|
||||
return isUnreserved(codepoint) || codepoint == '!'
|
||||
|| codepoint == '$'
|
||||
|| codepoint == '&'
|
||||
|| codepoint == '\''
|
||||
|| codepoint == '('
|
||||
|| codepoint == ')'
|
||||
|| codepoint == '*'
|
||||
|| codepoint == '+'
|
||||
|| codepoint == ','
|
||||
|| codepoint == ';'
|
||||
|| codepoint == '='
|
||||
|| codepoint == '"';
|
||||
}
|
||||
|
||||
public static boolean isIuserinfo(int codepoint) {
|
||||
return isIunreserved(codepoint) || codepoint == ';'
|
||||
|| codepoint == ':'
|
||||
|| codepoint == '&'
|
||||
|| codepoint == '='
|
||||
|| codepoint == '+'
|
||||
|| codepoint == '$'
|
||||
|| codepoint == ',';
|
||||
}
|
||||
|
||||
public static boolean isIserver(int codepoint) {
|
||||
return isIuserinfo(codepoint) || isIregname(codepoint)
|
||||
|| isAlphaDigit(codepoint)
|
||||
|| codepoint == '.'
|
||||
|| codepoint == ':'
|
||||
|| codepoint == '@'
|
||||
|| codepoint == '['
|
||||
|| codepoint == ']'
|
||||
|| codepoint == '%'
|
||||
|| codepoint == '-';
|
||||
}
|
||||
|
||||
/**
|
||||
* Verifies a sequence of codepoints using the specified filter.
|
||||
* @param ci code point iterator
|
||||
* @param profile profile
|
||||
*/
|
||||
public static void verify(CodepointIterator ci, Profile profile) {
|
||||
CodepointIterator rci = CodepointIterator.restrict(ci, profile.filter());
|
||||
while (rci.hasNext()) {
|
||||
rci.next();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Verifies a sequence of codepoints using the specified profile.
|
||||
* @param s string
|
||||
* @param profile profile
|
||||
*/
|
||||
public static void verify(String s, Profile profile) {
|
||||
if (s == null) {
|
||||
return;
|
||||
}
|
||||
verify(CodepointIterator.forCharSequence(s), profile);
|
||||
}
|
||||
|
||||
}
|
92
net/src/main/java/org/xbib/net/util/Codepoint.java
Normal file
92
net/src/main/java/org/xbib/net/util/Codepoint.java
Normal file
|
@ -0,0 +1,92 @@
|
|||
package org.xbib.net.util;
|
||||
|
||||
/**
|
||||
* Represents a single Unicode Codepoint.
|
||||
*/
|
||||
public class Codepoint implements Comparable<Codepoint> {
|
||||
|
||||
private final int value;
|
||||
|
||||
/**
|
||||
* Create a codepoint from a single char.
|
||||
* @param value char
|
||||
*/
|
||||
public Codepoint(char value) {
|
||||
this((int) value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a codepoint from a specific integer value.
|
||||
* @param value value
|
||||
*/
|
||||
public Codepoint(int value) {
|
||||
if (value < 0) {
|
||||
throw new IllegalArgumentException("invalid codepoint");
|
||||
}
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
/**
|
||||
* The codepoint value.
|
||||
* @return value
|
||||
*/
|
||||
public int getValue() {
|
||||
return value;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int compareTo(Codepoint o) {
|
||||
return value < o.value ? -1 : value == o.value ? 0 : 1;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return CharUtils.toString(value);
|
||||
}
|
||||
|
||||
public char[] toChars() {
|
||||
return toString().toCharArray();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the number of chars necessary to represent this codepoint. Returns 2 if this is a supplementary codepoint.
|
||||
* @return char count
|
||||
*/
|
||||
public int getCharCount() {
|
||||
return toChars().length;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
final int prime = 31;
|
||||
int result = 1;
|
||||
result = prime * result + value;
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
if (this == obj) {
|
||||
return true;
|
||||
}
|
||||
if (obj == null) {
|
||||
return false;
|
||||
}
|
||||
if (getClass() != obj.getClass()) {
|
||||
return false;
|
||||
}
|
||||
final Codepoint other = (Codepoint) obj;
|
||||
return value == other.value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the next codepoint.
|
||||
* @return next code point
|
||||
*/
|
||||
public Codepoint next() {
|
||||
if (value == 0x10ffff) {
|
||||
throw new IndexOutOfBoundsException();
|
||||
}
|
||||
return new Codepoint(value + 1);
|
||||
}
|
||||
}
|
10
net/src/main/java/org/xbib/net/util/CodepointFilter.java
Normal file
10
net/src/main/java/org/xbib/net/util/CodepointFilter.java
Normal file
|
@ -0,0 +1,10 @@
|
|||
package org.xbib.net.util;
|
||||
|
||||
/**
|
||||
* Filters are used in a variety of ways to filter or verify unicode codepoints.
|
||||
*/
|
||||
@FunctionalInterface
|
||||
public interface CodepointFilter {
|
||||
|
||||
boolean accept(int ch);
|
||||
}
|
268
net/src/main/java/org/xbib/net/util/CodepointIterator.java
Normal file
268
net/src/main/java/org/xbib/net/util/CodepointIterator.java
Normal file
|
@ -0,0 +1,268 @@
|
|||
package org.xbib.net.util;
|
||||
|
||||
import java.util.Iterator;
|
||||
import java.util.NoSuchElementException;
|
||||
|
||||
/**
|
||||
* Provides an iterator over Unicode Codepoints.
|
||||
*/
|
||||
public abstract class CodepointIterator implements Iterator<Codepoint> {
|
||||
|
||||
protected int position = -1;
|
||||
|
||||
protected int limit = -1;
|
||||
|
||||
public CodepointIterator() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a CodepointIterator for the specified char array.
|
||||
* @param array char array
|
||||
* @return code point iterator
|
||||
*/
|
||||
public static CodepointIterator forCharArray(char[] array) {
|
||||
return new CharArrayCodepointIterator(array);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a CodepointIterator for the specified CharSequence.
|
||||
* @param seq char sequence
|
||||
* @return code point iterator
|
||||
*/
|
||||
public static CodepointIterator forCharSequence(CharSequence seq) {
|
||||
return new CharSequenceCodepointIterator(seq);
|
||||
}
|
||||
|
||||
public static CodepointIterator restrict(CodepointIterator ci, CodepointFilter filter) {
|
||||
return new RestrictedCodepointIterator(ci, filter, false);
|
||||
}
|
||||
|
||||
public static CodepointIterator restrict(CodepointIterator ci, CodepointFilter filter, boolean scanning) {
|
||||
return new RestrictedCodepointIterator(ci, filter, scanning);
|
||||
}
|
||||
|
||||
public static CodepointIterator restrict(CodepointIterator ci, CodepointFilter filter, boolean scanning, boolean invert) {
|
||||
return new RestrictedCodepointIterator(ci, filter, scanning, invert);
|
||||
}
|
||||
|
||||
public CodepointIterator restrict(CodepointFilter filter) {
|
||||
return restrict(this, filter);
|
||||
}
|
||||
|
||||
public CodepointIterator restrict(CodepointFilter filter, boolean scanning) {
|
||||
return restrict(this, filter, scanning);
|
||||
}
|
||||
|
||||
public CodepointIterator restrict(CodepointFilter filter, boolean scanning, boolean invert) {
|
||||
return restrict(this, filter, scanning, invert);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the next char.
|
||||
* @return char
|
||||
*/
|
||||
protected abstract char get();
|
||||
|
||||
/**
|
||||
* Get the specified char.
|
||||
* @param index index
|
||||
* @return char
|
||||
*/
|
||||
protected abstract char get(int index);
|
||||
|
||||
/**
|
||||
* Checks if there are codepoints remaining.
|
||||
* @return true if there are codepoints remaining
|
||||
*/
|
||||
@Override
|
||||
public boolean hasNext() {
|
||||
return remaining() > 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the final index position.
|
||||
* @return final index position
|
||||
*/
|
||||
public int lastPosition() {
|
||||
int p = position();
|
||||
return (p > -1) ? (p >= limit()) ? p : p - 1 : -1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the next chars. If the codepoint is not supplemental, the char array will have a single member. If the
|
||||
* codepoint is supplemental, the char array will have two members, representing the high and low surrogate chars.
|
||||
* @return next chars
|
||||
*/
|
||||
public char[] nextChars(){
|
||||
if (hasNext()) {
|
||||
if (isNextSurrogate()) {
|
||||
char c1 = get();
|
||||
if (CharUtils.isHighSurrogate(c1) && position() < limit()) {
|
||||
char c2 = get();
|
||||
if (CharUtils.isLowSurrogate(c2)) {
|
||||
return new char[]{c1, c2};
|
||||
} else {
|
||||
throw new InvalidCharacterException(c2);
|
||||
}
|
||||
} else if (CharUtils.isLowSurrogate(c1) && position() > 0) {
|
||||
char c2 = get(position() - 2);
|
||||
if (CharUtils.isHighSurrogate(c2)) {
|
||||
return new char[]{c1, c2};
|
||||
} else {
|
||||
throw new InvalidCharacterException(c2);
|
||||
}
|
||||
}
|
||||
}
|
||||
return new char[]{get()};
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Peek the next chars in the iterator. If the codepoint is not supplemental, the char array will have a single
|
||||
* member. If the codepoint is supplemental, the char array will have two members, representing the high and low
|
||||
* surrogate chars.
|
||||
* @return chars
|
||||
*/
|
||||
public char[] peekChars() {
|
||||
return peekChars(position());
|
||||
}
|
||||
|
||||
/**
|
||||
* Peek the specified chars in the iterator. If the codepoint is not supplemental, the char array will have a single
|
||||
* member. If the codepoint is supplemental, the char array will have two members, representing the high and low
|
||||
* surrogate chars.
|
||||
* @return chars
|
||||
*/
|
||||
private char[] peekChars(int pos) {
|
||||
if (pos < 0 || pos >= limit()) {
|
||||
return null;
|
||||
}
|
||||
char c1 = get(pos);
|
||||
if (CharUtils.isHighSurrogate(c1) && pos < limit()) {
|
||||
char c2 = get(pos + 1);
|
||||
if (CharUtils.isLowSurrogate(c2)) {
|
||||
return new char[]{c1, c2};
|
||||
} else {
|
||||
throw new InvalidCharacterException(c2);
|
||||
}
|
||||
} else if (CharUtils.isLowSurrogate(c1) && pos > 1) {
|
||||
char c2 = get(pos - 1);
|
||||
if (CharUtils.isHighSurrogate(c2)) {
|
||||
return new char[]{c2, c1};
|
||||
} else {
|
||||
throw new InvalidCharacterException(c2);
|
||||
}
|
||||
} else {
|
||||
return new char[]{c1};
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the next codepoint.
|
||||
* @return code point
|
||||
*/
|
||||
@Override
|
||||
public Codepoint next() {
|
||||
if (remaining() > 0) {
|
||||
return toCodepoint(nextChars());
|
||||
} else {
|
||||
throw new NoSuchElementException();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Peek the next codepoint.
|
||||
* @return code point
|
||||
*/
|
||||
public Codepoint peek() {
|
||||
return toCodepoint(peekChars());
|
||||
}
|
||||
|
||||
/**
|
||||
* Peek the specified codepoint.
|
||||
* @param index index
|
||||
* @return code point
|
||||
*/
|
||||
public Codepoint peek(int index) {
|
||||
return toCodepoint(peekChars(index));
|
||||
}
|
||||
|
||||
private Codepoint toCodepoint(char[] chars) {
|
||||
return (chars == null) ? null : (chars.length == 1) ? new Codepoint(chars[0]) : CharUtils
|
||||
.toSupplementary(chars[0], chars[1]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the iterator position.
|
||||
* @param n iterator position
|
||||
*/
|
||||
public void position(int n) {
|
||||
if (n < 0 || n > limit()) {
|
||||
throw new ArrayIndexOutOfBoundsException(n);
|
||||
}
|
||||
position = n;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the iterator position.
|
||||
* @return position
|
||||
*/
|
||||
public int position() {
|
||||
return position;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the iterator limit.
|
||||
* @return limit
|
||||
*/
|
||||
public int limit() {
|
||||
return limit;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the remaining iterator size.
|
||||
* @return remaining size
|
||||
*/
|
||||
public int remaining() {
|
||||
return limit - position();
|
||||
}
|
||||
|
||||
private boolean isNextSurrogate() {
|
||||
if (!hasNext()) {
|
||||
return false;
|
||||
}
|
||||
char c = get(position());
|
||||
return CharUtils.isHighSurrogate(c) || CharUtils.isLowSurrogate(c);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if the char at the specified index is a high surrogate.
|
||||
* @param index index
|
||||
* @return true if the char at the specified index is a high surrogate
|
||||
*/
|
||||
public boolean isHigh(int index) {
|
||||
if (index < 0 || index > limit()) {
|
||||
throw new ArrayIndexOutOfBoundsException(index);
|
||||
}
|
||||
return CharUtils.isHighSurrogate(get(index));
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if the char at the specified index is a low surrogate.
|
||||
* @param index index
|
||||
* @return true if the char at the specified index is a low surrogate
|
||||
*/
|
||||
public boolean isLow(int index) {
|
||||
if (index < 0 || index > limit()) {
|
||||
throw new ArrayIndexOutOfBoundsException(index);
|
||||
}
|
||||
return CharUtils.isLowSurrogate(get(index));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void remove() {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,92 @@
|
|||
package org.xbib.net.util;
|
||||
|
||||
import java.util.NoSuchElementException;
|
||||
|
||||
/**
|
||||
* Base implementation of a CodepointIterator that filters the output of another CodpointIterator.
|
||||
*/
|
||||
public abstract class DelegatingCodepointIterator extends CodepointIterator {
|
||||
|
||||
private final CodepointIterator internal;
|
||||
|
||||
private boolean hasNext;
|
||||
|
||||
protected DelegatingCodepointIterator(CodepointIterator internal) {
|
||||
this.internal = internal;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected char get() {
|
||||
return internal.get();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected char get(int index) {
|
||||
return internal.get(index);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasNext() {
|
||||
hasNext = internal.hasNext();
|
||||
return hasNext;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isHigh(int index) {
|
||||
return internal.isHigh(index);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isLow(int index) {
|
||||
return internal.isLow(index);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int limit() {
|
||||
return internal.limit();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Codepoint next() {
|
||||
if (!hasNext) {
|
||||
throw new NoSuchElementException();
|
||||
}
|
||||
return internal.next();
|
||||
}
|
||||
|
||||
@Override
|
||||
public char[] nextChars() {
|
||||
return internal.nextChars();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Codepoint peek() {
|
||||
return internal.peek();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Codepoint peek(int index) {
|
||||
return internal.peek(index);
|
||||
}
|
||||
|
||||
@Override
|
||||
public char[] peekChars() {
|
||||
return internal.peekChars();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int position() {
|
||||
return internal.position();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int remaining() {
|
||||
return internal.remaining();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void position(int position) {
|
||||
internal.position(position);
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,17 @@
|
|||
package org.xbib.net.util;
|
||||
|
||||
@SuppressWarnings("serial")
|
||||
public class InvalidCharacterException extends RuntimeException {
|
||||
|
||||
private final int input;
|
||||
|
||||
public InvalidCharacterException(int input) {
|
||||
this.input = input;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getMessage() {
|
||||
return "Invalid Character 0x" + Integer.toHexString(input) + "(" + (char) input + ")";
|
||||
}
|
||||
|
||||
}
|
54
net/src/main/java/org/xbib/net/util/Profile.java
Normal file
54
net/src/main/java/org/xbib/net/util/Profile.java
Normal file
|
@ -0,0 +1,54 @@
|
|||
package org.xbib.net.util;
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
public enum Profile {
|
||||
NONE(codepoint -> true),
|
||||
ALPHA(codepoint -> !CharUtils.isAlpha(codepoint)),
|
||||
ALPHANUM(codepoint -> !CharUtils.isAlphaDigit(codepoint)),
|
||||
FRAGMENT(codepoint -> !CharUtils.isFragment(codepoint)),
|
||||
IFRAGMENT(codepoint -> !CharUtils.isIfragment(codepoint)),
|
||||
PATH(codepoint -> !CharUtils.isPath(codepoint)),
|
||||
IPATH(codepoint -> !CharUtils.isIpath(codepoint)),
|
||||
IUSERINFO(codepoint -> !CharUtils.isIuserinfo(codepoint)),
|
||||
USERINFO(codepoint -> !CharUtils.isUserInfo(codepoint)),
|
||||
QUERY(codepoint -> !CharUtils.isQuery(codepoint)),
|
||||
IQUERY(codepoint -> !CharUtils.isIquery(codepoint)),
|
||||
SCHEME(codepoint -> !CharUtils.isScheme(codepoint)),
|
||||
PATHNODELIMS(codepoint -> !CharUtils.isPathNoDelims(codepoint)),
|
||||
IPATHNODELIMS(codepoint -> !CharUtils.isIpathnodelims(codepoint)),
|
||||
IPATHNODELIMS_SEG(codepoint -> !CharUtils.isIpathnodelims(codepoint) && codepoint != '@' && codepoint != ':'),
|
||||
IREGNAME(codepoint -> !CharUtils.isIregname(codepoint)),
|
||||
IHOST(codepoint -> !CharUtils.isIhost(codepoint)),
|
||||
IPRIVATE(codepoint -> !CharUtils.isIprivate(codepoint)),
|
||||
RESERVED(codepoint -> !CharUtils.isReserved(codepoint)),
|
||||
IUNRESERVED(codepoint -> !CharUtils.isIunreserved(codepoint)),
|
||||
UNRESERVED(codepoint -> !CharUtils.isUnreserved(codepoint)),
|
||||
SCHEMESPECIFICPART(codepoint -> !CharUtils.isIunreserved(codepoint) && !CharUtils.isReserved(codepoint)
|
||||
&& !CharUtils.isIprivate(codepoint)
|
||||
&& !CharUtils.isPctEnc(codepoint)
|
||||
&& codepoint != '#'),
|
||||
AUTHORITY(codepoint -> !CharUtils.isRegname(codepoint) && !CharUtils.isUserInfo(codepoint) && !CharUtils.isGenDelim(codepoint)),
|
||||
ASCIISANSCRLF(codepoint -> !CharUtils.inRange(codepoint, 1, 9) && !CharUtils.inRange(codepoint, 14, 127)),
|
||||
PCT(codepoint -> !CharUtils.isPctEnc(codepoint)),
|
||||
STD3ASCIIRULES(codepoint -> !CharUtils.inRange(codepoint, 0x0000, 0x002C) &&
|
||||
!CharUtils.inRange(codepoint, 0x002E, 0x002F) &&
|
||||
!CharUtils.inRange(codepoint, 0x003A, 0x0040) &&
|
||||
!CharUtils.inRange(codepoint, 0x005B, 0x0060) &&
|
||||
!CharUtils.inRange(codepoint, 0x007B, 0x007F));
|
||||
|
||||
private final CodepointFilter filter;
|
||||
|
||||
Profile(CodepointFilter filter) {
|
||||
this.filter = filter;
|
||||
}
|
||||
|
||||
public CodepointFilter filter() {
|
||||
return filter;
|
||||
}
|
||||
|
||||
public boolean check(int codepoint) {
|
||||
return filter.accept(codepoint);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,83 @@
|
|||
package org.xbib.net.util;
|
||||
|
||||
class RestrictedCodepointIterator extends DelegatingCodepointIterator {
|
||||
|
||||
private final CodepointFilter filter;
|
||||
private final boolean scanningOnly;
|
||||
private final boolean notset;
|
||||
|
||||
RestrictedCodepointIterator(CodepointIterator internal, CodepointFilter filter, boolean scanningOnly) {
|
||||
this(internal, filter, scanningOnly, false);
|
||||
}
|
||||
|
||||
RestrictedCodepointIterator(CodepointIterator internal,
|
||||
CodepointFilter filter,
|
||||
boolean scanningOnly,
|
||||
boolean notset) {
|
||||
super(internal);
|
||||
this.filter = filter;
|
||||
this.scanningOnly = scanningOnly;
|
||||
this.notset = notset;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasNext() {
|
||||
boolean b = super.hasNext();
|
||||
if (scanningOnly) {
|
||||
try {
|
||||
int cp = super.peek(super.position()).getValue();
|
||||
if (b && cp != -1 && check(cp)) {
|
||||
return false;
|
||||
}
|
||||
} catch (InvalidCharacterException e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return b;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Codepoint next() {
|
||||
Codepoint cp = super.next();
|
||||
int v = cp.getValue();
|
||||
if (v != -1 && check(v)) {
|
||||
if (scanningOnly) {
|
||||
super.position(super.position() - 1);
|
||||
return null;
|
||||
} else {
|
||||
throw new InvalidCharacterException(v);
|
||||
}
|
||||
}
|
||||
return cp;
|
||||
}
|
||||
|
||||
private boolean check(int cp) {
|
||||
return notset == !filter.accept(cp);
|
||||
}
|
||||
|
||||
@Override
|
||||
public char[] nextChars() {
|
||||
char[] chars = super.nextChars();
|
||||
if (chars != null && chars.length > 0) {
|
||||
if (chars.length == 1 && check(chars[0])) {
|
||||
if (scanningOnly) {
|
||||
super.position(super.position() - 1);
|
||||
return null;
|
||||
} else {
|
||||
throw new InvalidCharacterException(chars[0]);
|
||||
}
|
||||
} else if (chars.length == 2) {
|
||||
int cp = CharUtils.toSupplementary(chars[0], chars[1]).getValue();
|
||||
if (check(cp)) {
|
||||
if (scanningOnly) {
|
||||
super.position(super.position() - 2);
|
||||
return null;
|
||||
} else {
|
||||
throw new InvalidCharacterException(cp);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return chars;
|
||||
}
|
||||
}
|
202
net/src/test/java/org/xbib/net/OtherIRITest.java
Normal file
202
net/src/test/java/org/xbib/net/OtherIRITest.java
Normal file
|
@ -0,0 +1,202 @@
|
|||
package org.xbib.net;
|
||||
|
||||
import java.net.URI;
|
||||
import java.net.URISyntaxException;
|
||||
import org.junit.jupiter.api.Disabled;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertFalse;
|
||||
|
||||
@Disabled
|
||||
public class OtherIRITest {
|
||||
|
||||
@Test
|
||||
public void testSimple() throws Exception {
|
||||
IRI iri = IRI.create("http://validator.w3.org/check?uri=http%3A%2F%2Fr\u00E9sum\u00E9.example.org");
|
||||
assertEquals("http", iri.getScheme());
|
||||
assertEquals("validator.w3.org", iri.getHost());
|
||||
assertEquals("/check", iri.getPath());
|
||||
assertEquals("//validator.w3.org/check?uri=http%3A%2F%2Frésumé.example.org", iri.getSchemeSpecificPart());
|
||||
|
||||
assertEquals("http://validator.w3.org/check?uri=http%3A%2F%2Fr\u00E9sum\u00E9.example.org", iri.toString());
|
||||
assertEquals("http://validator.w3.org/check?uri=http%3A%2F%2Fr%C3%A9sum%C3%A9.example.org", iri.toURI().toString());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testIpv4() throws Exception {
|
||||
IRI iri = IRI.create("http://127.0.0.1");
|
||||
assertEquals("http://127.0.0.1", iri.toURI().toString());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testIpv6() throws Exception {
|
||||
IRI iri = IRI.create("http://[2001:0db8:85a3:08d3:1319:8a2e:0370:7344]");
|
||||
assertEquals("http://[2001:0db8:85a3:08d3:1319:8a2e:0370:7344]", iri.toURI().toString());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testUnderscore() throws Exception{
|
||||
IRI iri = IRI.create("http://its_gbsc.cn.ibm.com/");
|
||||
assertEquals("http://its_gbsc.cn.ibm.com/", iri.toURI().toString());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testIpv6Invalid() throws URISyntaxException {
|
||||
IRI iri = IRI.create("http://[2001:0db8:85a3:08d3:1319:8a2e:0370:734o]");
|
||||
iri.toURI().toString();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testFile() throws Exception {
|
||||
IRI iri = IRI.create("file:///tmp/test/foo");
|
||||
assertEquals("file:///tmp/test/foo", iri.toURI().toString());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSimple2() throws Exception {
|
||||
IRI iri = IRI.create("http://www.example.org/red%09ros\u00E9#red");
|
||||
assertEquals("http://www.example.org/red%09ros%C3%A9#red", iri.toURI().toString());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testNotSoSimple() throws Exception {
|
||||
IRI iri = IRI.create("http://example.com/\uD800\uDF00\uD800\uDF01\uD800\uDF02");
|
||||
assertEquals("http://example.com/%F0%90%8C%80%F0%90%8C%81%F0%90%8C%82", iri.toURI().toString());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testIRItoURI() throws Exception {
|
||||
IRI iri = IRI.create("http://\u7D0D\u8C46.example.org/%E2%80%AE");
|
||||
URI uri = iri.toURI();
|
||||
assertEquals("http://xn--99zt52a.example.org/%E2%80%AE", uri.toString());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testComparison() throws Exception {
|
||||
IRI iri1 = IRI.create("http://www.example.org/");
|
||||
IRI iri2 = IRI.create("http://www.example.org/..");
|
||||
IRI iri3 = IRI.create("http://www.Example.org:80");
|
||||
|
||||
assertFalse(iri1.equals(iri2)); // false
|
||||
assertFalse(iri1.equals(iri3)); // false
|
||||
assertFalse(iri2.equals(iri1)); // false
|
||||
assertFalse(iri2.equals(iri3)); // false
|
||||
assertFalse(iri3.equals(iri1)); // false
|
||||
assertFalse(iri3.equals(iri2)); // false
|
||||
|
||||
/*assertTrue(iri1.normalize().equals(iri2.normalize()));
|
||||
assertTrue(iri1.normalize().equals(iri3.normalize()));
|
||||
assertTrue(iri2.normalize().equals(iri1.normalize()));
|
||||
assertTrue(iri2.normalize().equals(iri3.normalize()));
|
||||
assertTrue(iri3.normalize().equals(iri1.normalize()));
|
||||
assertTrue(iri3.normalize().equals(iri2.normalize()));*/
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testUCN() throws Exception {
|
||||
//IRI iri1 = IRI.create("http://www.example.org/r\u00E9sum\u00E9.html");
|
||||
//IRI iri2 = IRI.create("http://www.example.org/re\u0301sume\u0301.html", Normalizer.Form.NFC);
|
||||
//assertEquals(iri2, iri1);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testPercent() throws Exception {
|
||||
IRI iri1 = IRI.create("http://example.org/%7e%2Fuser?%2f");
|
||||
IRI iri2 = IRI.create("http://example.org/%7E%2fuser?/");
|
||||
//assertTrue(iri1.normalize().equals(iri2.normalize()));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testIDN() throws Exception {
|
||||
IRI iri1 = IRI.create("http://r\u00E9sum\u00E9.example.org");
|
||||
assertEquals("xn--rsum-bpad.example.org", iri1.getASCIIHost());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testRelative() throws Exception {
|
||||
IRI base = IRI.create("http://example.org/foo/");
|
||||
|
||||
assertEquals("http://example.org/", base.resolve("/").toString());
|
||||
assertEquals("http://example.org/test", base.resolve("/test").toString());
|
||||
assertEquals("http://example.org/foo/test", base.resolve("test").toString());
|
||||
assertEquals("http://example.org/test", base.resolve("../test").toString());
|
||||
assertEquals("http://example.org/foo/test", base.resolve("./test").toString());
|
||||
assertEquals("http://example.org/foo/", base.resolve("test/test/../../").toString());
|
||||
assertEquals("http://example.org/foo/?test", base.resolve("?test").toString());
|
||||
assertEquals("http://example.org/foo/#test", base.resolve("#test").toString());
|
||||
assertEquals("http://example.org/foo/", base.resolve(".").toString());
|
||||
}
|
||||
|
||||
/**
|
||||
* Try a variety of URI schemes. If any problematic schemes pop up, we should add a test for 'em here
|
||||
*/
|
||||
@Test
|
||||
public void testSchemes() throws Exception {
|
||||
|
||||
IRI iri = IRI.create("http://a:b@c.org:80/d/e?f#g");
|
||||
assertEquals("http", iri.getScheme());
|
||||
assertEquals("a:b", iri.getUserInfo());
|
||||
assertEquals("c.org", iri.getHost());
|
||||
assertEquals(80, iri.getPort());
|
||||
assertEquals("/d/e", iri.getPath());
|
||||
assertEquals("f", iri.getQuery());
|
||||
assertEquals("g", iri.getFragment());
|
||||
|
||||
iri = IRI.create("https://a:b@c.org:80/d/e?f#g");
|
||||
assertEquals("https", iri.getScheme());
|
||||
assertEquals("a:b", iri.getUserInfo());
|
||||
assertEquals("c.org", iri.getHost());
|
||||
assertEquals(80, iri.getPort());
|
||||
assertEquals("/d/e", iri.getPath());
|
||||
assertEquals("f", iri.getQuery());
|
||||
assertEquals("g", iri.getFragment());
|
||||
|
||||
iri = IRI.create("ftp://a:b@c.org:80/d/e?f#g");
|
||||
assertEquals("ftp", iri.getScheme());
|
||||
assertEquals("a:b", iri.getUserInfo());
|
||||
assertEquals("c.org", iri.getHost());
|
||||
assertEquals(80, iri.getPort());
|
||||
assertEquals("/d/e", iri.getPath());
|
||||
assertEquals("f", iri.getQuery());
|
||||
assertEquals("g", iri.getFragment());
|
||||
|
||||
iri = IRI.create("mailto:joe@example.org?subject=foo");
|
||||
assertEquals("mailto", iri.getScheme());
|
||||
assertEquals(null, iri.getUserInfo());
|
||||
assertEquals(null, iri.getHost());
|
||||
assertEquals(-1, iri.getPort());
|
||||
assertEquals("joe@example.org", iri.getPath());
|
||||
assertEquals("subject=foo", iri.getQuery());
|
||||
assertEquals(null, iri.getFragment());
|
||||
|
||||
iri = IRI.create("tag:example.org,2006:foo");
|
||||
assertEquals("tag", iri.getScheme());
|
||||
assertEquals(null, iri.getUserInfo());
|
||||
assertEquals(null, iri.getHost());
|
||||
assertEquals(-1, iri.getPort());
|
||||
assertEquals("example.org,2006:foo", iri.getPath());
|
||||
assertEquals(null, iri.getQuery());
|
||||
assertEquals(null, iri.getFragment());
|
||||
|
||||
iri = IRI.create("urn:lsid:ibm.com:example:82437234964354895798234d");
|
||||
assertEquals("urn", iri.getScheme());
|
||||
assertEquals(null, iri.getUserInfo());
|
||||
assertEquals(null, iri.getHost());
|
||||
assertEquals(-1, iri.getPort());
|
||||
assertEquals("lsid:ibm.com:example:82437234964354895798234d", iri.getPath());
|
||||
assertEquals(null, iri.getQuery());
|
||||
assertEquals(null, iri.getFragment());
|
||||
|
||||
iri = IRI.create("data:image/gif;base64,R0lGODdhMAAwAPAAAAAAAP");
|
||||
assertEquals("data", iri.getScheme());
|
||||
assertEquals(null, iri.getUserInfo());
|
||||
assertEquals(null, iri.getHost());
|
||||
assertEquals(-1, iri.getPort());
|
||||
assertEquals("image/gif;base64,R0lGODdhMAAwAPAAAAAAAP", iri.getPath());
|
||||
assertEquals(null, iri.getQuery());
|
||||
assertEquals(null, iri.getFragment());
|
||||
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in a new issue