blob: c8280b208b3e59ea7f5c574bf9fbe0d732389fea [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. The ASF licenses this file to You
* under the Apache License, Version 2.0 (the "License"); you may not
* use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License. For additional information regarding
* copyright in this work, please see the NOTICE file in the top level
* directory of this distribution.
*/
package org.apache.abdera2.common.iri;
import java.io.IOException;
import java.io.Serializable;
import java.net.MalformedURLException;
import java.net.URISyntaxException;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.apache.abdera2.common.text.CharUtils;
import org.apache.abdera2.common.text.InvalidCharacterException;
import org.apache.abdera2.common.text.NormalizationForm;
import org.apache.abdera2.common.text.UrlEncoding;
import org.apache.abdera2.common.text.CharUtils.Profile;
import com.ibm.icu.text.IDNA;
public final class IRI implements Serializable, Cloneable {
private static final long serialVersionUID = -4530530782760282284L;
protected Scheme _scheme;
private String authority;
private String userinfo;
private String host;
private int port = -1;
private String path;
private String query;
private String fragment;
private String a_host;
private String a_fragment;
private String a_path;
private String a_query;
private String a_userinfo;
private String a_authority;
public IRI(java.net.URL url) {
this(url.toString());
}
public IRI(java.net.URI uri) {
this(uri.toString());
}
public IRI(String iri) {
parse(iri);
init();
}
public IRI(String iri, NormalizationForm nf) throws IOException {
this(nf.normalize(iri));
}
public IRI(String scheme, String userinfo, String host, int port, String path, String query, String fragment) {
this._scheme = SchemeRegistry.get(scheme);
this.userinfo = userinfo;
this.host = host;
this.port = port;
this.path = path;
this.query = query;
this.fragment = fragment;
StringBuilder buf = new StringBuilder();
buildAuthority(buf, userinfo, host, port);
this.authority = (buf.length() != 0) ? buf.toString() : null;
init();
}
public IRI(String scheme, String authority, String path, String query, String fragment) {
this._scheme = SchemeRegistry.get(scheme);
this.authority = authority;
this.path = path;
this.query = query;
this.fragment = fragment;
parseAuthority();
init();
}
public IRI(String scheme, String host, String path, String fragment) {
this(scheme, null, host, -1, path, null, fragment);
}
IRI(Scheme _scheme,
String authority,
String userinfo,
String host,
int port,
String path,
String query,
String fragment) {
this._scheme = _scheme;
this.authority = authority;
this.userinfo = userinfo;
this.host = host;
this.port = port;
this.path = path;
this.query = query;
this.fragment = fragment;
init();
}
private void init() {
if (host != null && host.startsWith("[")) {
a_host = host;
} else {
try {
if (getHost() != null) {
a_host = IDNA.convertIDNToASCII(host, IDNA.USE_STD3_RULES).toString();
}
} catch (Throwable t) {
throw new IRISyntaxException("Invalid Internationalized Domain Name");
}
}
a_fragment = UrlEncoding.encode(fragment, Profile.FRAGMENT);
a_path = UrlEncoding.encode(path, Profile.PATH);
a_query = UrlEncoding.encode(query, Profile.QUERY, Profile.PATH);
a_userinfo = UrlEncoding.encode(userinfo, Profile.USERINFO);
a_authority = buildASCIIAuthority();
}
@Override
public int hashCode() {
final int PRIME = 31;
int result = 1;
result = PRIME * result + ((authority == null) ? 0 : authority.hashCode());
result = PRIME * result + ((fragment == null) ? 0 : fragment.hashCode());
result = PRIME * result + ((host == null) ? 0 : host.hashCode());
result = PRIME * result + ((path == null) ? 0 : path.hashCode());
result = PRIME * result + port;
result = PRIME * result + ((query == null) ? 0 : query.hashCode());
result = PRIME * result + ((_scheme == null) ? 0 : _scheme.hashCode());
result = PRIME * result + ((userinfo == null) ? 0 : 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 (authority == null) {
if (other.authority != null)
return false;
} else if (!authority.equals(other.authority))
return false;
if (fragment == null) {
if (other.fragment != null)
return false;
} else if (!fragment.equals(other.fragment))
return false;
if (host == null) {
if (other.host != null)
return false;
} else if (!host.equals(other.host))
return false;
if (path == null) {
if (other.path != null)
return false;
} else if (!path.equals(other.path))
return false;
if (port != other.port)
return false;
if (query == null) {
if (other.query != null)
return false;
} else if (!query.equals(other.query))
return false;
if (_scheme == null) {
if (other._scheme != null)
return false;
} else if (!_scheme.equals(other._scheme))
return false;
if (userinfo == null) {
if (other.userinfo != null)
return false;
} else if (!userinfo.equals(other.userinfo))
return false;
return true;
}
public String getAuthority() {
return (authority != null && authority.length() > 0) ? authority : null;
}
public String getFragment() {
return fragment;
}
public String getHost() {
return (host != null && host.length() > 0) ? host : null;
}
public String getASCIIHost() {
return (a_host != null && a_host.length() > 0) ? a_host : null;
}
public String getPath() {
return path;
}
public int getPort() {
return port;
}
public String getQuery() {
return query;
}
public String getScheme() {
return _scheme != null ? _scheme.name() : null;
}
public String getSchemeSpecificPart() {
return buildSchemeSpecificPart(authority, path, query, fragment);
}
public String getUserInfo() {
return userinfo;
}
void buildAuthority(StringBuilder buf, String aui, String ah, int port) {
if (aui != null && aui.length() != 0)
buf.append(aui)
.append('@');
if (ah != null && ah.length() != 0)
buf.append(ah);
if (port != -1)
buf.append(':')
.append(port);
}
private String buildASCIIAuthority() {
if (_scheme instanceof HttpScheme) {
StringBuilder buf = new StringBuilder();
buildAuthority(buf, getASCIIUserInfo(), getASCIIHost(), getPort());
return buf.toString();
} else {
return UrlEncoding.encode(authority, Profile.AUTHORITY);
}
}
public String getASCIIAuthority() {
return (a_authority != null && a_authority.length() > 0) ? a_authority : null;
}
public String getASCIIFragment() {
return a_fragment;
}
public String getASCIIPath() {
return a_path;
}
public String getASCIIQuery() {
return a_query;
}
public String getASCIIUserInfo() {
return a_userinfo;
}
public String getASCIISchemeSpecificPart() {
return buildSchemeSpecificPart(a_authority, a_path, a_query, a_fragment);
}
private String buildSchemeSpecificPart(String authority, String path, String query, String fragment) {
StringBuilder buf = new StringBuilder();
if (authority != null)
buf.append("//")
.append(authority);
if (path != null && path.length() != 0)
buf.append(path);
if (query != null)
buf.append('?')
.append(query);
if (fragment != null)
buf.append('#')
.append(fragment);
return buf.toString();
}
public Object clone() {
try {
return super.clone();
} catch (CloneNotSupportedException e) {
return new IRI(toString()); // not going to happen, but we have to
// catch it just in case
}
}
public boolean isAbsolute() {
return _scheme != null;
}
public boolean isOpaque() {
return path == null;
}
public static IRI relativize(IRI b, IRI c) {
if (c.isOpaque() || b.isOpaque())
return c;
if ((b._scheme == null && c._scheme != null) ||
(b._scheme != null && c._scheme == null) ||
(b._scheme != null && c._scheme != null &&
!b._scheme.equals(c._scheme)))
return c;
String bpath = normalize(b.getPath());
String cpath = normalize(c.getPath());
bpath = (bpath != null) ? bpath : "/";
cpath = (cpath != null) ? cpath : "/";
if (!bpath.equals(cpath)) {
if (bpath.charAt(bpath.length() - 1) != '/')
bpath += "/";
if (!cpath.startsWith(bpath))
return c;
}
IRI iri =
new IRI(null, null, null, null, -1, normalize(cpath.substring(bpath.length())), c.getQuery(), c
.getFragment());
return iri;
}
public IRI relativize(IRI iri) {
return relativize(this, iri);
}
public boolean isPathAbsolute() {
String path = getPath();
return (path != null) && path.length() > 0 && path.charAt(0) == '/';
}
public boolean isSameDocumentReference() {
return _scheme == null && authority == null
&& (path == null || path.length() == 0 || path.equals("."))
&& query == null;
}
public static IRI resolve(IRI b, String c) throws IOException {
return resolve(b, new IRI(c));
}
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 (IRI)b.clone();
} else {
return new IRI(b._scheme, b.getAuthority(), b.getUserInfo(), b.getHost(), b.getPort(),
normalize(b.getPath()), b.getQuery(), cfragment);
}
}
if (c.isAbsolute())
return c;
Scheme _scheme = b._scheme;
String query = c.getQuery();
String fragment = c.getFragment();
String userinfo = null;
String authority = null;
String host = null;
int port = -1;
String path = null;
if (c.getAuthority() == null) {
authority = b.getAuthority();
userinfo = b.getUserInfo();
host = b.getHost();
port = b.getPort();
path = c.isPathAbsolute() ? normalize(c.getPath()) : resolve(b.getPath(), c.getPath());
} else {
authority = c.getAuthority();
userinfo = c.getUserInfo();
host = c.getHost();
port = c.getPort();
path = normalize(c.getPath());
}
return new IRI(_scheme, authority, userinfo, host, port, path, query, fragment);
}
public IRI normalize() {
return normalize(this);
}
public static String normalizeString(String iri) {
return normalize(new IRI(iri)).toString();
}
public static IRI normalize(IRI iri) {
if (iri.isOpaque() || iri.getPath() == null)
return iri;
IRI normalized = null;
if (iri._scheme != null)
normalized = iri._scheme.normalize(iri);
return (normalized != null) ?
normalized :
new IRI(
iri._scheme,
iri.getAuthority(),
iri.getUserInfo(),
iri.getHost(),
iri.getPort(),
normalize(iri.getPath()),
UrlEncoding.encode(
UrlEncoding.decode(
iri.getQuery()),
Profile.IQUERY),
UrlEncoding.encode(
UrlEncoding.decode(
iri.getFragment()),
Profile.IFRAGMENT));
}
protected static String normalize(String path) {
if (path == null || path.length() == 0)
return "/";
String[] segments = path.split("/");
boolean trailingslash =
path.matches(".*/\\.{1}|.*\\./\\.{2}|.*/{1}$");
int pos = 0;
for (String segment : segments)
if ("..".equals(segment)) {
pos = (pos-1>-1)?pos-1:0;
segments[pos] = null;
} else if (!".".equals(segment))
segments[pos++] = segment;
if (pos < segments.length)
segments[pos] = null;
StringBuilder buf = new StringBuilder();
buf.ensureCapacity(path.length());
if (pos != 0) {
pos = 0;
while (pos<segments.length) {
String segment = segments[pos++];
if (segment == null) break;
if(pos-1>0||segment.length()>0)
buf.append('/');
buf.append(
UrlEncoding.encode(
UrlEncoding.decode(segment),
Profile.IPATHNODELIMS_SEG));
}
}
if (trailingslash && buf.length() > 1)
buf.append('/');
return buf.toString();
}
private static String resolve(String bpath, String cpath) {
if (bpath == null && cpath == null)
return null;
if (bpath == null && cpath != null) {
return (!cpath.startsWith("/")) ? "/" + cpath : cpath;
}
if (bpath != null && cpath == null)
return bpath;
if (bpath.equals(cpath))
return bpath;
StringBuilder buf = new StringBuilder("");
int n = bpath.lastIndexOf('/');
if (n > -1)
buf.append(bpath.substring(0, n + 1));
if (cpath.length() != 0)
buf.append(cpath);
if (buf.charAt(0) != '/')
buf.insert(0, '/');
return normalize(buf.toString());
}
public IRI resolve(IRI iri) {
return resolve(this, iri);
}
public IRI resolve(String iri) {
return resolve(this, new IRI(iri));
}
public String toString() {
StringBuilder buf = new StringBuilder();
String scheme = getScheme();
if (scheme != null && scheme.length() != 0)
buf.append(scheme)
.append(':');
buf.append(getSchemeSpecificPart());
return UrlEncoding.encode(buf.toString(), Profile.SCHEMESPECIFICPART);
}
public String toASCIIString() {
StringBuilder buf = new StringBuilder();
String scheme = getScheme();
if (scheme != null && scheme.length() != 0)
buf.append(scheme)
.append(':');
buf.append(getASCIISchemeSpecificPart());
return buf.toString();
}
public java.net.URI toURI() throws URISyntaxException {
return new java.net.URI(toASCIIString());
}
public java.net.URL toURL() throws MalformedURLException, URISyntaxException {
return toURI().toURL();
}
private void parseAuthority() {
if (authority != null) {
Matcher auth = AUTHORITYPATTERN.matcher(authority);
if (auth.find()) {
userinfo = auth.group(1);
host = auth.group(2);
if (auth.group(3) != null)
port = Integer.parseInt(auth.group(3));
else
port = -1;
}
try {
CharUtils.verify(userinfo, Profile.IUSERINFO);
CharUtils.verify(host, Profile.IHOST);
} catch (InvalidCharacterException e) {
throw new IRISyntaxException(e);
}
}
}
private void parse(String iri) {
try {
Matcher irim = IRIPATTERN.matcher(iri);
if (irim.find()) {
String scheme = irim.group(1);
_scheme = SchemeRegistry.get(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 static final Pattern IRIPATTERN =
Pattern.compile("^(?:([^:/?#]+):)?(?://([^/?#]*))?([^?#]*)(?:\\?([^#]*))?(?:#(.*))?");
private static final Pattern AUTHORITYPATTERN =
Pattern.compile("^(?:(.*)?@)?((?:\\[.*\\])|(?:[^:]*))?(?::(\\d+))?");
/**
* Returns a new IRI with a trailing slash appended to the path, if necessary.
* The query and fragment are omitted from the resulting IRI
*/
public IRI trailingSlash() {
return this.resolve(path + "/");
}
}