org.restlet.data.Reference Maven / Gradle / Ivy
Show all versions of org.apache.servicemix.bundles.restlet
/**
* Copyright 2005-2008 Noelios Technologies.
*
* The contents of this file are subject to the terms of the following open
* source licenses: LGPL 3.0 or LGPL 2.1 or CDDL 1.0 (the "Licenses"). You can
* select the license that you prefer but you may not use this file except in
* compliance with one of these Licenses.
*
* You can obtain a copy of the LGPL 3.0 license at
* http://www.gnu.org/licenses/lgpl-3.0.html
*
* You can obtain a copy of the LGPL 2.1 license at
* http://www.gnu.org/licenses/lgpl-2.1.html
*
* You can obtain a copy of the CDDL 1.0 license at
* http://www.sun.com/cddl/cddl.html
*
* See the Licenses for the specific language governing permissions and
* limitations under the Licenses.
*
* Alternatively, you can obtain a royaltee free commercial license with less
* limitations, transferable or non-transferable, directly at
* http://www.noelios.com/products/restlet-engine
*
* Restlet is a registered trademark of Noelios Technologies.
*/
package org.restlet.data;
import java.io.UnsupportedEncodingException;
import java.net.URLDecoder;
import java.net.URLEncoder;
import java.util.ArrayList;
import java.util.List;
import java.util.logging.Level;
import org.restlet.Context;
/**
* Reference to a Uniform Resource Identifier (URI). Contrary to the
* java.net.URI class, this interface represents mutable references. It strictly
* conforms to the RFC 3986 specifying URIs and follow its naming conventions.
*
*
* URI reference = absolute-reference | relative-reference
*
* absolute-reference = scheme ":" scheme-specific-part [ "#" fragment ]
* scheme-specific-part = ( hierarchical-part [ "?" query ] ) | opaque-part
* hierarchical-part = ( "//" authority path-abempty ) | path-absolute | path-rootless | path-empty
* authority = [ user-info "@" ] host-domain [ ":" host-port ]
*
* relative-reference = relative-part [ "?" query ] [ "#" fragment ]
* relative-part = ( "//" authority path-abempty ) | path-absolute | path-noscheme | path-empty
*
* path-abempty = begins with "/" or is empty
* path-absolute = begins with "/" but not "//"
* path-noscheme = begins with a non-colon segment
* path-rootless = begins with a segment
* path-empty = zero characters
*
*
*
* Note that this class doesn't encode or decode the reserved characters. It
* assumes that the URIs or the URI parts passed in are properly encoded using
* the standard URI encoding mechanism. You can use the static "encode()" and
* "decode()" methods for this purpose. Note that if an invalid URI character is
* detected by the constructor or one of the setters, a trace will be logged and
* the character will be automatically encoded.
*
*
* The fundamental point to underline is the difference between an URI
* "reference" and an URI. Contrary to an URI (the target identifier of a REST
* resource), an URI reference can be relative (with or without query and
* fragment part). This relative URI reference can then be resolved against a
* base reference via the getTargetRef() method which will return a new resolved
* Reference instance, an absolute URI reference with no base reference and with
* no dot-segments (the path segments "." and "..").
*
*
* You can also apply the getTargetRef() method on absolute references in order
* to solve the dot-segments. Note that applying the getRelativeRef() method on
* an absolute reference returns the current reference relatively to a base
* reference, if any, and solves the dot-segments.
*
*
* The Reference stores its data as a single string, the one passed to the
* constructor. This string can always be obtained using the toString() method.
* A couple of integer indexes are maintained to improve the extraction time of
* various reference properties (URI components).
*
*
* When you modify a specific component of the URI reference, via the setPath()
* method for example, the internal string is simply regenerated by updating
* only the relevant part. We try as much as possible to protect the bytes given
* to the Reference class instead of transparently parsing and normalizing the
* URI data. Our idea is to protect encodings and special characters in all case
* and reduce the memory size taken by this class while making Reference
* instances mutable.
*
*
* Because the base reference is only a property of the Reference ("baseRef").
* When you use the "Reference(base, path)" constructor, it is equivalent to
* doing:
* ref = new Reference(path);
* ref.setBaseRef(base);
*
*
* The base ref is not automatically resolved or "merged" with the rest of the
* reference information (the path here). For example, this let's you reuse a
* single reference as the base of several relative references. If you modify
* the base reference, all relative references are still accurate.
*
* Note that the name and value properties are thread safe, stored in volatile
* members.
*
* @author Jerome Louvel
* @see RFC 3986
*/
public class Reference {
/**
* Decodes a given string using the standard URI encoding mechanism and the
* UTF-8 character set.
*
* @param toDecode
* The string to decode.
* @return The decoded string.
*/
public static String decode(String toDecode) {
String result = null;
try {
result = URLDecoder.decode(toDecode, "UTF-8");
} catch (UnsupportedEncodingException uee) {
Context
.getCurrentLogger()
.log(
Level.WARNING,
"Unable to decode the string with the UTF-8 character set.",
uee);
}
return result;
}
/**
* Decodes a given string using the standard URI encoding mechanism. If the
* provided character set is null, the string is returned but not decoded.
* Note: The
* World Wide Web Consortium Recommendation states that UTF-8 should be
* used. Not doing so may introduce incompatibilites.
*
* @param toDecode
* The string to decode.
* @param characterSet
* The name of a supported character encoding.
* @return The decoded string or null if the named character encoding is not
* supported.
*/
public static String decode(String toDecode, CharacterSet characterSet) {
String result = null;
try {
result = (characterSet == null) ? toDecode : URLDecoder.decode(
toDecode, characterSet.getName());
} catch (UnsupportedEncodingException uee) {
Context
.getCurrentLogger()
.log(
Level.WARNING,
"Unable to decode the string with the UTF-8 character set.",
uee);
}
return result;
}
/**
* Encodes a given string using the standard URI encoding mechanism and the
* UTF-8 character set.
*
* @param toEncode
* The string to encode.
* @return The encoded string.
*/
public static String encode(String toEncode) {
String result = null;
if (toEncode != null) {
try {
result = URLEncoder.encode(toEncode, "UTF-8");
} catch (UnsupportedEncodingException uee) {
Context
.getCurrentLogger()
.log(
Level.WARNING,
"Unable to encode the string with the UTF-8 character set.",
uee);
}
}
return result;
}
/**
* Encodes a given string using the standard URI encoding mechanism. If the
* provided character set is null, the string is returned but not encoded.
*
* Note: The
* World Wide Web Consortium Recommendation states that UTF-8 should be
* used. Not doing so may introduce incompatibilites.
*
* @param toEncode
* The string to encode.
* @param characterSet
* The supported character encoding.
* @return The encoded string or null if the named character encoding is not
* supported.
*/
public static String encode(String toEncode, CharacterSet characterSet) {
String result = null;
try {
result = (characterSet == null) ? toEncode : URLEncoder.encode(
toEncode, characterSet.getName());
} catch (UnsupportedEncodingException uee) {
Context
.getCurrentLogger()
.log(
Level.WARNING,
"Unable to encode the string with the UTF-8 character set.",
uee);
}
return result;
}
/**
* Indicates if the given character is alphabetical (a-z or A-Z).
*
* @param character
* The character to test.
* @return True if the given character is alphabetical (a-z or A-Z).
*/
private static boolean isAlpha(int character) {
return isUpperCase(character) || isLowerCase(character);
}
/**
* Indicates if the given character is a digit (0-9).
*
* @param character
* The character to test.
* @return True if the given character is a digit (0-9).
*/
private static boolean isDigit(int character) {
return (character >= '0') && (character <= '9');
}
/**
* Indicates if the given character is a generic URI component delimiter
* character.
*
* @param character
* The character to test.
* @return True if the given character is a generic URI component delimiter
* character.
*/
public static boolean isGenericDelimiter(int character) {
return (character == ':') || (character == '/') || (character == '?')
|| (character == '#') || (character == '[')
|| (character == ']') || (character == '@');
}
/**
* Indicates if the given character is lower case (a-z).
*
* @param character
* The character to test.
* @return True if the given character is lower case (a-z).
*/
private static boolean isLowerCase(int character) {
return (character >= 'a') && (character <= 'z');
}
/**
* Indicates if the given character is a reserved URI character.
*
* @param character
* The character to test.
* @return True if the given character is a reserved URI character.
*/
public static boolean isReserved(int character) {
return isGenericDelimiter(character) || isSubDelimiter(character);
}
/**
* Indicates if the given character is an URI subcomponent delimiter
* character.
*
* @param character
* The character to test.
* @return True if the given character is an URI subcomponent delimiter
* character.
*/
public static boolean isSubDelimiter(int character) {
return (character == '!') || (character == '$') || (character == '&')
|| (character == '\'') || (character == '(')
|| (character == ')') || (character == '*')
|| (character == '+') || (character == ',')
|| (character == ';') || (character == '=');
}
/**
* Indicates if the given character is an unreserved URI character.
*
* @param character
* The character to test.
* @return True if the given character is an unreserved URI character.
*/
public static boolean isUnreserved(int character) {
return isAlpha(character) || isDigit(character) || (character == '-')
|| (character == '.') || (character == '_')
|| (character == '~');
}
/**
* Indicates if the given character is upper case (A-Z).
*
* @param character
* The character to test.
* @return True if the given character is upper case (A-Z).
*/
private static boolean isUpperCase(int character) {
return (character >= 'A') && (character <= 'Z');
}
/**
* Indicates if the given character is a valid URI character.
*
* @param character
* The character to test.
* @return True if the given character is a valid URI character.
*/
public static boolean isValid(int character) {
return isReserved(character) || isUnreserved(character)
|| (character == '%');
}
/**
* Creates a reference string from its parts.
*
* @param scheme
* The scheme ("http", "https" or "ftp").
* @param hostName
* The host name or IP address.
* @param hostPort
* The host port (default ports are correctly ignored).
* @param path
* The path component for hierarchical identifiers.
* @param query
* The optional query component for hierarchical identifiers.
* @param fragment
* The optional fragment identifier.
* @return The reference as String.
*/
public static String toString(String scheme, String hostName,
Integer hostPort, String path, String query, String fragment) {
String host = hostName;
// Appends the host port number
if (hostPort != null) {
final int defaultPort = Protocol.valueOf(scheme).getDefaultPort();
if (hostPort != defaultPort) {
host = hostName + ':' + hostPort;
}
}
return toString(scheme, host, path, query, fragment);
}
/**
* Creates a relative reference string from its parts.
*
* @param relativePart
* The relative part component.
* @param query
* The optional query component for hierarchical identifiers.
* @param fragment
* The optional fragment identifier.
* @return The relative reference as a String.
*/
public static String toString(String relativePart, String query,
String fragment) {
final StringBuilder sb = new StringBuilder();
// Append the path
if (relativePart != null) {
sb.append(relativePart);
}
// Append the query string
if (query != null) {
sb.append('?').append(query);
}
// Append the fragment identifier
if (fragment != null) {
sb.append('#').append(fragment);
}
// Actually construct the reference
return sb.toString();
}
/**
* Creates a reference string from its parts.
*
* @param scheme
* The scheme ("http", "https" or "ftp").
* @param host
* The host name or IP address plus the optional port number.
* @param path
* The path component for hierarchical identifiers.
* @param query
* The optional query component for hierarchical identifiers.
* @param fragment
* The optional fragment identifier.
* @return The reference a String.
*/
public static String toString(String scheme, String host, String path,
String query, String fragment) {
final StringBuilder sb = new StringBuilder();
if (scheme != null) {
// Append the scheme and host name
sb.append(scheme.toLowerCase()).append("://").append(host);
}
// Append the path
if (path != null) {
sb.append(path);
}
// Append the query string
if (query != null) {
sb.append('?').append(query);
}
// Append the fragment identifier
if (fragment != null) {
sb.append('#').append(fragment);
}
// Actually construct the reference
return sb.toString();
}
/** The base reference for relative references. */
private volatile Reference baseRef;
/** The fragment separator index. */
private volatile int fragmentIndex;
/** The internal reference. */
private volatile String internalRef;
/** The query separator index. */
private volatile int queryIndex;
/** The scheme separator index. */
private volatile int schemeIndex;
/**
* Empty constructor.
*/
public Reference() {
this((Reference) null, (String) null);
}
/**
* Constructor for a protocol and host name. Uses the default port for the
* given protocol.
*
* @param protocol
* Protocol/scheme to use
* @param hostName
* The host name or IP address.
*/
public Reference(Protocol protocol, String hostName) {
this(protocol, hostName, protocol.getDefaultPort());
}
/**
* Constructor for a protocol, host name and host port
*
* @param protocol
* Protocol/scheme to use
* @param hostName
* The host name or IP address.
* @param hostPort
* The host port (default ports are correctly ignored).
*/
public Reference(Protocol protocol, String hostName, int hostPort) {
this(protocol.getSchemeName(), hostName, hostPort, null, null, null);
}
/**
* Clone constructor.
*
* @param ref
* The reference to clone.
*/
public Reference(Reference ref) {
this(ref.baseRef, ref.internalRef);
}
/**
* Constructor from an URI reference (most likely relative).
*
* @param baseRef
* The base reference.
* @param uriReference
* The URI reference, either absolute or relative.
*/
public Reference(Reference baseRef, Reference uriReference) {
this(baseRef, uriReference.toString());
}
/**
* Constructor from an URI reference (most likely relative).
*
* @param baseRef
* The base reference.
* @param uriRef
* The URI reference, either absolute or relative.
*/
public Reference(Reference baseRef, String uriRef) {
uriRef = encodeInvalidCharacters(uriRef);
this.baseRef = baseRef;
this.internalRef = uriRef;
updateIndexes();
}
/**
* Constructor of relative reference from its parts.
*
* @param baseRef
* The base reference.
* @param relativePart
* The relative part component (most of the time it is the path
* component).
* @param query
* The optional query component for hierarchical identifiers.
* @param fragment
* The optional fragment identifier.
*/
public Reference(Reference baseRef, String relativePart, String query,
String fragment) {
this(baseRef, toString(relativePart, query, fragment));
}
/**
* Constructor from an URI reference.
*
* @param uriReference
* The URI reference, either absolute or relative.
*/
public Reference(String uriReference) {
this((Reference) null, uriReference);
}
/**
* Constructor from an identifier and a fragment.
*
* @param identifier
* The resource identifier.
* @param fragment
* The fragment identifier.
*/
public Reference(String identifier, String fragment) {
this((fragment == null) ? identifier : identifier + '#' + fragment);
}
/**
* Constructor of absolute reference from its parts.
*
* @param scheme
* The scheme ("http", "https" or "ftp").
* @param hostName
* The host name or IP address.
* @param hostPort
* The host port (default ports are correctly ignored).
* @param path
* The path component for hierarchical identifiers.
* @param query
* The optional query component for hierarchical identifiers.
* @param fragment
* The optional fragment identifier.
*/
public Reference(String scheme, String hostName, int hostPort, String path,
String query, String fragment) {
this(toString(scheme, hostName, hostPort, path, query, fragment));
}
/**
* Adds a parameter to the query component. The name and value are
* automatically encoded if necessary.
*
* @param name
* The parameter name.
* @param value
* The optional parameter value.
* @return The updated reference.
*/
public Reference addQueryParameter(String name, String value) {
final String query = getQuery();
if (query == null) {
if (value == null) {
setQuery(encode(name));
} else {
setQuery(encode(name) + '=' + encode(value));
}
} else {
if (value == null) {
setQuery(query + '&' + encode(name));
} else {
setQuery(query + '&' + encode(name) + '=' + encode(value));
}
}
return this;
}
/**
* Adds a segment at the end of the path. If the current path doesn't end
* with a slash character, one is inserted before the new segment value. The
* value is automatically encoded if necessary.
*
* @param value
* The segment value to add.
* @return The updated reference.
*/
public Reference addSegment(String value) {
final String path = getPath();
if (value != null) {
if (path == null) {
setPath("/" + value);
} else {
if (path.endsWith("/")) {
setPath(path + encode(value));
} else {
setPath(path + "/" + encode(value));
}
}
}
return this;
}
@Override
public Reference clone() {
final Reference newRef = new Reference();
if (this.baseRef == null) {
newRef.baseRef = null;
} else if (equals(this.baseRef)) {
newRef.baseRef = newRef;
} else {
newRef.baseRef = this.baseRef.clone();
}
newRef.fragmentIndex = this.fragmentIndex;
newRef.internalRef = this.internalRef;
newRef.queryIndex = this.queryIndex;
newRef.schemeIndex = this.schemeIndex;
return newRef;
}
/**
* Checks if all characters are valid and encodes invalid characters if
* necessary.
*
* @param uriRef
* The URI reference to check.
* @return The original reference, eventually with invalid URI characters
* encoded.
*/
private String encodeInvalidCharacters(String uriRef)
throws IllegalArgumentException {
String result = uriRef;
if (uriRef != null) {
boolean valid = true;
// Ensure that all characters are valid, otherwise encode them
for (int i = 0; valid && (i < uriRef.length()); i++) {
if (!isValid(uriRef.charAt(i))) {
valid = false;
Context.getCurrentLogger().fine(
"Invalid character detected in URI reference at index '"
+ i + "': \"" + uriRef.charAt(i)
+ "\". It will be automatically encoded.");
}
}
if (!valid) {
StringBuilder sb = new StringBuilder();
for (int i = 0; (i < uriRef.length()); i++) {
if (isValid(uriRef.charAt(i))) {
sb.append(uriRef.charAt(i));
} else {
sb.append(encode(String.valueOf(uriRef.charAt(i))));
}
}
result = sb.toString();
}
}
return result;
}
/**
* Indicates whether some other object is "equal to" this one.
*
* @param object
* The object to compare to.
* @return True if this object is the same as the obj argument.
*/
@Override
public boolean equals(Object object) {
if (object instanceof Reference) {
final Reference ref = (Reference) object;
if (this.internalRef == null) {
return ref.internalRef == null;
}
return this.internalRef.equals(ref.internalRef);
}
return false;
}
/**
* Returns the authority component for hierarchical identifiers. Includes
* the user info, host name and the host port number.
* Note that no URI decoding is done by this method.
*
* @return The authority component for hierarchical identifiers.
*/
public String getAuthority() {
final String part = isRelative() ? getRelativePart()
: getSchemeSpecificPart();
if ((part != null) && part.startsWith("//")) {
int index = part.indexOf('/', 2);
if (index != -1) {
return part.substring(2, index);
}
index = part.indexOf('?');
if (index != -1) {
return part.substring(2, index);
}
return part.substring(2);
}
return null;
}
/**
* Returns the optionnally decoded authority component.
*
* @param decode
* Indicates if the result should be decoded using the
* {@link #decode(String)} method.
* @return The optionnally decoded authority component.
* @see #getAuthority()
*/
public String getAuthority(boolean decode) {
return decode ? decode(getAuthority()) : getAuthority();
}
/**
* Returns the base reference for relative references.
*
* @return The base reference for relative references.
*/
public Reference getBaseRef() {
return this.baseRef;
}
/**
* Returns the optional extensions for hierarchical identifiers. An
* extensions part starts after the first '.' character of the last path
* segment and ends with either the end of the segment of with the first ';'
* character (matrix start). It is a token similar to file extensions
* separated by '.' characters. The value can be ommited.
* Note that no URI decoding is done by this method.
*
* @return The extensions or null.
* @see #getExtensionsAsArray()
* @see #setExtensions(String)
*/
public String getExtensions() {
String result = null;
final String lastSegment = getLastSegment();
if (lastSegment != null) {
final int extensionIndex = lastSegment.indexOf('.');
final int matrixIndex = lastSegment.indexOf(';');
if (extensionIndex != -1) {
// Extensions found
if (matrixIndex != -1) {
result = lastSegment.substring(extensionIndex + 1,
matrixIndex);
} else {
// No matrix found
result = lastSegment.substring(extensionIndex + 1);
}
}
}
return result;
}
/**
* Returns the extensions as an array or null if no extension is found.
*
* @return The extensions as an array or null if no extension is found.
* @see #getExtensions()
*/
public String[] getExtensionsAsArray() {
String[] result = null;
final String extensions = getExtensions();
if (extensions != null) {
result = extensions.split("\\.");
}
return result;
}
/**
* Returns the fragment identifier.
* Note that no URI decoding is done by this method.
*
* @return The fragment identifier.
*/
public String getFragment() {
if (hasFragment()) {
return this.internalRef.substring(this.fragmentIndex + 1);
}
return null;
}
/**
* Returns the optionnally decoded fragment identifier.
*
* @param decode
* Indicates if the result should be decoded using the
* {@link #decode(String)} method.
* @return The optionnally decoded fragment identifier.
* @see #getFragment()
*/
public String getFragment(boolean decode) {
return decode ? decode(getFragment()) : getFragment();
}
/**
* Returns the hierarchical part which is equivalent to the scheme specific
* part less the query component.
* Note that no URI decoding is done by this method.
*
* @return The hierarchical part .
*/
public String getHierarchicalPart() {
if (hasScheme()) {
// Scheme found
if (hasQuery()) {
// Query found
return this.internalRef.substring(this.schemeIndex + 1,
this.queryIndex);
}
// No query found
if (hasFragment()) {
// Fragment found
return this.internalRef.substring(this.schemeIndex + 1,
this.fragmentIndex);
}
// No fragment found
return this.internalRef.substring(this.schemeIndex + 1);
}
// No scheme found
if (hasQuery()) {
// Query found
return this.internalRef.substring(0, this.queryIndex);
}
if (hasFragment()) {
// Fragment found
return this.internalRef.substring(0, this.fragmentIndex);
}
// No fragment found
return this.internalRef;
}
/**
* Returns the optionnally decoded hierarchical part.
*
* @param decode
* Indicates if the result should be decoded using the
* {@link #decode(String)} method.
* @return The optionnally decoded hierarchical part.
* @see #getHierarchicalPart()
*/
public String getHierarchicalPart(boolean decode) {
return decode ? decode(getHierarchicalPart()) : getHierarchicalPart();
}
/**
* Returns the host domain name component for server based hierarchical
* identifiers. It can also be replaced by an IP address when no domain name
* was registered.
* Note that no URI decoding is done by this method.
*
* @return The host domain name component for server based hierarchical
* identifiers.
*/
public String getHostDomain() {
String result = null;
final String authority = getAuthority();
if (authority != null) {
final int index1 = authority.indexOf('@');
// We must prevent the case where the userinfo part contains ':'
final int index2 = authority.indexOf(':', (index1 == -1 ? 0
: index1));
if (index1 != -1) {
// User info found
if (index2 != -1) {
// Port found
result = authority.substring(index1 + 1, index2);
} else {
// No port found
result = authority.substring(index1 + 1);
}
} else {
// No user info found
if (index2 != -1) {
// Port found
result = authority.substring(0, index2);
} else {
// No port found
result = authority;
}
}
}
return result;
}
/**
* Returns the optionnally decoded host domain name component.
*
* @param decode
* Indicates if the result should be decoded using the
* {@link #decode(String)} method.
* @return The optionnally decoded host domain name component.
* @see #getHostDomain()
*/
public String getHostDomain(boolean decode) {
return decode ? decode(getHostDomain()) : getHostDomain();
}
/**
* Returns the host identifier. Includes the scheme, the host name and the
* host port number.
* Note that no URI decoding is done by this method.
*
* @return The host identifier.
*/
public String getHostIdentifier() {
final StringBuilder result = new StringBuilder();
result.append(getScheme()).append("://").append(getAuthority());
return result.toString();
}
/**
* Returns the optionnally decoded host identifier.
*
* @param decode
* Indicates if the result should be decoded using the
* {@link #decode(String)} method.
* @return The optionnally decoded host identifier.
* @see #getHostIdentifier()
*/
public String getHostIdentifier(boolean decode) {
return decode ? decode(getHostIdentifier()) : getHostIdentifier();
}
/**
* Returns the optional port number for server based hierarchical
* identifiers.
*
* @return The optional port number for server based hierarchical
* identifiers or -1 if the port number does not exist.
*/
public int getHostPort() {
int result = -1;
final String authority = getAuthority();
if (authority != null) {
final int index1 = authority.indexOf('@');
// We must prevent the case where the userinfo part contains ':'
final int index = authority.indexOf(':',
(index1 == -1 ? 0 : index1));
if (index != -1) {
try {
result = Integer.parseInt(authority.substring(index + 1));
} catch (NumberFormatException nfe) {
Context.getCurrentLogger().log(
Level.WARNING,
"Can't parse hostPort : [hostRef,requestUri]=["
+ getBaseRef() + "," + this.internalRef
+ "]");
}
}
}
return result;
}
/**
* Returns the absolute resource identifier, without the fragment.
* Note that no URI decoding is done by this method.
*
* @return The absolute resource identifier, without the fragment.
*/
public String getIdentifier() {
if (hasFragment()) {
// Fragment found
return this.internalRef.substring(0, this.fragmentIndex);
}
// No fragment found
return this.internalRef;
}
/**
* Returns the optionnally decoded absolute resource identifier.
*
* @param decode
* Indicates if the result should be decoded using the
* {@link #decode(String)} method.
* @return The optionnally decoded absolute resource identifier.
* @see #getIdentifier()
*/
public String getIdentifier(boolean decode) {
return decode ? decode(getIdentifier()) : getIdentifier();
}
/**
* Returns the last segment of a hierarchical path.
* For example the "/a/b/c" and "/a/b/c/" paths have the same segments: "a",
* "b", "c.
* Note that no URI decoding is done by this method.
*
* @return The last segment of a hierarchical path.
*/
public String getLastSegment() {
String result = null;
String path = getPath();
if (path != null) {
if (path.endsWith("/")) {
path = path.substring(0, path.length() - 1);
}
final int lastSlash = path.lastIndexOf('/');
if (lastSlash != -1) {
result = path.substring(lastSlash + 1);
}
}
return result;
}
/**
* Returns the optionnally decoded last segment.
*
* @param decode
* Indicates if the result should be decoded using the
* {@link #decode(String)} method.
* @return The optionnally decoded last segment.
* @see #getLastSegment()
*/
public String getLastSegment(boolean decode) {
return getLastSegment(decode, false);
}
/**
* Returns the optionnally decoded last segment.
*
* @param decode
* Indicates if the result should be decoded using the
* {@link #decode(String)} method.
* @param excludeMatrix
* @return The optionnally decoded last segment.
* @see #getLastSegment()
*/
public String getLastSegment(boolean decode, boolean excludeMatrix) {
String result = getLastSegment();
if (excludeMatrix && (result != null)) {
final int matrixIndex = result.indexOf(';');
if (matrixIndex != -1) {
result = result.substring(0, matrixIndex);
}
}
return decode ? decode(result) : result;
}
/**
* Returns the optional matrix for hierarchical identifiers. A matrix part
* starts after the first ';' character of the last path segment. It is a
* sequence of 'name=value' parameters separated by ';' characters. The
* value can be ommited.
* Note that no URI decoding is done by this method.
*
* @return The matrix or null.
*/
public String getMatrix() {
final String lastSegment = getLastSegment();
if (lastSegment != null) {
final int matrixIndex = lastSegment.indexOf(';');
if (matrixIndex != -1) {
return lastSegment.substring(matrixIndex + 1);
}
}
// No matrix found
return null;
}
/**
* Returns the optionnally decoded matrix.
*
* @param decode
* Indicates if the result should be decoded using the
* {@link #decode(String)} method.
* @return The optionnally decoded matrix.
* @see #getMatrix()
*/
public String getMatrix(boolean decode) {
return decode ? decode(getMatrix()) : getMatrix();
}
/**
* Returns the optional matrix as a form.
*
* @return The optional matrix component as a form.
*/
public Form getMatrixAsForm() {
return new Form(getMatrix(), ';');
}
/**
* Returns the optional matrix as a form submission.
*
* @param characterSet
* The supported character encoding.
* @return The optional matrix as a form.
*/
public Form getMatrixAsForm(CharacterSet characterSet) {
return new Form(getMatrix(), characterSet, ';');
}
/**
* Returns the parent reference of a hierarchical reference. The last slash
* of the path will be considered as the end of the parent path.
*
* @return The parent reference of a hierarchical reference.
*/
public Reference getParentRef() {
Reference result = null;
if (isHierarchical()) {
String parentRef = null;
String path = getPath();
if (!path.equals("/") && !path.equals("")) {
if (path.endsWith("/")) {
path = path.substring(0, path.length() - 1);
}
parentRef = getHostIdentifier()
+ path.substring(0, path.lastIndexOf('/') + 1);
} else {
parentRef = this.internalRef;
}
result = new Reference(parentRef);
}
return result;
}
/**
* Returns the path component for hierarchical identifiers.
* Note that no URI decoding is done by this method.
*
* @return The path component for hierarchical identifiers.
*/
public String getPath() {
String result = null;
final String part = isRelative() ? getRelativePart()
: getSchemeSpecificPart();
if (part != null) {
if (part.startsWith("//")) {
// Authority found
final int index1 = part.indexOf('/', 2);
if (index1 != -1) {
// Path found
final int index2 = part.indexOf('?');
if (index2 != -1) {
// Query found
result = part.substring(index1, index2);
} else {
// No query found
result = part.substring(index1);
}
} else {
// Path must be empty in this case
}
} else {
// No authority found
final int index = part.indexOf('?');
if (index != -1) {
// Query found
result = part.substring(0, index);
} else {
// No query found
result = part;
}
}
}
return result;
}
/**
* Returns the optionnally decoded path component.
*
* @param decode
* Indicates if the result should be decoded using the
* {@link #decode(String)} method.
* @return The optionnally decoded path component.
* @see #getPath()
*/
public String getPath(boolean decode) {
return decode ? decode(getPath()) : getPath();
}
/**
* Returns the optional query component for hierarchical identifiers.
* Note that no URI decoding is done by this method.
*
* @return The query component or null.
*/
public String getQuery() {
if (hasQuery()) {
// Query found
if (hasFragment()) {
if (this.queryIndex < this.fragmentIndex) {
// Fragment found and query sign not inside fragment
return this.internalRef.substring(this.queryIndex + 1,
this.fragmentIndex);
}
return null;
}
// No fragment found
return this.internalRef.substring(this.queryIndex + 1);
}
// No query found
return null;
}
/**
* Returns the optionnally decoded query component.
*
* @param decode
* Indicates if the result should be decoded using the
* {@link #decode(String)} method.
* @return The optionnally decoded query component.
* @see #getQuery()
*/
public String getQuery(boolean decode) {
return decode ? decode(getQuery()) : getQuery();
}
/**
* Returns the optional query component as a form.
*
* @return The optional query component as a form.
*/
public Form getQueryAsForm() {
return new Form(getQuery());
}
/**
* Returns the optional query component as a form submission.
*
* @param characterSet
* The supported character encoding.
* @return The optional query component as a form submission.
*/
public Form getQueryAsForm(CharacterSet characterSet) {
return new Form(getQuery(), characterSet);
}
/**
* Returns the relative part of relative references, without the query and
* fragment. If the reference is absolute, then null is returned.
* Note that no URI decoding is done by this method.
*
* @return The relative part.
*/
public String getRelativePart() {
return isRelative() ? toString(false, false) : null;
}
/**
* Returns the optionnally decoded relative part.
*
* @param decode
* Indicates if the result should be decoded using the
* {@link #decode(String)} method.
* @return The optionnally decoded relative part.
* @see #getRelativePart()
*/
public String getRelativePart(boolean decode) {
return decode ? decode(getRelativePart()) : getRelativePart();
}
/**
* Returns the current reference as a relative reference to the current base
* reference. This method should only be invoked for absolute references,
* otherwise an IllegalArgumentException will be raised.
*
* @return The current reference as a relative reference to the current base
* reference.
* @see #getRelativeRef(Reference)
*/
public Reference getRelativeRef() {
return getRelativeRef(getBaseRef());
}
/**
* Returns the current reference relatively to a base reference. This method
* should only be invoked for absolute references, otherwise an
* IllegalArgumentException will be raised.
*
* @param base
* The base reference to use.
* @throws IllegalArgumentException
* If the relative reference is computed although the reference
* or the base reference are not absolute or not hierarchical.
* @return The current reference relatively to a base reference.
*/
public Reference getRelativeRef(Reference base) {
Reference result = null;
if (base == null) {
result = this;
} else if (!isAbsolute() || !isHierarchical()) {
throw new IllegalArgumentException(
"The reference must have an absolute hierarchical path component");
} else if (!base.isAbsolute() || !base.isHierarchical()) {
throw new IllegalArgumentException(
"The base reference must have an absolute hierarchical path component");
} else if (!getHostIdentifier().equals(base.getHostIdentifier())) {
result = this;
} else {
final String localPath = getPath();
final String basePath = base.getPath();
String relativePath = null;
if ((basePath == null) || (localPath == null)) {
relativePath = localPath;
} else {
// Find the junction point
boolean diffFound = false;
int lastSlashIndex = -1;
int i = 0;
char current;
while (!diffFound && (i < localPath.length())
&& (i < basePath.length())) {
current = localPath.charAt(i);
if (current != basePath.charAt(i)) {
diffFound = true;
} else {
if (current == '/') {
lastSlashIndex = i;
}
i++;
}
}
if (!diffFound) {
if (localPath.length() == basePath.length()) {
// Both paths are strictely equivalent
relativePath = ".";
} else if (i == localPath.length()) {
// End of local path reached
if (basePath.charAt(i) == '/') {
if ((i + 1) == basePath.length()) {
// Both paths are strictely equivalent
relativePath = ".";
} else {
// The local path is a direct parent of the base
// path
// We need to add enough ".." in the relative
// path
final StringBuilder sb = new StringBuilder();
sb.append("..");
boolean canAdd = false;
for (int j = i + 1; j < basePath.length(); j++) {
if (basePath.charAt(j) == '/') {
canAdd = true;
} else if (canAdd) {
sb.append("/..");
canAdd = false;
}
}
relativePath = sb.toString();
}
} else {
// The base path has a segment that starts like
// the last local path segment
// But that is longer. Situation similar to a
// junction
final StringBuilder sb = new StringBuilder();
boolean firstAdd = true;
boolean canAdd = false;
for (int j = i; j < basePath.length(); j++) {
if (basePath.charAt(j) == '/') {
canAdd = true;
} else if (canAdd) {
if (firstAdd) {
firstAdd = false;
} else {
sb.append("/");
}
sb.append("..");
canAdd = false;
}
}
if (lastSlashIndex + 1 < localPath.length()) {
if (!firstAdd) {
sb.append('/');
}
sb.append(localPath
.substring(lastSlashIndex + 1));
}
relativePath = sb.toString();
if (relativePath.equals("")) {
relativePath = ".";
}
}
} else if (i == basePath.length()) {
if (localPath.charAt(i) == '/') {
if ((i + 1) == localPath.length()) {
// Both paths are strictely equivalent
relativePath = ".";
} else {
// The local path is a direct child of the base
// path
relativePath = localPath.substring(i + 1);
}
} else {
if (lastSlashIndex == (i - 1)) {
// The local path is a direct subpath of the
// base path
relativePath = localPath.substring(i);
} else {
relativePath = ".."
+ localPath.substring(lastSlashIndex);
}
}
}
} else {
// We found a junction point, we need to add enough ".." in
// the relative path and append the rest of the local path
// the local path is a direct subpath of the base path
final StringBuilder sb = new StringBuilder();
boolean canAdd = false;
boolean firstAdd = true;
for (int j = i; j < basePath.length(); j++) {
if (basePath.charAt(j) == '/') {
canAdd = true;
} else if (canAdd) {
if (firstAdd) {
firstAdd = false;
} else {
sb.append("/");
}
sb.append("..");
canAdd = false;
}
}
if (!firstAdd) {
sb.append('/');
}
sb.append(localPath.substring(lastSlashIndex + 1));
relativePath = sb.toString();
}
}
// Builde the result reference
result = new Reference();
final String query = getQuery();
final String fragment = getFragment();
boolean modified = false;
if ((query != null) && (!query.equals(base.getQuery()))) {
result.setQuery(query);
modified = true;
}
if ((fragment != null) && (!fragment.equals(base.getFragment()))) {
result.setFragment(fragment);
modified = true;
}
if (!modified || !relativePath.equals(".")) {
result.setPath(relativePath);
}
}
return result;
}
/**
* Returns the part of the resource identifier remaining after the base
* reference. Note that the optional fragment is not returned by this
* method. Must be used with the following prerequisites:
*
* - the reference is absolute
* - the reference identifier starts with the resource baseRef
*
*
* Note that no URI decoding is done by this method.
*
* @return The remaining resource part or null if the prerequisites are not
* satisfied.
* @see #getRemainingPart(boolean)
*/
public String getRemainingPart() {
return getRemainingPart(false, true);
}
/**
* Returns the optionally decoded remaining part.
*
* @param decode
* Indicates if the result should be decoded using the
* {@link #decode(String)} method.
* @return The optionally decoded remaining part.
* @see #getRemainingPart()
*/
public String getRemainingPart(boolean decode) {
return getRemainingPart(true, true);
}
/**
* Returns the optionally decoded remaining part with or without the query
* part of the reference.
*
* @param decode
* Indicates if the result should be decoded using the
* {@link #decode(String)} method.
* @param query
* True if the query part should be returned, false otherwise.
* @return The optionally decoded remaining part.
* @see #getRemainingPart()
*/
public String getRemainingPart(boolean decode, boolean query) {
String result = null;
final String all = toString(query, false);
if (getBaseRef() != null) {
final String base = getBaseRef().toString(query, false);
if ((base != null) && all.startsWith(base)) {
result = all.substring(base.length());
}
} else {
result = all;
}
return decode ? decode(result) : result;
}
/**
* Returns the scheme component.
* Note that no URI decoding is done by this method.
*
* @return The scheme component.
*/
public String getScheme() {
if (hasScheme()) {
// Scheme found
return this.internalRef.substring(0, this.schemeIndex);
}
// No scheme found
return null;
}
/**
* Returns the optionnally decoded scheme component.
*
* @param decode
* Indicates if the result should be decoded using the
* {@link #decode(String)} method.
* @return The optionnally decoded scheme component.
* @see #getScheme()
*/
public String getScheme(boolean decode) {
return decode ? decode(getScheme()) : getScheme();
}
/**
* Returns the protocol associated with the scheme component.
*
* @return The protocol associated with the scheme component.
*/
public Protocol getSchemeProtocol() {
return Protocol.valueOf(getScheme());
}
/**
* Returns the scheme specific part.
* Note that no URI decoding is done by this method.
*
* @return The scheme specific part.
*/
public String getSchemeSpecificPart() {
String result = null;
if (hasScheme()) {
// Scheme found
if (hasFragment()) {
// Fragment found
result = this.internalRef.substring(this.schemeIndex + 1,
this.fragmentIndex);
} else {
// No fragment found
result = this.internalRef.substring(this.schemeIndex + 1);
}
}
return result;
}
/**
* Returns the optionnally decoded scheme specific part.
*
* @param decode
* Indicates if the result should be decoded using the
* {@link #decode(String)} method.
* @return The optionnally decoded scheme specific part.
* @see #getSchemeSpecificPart()
*/
public String getSchemeSpecificPart(boolean decode) {
return decode ? decode(getSchemeSpecificPart())
: getSchemeSpecificPart();
}
/**
* Returns the list of segments in a hierarchical path.
* A new list is created for each call.
* Note that no URI decoding is done by this method.
*
* @return The segments of a hierarchical path.
*/
public List getSegments() {
final List result = new ArrayList();
final String path = getPath();
int start = -2; // The index of the slash starting the segment
char current;
if (path != null) {
for (int i = 0; i < path.length(); i++) {
current = path.charAt(i);
if (current == '/') {
if (start == -2) {
// Beginning of an absolute path or sequence of two
// separators
start = i;
} else {
// End of a segment
result.add(path.substring(start + 1, i));
start = i;
}
} else {
if (start == -2) {
// Starting a new segment for a relative path
start = -1;
} else {
// Looking for the next character
}
}
}
if (start != -2) {
// Add the last segment
result.add(path.substring(start + 1));
}
}
return result;
}
/**
* Returns the optionnally decoded list of segments.
*
* @param decode
* Indicates if the result should be decoded using the
* {@link #decode(String)} method.
* @return The optionnally decoded list of segments.
* @see #getSegments()
*/
public List getSegments(boolean decode) {
final List result = getSegments();
if (decode) {
for (int i = 0; i < result.size(); i++) {
result.set(i, decode(result.get(i)));
}
}
return result;
}
/**
* Returns the target reference. This method resolves relative references
* against the base reference then normalize them.
*
* @throws IllegalArgumentException
* If the base reference (after resolution) is not absolute.
* @throws IllegalArgumentException
* If the reference is relative and not base reference has been
* provided.
*
* @return The target reference.
*/
public Reference getTargetRef() {
Reference result = null;
// Step 1 - Resolve relative reference against their base reference
if (isRelative() && (this.baseRef != null)) {
Reference baseReference = null;
if (this.baseRef.isAbsolute()) {
baseReference = this.baseRef;
} else {
baseReference = this.baseRef.getTargetRef();
}
if (baseReference.isRelative()) {
throw new IllegalArgumentException(
"The base reference must have an absolute hierarchical path component");
}
// Relative URI detected
final String authority = getAuthority();
final String path = getPath();
final String query = getQuery();
final String fragment = getFragment();
// Create an empty reference
result = new Reference();
result.setScheme(baseReference.getScheme());
if (authority != null) {
result.setAuthority(authority);
result.setPath(path);
result.setQuery(query);
} else {
result.setAuthority(baseReference.getAuthority());
if ((path == null) || (path.equals(""))) {
result.setPath(baseReference.getPath());
if (query != null) {
result.setQuery(query);
} else {
result.setQuery(baseReference.getQuery());
}
} else {
if (path.startsWith("/")) {
result.setPath(path);
} else {
final String basePath = baseReference.getPath();
String mergedPath = null;
if ((baseReference.getAuthority() != null)
&& ((basePath == null) || (basePath.equals("")))) {
mergedPath = "/" + path;
} else {
// Remove the last segment which may be empty if
// the path is ending with a slash
final int lastSlash = basePath.lastIndexOf('/');
if (lastSlash == -1) {
mergedPath = path;
} else {
mergedPath = basePath.substring(0,
lastSlash + 1)
+ path;
}
}
result.setPath(mergedPath);
}
result.setQuery(query);
}
}
result.setFragment(fragment);
} else if (isRelative()) {
// Relative reference with no baseRef detected
throw new IllegalArgumentException(
"Relative references are only usable when a base reference is set.");
} else {
// Absolute URI detected
result = new Reference(this.internalRef);
}
// Step 2 - Normalize the target reference
result.normalize();
return result;
}
/**
* Returns the user info component for server based hierarchical
* identifiers.
* Note that no URI decoding is done by this method.
*
* @return The user info component for server based hierarchical
* identifiers.
*/
public String getUserInfo() {
String result = null;
final String authority = getAuthority();
if (authority != null) {
final int index = authority.indexOf('@');
if (index != -1) {
result = authority.substring(0, index);
}
}
return result;
}
/**
* Returns the optionnally decoded user info component.
*
* @param decode
* Indicates if the result should be decoded using the
* {@link #decode(String)} method.
* @return The optionnally decoded user info component.
* @see #getUserInfo()
*/
public String getUserInfo(boolean decode) {
return decode ? decode(getUserInfo()) : getUserInfo();
}
/**
* Indicates if this reference has file-like extensions on its last path
* segment.
*
* @return True if there is are extensions.
* @see #getExtensions()
*/
public boolean hasExtensions() {
boolean result = false;
// If these reference ends with a "/", it cannot be a file.
final String path = getPath();
if (!((path != null) && path.endsWith("/"))) {
final String lastSegment = getLastSegment();
if (lastSegment != null) {
final int extensionsIndex = lastSegment.indexOf('.');
final int matrixIndex = lastSegment.indexOf(';');
result = (extensionsIndex != -1)
&& ((matrixIndex == -1) || (extensionsIndex < matrixIndex));
}
}
return result;
}
/**
* Indicates if this reference has a fragment identifier.
*
* @return True if there is a fragment identifier.
*/
public boolean hasFragment() {
return (this.fragmentIndex != -1);
}
/**
* Returns a hash code value for the object.
*
* @return A hash code value for the object.
*/
@Override
public int hashCode() {
return (this.internalRef == null) ? 0 : this.internalRef.hashCode();
}
/**
* Indicates if this reference has a matrix.
*
* @return True if there is a matrix.
* @see #getMatrix()
*/
public boolean hasMatrix() {
return (getLastSegment().indexOf(';') != -1);
}
/**
* Indicates if this reference has a query component.
*
* @return True if there is a query.
*/
public boolean hasQuery() {
return (this.queryIndex != -1);
}
/**
* Indicates if this reference has a scheme component.
*
* @return True if there is a scheme component.
*/
public boolean hasScheme() {
return (this.schemeIndex != -1);
}
/**
* Indicates if the reference is absolute.
*
* @return True if the reference is absolute.
*/
public boolean isAbsolute() {
return (getScheme() != null);
}
/**
* Returns true if both reference are equivalent, meaning that they resolve
* to the same target reference.
*
* @param ref
* The reference to compare.
* @return True if both reference are equivalent.
*/
public boolean isEquivalentTo(Reference ref) {
return getTargetRef().equals(ref.getTargetRef());
}
/**
* Indicates if the identifier is hierarchical.
*
* @return True if the identifier is hierarchical, false if it is opaque.
*/
public boolean isHierarchical() {
return isRelative() || (getSchemeSpecificPart().charAt(0) == '/');
}
/**
* Indicates if the identifier is opaque.
*
* @return True if the identifier is opaque, false if it is hierarchical.
*/
public boolean isOpaque() {
return isAbsolute() && (getSchemeSpecificPart().charAt(0) != '/');
}
/**
* Indicates if the reference is a parent of the hierarchical child
* reference.
*
* @param childRef
* The hierarchical reference.
* @return True if the reference is a parent of the hierarchical child
* reference.
*/
public boolean isParent(Reference childRef) {
boolean result = false;
if ((childRef != null) && (childRef.isHierarchical())) {
result = childRef.toString(false, false).startsWith(
toString(false, false));
}
return result;
}
/**
* Indicates if the reference is relative.
*
* @return True if the reference is relative.
*/
public boolean isRelative() {
return (getScheme() == null);
}
/**
* Normalizes the reference. Useful before comparison between references or
* when building a target reference from a base and a relative references.
*
* @return The current reference.
*/
public Reference normalize() {
// 1. The input buffer is initialized with the now-appended path
// components and the output buffer is initialized to the empty string.
final StringBuilder output = new StringBuilder();
final StringBuilder input = new StringBuilder();
final String path = getPath();
if (path != null) {
input.append(path);
}
// 2. While the input buffer is not empty, loop as follows:
while (input.length() > 0) {
// A. If the input buffer begins with a prefix of "../" or "./",
// then remove that prefix from the input buffer; otherwise,
if ((input.length() >= 3) && input.substring(0, 3).equals("../")) {
input.delete(0, 3);
} else if ((input.length() >= 2)
&& input.substring(0, 2).equals("./")) {
input.delete(0, 2);
}
// B. if the input buffer begins with a prefix of "/./" or "/.",
// where "." is a complete path segment, then replace that
// prefix with "/" in the input buffer; otherwise,
else if ((input.length() >= 3)
&& input.substring(0, 3).equals("/./")) {
input.delete(0, 2);
} else if ((input.length() == 2)
&& input.substring(0, 2).equals("/.")) {
input.delete(1, 2);
}
// C. if the input buffer begins with a prefix of "/../" or "/..",
// where ".." is a complete path segment, then replace that prefix
// with "/" in the input buffer and remove the last segment and its
// preceding "/" (if any) from the output buffer; otherwise,
else if ((input.length() >= 4)
&& input.substring(0, 4).equals("/../")) {
input.delete(0, 3);
removeLastSegment(output);
} else if ((input.length() == 3)
&& input.substring(0, 3).equals("/..")) {
input.delete(1, 3);
removeLastSegment(output);
}
// D. if the input buffer consists only of "." or "..", then remove
// that from the input buffer; otherwise,
else if ((input.length() == 1) && input.substring(0, 1).equals(".")) {
input.delete(0, 1);
} else if ((input.length() == 2)
&& input.substring(0, 2).equals("..")) {
input.delete(0, 2);
}
// E. move the first path segment in the input buffer to the end of
// the output buffer, including the initial "/" character (if any)
// and any subsequent characters up to, but not including, the next
// "/" character or the end of the input buffer.
else {
int max = -1;
for (int i = 1; (max == -1) && (i < input.length()); i++) {
if (input.charAt(i) == '/') {
max = i;
}
}
if (max != -1) {
// We found the next "/" character.
output.append(input.substring(0, max));
input.delete(0, max);
} else {
// End of input buffer reached
output.append(input);
input.delete(0, input.length());
}
}
}
// Finally, the output buffer is returned as the result
setPath(output.toString());
// Ensure that the scheme and host names are reset in lower case
setScheme(getScheme());
setHostDomain(getHostDomain());
// Remove the port if it is equal to the default port of the reference's
// Protocol.
final int hostPort = getHostPort();
if (hostPort != -1) {
final int defaultPort = Protocol.valueOf(getScheme())
.getDefaultPort();
if (hostPort == defaultPort) {
setHostPort(null);
}
}
return this;
}
/**
* Removes the last segement from the output builder.
*
* @param output
* The output builder to update.
*/
private void removeLastSegment(StringBuilder output) {
int min = -1;
for (int i = output.length() - 1; (min == -1) && (i >= 0); i--) {
if (output.charAt(i) == '/') {
min = i;
}
}
if (min != -1) {
// We found the previous "/" character.
output.delete(min, output.length());
} else {
// End of output buffer reached
output.delete(0, output.length());
}
}
/**
* Sets the authority component for hierarchical identifiers.
*
* @param authority
* The authority component for hierarchical identifiers.
*/
public void setAuthority(String authority) {
final String oldPart = isRelative() ? getRelativePart()
: getSchemeSpecificPart();
String newPart;
final String newAuthority = (authority == null) ? "" : "//" + authority;
if (oldPart == null) {
newPart = newAuthority;
} else if (oldPart.startsWith("//")) {
int index = oldPart.indexOf('/', 2);
if (index != -1) {
newPart = newAuthority + oldPart.substring(index);
} else {
index = oldPart.indexOf('?');
if (index != -1) {
newPart = newAuthority + oldPart.substring(index);
} else {
newPart = newAuthority;
}
}
} else {
newPart = newAuthority + oldPart;
}
if (isAbsolute()) {
setSchemeSpecificPart(newPart);
} else {
setRelativePart(newPart);
}
}
/**
* Sets the base reference for relative references.
*
* @param baseRef
* The base reference for relative references.
*/
public void setBaseRef(Reference baseRef) {
this.baseRef = baseRef;
}
/**
* Sets the base reference for relative references.
*
* @param baseUri
* The base URI for relative references.
*/
public void setBaseRef(String baseUri) {
setBaseRef(new Reference(baseUri));
}
/**
* Sets the extensions for hierarchical identifiers. An extensions part
* starts after the first '.' character of the last path segment and ends
* with either the end of the segment of with the first ';' character
* (matrix start). It is a token similar to file extensions separated by '.'
* characters. The value can be ommited.
* Note that no URI decoding is done by this method.
*
* @param extensions
* The extensions to set or null (without leading or trailing
* dots).
* @see #getExtensions()
* @see #getExtensionsAsArray()
* @see #setExtensions(String[])
*/
public void setExtensions(String extensions) {
final String lastSegment = getLastSegment();
if (lastSegment != null) {
final int extensionIndex = lastSegment.indexOf('.');
final int matrixIndex = lastSegment.indexOf(';');
final StringBuilder sb = new StringBuilder();
if (extensionIndex != -1) {
// Extensions found
sb.append(lastSegment.substring(0, extensionIndex));
if ((extensions != null) && (extensions.length() > 0)) {
sb.append('.').append(extensions);
}
if (matrixIndex != -1) {
sb.append(lastSegment.substring(matrixIndex));
}
} else {
// Extensions not found
if ((extensions != null) && (extensions.length() > 0)) {
if (matrixIndex != -1) {
// Matrix found, make sure we append it
// after the extensions
sb.append(lastSegment.substring(0, matrixIndex))
.append('.').append(extensions).append(
lastSegment.substring(matrixIndex));
} else {
// No matrix found, just append the extensions
sb.append(lastSegment).append('.').append(extensions);
}
} else {
// No change necessary
sb.append(lastSegment);
}
}
// Finally update the last segment
setLastSegment(sb.toString());
} else {
setLastSegment('.' + extensions);
}
}
/**
* Sets the extensions based on an array of extension tokens (without dots).
*
* @param extensions
* The array of extensions.
* @see #getExtensions()
* @see #getExtensionsAsArray()
* @see #setExtensions(String)
*/
public void setExtensions(String[] extensions) {
String exts = null;
if (extensions != null) {
final StringBuilder sb = new StringBuilder();
for (int i = 0; i < extensions.length; i++) {
if (i > 0) {
sb.append('.');
}
sb.append(extensions[i]);
}
exts = sb.toString();
}
setExtensions(exts);
}
/**
* Sets the fragment identifier.
*
* @param fragment
* The fragment identifier.
* @throws IllegalArgumentException
* if the fragment parameter contains the fragment delimiter
* ('#').
*/
public void setFragment(String fragment) {
fragment = encodeInvalidCharacters(fragment);
if ((fragment != null) && (fragment.indexOf('#') != -1)) {
throw new IllegalArgumentException(
"Illegal '#' character detected in parameter");
}
if (hasFragment()) {
// Existing fragment
if (fragment != null) {
this.internalRef = this.internalRef.substring(0,
this.fragmentIndex + 1)
+ fragment;
} else {
this.internalRef = this.internalRef.substring(0,
this.fragmentIndex);
}
} else {
// No existing fragment
if (fragment != null) {
if (this.internalRef != null) {
this.internalRef = this.internalRef + '#' + fragment;
} else {
this.internalRef = '#' + fragment;
}
} else {
// Do nothing
}
}
updateIndexes();
}
/**
* Sets the host domain component for server based hierarchical identifiers.
*
* @param domain
* The host component for server based hierarchical identifiers.
*/
public void setHostDomain(String domain) {
final String authority = getAuthority();
if (authority == null) {
setAuthority(domain);
} else {
if (domain == null) {
domain = "";
} else {
// URI specification indicates that host names should be
// produced in lower case
domain = domain.toLowerCase();
}
final int index1 = authority.indexOf('@');
// We must prevent the case where the userinfo part contains ':'
final int index2 = authority.indexOf(':', (index1 == -1 ? 0
: index1));
if (index1 != -1) {
// User info found
if (index2 != -1) {
// Port found
setAuthority(authority.substring(0, index1 + 1) + domain
+ authority.substring(index2));
} else {
// No port found
setAuthority(authority.substring(0, index1 + 1) + domain);
}
} else {
// No user info found
if (index2 != -1) {
// Port found
setAuthority(domain + authority.substring(index2));
} else {
// No port found
setAuthority(domain);
}
}
}
}
/**
* Sets the optional port number for server based hierarchical identifiers.
*
* @param port
* The optional port number for server based hierarchical
* identifiers.
* @throws IllegalArgumentException
* If the autority has not been defined.
*/
public void setHostPort(Integer port) {
final String authority = getAuthority();
if (authority != null) {
final int index1 = authority.indexOf('@');
// We must prevent the case where the userinfo part contains ':'
final int index = authority.indexOf(':',
(index1 == -1 ? 0 : index1));
final String newPort = (port == null) ? "" : ":" + port;
if (index != -1) {
setAuthority(authority.substring(0, index) + newPort);
} else {
setAuthority(authority + newPort);
}
} else {
throw new IllegalArgumentException(
"No authority defined, please define a host name first");
}
}
/**
* Sets the absolute resource identifier.
*
* @param identifier
* The absolute resource identifier.
* @throws IllegalArgumentException
* If the identifier parameter contains the fragment delimiter
* ('#').
*/
public void setIdentifier(String identifier) {
identifier = encodeInvalidCharacters(identifier);
if (identifier == null) {
identifier = "";
}
if (identifier.indexOf('#') != -1) {
throw new IllegalArgumentException(
"Illegal '#' character detected in parameter");
}
if (hasFragment()) {
// Fragment found
this.internalRef = identifier
+ this.internalRef.substring(this.fragmentIndex);
} else {
// No fragment found
this.internalRef = identifier;
}
updateIndexes();
}
/**
* Sets the last segment of the path. If no path is available, then it
* creates one and adds a slash in front of the given last segmetn.
* Note that no URI decoding is done by this method.
*
* @param lastSegment
* The last segment of a hierarchical path.
*/
public void setLastSegment(String lastSegment) {
final String path = getPath();
final int lastSlashIndex = path.lastIndexOf('/');
if (lastSlashIndex != -1) {
setPath(path.substring(0, lastSlashIndex + 1) + lastSegment);
} else {
setPath('/' + lastSegment);
}
}
/**
* Sets the path component for hierarchical identifiers.
*
* @param path
* The path component for hierarchical identifiers.
*/
public void setPath(String path) {
final String oldPart = isRelative() ? getRelativePart()
: getSchemeSpecificPart();
String newPart = null;
if (oldPart != null) {
if (path == null) {
path = "";
}
if (oldPart.startsWith("//")) {
// Authority found
final int index1 = oldPart.indexOf('/', 2);
if (index1 != -1) {
// Path found
final int index2 = oldPart.indexOf('?');
if (index2 != -1) {
// Query found
newPart = oldPart.substring(0, index1) + path
+ oldPart.substring(index2);
} else {
// No query found
newPart = oldPart.substring(0, index1) + path;
}
} else {
// No path found
final int index2 = oldPart.indexOf('?');
if (index2 != -1) {
// Query found
newPart = oldPart.substring(0, index2) + path
+ oldPart.substring(index2);
} else {
// No query found
newPart = oldPart + path;
}
}
} else {
// No authority found
final int index = oldPart.indexOf('?');
if (index != -1) {
// Query found
newPart = path + oldPart.substring(index);
} else {
// No query found
newPart = path;
}
}
} else {
newPart = path;
}
if (isAbsolute()) {
setSchemeSpecificPart(newPart);
} else {
setRelativePart(newPart);
}
}
/**
* Sets the scheme component based on this protocol.
*
* @param protocol
* The protocol of the scheme component.
*/
public void setProtocol(Protocol protocol) {
setScheme(protocol.getSchemeName());
}
/**
* Sets the query component for hierarchical identifiers.
*
* @param query
* The query component for hierarchical identifiers.
*/
public void setQuery(String query) {
query = encodeInvalidCharacters(query);
final boolean emptyQueryString = ((query == null) || (query.length() <= 0));
if (hasQuery()) {
// Query found
if (hasFragment()) {
// Fragment found
if (!emptyQueryString) {
this.internalRef = this.internalRef.substring(0,
this.queryIndex + 1)
+ query
+ this.internalRef.substring(this.fragmentIndex);
} else {
this.internalRef = this.internalRef.substring(0,
this.queryIndex)
+ this.internalRef.substring(this.fragmentIndex);
}
} else {
// No fragment found
if (!emptyQueryString) {
this.internalRef = this.internalRef.substring(0,
this.queryIndex + 1)
+ query;
} else {
this.internalRef = this.internalRef.substring(0,
this.queryIndex);
}
}
} else {
// No query found
if (hasFragment()) {
// Fragment found
if (!emptyQueryString) {
this.internalRef = this.internalRef.substring(0,
this.fragmentIndex)
+ '?'
+ query
+ this.internalRef.substring(this.fragmentIndex);
} else {
// Do nothing;
}
} else {
// No fragment found
if (!emptyQueryString) {
if (this.internalRef != null) {
this.internalRef = this.internalRef + '?' + query;
} else {
this.internalRef = '?' + query;
}
} else {
// Do nothing;
}
}
}
updateIndexes();
}
/**
* Sets the relative part for relative references only.
*
* @param relativePart
* The relative part to set.
*/
public void setRelativePart(String relativePart) {
relativePart = encodeInvalidCharacters(relativePart);
if (relativePart == null) {
relativePart = "";
}
if (!hasScheme()) {
// This is a relative reference, no scheme found
if (hasQuery()) {
// Query found
this.internalRef = relativePart
+ this.internalRef.substring(this.queryIndex);
} else if (hasFragment()) {
// Fragment found
this.internalRef = relativePart
+ this.internalRef.substring(this.fragmentIndex);
} else {
// No fragment found
this.internalRef = relativePart;
}
}
updateIndexes();
}
/**
* Sets the scheme component.
*
* @param scheme
* The scheme component.
*/
public void setScheme(String scheme) {
scheme = encodeInvalidCharacters(scheme);
if (scheme != null) {
// URI specification indicates that scheme names should be
// produced in lower case
scheme = scheme.toLowerCase();
}
if (hasScheme()) {
// Scheme found
if (scheme != null) {
this.internalRef = scheme
+ this.internalRef.substring(this.schemeIndex);
} else {
this.internalRef = this.internalRef
.substring(this.schemeIndex + 1);
}
} else {
// No scheme found
if (scheme != null) {
if (this.internalRef == null) {
this.internalRef = scheme + ':';
} else {
this.internalRef = scheme + ':' + this.internalRef;
}
}
}
updateIndexes();
}
/**
* Sets the scheme specific part.
*
* @param schemeSpecificPart
* The scheme specific part.
*/
public void setSchemeSpecificPart(String schemeSpecificPart) {
schemeSpecificPart = encodeInvalidCharacters(schemeSpecificPart);
if (schemeSpecificPart == null) {
schemeSpecificPart = "";
}
if (hasScheme()) {
// Scheme found
if (hasFragment()) {
// Fragment found
this.internalRef = this.internalRef.substring(0,
this.schemeIndex + 1)
+ schemeSpecificPart
+ this.internalRef.substring(this.fragmentIndex);
} else {
// No fragment found
this.internalRef = this.internalRef.substring(0,
this.schemeIndex + 1)
+ schemeSpecificPart;
}
} else {
// No scheme found
if (hasFragment()) {
// Fragment found
this.internalRef = schemeSpecificPart
+ this.internalRef.substring(this.fragmentIndex);
} else {
// No fragment found
this.internalRef = schemeSpecificPart;
}
}
updateIndexes();
}
/**
* Sets the segments of a hierarchical path.
* A new absolute path will replace any existing one.
*
* @param segments
* The segments of the hierarchical path.
*/
public void setSegments(List segments) {
final StringBuilder sb = new StringBuilder();
for (final String segment : segments) {
sb.append('/').append(segment);
}
setPath(sb.toString());
}
/**
* Sets the user info component for server based hierarchical identifiers.
*
* @param userInfo
* The user info component for server based hierarchical
* identifiers.
* @throws IllegalArgumentException
* If the autority part has not been defined.
*/
public void setUserInfo(String userInfo) {
final String authority = getAuthority();
if (authority != null) {
final int index = authority.indexOf('@');
final String newUserInfo = (userInfo == null) ? "" : userInfo + '@';
if (index != -1) {
setAuthority(newUserInfo + authority.substring(index + 1));
} else {
setAuthority(newUserInfo + authority);
}
} else {
throw new IllegalArgumentException(
"No authority defined, please define a host name first");
}
}
/**
* Returns the reference as an URI string.
*
* @return The reference as an URI string.
*/
@Override
public String toString() {
return this.internalRef;
}
/**
* Returns the URI reference string.
*
* @param query
* Indicates if the query should be included;
* @param fragment
* Indicates if the fragment should be included;
* @return The URI reference string.
*/
public String toString(boolean query, boolean fragment) {
if (query) {
if (fragment) {
return this.internalRef;
}
if (hasFragment()) {
return this.internalRef.substring(0, this.fragmentIndex);
}
return this.internalRef;
}
if (fragment) {
// Fragment should be included
if (hasQuery()) {
// Query found
if (hasFragment()) {
// Fragment found
return this.internalRef.substring(0, this.queryIndex) + "#"
+ getFragment();
}
// No fragment found
return this.internalRef.substring(0, this.queryIndex);
}
// No query found
return this.internalRef;
}
// Fragment should not be included
if (hasQuery()) {
// Query found
return this.internalRef.substring(0, this.queryIndex);
}
if (hasFragment()) {
// Fragment found
return this.internalRef.substring(0, this.fragmentIndex);
}
return this.internalRef;
}
/**
* Updates internal indexes.
*/
private void updateIndexes() {
if (this.internalRef != null) {
// Compute the indexes
final int firstSlashIndex = this.internalRef.indexOf('/');
this.schemeIndex = this.internalRef.indexOf(':');
if ((firstSlashIndex != -1) && (this.schemeIndex > firstSlashIndex)) {
// We are in the rare case of a relative reference where one of
// the path segments contains a colon character. In this case,
// we ignore the colon as a valid scheme index.
// Note that this colon can't be in the first segment as it is
// forbidden by the URI RFC.
this.schemeIndex = -1;
}
this.queryIndex = this.internalRef.indexOf('?');
this.fragmentIndex = this.internalRef.indexOf('#');
if (hasQuery() && hasFragment()
&& (this.queryIndex > this.fragmentIndex)) {
// Query sign inside fragment
this.queryIndex = -1;
}
} else {
this.schemeIndex = -1;
this.queryIndex = -1;
this.fragmentIndex = -1;
}
}
}