Many resources are needed to download a project. Please understand that we have to compensate our server costs. Thank you in advance. Project price only 1 $
You can buy this project and download/modify it how often you want.
/**
* Copyright (c) 2002-2013 IBM Corporation and others.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v2.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v20.html
*
* Contributors:
* IBM - Initial API and implementation
*/
package org.eclipse.emf.common.util;
import java.io.File;
import java.lang.ref.Reference;
import java.lang.ref.ReferenceQueue;
import java.lang.ref.WeakReference;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.StringTokenizer;
/**
* A representation of a Uniform Resource Identifier (URI), as specified by
* RFC 2396, with certain
* enhancements. A URI instance can be created by specifying
* values for its components, or by providing a single URI string, which is
* parsed into its components. Static factory methods whose names begin
* with "create" are used for both forms of object creation. No public or
* protected constructors are provided; this class can not be subclassed.
*
*
Like String, URI is an immutable class;
* a URI instance offers several by-value methods that return a
* new URI object based on its current state. Most useful,
* a relative URI can be {@link #resolve(URI) resolve}d against
* a base absolute URI -- the latter typically identifies the
* document in which the former appears. The inverse to this is {@link
* #deresolve(URI) deresolve}, which answers the question, "what relative
* URI will resolve, against the given base, to this absolute URI?"
*
*
In the RFC, much
* attention is focused on a hierarchical naming system used widely to
* locate resources via common protocols such as HTTP, FTP, and Gopher, and
* to identify files on a local file system. Accordingly, most of this
* class's functionality is for handling such URIs, which can be identified
* via {@link #isHierarchical isHierarchical}.
*
*
Finally, note the difference between a null parameter to
* the static factory methods and an empty string. The former signifies the
* absence of a given URI component, while the latter simply makes the
* component blank. This can have a significant effect when resolving. For
* example, consider the following two URIs: /bar (with no
* authority) and ///bar (with a blank authority). Imagine
* resolving them against a base with an authority, such as
* http://www.eclipse.org/. The former case will yield
* http://www.eclipse.org/bar, as the base authority will be
* preserved. In the latter case, the empty authority will override the
* base authority, resulting in http:///bar!
*/
public abstract class URI
{
protected static final boolean DEBUG = false;
/**
* The cached hash code of the URI.
* This is always equal to the hash code of {@link #toString()}
*/
protected int hashCode;
/**
* A pool for caching URIs.
*/
protected static class URIPool extends Pool
{
protected static final long serialVersionUID = 1L;
/**
* A reference queue for managing the {@link URI#toString} values.
*/
protected final ReferenceQueue cachedToStrings;
public URIPool(ReferenceQueue
}, cannot be expressed as a valid {@link #isFile() file} URI.
*
*
* On Windows, {@code C:/myfile.text} is an absolute path, but on a Unix-style file system, it is a relative path.
*
* On Windows, {@code /myfile.text} is a relative path, but on a Unix-style file system, it is an absolute path.
*
*
*
* Furthermore, any application that uses a relative URI to locate some associated resource will generally map the relative URI to an absolute URI in some manner,
* likely depending on the system's state, e.g., the current user directory that can change over time,
* exactly as is the case for {@code new File(path).getAbsolutePath()}.
* Not only that, when an accessed resource itself contains URIs,
* the general XML interpretation of any such contained relative URI is to {@link #resolve(URI) resolve} it to an absolute URI based on the absolute URI of the containing resource;
* that resolution isn't possible (well-defined) unless the base URI of the resource is an absolute URI.
*
*
* @exception java.lang.IllegalArgumentException if pathName
* specifies a device and a relative path, or if any component of the path
* is not valid according to {@link #validAuthority validAuthority}, {@link
* #validDevice validDevice}, or {@link #validSegments validSegments},
* {@link #validQuery validQuery}, or {@link #validFragment validFragment}.
*/
public static URI createFileURI(String pathName)
{
return POOL.internFile(pathName);
}
/**
* Static factory method based on parsing a workspace-relative path string.
*
*
The pathName must be of the form:
*
* /project-name/path
*
*
Platform-specific path separators will be converted to slashes.
* If not included, the leading path separator will be added. The
* result will be of this form, which is parsed using {@link #createURI(String)
* createURI}:
*
* platform:/resource/project-name/path
*
*
This scheme supports relocatable projects in Eclipse and in
* stand-alone EMF.
*
*
Path encoding is performed only if the
* org.eclipse.emf.common.util.URI.encodePlatformResourceURIs
* system property is set to "true". Decoding can be performed with the
* static {@link #decode(String) decode} method.
*
* @exception java.lang.IllegalArgumentException if any component parsed
* from the path is not valid according to {@link #validDevice validDevice},
* {@link #validSegments validSegments}, {@link #validQuery validQuery}, or
* {@link #validFragment validFragment}.
*
* @see org.eclipse.core.runtime.Platform#resolve
* @see #createPlatformResourceURI(String, boolean)
* @deprecated Use {@link #createPlatformResourceURI(String, boolean)} instead.
*/
@Deprecated
public static URI createPlatformResourceURI(String pathName)
{
return createPlatformResourceURI(pathName, ENCODE_PLATFORM_RESOURCE_URIS);
}
/**
* Static factory method based on parsing a workspace-relative path string,
* with an option to encode the created URI.
*
*
The pathName must be of the form:
*
* /project-name/path
*
*
Platform-specific path separators will be converted to slashes.
* If not included, the leading path separator will be added. The
* result will be of this form, which is parsed using {@link #createURI(String)
* createURI}:
*
* platform:/resource/project-name/path
*
*
This scheme supports relocatable projects in Eclipse and in
* stand-alone EMF.
*
*
Depending on the encode argument, the path may be
* automatically encoded to escape all spaces, # characters,
* and other characters disallowed in URIs, as well as ?,
* which would delimit a path from a query. Decoding can be performed with
* the static {@link #decode(String) decode} method. It is strongly
* recommended to specify true to enable encoding, unless the
* path string has already been encoded.
*
* @exception java.lang.IllegalArgumentException if any component parsed
* from the path is not valid according to {@link #validDevice validDevice},
* {@link #validSegments validSegments}, {@link #validQuery validQuery}, or
* {@link #validFragment validFragment}.
*
* @see org.eclipse.core.runtime.Platform#resolve
*/
public static URI createPlatformResourceURI(String pathName, boolean encode)
{
return POOL.intern(SEGMENT_RESOURCE, pathName, encode);
}
/**
* Static factory method based on parsing a plug-in-based path string,
* with an option to encode the created URI.
*
*
The pathName must be of the form:
*
* /plugin-id/path
*
*
Platform-specific path separators will be converted to slashes.
* If not included, the leading path separator will be added. The
* result will be of this form, which is parsed using {@link #createURI(String)
* createURI}:
*
* platform:/plugin/plugin-id/path
*
*
This scheme supports relocatable plug-in content in Eclipse.
*
*
Depending on the encode argument, the path may be
* automatically encoded to escape all spaces, # characters,
* and other characters disallowed in URIs, as well as ?,
* which would delimit a path from a query. Decoding can be performed with
* the static {@link #decode(String) decode} method. It is strongly
* recommended to specify true to enable encoding, unless the
* path string has already been encoded.
*
* @exception java.lang.IllegalArgumentException if any component parsed
* from the path is not valid according to {@link #validDevice validDevice},
* {@link #validSegments validSegments}, {@link #validQuery validQuery}, or
* {@link #validFragment validFragment}.
*
* @see org.eclipse.core.runtime.Platform#resolve
* @since org.eclipse.emf.common 2.3
*/
public static URI createPlatformPluginURI(String pathName, boolean encode)
{
return POOL.intern(SEGMENT_PLUGIN, pathName, encode);
}
// Splits the fragment into a segment sequence if it starts with a /, i.e., if it's used as a fragment path by EMF's resource implementation.
//
protected static CharSequence splitInternFragment(String fragment)
{
if (fragment.length() > 0 && fragment.charAt(0) == SEGMENT_SEPARATOR)
{
return SegmentSequence.create("/", fragment);
}
else
{
return CommonUtil.intern(fragment);
}
}
// Protected constructor for use of pool.
//
protected URI(int hashCode)
{
this.hashCode = hashCode;
}
// Validates all of the URI components. Factory methods should call this
// before using the constructor, though they must ensure that the
// inter-component requirements described in their own Javadocs are all
// satisfied, themselves. If a new URI is being constructed out of
// an existing URI, this need not be called. Instead, just the new
// components may be validated individually.
protected static boolean validateURI(boolean hierarchical, String scheme, String authority, String device, boolean absolutePath, String[] segments, String query, String fragment)
{
if (!validScheme(scheme))
{
throw new IllegalArgumentException("invalid scheme: " + scheme);
}
if (!hierarchical)
{
if (!validOpaquePart(authority))
{
throw new IllegalArgumentException("invalid opaquePart: " + authority);
}
}
else
{
if (isArchiveScheme(scheme) ? !validArchiveAuthority(authority) : !validAuthority(authority))
{
throw new IllegalArgumentException("invalid authority: " + authority);
}
if (!validSegments(segments))
{
String s = segments == null ? "invalid segments: null" :
"invalid segment: " + firstInvalidSegment(segments);
throw new IllegalArgumentException(s);
}
}
if (!validDevice(device))
{
throw new IllegalArgumentException("invalid device: " + device);
}
if (!validQuery(query))
{
throw new IllegalArgumentException("invalid query: " + query);
}
if (!validFragment(fragment))
{
throw new IllegalArgumentException("invalid fragment: " + fragment);
}
return true;
}
// Alternate, stricter implementations of the following validation methods
// are provided, commented out, for possible future use...
/**
* Returns true if the specified value would be
* valid as the scheme component of a URI; false otherwise.
*
*
A valid scheme may be null or contain any characters except for the
* following: : / ? #
*/
public static boolean validScheme(String value)
{
return value == null || !contains(value, MAJOR_SEPARATOR_HI, MAJOR_SEPARATOR_LO);
//
A valid scheme may be null, or consist of a single letter followed
// by any number of letters, numbers, and the following characters:
// + - .
//if (value == null) return true;
//return value.length() != 0 &&
// matches(value.charAt(0), ALPHA_HI, ALPHA_LO) &&
// validate(value, SCHEME_CHAR_HI, SCHEME_CHAR_LO, false, false);
}
/**
* Returns true if the specified value would be
* valid as the opaque part component of a URI; false
* otherwise.
*
*
A valid opaque part must be non-null, non-empty, and not contain the
* # character. In addition, its first character must not be
* /
*/
public static boolean validOpaquePart(String value)
{
return value != null && value.indexOf(FRAGMENT_SEPARATOR) == -1 && value.length() > 0 && value.charAt(0) != SEGMENT_SEPARATOR;
//
A valid opaque part must be non-null and non-empty. It may contain
// any allowed URI characters, but its first character may not be
// /
//return value != null && value.length() != 0 &&
// value.charAt(0) != SEGMENT_SEPARATOR &&
// validate(value, URIC_HI, URIC_LO, true, true);
}
/**
* Returns true if the specified value would be
* valid as the authority component of a URI; false otherwise.
*
*
A valid authority may be null or contain any characters except for
* the following: / ? #
*/
public static boolean validAuthority(String value)
{
return value == null || !contains(value, SEGMENT_END_HI, SEGMENT_END_LO);
// A valid authority may be null or contain any allowed URI characters except
// for the following: / ?
//return value == null || validate(value, SEGMENT_CHAR_HI, SEGMENT_CHAR_LO, true, true);
}
/**
* Returns true if the specified value would be
* valid as the authority component of an archive URI; false
* otherwise.
*
*
To be valid, the authority, itself, must be a URI with no fragment or query,
* followed by the character !.
*/
public static boolean validArchiveAuthority(String value)
{
if (value != null && value.length() > 0 && value.charAt(value.length() - 1) == ARCHIVE_IDENTIFIER)
{
try
{
URI archiveURI = createURI(value.substring(0, value.length() - 1));
return !archiveURI.hasFragment();
}
catch (IllegalArgumentException e)
{
// Ignore the exception and return false.
}
}
return false;
}
/**
* Tests whether the specified value would be valid as the
* authority component of an archive
* URI. This method has been replaced by {@link #validArchiveAuthority
* validArchiveAuthority} since the same form of URI is now supported
* for schemes other than "jar". This now simply calls that method.
*
* @deprecated As of EMF 2.0, replaced by {@link #validArchiveAuthority
* validArchiveAuthority}.
*/
@Deprecated
public static boolean validJarAuthority(String value)
{
return validArchiveAuthority(value);
}
/**
* Returns true if the specified value would be
* valid as the device component of a URI; false otherwise.
*
*
A valid device may be null or non-empty, containing any characters
* except for the following: / ? # In addition, its last
* character must be :
*/
public static boolean validDevice(String value)
{
if (value == null) return true;
int len = value.length();
return len > 0 && value.charAt(len - 1) == DEVICE_IDENTIFIER && !contains(value, SEGMENT_END_HI, SEGMENT_END_LO);
}
/**
* Returns true if the specified value would be
* a valid path segment of a URI; false otherwise.
*
*
A valid path segment must be non-null and not contain any of the
* following characters: / ? #
*/
public static boolean validSegment(String value)
{
return value != null && !contains(value, SEGMENT_END_HI, SEGMENT_END_LO);
//
A valid path segment must be non-null and may contain any allowed URI
// characters except for the following: / ?
//return value != null && validate(value, SEGMENT_CHAR_HI, SEGMENT_CHAR_LO, true, true);
}
/**
* Returns true if the specified value would be
* a valid path segment array of a URI; false otherwise.
*
*
A valid path segment array must be non-null and contain only path
* segments that are valid according to {@link #validSegment validSegment}.
*/
public static boolean validSegments(String[] value)
{
if (value == null) return false;
for (int i = 0, len = value.length; i < len; i++)
{
if (!validSegment(value[i])) return false;
}
return true;
}
// Returns null if the specified value is null or would be a valid path
// segment array of a URI; otherwise, the value of the first invalid
// segment.
protected static String firstInvalidSegment(String[] value)
{
if (value == null) return null;
for (int i = 0, len = value.length; i < len; i++)
{
if (!validSegment(value[i])) return value[i];
}
return null;
}
/**
* Returns true if the specified value would be
* valid as the query component of a URI; false otherwise.
*
*
A valid query may be null or contain any characters except for
* #
*/
public static boolean validQuery(String value)
{
return value == null || value.indexOf(FRAGMENT_SEPARATOR) == -1;
//
A valid query may be null or contain any allowed URI characters.
//return value == null || validate(value, URIC_HI, URIC_LO, true, true);
}
/**
* Returns true if the specified value would be
* valid as the fragment component of a URI; false otherwise.
*
*
A fragment is taken to be unconditionally valid.
*/
public static boolean validFragment(String value)
{
return true;
//
A valid fragment may be null or contain any allowed URI characters.
//return value == null || validate(value, URIC_HI, URIC_LO, true, true);
}
// Searches the specified string for any characters in the set represented
// by the 128-bit bitmask. Returns true if any occur, or false otherwise.
//
protected static boolean contains(String s, long highBitmask, long lowBitmask)
{
for (int i = 0, len = s.length(); i < len; i++)
{
if (matches(s.charAt(i), highBitmask, lowBitmask)) return true;
}
return false;
}
/**
* A subclass for representing a hierarchical URI.
*/
protected static final class Hierarchical extends URI
{
/**
* The {@link #flags} bit for representing {@link URI#hasAbsolutePath()}.
*/
protected static final int HAS_ABSOLUTE_PATH = 1 << 0;
/**
* The {@link #flags} bit for representing {@link URI#hasRelativePath()}.
*/
protected static final int HAS_RELATIVE_PATH = 1 << 1;
/**
* The {@link #flags} bit for representing {@link URI#hasEmptyPath()}.
*/
protected static final int HAS_EMPTY_PATH = 1 << 2;
/**
* The {@link #flags} bit for representing {@link URI#isCurrentDocumentReference()}.
*/
protected static final int IS_CURRENT_DOCUMENT_REFERENCE = 1 << 3;
/**
* The {@link #flags} bit for representing {@link URI#isFile()}.
*/
protected static final int IS_FILE = 1 << 4;
/**
* The {@link #flags} bit for representing {@link URI#isPlatform()}.
*/
protected static final int IS_PLATFORM = 1 << 5;
/**
* The {@link #flags} bit for representing {@link URI#isPlatformResource()}.
*/
protected static final int IS_PLATFORM_RESOURCE = 1 << 6;
/**
* The {@link #flags} bit for representing {@link URI#isPlatformPlugin()}.
*/
protected static final int IS_PLATFORM_PLUGIN = 1 << 7;
/**
* The {@link #flags} bit for representing {@link URI#isArchive()}.
*/
protected static final int IS_ARCHIVE = 1 << 8;
/**
* The {@link #flags} bit for representing {@link URI#hasTrailingPathSeparator()}.
*/
protected static final int HAS_TRAILING_PATH_SEPARATOR = 1 << 9;
/**
* The {@link #flags} bit for representing {@link URI#isPrefix()}.
*/
protected static final int IS_PREFIX = 1 << 10;
/**
* The {@link #flags} bits for representing {@link URI#hasPath()}.
*/
protected static final int HAS_PATH = HAS_ABSOLUTE_PATH | HAS_RELATIVE_PATH;
/**
* Bit flags for the results of all the boolean no-argument methods.
*/
protected final int flags;
/**
* The scheme of the hierarchical URIs.
*/
protected final String scheme; // null -> relative URI reference
/**
* The authority of the hierarchical URI.
*/
protected final String authority;
/**
* The device of the hierarchical URI.
*/
protected final String device;
/**
* The segments of the hierarchical URI.
*/
protected final String[] segments; // empty last segment -> trailing separator
/**
* The query of the hierarchical URI.
*/
protected final String query;
/**
* A weakly cached reference to the string representation.
*/
protected WeakReference toString;
/**
* Creates an instance from the components, computing the {@link #flags} bits.
* Assertions are used to validate the integrity of the result.
* I.e., all components must be interned and the hash code must be equal to the hash code of the {@link #toString()}.
*/
protected Hierarchical(int hashCode, boolean hierarchical, String scheme, String authority, String device, boolean absolutePath, String[] segments, String query)
{
super(hashCode);
int flags = 0;
if (absolutePath)
{
flags |= HAS_ABSOLUTE_PATH;
}
else if (device == null && authority == null)
{
flags |= HAS_RELATIVE_PATH;
if (segments == NO_SEGMENTS)
{
flags |= HAS_EMPTY_PATH;
if (query == null)
{
flags |= IS_CURRENT_DOCUMENT_REFERENCE;
}
}
if (query == null)
{
flags |= IS_FILE;
}
}
if (scheme != null)
{
if (scheme == SCHEME_FILE)
{
flags |= IS_FILE;
}
else if (scheme == SCHEME_PLATFORM)
{
if (authority == null && device == null && segments.length >= 2)
{
flags |= IS_PLATFORM;
String firstSegment = segments[0];
if (firstSegment == SEGMENT_RESOURCE)
{
flags |= IS_PLATFORM_RESOURCE;
}
else if (firstSegment == SEGMENT_PLUGIN)
{
flags |= IS_PLATFORM_PLUGIN;
}
}
}
else
{
for (String archiveScheme : ARCHIVE_SCHEMES)
{
if (scheme == archiveScheme)
{
flags |= IS_ARCHIVE;
break;
}
}
}
}
if (segments == NO_SEGMENTS)
{
if (absolutePath && query == null)
{
flags |= IS_PREFIX;
}
}
else if (segments[segments.length - 1] == SEGMENT_EMPTY)
{
flags |= HAS_TRAILING_PATH_SEPARATOR;
if (query == null)
{
flags |= IS_PREFIX;
}
}
this.flags = flags;
this.scheme = scheme;
this.authority = authority;
this.device = device;
this.segments = segments;
this.query = query;
// The segments array must be interned.
//
assert segments == SegmentSequence.STRING_ARRAY_POOL.intern(true, true, segments, segments.length);
// The scheme must be interned and must be lower cased.
//
assert scheme == CommonUtil.internToLowerCase(scheme);
// The authority must be interned.
//
assert authority == CommonUtil.intern(authority);
// The query must be interned.
//
assert query == CommonUtil.intern(query);
// The device must be interned.
//
assert device == CommonUtil.intern(device);
// The components must be valid.
//
assert validateURI(true, scheme, authority, device, hasAbsolutePath(), segments, query, null);
// The hash code must be the same as that of the string representation
//
assert hashCode == toString().hashCode();
}
@Override
public boolean isRelative()
{
return scheme == null;
}
@Override
protected boolean isBase()
{
return scheme != null;
}
@Override
public boolean isHierarchical()
{
return true;
}
@Override
public boolean hasAuthority()
{
return authority != null;
}
@Override
public boolean hasDevice()
{
return device != null;
}
@Override
public boolean hasPath()
{
// note: (absolutePath || authority == null) -> hierarchical
// (authority == null && device == null && !absolutePath) -> scheme == null
return (flags & HAS_PATH) != 0;
}
@Override
protected boolean hasDeviceOrPath()
{
return (flags & HAS_PATH) != 0 || device != null;
}
@Override
public boolean hasAbsolutePath()
{
// note: absolutePath -> hierarchical
return (flags & HAS_ABSOLUTE_PATH) != 0;
}
@Override
public boolean hasRelativePath()
{
// note: authority == null -> hierarchical
// (authority == null && device == null && !absolutePath) -> scheme == null
return (flags & HAS_RELATIVE_PATH) != 0;
}
@Override
public boolean hasEmptyPath()
{
// note: authority == null -> hierarchical
// (authority == null && device == null && !absolutePath) -> scheme == null
return (flags & HAS_EMPTY_PATH) != 0;
}
@Override
public boolean hasQuery()
{
// note: query != null -> hierarchical
return query != null;
}
@Override
public boolean isCurrentDocumentReference()
{
// note: authority == null -> hierarchical
// (authority == null && device == null && !absolutePath) -> scheme == null
return (flags & IS_CURRENT_DOCUMENT_REFERENCE) != 0;
}
@Override
public boolean isEmpty()
{
// note: authority == null -> hierarchical
// (authority == null && device == null && !absolutePath) -> scheme == null
return (flags & IS_CURRENT_DOCUMENT_REFERENCE) != 0;
}
@Override
public boolean isFile()
{
return (flags & IS_FILE) != 0;
}
@Override
public boolean isPlatform()
{
return (flags & IS_PLATFORM) != 0;
}
@Override
public boolean isPlatformResource()
{
return (flags & IS_PLATFORM_RESOURCE) != 0;
}
@Override
public boolean isPlatformPlugin()
{
return (flags & IS_PLATFORM_PLUGIN) != 0;
}
@Override
public boolean isArchive()
{
return (flags & IS_ARCHIVE) != 0;
}
@Override
protected boolean segmentsEqual(URI uri)
{
String[] segments = this.segments;
int length = segments.length;
if (length != uri.segmentCount()) return false;
for (int i = 0; i < length; i++)
{
if (segments[i] != uri.segment(i)) return false;
}
return true;
}
@Override
public String scheme()
{
return scheme;
}
@Override
public String authority()
{
return authority;
}
@Override
public String userInfo()
{
if (!hasAuthority()) return null;
int i = authority.indexOf(USER_INFO_SEPARATOR);
return i < 0 ? null : authority.substring(0, i);
}
@Override
public String host()
{
if (!hasAuthority()) return null;
int i = authority.indexOf(USER_INFO_SEPARATOR);
int j = authority.indexOf(PORT_SEPARATOR, i);
return j < 0 ? authority.substring(i + 1) : authority.substring(i + 1, j);
}
@Override
public String port()
{
if (!hasAuthority()) return null;
int i = authority.indexOf(USER_INFO_SEPARATOR);
int j = authority.indexOf(PORT_SEPARATOR, i);
return j < 0 ? null : authority.substring(j + 1);
}
@Override
public String device()
{
return device;
}
@Override
public String[] segments()
{
return segments.clone();
}
@Override
protected String[] rawSegments()
{
return segments;
}
@Override
public List segmentsList()
{
return Collections.unmodifiableList(Arrays.asList(segments));
}
@Override
public int segmentCount()
{
return segments.length;
}
@Override
public String segment(int i)
{
return segments[i];
}
@Override
public String lastSegment()
{
int len = segments.length;
if (len == 0) return null;
return segments[len - 1];
}
@Override
public String path()
{
if (!hasPath()) return null;
CommonUtil.StringPool.StringsAccessUnit result = CommonUtil.STRING_POOL.getStringBuilder();
if (hasAbsolutePath()) result.append(SEGMENT_SEPARATOR);
String[] segments = this.segments;
for (int i = 0, len = segments.length; i < len; i++)
{
if (i != 0) result.append(SEGMENT_SEPARATOR);
result.append(segments[i]);
}
return CommonUtil.STRING_POOL.intern(result);
}
@Override
public String devicePath()
{
if (!hasPath()) return null;
CommonUtil.StringPool.StringsAccessUnit result = CommonUtil.STRING_POOL.getStringBuilder();
if (hasAuthority())
{
if (!isArchive()) result.append(AUTHORITY_SEPARATOR);
result.append(authority);
if (hasDevice()) result.append(SEGMENT_SEPARATOR);
}
if (hasDevice()) result.append(device);
if (hasAbsolutePath()) result.append(SEGMENT_SEPARATOR);
String[] segments = this.segments;
for (int i = 0, len = segments.length; i < len; i++)
{
if (i != 0) result.append(SEGMENT_SEPARATOR);
result.append(segments[i]);
}
return CommonUtil.STRING_POOL.intern(result);
}
@Override
public String query()
{
return query;
}
@Override
public URI appendQuery(String query)
{
return POOL.intern(false, URIPool.URIComponentsAccessUnit.VALIDATE_QUERY, true, scheme, authority, device, hasAbsolutePath(), segments, query);
}
@Override
public URI trimQuery()
{
if (query == null)
{
return this;
}
else
{
return POOL.intern(false, URIPool.URIComponentsAccessUnit.VALIDATE_NONE, true, scheme, authority, device, hasAbsolutePath(), segments, null);
}
}
@Override
public URI resolve(URI base, boolean preserveRootParents)
{
if (!base.isBase())
{
throw new IllegalArgumentException("resolve against non-hierarchical or relative base");
}
// an absolute URI needs no resolving
if (!isRelative()) return this;
String newAuthority = authority;
String newDevice = device;
boolean newAbsolutePath = hasAbsolutePath();
String[] newSegments = segments;
String newQuery = query;
// note: it's okay for two URIs to share a segments array, since neither will ever modify it
if (authority == null)
{
// no authority: use base's
newAuthority = base.authority();
if (device == null)
{
// no device: use base's
newDevice = base.device();
if (hasEmptyPath() && query == null)
{
// current document reference: use base path and query
newAbsolutePath = base.hasAbsolutePath();
newSegments = base.rawSegments();
newQuery = base.query();
}
else if (hasRelativePath())
{
// relative path: merge with base and keep query.
// note that if the base has no path and this a non-empty relative path, there is an implied root in the resulting path
newAbsolutePath = base.hasAbsolutePath() || !hasEmptyPath();
newSegments = newAbsolutePath ? mergePath(base, preserveRootParents) : NO_SEGMENTS;
}
// else absolute path: keep it and query
}
// else keep device, path, and query
}
// else keep authority, device, path, and query
// Use scheme from base; no validation needed because all components are from existing URIs
return POOL.intern(false, URIPool.URIComponentsAccessUnit.VALIDATE_NONE, true, base.scheme(), newAuthority, newDevice, newAbsolutePath, newSegments, newQuery);
}
// Merges this URI's relative path with the base non-relative path.
// If base has no path, treat it as the root absolute path, unless this has no path either.
//
protected String[] mergePath(URI base, boolean preserveRootParents)
{
if (base.hasRelativePath())
{
throw new IllegalArgumentException("merge against relative path");
}
if (!hasRelativePath())
{
throw new IllegalStateException("merge non-relative path");
}
int baseSegmentCount = base.segmentCount();
int segmentCount = segments.length;
String[] stack = new String[baseSegmentCount + segmentCount];
int sp = 0;
// use a stack to accumulate segments of base, except for the last
// (i.e. skip trailing separator and anything following it), and of relative path
//
for (int i = 0; i < baseSegmentCount - 1; i++)
{
sp = accumulate(stack, sp, base.segment(i), preserveRootParents);
}
for (int i = 0; i < segmentCount; i++)
{
sp = accumulate(stack, sp, segments[i], preserveRootParents);
}
// if the relative path is empty or ends in an empty segment, a parent
// reference, or a self reference, add a trailing separator to a
// non-empty path
if (sp > 0)
{
if (segmentCount == 0)
{
stack[sp++] = SEGMENT_EMPTY;
}
else
{
String segment = segments[segmentCount - 1];
if (segment == SEGMENT_EMPTY || segment == SEGMENT_PARENT || segment == SEGMENT_SELF)
{
stack[sp++] = SEGMENT_EMPTY;
}
}
}
// return a correctly sized result
return SegmentSequence.STRING_ARRAY_POOL.intern(stack, 0, sp);
}
// Adds a segment to a stack, skipping empty segments and self references,
// and interpreting parent references.
protected static int accumulate(String[] stack, int sp, String segment, boolean preserveRootParents)
{
if (SEGMENT_PARENT == segment)
{
if (sp == 0)
{
// special care must be taken for a root's parent reference: it is
// either ignored or the symbolic reference itself is pushed
if (preserveRootParents) stack[sp++] = segment;
}
else
{
// unless we're already accumulating root parent references,
// parent references simply pop the last segment descended
if (SEGMENT_PARENT == stack[sp - 1]) stack[sp++] = segment;
else sp--;
}
}
else if (SEGMENT_EMPTY != segment && SEGMENT_SELF != segment)
{
// skip empty segments and self references; push everything else
stack[sp++] = segment;
}
return sp;
}
@Override
public URI deresolve(URI base, boolean preserveRootParents, boolean anyRelPath, boolean shorterRelPath)
{
if (!base.isBase() || isRelative()) return this;
// note: these assertions imply that neither this nor the base URI has a
// relative path; thus, both have either an absolute path or no path
// different scheme: need complete, absolute URI
if (scheme != base.scheme()) return this;
String newAuthority = authority;
String newDevice = device;
boolean newAbsolutePath = hasAbsolutePath();
String[] newSegments = segments;
String newQuery = query;
if (authority == base.authority() && (hasDeviceOrPath() || !base.hasDeviceOrPath()))
{
// matching authorities and no device or path removal
newAuthority = null;
if (device == base.device())
{
boolean hasPath = hasPath();
boolean baseHasPath = base.hasPath();
if (hasPath || !baseHasPath)
{
// matching devices and no path removal
newDevice = null;
// exception if (!hasPath() && base.hasPath())
if (!anyRelPath && !shorterRelPath)
{
// user rejects a relative path: keep absolute or no path
}
else if (hasPath == baseHasPath && segmentsEqual(base) && query == base.query())
{
// current document reference: keep no path or query
newAbsolutePath = false;
newSegments = NO_SEGMENTS;
newQuery = null;
}
else if (hasPath && !baseHasPath)
{
// no paths: keep query only
newAbsolutePath = false;
newSegments = NO_SEGMENTS;
}
// exception if (!hasAbsolutePath())
else if (hasCollapsableSegments(preserveRootParents))
{
// path form demands an absolute path: keep it and query
}
else
{
// keep query and select relative or absolute path based on length
String[] rel = findRelativePath(base, preserveRootParents);
if (anyRelPath || segments.length > rel.length)
{
// user demands a relative path or the absolute path is longer
newAbsolutePath = false;
newSegments = rel;
}
// else keep shorter absolute path
}
}
}
// else keep device, path, and query
}
// else keep authority, device, path, and query
// always include fragment, even if null;
// no validation needed since all components are from existing URIs
return POOL.intern(false, URIPool.URIComponentsAccessUnit.VALIDATE_NONE, true, null, newAuthority, newDevice, newAbsolutePath, newSegments, newQuery);
}
// Returns true if the non-relative path includes segments that would be
// collapsed when resolving; false otherwise. If preserveRootParents is
// true, collapsible segments include any empty segments, except for the
// last segment, as well as and parent and self references. If
// preserveRootsParents is false, parent references are not collapsible if
// they are the first segment or preceded only by other parent
// references.
protected boolean hasCollapsableSegments(boolean preserveRootParents)
{
if (hasRelativePath())
{
throw new IllegalStateException("test collapsability of relative path");
}
String[] segments = this.segments;
for (int i = 0, len = segments.length; i < len; i++)
{
String segment = segments[i];
if ((i < len - 1 && SEGMENT_EMPTY == segment) ||
SEGMENT_SELF == segment ||
SEGMENT_PARENT == segment && (!preserveRootParents || (i != 0 && SEGMENT_PARENT != segments[i - 1])))
{
return true;
}
}
return false;
}
// Returns the shortest relative path between the the non-relative path of
// the given base and this absolute path. If the base has no path, it is
// treated as the root absolute path.
protected String[] findRelativePath(URI base, boolean preserveRootParents)
{
if (base.hasRelativePath())
{
throw new IllegalArgumentException(
"find relative path against base with relative path");
}
if (!hasAbsolutePath())
{
throw new IllegalArgumentException(
"find relative path of non-absolute path");
}
// treat an empty base path as the root absolute path
String[] startPath = base.collapseSegments(preserveRootParents);
String[] endPath = segments;
// drop last segment from base, as in resolving
int startCount = startPath.length > 0 ? startPath.length - 1 : 0;
int endCount = endPath.length;
// index of first segment that is different between endPath and startPath
int diff = 0;
// if endPath is shorter than startPath, the last segment of endPath may
// not be compared: because startPath has been collapsed and had its
// last segment removed, all preceding segments can be considered non-
// empty and followed by a separator, while the last segment of endPath
// will either be non-empty and not followed by a separator, or just empty
for (int count = startCount < endCount ? startCount : endCount - 1; diff < count && startPath[diff] == endPath[diff]; diff++)
{
// Empty statement.
}
int upCount = startCount - diff;
int downCount = endCount - diff;
// a single separator, possibly preceded by some parent reference
// segments, is redundant
if (downCount == 1 && SEGMENT_EMPTY == endPath[endCount - 1])
{
downCount = 0;
}
// an empty path needs to be replaced by a single "." if there is no
// query, to distinguish it from a current document reference
int length = upCount + downCount;
if (length == 0)
{
if (query == null) return ONE_SELF_SEGMENT;
return NO_SEGMENTS;
}
// return a correctly sized result
String[] result = new String[length];
Arrays.fill(result, 0, upCount, SEGMENT_PARENT);
System.arraycopy(endPath, diff, result, upCount, downCount);
return SegmentSequence.STRING_ARRAY_POOL.intern(false, false, result, length);
}
@Override
protected String[] collapseSegments(boolean preserveRootParents)
{
if (hasRelativePath())
{
throw new IllegalStateException("collapse relative path");
}
if (!hasCollapsableSegments(preserveRootParents)) return rawSegments();
// use a stack to accumulate segments
String[] segments = this.segments;
int segmentCount = segments.length;
String[] stack = new String[segmentCount];
int sp = 0;
for (int i = 0; i < segmentCount; i++)
{
sp = accumulate(stack, sp, segments[i], preserveRootParents);
}
// if the path is non-empty and originally ended in an empty segment, a
// parent reference, or a self reference, add a trailing separator
if (sp > 0)
{
String segment = segments[segmentCount - 1];
if (segment == SEGMENT_EMPTY || segment == SEGMENT_PARENT || segment == SEGMENT_SELF)
{
stack[sp++] = SEGMENT_EMPTY;
}
}
// return a correctly sized result
return SegmentSequence.STRING_ARRAY_POOL.intern(stack, 0, sp);
}
@Override
protected void cacheString(String string)
{
toString = POOL.newCachedToString(this, string);
}
@Override
protected void flushCachedString()
{
toString = null;
}
@Override
protected String getCachedString()
{
WeakReference toString = this.toString;
if (toString != null)
{
String result = toString.get();
if (result == null)
{
toString.clear();
}
else
{
return result;
}
}
return null;
}
@Override
public String toString()
{
String cachedToString = getCachedString();
if (cachedToString != null)
{
return cachedToString;
}
CommonUtil.StringPool.StringsAccessUnit result = CommonUtil.STRING_POOL.getStringBuilder();
if (!isRelative())
{
result.append(scheme);
result.append(SCHEME_SEPARATOR);
}
if (hasAuthority())
{
if (!isArchive()) result.append(AUTHORITY_SEPARATOR);
result.append(authority);
}
if (hasDevice())
{
result.append(SEGMENT_SEPARATOR);
result.append(device);
}
if (hasAbsolutePath()) result.append(SEGMENT_SEPARATOR);
for (int i = 0, len = segments.length; i < len; i++)
{
if (i != 0) result.append(SEGMENT_SEPARATOR);
result.append(segments[i]);
}
if (hasQuery())
{
result.append(QUERY_SEPARATOR);
result.append(query);
}
String string = CommonUtil.STRING_POOL.intern(result);
this.toString = POOL.newCachedToString(this, string);
return string;
}
@Override
protected boolean matches(String string)
{
String cachedString = getCachedString();
if (cachedString != null)
{
return cachedString.equals(string);
}
int length = string.length();
int index = 0;
if (!isRelative())
{
if (!string.startsWith(scheme))
{
return false;
}
index += scheme.length();
if (index >= length || string.charAt(index) != SCHEME_SEPARATOR)
{
return false;
}
++index;
}
if (hasAuthority())
{
if (!isArchive())
{
if (!string.startsWith(AUTHORITY_SEPARATOR, index))
{
return false;
}
index += 2;
}
if (!string.startsWith(authority, index))
{
return false;
}
index += authority.length();
}
if (hasDevice())
{
if (index >= length || string.charAt(index) != SEGMENT_SEPARATOR)
{
return false;
}
++index;
if (!string.startsWith(device, index))
{
return false;
}
index += device.length();
}
if (hasAbsolutePath())
{
if (index >= length || string.charAt(index) != SEGMENT_SEPARATOR)
{
return false;
}
++index;
}
String[] segments = this.segments;
for (int i = 0, len = segments.length; i < len; i++)
{
if (i != 0)
{
if (index >= length || string.charAt(index) != SEGMENT_SEPARATOR)
{
return false;
}
++index;
}
String segment = segments[i];
if (!string.startsWith(segment, index))
{
return false;
}
index += segment.length();
}
if (hasQuery())
{
if (index >= length || string.charAt(index) != QUERY_SEPARATOR)
{
return false;
}
++index;
if (!string.startsWith(query, index))
{
return false;
}
index += query.length();
}
return index == length;
}
@Override
protected boolean matches(int validate, boolean hierarchical, String scheme, String authority, String device, boolean absolutePath, String[] segments, String query)
{
return
hierarchical &&
hasAbsolutePath() == absolutePath &&
(validate >= URIPool.URIComponentsAccessUnit.VALIDATE_NONE ?
this.segments == segments && this.scheme == scheme && this.authority == authority && this.device == device && this.query == query :
Arrays.equals(this.segments, segments) && equals(this.scheme, scheme) && equals(this.authority, authority) && equals(this.device, device) && equals(this.query, query));
}
@Override
protected boolean matches(String base, String path)
{
if (!isPlatform() || segments[0] != base)
{
return false;
}
String[] segments = this.segments;
int length = path.length();
for (int i = 1, len = segments.length, index = 1; i < len; i++)
{
if (i != 1)
{
if (index >= length || path.charAt(index) != SEGMENT_SEPARATOR)
{
return false;
}
++index;
}
String segment = segments[i];
if (!path.startsWith(segment, index))
{
return false;
}
index += segment.length();
}
return true;
}
@Override
public String toFileString()
{
if (!isFile()) return null;
CommonUtil.StringPool.StringsAccessUnit result = CommonUtil.STRING_POOL.getStringBuilder();
char separator = File.separatorChar;
boolean hasDevice = hasDevice();
if (hasAuthority())
{
result.append(separator);
result.append(separator);
result.append(authority);
if (hasDevice) result.append(separator);
}
if (hasDevice) result.append(device);
if (hasAbsolutePath()) result.append(separator);
String[] segments = this.segments;
for (int i = 0, len = segments.length; i < len; i++)
{
if (i != 0) result.append(separator);
result.append(segments[i]);
}
return decode(CommonUtil.STRING_POOL.intern(result));
}
@Override
public String toPlatformString(boolean decode)
{
if (isPlatform())
{
CommonUtil.StringPool.StringsAccessUnit result = CommonUtil.STRING_POOL.getStringBuilder();
String[] segments = this.segments;
for (int i = 1, len = segments.length; i < len; i++)
{
result.append('/');
result.append(decode ? URI.decode(segments[i]) : segments[i]);
}
return CommonUtil.STRING_POOL.intern(result);
}
return null;
}
@Override
public URI appendSegment(String segment)
{
// Do preliminary checking now but full checking later.
//
if (segment == null)
{
throw new IllegalArgumentException("invalid segment: null");
}
boolean isEmptySegment = segment.length() == 0;
if (isEmptySegment && scheme != null && authority == null && device == null && segments.length == 0)
{
// Ignore appending an empty segment that would turn scheme:/ into schema:// because that would look like an empty authority without segments.
return this;
}
// absolute path or no path -> absolute path
boolean newAbsolutePath = !hasRelativePath();
String[] newSegments;
String newDevice = device;
if (isEmptySegment && segments.length == 0)
{
// Turn it into trailing separator.
newAbsolutePath = true;
newSegments = NO_SEGMENTS;
}
else if (device == null && segments.length == 0 && !isEmptySegment && segment.charAt(segment.length() - 1)== DEVICE_IDENTIFIER)
{
// Capture the first segment as the device.
newDevice = CommonUtil.intern(segment);
newSegments = NO_SEGMENTS;
newAbsolutePath = false;
}
else
{
// Really append the segment.
newSegments = SegmentSequence.STRING_ARRAY_POOL.intern(segments, segments.length, segment, true);
}
return POOL.intern(false, segments.length, true, scheme, authority, newDevice, newAbsolutePath, newSegments, query);
}
@Override
public URI appendSegments(String[] segments)
{
// Do preliminary checking now but full checking later.
//
if (segments == null)
{
throw new IllegalArgumentException("invalid segments: null");
}
else if (segments.length == 1)
{
return appendSegment(segments[0]);
}
else
{
if (device == null && authority == null && this.segments.length == 0)
{
String[] newSegments = SegmentSequence.STRING_ARRAY_POOL.intern(segments, 1, segments.length - 1);
return appendSegment(segments[0]).appendSegments(newSegments);
}
for (String segment : segments)
{
if (segment == null)
{
throw new IllegalArgumentException("invalid segment: null");
}
}
}
// absolute path or no path -> absolute path
boolean newAbsolutePath = !hasRelativePath();
String[] newSegments = SegmentSequence.STRING_ARRAY_POOL.intern(this.segments, segments, true);
return POOL.intern(false, this.segments.length, true, scheme, authority, device, newAbsolutePath, newSegments, query);
}
@Override
public URI trimSegments(int i)
{
if (i < 1) return this;
String[] newSegments;
int len = segments.length - i;
if (len > 0)
{
newSegments = SegmentSequence.STRING_ARRAY_POOL.intern(segments, 0, len);
}
else
{
newSegments = NO_SEGMENTS;
}
return POOL.intern(false, URIPool.URIComponentsAccessUnit.VALIDATE_NONE, true, scheme, authority, device, hasAbsolutePath(), newSegments, query);
}
@Override
public boolean hasTrailingPathSeparator()
{
return (flags & HAS_TRAILING_PATH_SEPARATOR) != 0;
}
@Override
public String fileExtension()
{
int len = segments.length;
if (len == 0) return null;
String lastSegment = segments[len - 1];
int i = lastSegment.lastIndexOf(FILE_EXTENSION_SEPARATOR);
return i < 0 ? null : lastSegment.substring(i + 1);
}
@Override
public URI appendFileExtension(String fileExtension)
{
// Do preliminary checking now and full validation later.
if (fileExtension == null)
{
throw new IllegalArgumentException("invalid segment portion: null");
}
int len = segments.length;
if (len == 0)
{
if (!validSegment(fileExtension))
{
throw new IllegalArgumentException("invalid segment portion: " + fileExtension);
}
return this;
}
String lastSegment = segments[len - 1];
if (SEGMENT_EMPTY == lastSegment)
{
if (!validSegment(fileExtension))
{
throw new IllegalArgumentException("invalid segment portion: " + fileExtension);
}
return this;
}
CommonUtil.StringPool.StringsAccessUnit newLastSegment = CommonUtil.STRING_POOL.getStringBuilder();
newLastSegment.append(lastSegment);
newLastSegment.append(FILE_EXTENSION_SEPARATOR);
newLastSegment.append(fileExtension);
String[] newSegments = SegmentSequence.STRING_ARRAY_POOL.intern(segments, segments.length - 1, CommonUtil.STRING_POOL.intern(newLastSegment), false);
// note: segments.length > 0 -> hierarchical
return POOL.intern(false, len, true, scheme, authority, device, hasAbsolutePath(), newSegments, query);
}
@Override
public URI trimFileExtension()
{
int len = segments.length;
if (len == 0) return this;
String lastSegment = segments[len - 1];
int i = lastSegment.lastIndexOf(FILE_EXTENSION_SEPARATOR);
if (i < 0) return this;
String newLastSegment = lastSegment.substring(0, i);
String[] newSegments = SegmentSequence.STRING_ARRAY_POOL.intern(segments, len - 1, newLastSegment, true);
// note: segments.length > 0 -> hierarchical
return POOL.intern(false, URIPool.URIComponentsAccessUnit.VALIDATE_NONE, true, scheme, authority, device, hasAbsolutePath(), newSegments, query);
}
@Override
public boolean isPrefix()
{
return (flags & IS_PREFIX) != 0;
}
@Override
public URI replacePrefix(URI oldPrefix, URI newPrefix)
{
if (!oldPrefix.isPrefix() || !newPrefix.isPrefix())
{
String which = oldPrefix.isPrefix() ? "new" : "old";
throw new IllegalArgumentException("non-prefix " + which + " value");
}
// Don't even consider it unless this is hierarchical and has scheme,
// authority, device and path absoluteness equal to those of the prefix.
if (scheme != oldPrefix.scheme() || authority != oldPrefix.authority() || device != oldPrefix.device() || hasAbsolutePath() != oldPrefix.hasAbsolutePath())
{
return null;
}
String[] segments = this.segments;
int segmentsLength = segments.length;
// If the prefix has no segments, then it is the root absolute path, and
// we know this is an absolute path, too.
// Get what's left of the segments after trimming the prefix.
String [] oldPrefixSegments = oldPrefix.rawSegments();
int oldPrefixSegmentCount = oldPrefixSegments.length;
int tailSegmentCount;
if (oldPrefixSegmentCount == 0)
{
tailSegmentCount = segmentsLength;
}
else
{
// This must have no fewer segments than the prefix. Since the prefix
// is not the root absolute path, its last segment is empty; all others
// must match.
int i = 0;
int segmentsToCompare = oldPrefixSegmentCount - 1;
if (segmentsLength <= segmentsToCompare) return null;
for (; i < segmentsToCompare; i++)
{
if (segments[i] != oldPrefixSegments[i]) return null;
}
// The prefix really is a prefix of this. If this has just one more,
// empty segment, the paths are the same.
if (i == segmentsLength - 1 && segments[i] == SEGMENT_EMPTY)
{
return newPrefix;
}
else
{
tailSegmentCount = segmentsLength - i;
}
}
// If the new prefix has segments, it is not the root absolute path,
// and we need to drop the trailing empty segment and append the tail
// segments.
String [] newPrefixSegments = newPrefix.rawSegments();
int newPrefixSegmentCount = newPrefixSegments.length;
String[] mergedSegments;
if (newPrefixSegmentCount == 0)
{
mergedSegments = tailSegmentCount == segmentsLength ? segments : SegmentSequence.STRING_ARRAY_POOL.intern(segments, segmentsLength - tailSegmentCount, tailSegmentCount);
}
else
{
mergedSegments = SegmentSequence.STRING_ARRAY_POOL.intern(newPrefixSegments, 0, newPrefixSegmentCount - 1, segments, segmentsLength - tailSegmentCount, tailSegmentCount);
}
// no validation needed since all components are from existing URIs
return POOL.intern(false, URIPool.URIComponentsAccessUnit.VALIDATE_NONE, true, newPrefix.scheme(), newPrefix.authority(), newPrefix.device(), newPrefix.hasAbsolutePath(), mergedSegments, query);
}
}
/**
* A subclass for representing an opaque URI.
*/
protected final static class Opaque extends URI
{
/**
* The scheme of the opaque URI.
*/
protected final String scheme;
/**
* The opaque part of the opaque URI.
*/
protected final String opaquePart;
/**
* A weakly cached reference to the string representation.
*/
protected WeakReference toString;
/**
* Creates an instance from the components.
* Assertions are used to validate the integrity of the result.
* I.e., both components must be interned and the hash code must be equal to the hash code of the {@link #toString()}.
*/
protected Opaque(int hashCode, String scheme, String opaquePart)
{
super(hashCode);
this.scheme = scheme;
this.opaquePart = opaquePart;
// The scheme must be interned and must be lower cased.
//
assert scheme == CommonUtil.internToLowerCase(scheme);
// The authority must be interned.
//
assert opaquePart == CommonUtil.intern(opaquePart);
// The components must be valid.
//
assert validateURI(false, scheme, opaquePart, null, false, NO_SEGMENTS, null, null);
// The hash code must be the same as that of the string representation
//
assert hashCode == toString().hashCode();
}
@Override
public boolean hasOpaquePart()
{
return true;
}
@Override
public String scheme()
{
return scheme;
}
@Override
public String opaquePart()
{
return opaquePart;
}
@Override
protected void cacheString(String string)
{
toString = POOL.newCachedToString(this, string);
}
@Override
protected void flushCachedString()
{
toString = null;
}
@Override
protected String getCachedString()
{
WeakReference toString = this.toString;
if (toString != null)
{
String result = toString.get();
if (result == null)
{
toString.clear();
}
else
{
return result;
}
}
return null;
}
@Override
public String toString()
{
String cachedString = getCachedString();
if (cachedString != null)
{
return cachedString;
}
CommonUtil.StringPool.StringsAccessUnit result = CommonUtil.STRING_POOL.getStringBuilder();
result.append(scheme);
result.append(SCHEME_SEPARATOR);
result.append(opaquePart);
String string = CommonUtil.STRING_POOL.intern(result);
this.toString = POOL.newCachedToString(this, string);
return string;
}
@Override
protected boolean matches(String string)
{
String cachedString = getCachedString();
if (cachedString != null)
{
return cachedString.equals(string);
}
int index = 0;
if (!string.startsWith(scheme))
{
return false;
}
int length = string.length();
index += scheme.length();
if (index >= length || string.charAt(index) != SCHEME_SEPARATOR)
{
return false;
}
++index;
if (!string.startsWith(opaquePart, index))
{
return false;
}
index += opaquePart.length();
return index == length;
}
@Override
protected boolean matches(int validate, boolean hierarchical, String scheme, String authority, String device, boolean absolutePath, String[] segments, String query)
{
return
!hierarchical &&
!absolutePath &&
segments == null &&
query == null &&
(validate >= URIPool.URIComponentsAccessUnit.VALIDATE_NONE ?
this.scheme == scheme && this.opaquePart == authority :
equals(this.scheme, scheme) && equals(this.opaquePart, authority));
}
}
/**
* A subclass for representing a URI with a fragment.
* Most methods simply delegate to the {@link #trimFragment() base} URI.
*/
protected static final class Fragment extends URI
{
/**
* The {@link #trimFragment() base} URI.
*/
protected final URI uri;
/**
* The representation of the fragment.
* The fragment is {@link #splitInternFragment(String) split interned}.
*/
protected CharSequence fragment;
/**
* Creates an instance from the components.
* Assertions are used to validate the integrity of the result.
* I.e., the fragment must be non-null and {@link #splitInternFragment(String) split interned} and the hash code must be equal to the hash code of the {@link #toString()}.
*/
protected Fragment(int hashCode, URI uri, CharSequence fragment)
{
super(hashCode);
this.uri = uri;
this.fragment = fragment;
// There must be a fragment.
//
assert fragment != null;
// The hash code must be the same as that of the string representation,
// unless it's deferred.
//
assert hashCode == 0 || hashCode == toString().hashCode();
}
@Override
public boolean isRelative()
{
return uri.isRelative();
}
@Override
protected boolean isBase()
{
return uri.isBase();
}
@Override
public boolean isHierarchical()
{
return uri.isHierarchical();
}
@Override
public boolean hasAuthority()
{
return uri.hasAuthority();
}
@Override
public boolean hasOpaquePart()
{
return uri.hasOpaquePart();
}
@Override
public boolean hasDevice()
{
return uri.hasDevice();
}
@Override
public boolean hasPath()
{
return uri.hasPath();
}
@Override
protected boolean hasDeviceOrPath()
{
return uri.hasDeviceOrPath();
}
@Override
public boolean hasAbsolutePath()
{
return uri.hasAbsolutePath();
}
@Override
public boolean hasRelativePath()
{
return uri.hasRelativePath();
}
@Override
public boolean hasEmptyPath()
{
return uri.hasEmptyPath();
}
@Override
public boolean hasQuery()
{
return uri.hasQuery();
}
@Override
public boolean hasFragment()
{
return true;
}
@Override
public boolean isCurrentDocumentReference()
{
return uri.isCurrentDocumentReference();
}
@Override
public boolean isEmpty()
{
return false;
}
@Override
public boolean isFile()
{
return uri.isFile();
}
@Override
public boolean isPlatform()
{
return uri.isPlatform();
}
@Override
public boolean isPlatformResource()
{
return uri.isPlatformResource();
}
@Override
public boolean isPlatformPlugin()
{
return uri.isPlatformPlugin();
}
@Override
public boolean isArchive()
{
return uri.isArchive();
}
@Override
protected boolean segmentsEqual(URI uri)
{
return uri.segmentsEqual(uri);
}
@Override
public String scheme()
{
return uri.scheme();
}
@Override
public String opaquePart()
{
return uri.opaquePart();
}
@Override
public String authority()
{
return uri.authority();
}
@Override
public String userInfo()
{
return uri.userInfo();
}
@Override
public String host()
{
return uri.host();
}
@Override
public String port()
{
return uri.port();
}
@Override
public String device()
{
return uri.device();
}
@Override
public String[] segments()
{
return uri.segments();
}
@Override
protected String[] rawSegments()
{
return uri.rawSegments();
}
@Override
public List segmentsList()
{
return uri.segmentsList();
}
@Override
public int segmentCount()
{
return uri.segmentCount();
}
@Override
public String segment(int i)
{
return uri.segment(i);
}
@Override
public String lastSegment()
{
return uri.lastSegment();
}
@Override
public String path()
{
return uri.path();
}
@Override
public String devicePath()
{
return uri.devicePath();
}
@Override
public String query()
{
return uri.query();
}
private URI appendFragment(URI uri)
{
// If the hash code is 0 then it's highly likely we've deferred split interning the fragment, so don't use rawAppendFragment in that case.
//
return hashCode == 0 ? uri.appendFragment(fragment.toString()) : uri.rawAppendFragment(fragment);
}
@Override
public URI appendQuery(String query)
{
return appendFragment(uri.appendQuery(query));
}
@Override
public URI trimQuery()
{
URI result = uri.trimQuery();
return result == uri ? this : appendFragment(result);
}
@Override
public String fragment()
{
if (hashCode == 0)
{
hashCode();
}
return fragment.toString();
}
@Override
public URI appendFragment(String fragment)
{
return uri.appendFragment(fragment);
}
@Override
public URI trimFragment()
{
return uri;
}
@Override
public URI resolve(URI base, boolean preserveRootParents)
{
URI result = uri.resolve(base, preserveRootParents);
return result == uri ? this : appendFragment(result);
}
@Override
public URI deresolve(URI base, boolean preserveRootParents, boolean anyRelPath, boolean shorterRelPath)
{
URI result = uri.deresolve(base, preserveRootParents, anyRelPath, shorterRelPath);
return result == uri ? this : appendFragment(result);
}
@Override
protected String[] collapseSegments(boolean preserveRootParents)
{
return uri.collapseSegments(preserveRootParents);
}
@Override
public String toString()
{
CommonUtil.StringPool.StringsAccessUnit result = CommonUtil.STRING_POOL.getStringBuilder();
result.append(uri.toString());
result.append(FRAGMENT_SEPARATOR);
result.append(fragment);
return CommonUtil.STRING_POOL.intern(result);
}
@Override
public int hashCode()
{
// Check if we have a deferred hash code initialization pending...
// Note there is the very remote possibility that the hash code could really be 0...
//
if (hashCode == 0)
{
hashCode = ((uri.hashCode * 31) + FRAGMENT_SEPARATOR) * CommonUtil.powerOf31(fragment.length()) + fragment.hashCode();
// In that case, also split intern the fragment, but check if it's really a string, because otherwise it really must be split interned already.
//
if (fragment instanceof String)
{
fragment = splitInternFragment(fragment.toString());
}
}
return hashCode;
}
@Override
public boolean equals(Object object)
{
if (object == this)
{
return true;
}
if (!(object instanceof Fragment))
{
return false;
}
// Be careful to accommodate the case of a deferred split interned fragment.
//
Fragment that = (Fragment)object;
return uri == that.uri && (fragment == that.fragment || fragment.toString().equals(that.fragment().toString()));
}
@Override
public String toFileString()
{
return uri.toFileString();
}
@Override
public String toPlatformString(boolean decode)
{
return uri.toPlatformString(decode);
}
@Override
public URI appendSegment(String segment)
{
URI result = uri.appendSegment(segment);
return result == uri ? this : appendFragment(result);
}
@Override
public URI appendSegments(String[] segments)
{
URI result = uri.appendSegments(segments);
return result == uri ? this : appendFragment(result);
}
@Override
public URI trimSegments(int i)
{
URI result = uri.trimSegments(i);
return result == uri ? this : appendFragment(result);
}
@Override
public boolean hasTrailingPathSeparator()
{
return uri.hasTrailingPathSeparator();
}
@Override
public String fileExtension()
{
return uri.fileExtension();
}
@Override
public URI appendFileExtension(String fileExtension)
{
URI result = uri.appendFileExtension(fileExtension);
return result == uri ? this : appendFragment(result);
}
@Override
public URI trimFileExtension()
{
URI result = uri.trimFileExtension();
return result == uri ? this : result.rawAppendFragment(fragment);
}
@Override
public URI replacePrefix(URI oldPrefix, URI newPrefix)
{
URI result = uri.replacePrefix(oldPrefix, newPrefix);
return result == uri ? this : result == null ? null : appendFragment(result);
}
}
protected void flushCachedString()
{
// Do nothing.
}
protected void cacheString(String string)
{
// Do nothing.
}
protected String getCachedString()
{
return null;
}
/**
* Returns true if this is a relative URI, or
* false if it is an absolute URI.
*/
public boolean isRelative()
{
return false;
}
// Whether this this URI valid has a base URI against which to resolve.
//
protected boolean isBase()
{
return false;
}
/**
* Returns true if this a a hierarchical URI, or
* false if it is of the generic form.
*/
public boolean isHierarchical()
{
return false;
}
/**
* Returns true if this is a hierarchical URI with an authority
* component; false otherwise.
*/
public boolean hasAuthority()
{
return false;
}
/**
* Returns true if this is a non-hierarchical URI with an
* opaque part component; false otherwise.
*/
public boolean hasOpaquePart()
{
// note: hierarchical -> authority != null
return false;
}
/**
* Returns true if this is a hierarchical URI with a device
* component; false otherwise.
*/
public boolean hasDevice()
{
// note: device != null -> hierarchical
return false;
}
protected boolean hasDeviceOrPath()
{
return false;
}
/**
* Returns true if this is a hierarchical URI with an
* absolute or relative path; false otherwise.
*/
public boolean hasPath()
{
// note: (absolutePath || authority == null) -> hierarchical
// (authority == null && device == null && !absolutePath) -> scheme == null
return false;
}
/**
* Returns true if this is a hierarchical URI with an
* absolute path, or false if it is non-hierarchical, has no
* path, or has a relative path.
*/
public boolean hasAbsolutePath()
{
// note: absolutePath -> hierarchical
return false;
}
/**
* Returns true if this is a hierarchical URI with a relative
* path, or false if it is non-hierarchical, has no path, or
* has an absolute path.
*/
public boolean hasRelativePath()
{
// note: authority == null -> hierarchical
// (authority == null && device == null && !absolutePath) -> scheme == null
return false;
}
/**
* Returns true if this is a hierarchical URI with an empty
* relative path; false otherwise.
*
*
Note that !hasEmpty() does not imply that this
* URI has any path segments; however, hasRelativePath &&
* !hasEmptyPath() does.
*/
public boolean hasEmptyPath()
{
// note: authority == null -> hierarchical
// (authority == null && device == null && !absolutePath) -> scheme == null
return false;
}
/**
* Returns true if this is a hierarchical URI with a query
* component; false otherwise.
*/
public boolean hasQuery()
{
// note: query != null -> hierarchical
return false;
}
/**
* Returns true if this URI has a fragment component;
* false otherwise.
*/
public boolean hasFragment()
{
return false;
}
/**
* Returns true if this is a current document reference; that
* is, if it is a relative hierarchical URI with no authority, device or
* query components, and no path segments; false is returned
* otherwise.
*/
public boolean isCurrentDocumentReference()
{
// note: authority == null -> hierarchical
// (authority == null && device == null && !absolutePath) -> scheme == null
return false;
}
/**
* Returns true if this is a {@link
* #isCurrentDocumentReference() current document reference} with no
* fragment component; false otherwise.
*
* @see #isCurrentDocumentReference()
*/
public boolean isEmpty()
{
// note: authority == null -> hierarchical
// (authority == null && device == null && !absolutePath) -> scheme == null
return false;
}
/**
* Returns true if this is a hierarchical URI that may refer
* directly to a locally accessible file. This is considered to be the
* case for a file-scheme absolute URI, or for a relative URI with no query;
* false is returned otherwise.
*/
public boolean isFile()
{
return false;
}
/**
* Returns true if this is a platform URI, that is, an absolute,
* hierarchical URI, with "platform" scheme, no authority, and at least two
* segments; false is returned otherwise.
* @since org.eclipse.emf.common 2.3
*/
public boolean isPlatform()
{
return false;
}
/**
* Returns true if this is a platform resource URI, that is,
* a {@link #isPlatform platform URI} whose first segment is "resource";
* false is returned otherwise.
* @see #isPlatform
* @since org.eclipse.emf.common 2.3
*/
public boolean isPlatformResource()
{
return false;
}
/**
* Returns true if this is a platform plug-in URI, that is,
* a {@link #isPlatform platform URI} whose first segment is "plugin";
* false is returned otherwise.
* @see #isPlatform
* @since org.eclipse.emf.common 2.3
*/
public boolean isPlatformPlugin()
{
return false;
}
/**
* Returns true if this is an archive URI. If so, it is also
* hierarchical, with an authority (consisting of an absolute URI followed
* by "!"), no device, and an absolute path.
*/
public boolean isArchive()
{
return false;
}
/**
* Returns true if the specified value would be
* valid as the scheme of an archive URI; false
* otherwise.
*/
public static boolean isArchiveScheme(String value)
{
// Returns true if the given value is an archive scheme, as defined by
// the org.eclipse.emf.common.util.URI.archiveSchemes system property.
// By default, "jar", "zip", and "archive" are considered archives.
for (String scheme : ARCHIVE_SCHEMES)
{
if (scheme.equals(value))
{
return true;
}
}
return false;
}
/**
* Returns the hash code.
*/
@Override
public int hashCode()
{
return hashCode;
}
// Tests whether this URI's path segment array is equal to that of the given uri.
protected boolean segmentsEqual(URI uri)
{
return false;
}
// Tests two objects for equality, tolerating nulls; null is considered
// to be a valid value that is only equal to itself.
protected static boolean equals(Object o1, Object o2)
{
return o1 == o2 || o1 != null && o1.equals(o2);
}
/**
* If this is an absolute URI, returns the scheme component;
* null otherwise.
*/
public String scheme()
{
return null;
}
/**
* If this is a non-hierarchical URI, returns the opaque part component;
* null otherwise.
*/
public String opaquePart()
{
return null;
}
/**
* If this is a hierarchical URI with an authority component, returns it;
* null otherwise.
*/
public String authority()
{
return null;
}
/**
* If this is a hierarchical URI with an authority component that has a
* user info portion, returns it; null otherwise.
*/
public String userInfo()
{
return null;
}
/**
* If this is a hierarchical URI with an authority component that has a
* host portion, returns it; null otherwise.
*/
public String host()
{
return null;
}
/**
* If this is a hierarchical URI with an authority component that has a
* port portion, returns it; null otherwise.
*/
public String port()
{
return null;
}
/**
* If this is a hierarchical URI with a device component, returns it;
* null otherwise.
*/
public String device()
{
return null;
}
/**
* If this is a hierarchical URI with a path, returns an array containing
* the segments of the path; an empty array otherwise. The leading
* separator in an absolute path is not represented in this array, but a
* trailing separator is represented by an empty-string segment as the
* final element.
*/
public String[] segments()
{
return NO_SEGMENTS;
}
// Directly returns the underlying segments without cloning them.
//
protected String[] rawSegments()
{
return NO_SEGMENTS;
}
/**
* Returns an unmodifiable list containing the same segments as the array
* returned by {@link #segments segments}.
*/
public List segmentsList()
{
return Collections.emptyList();
}
/**
* Returns the number of elements in the segment array that would be
* returned by {@link #segments segments}.
*/
public int segmentCount()
{
return 0;
}
/**
* Provides fast, indexed access to individual segments in the path
* segment array.
*
* @exception java.lang.IndexOutOfBoundsException if i < 0 or
* i >= segmentCount().
*/
public String segment(int i)
{
throw new IndexOutOfBoundsException("No such segment: " + i);
}
/**
* Returns the last segment in the segment array, or null.
*/
public String lastSegment()
{
return null;
}
/**
* If this is a hierarchical URI with a path, returns a string
* representation of the path; null otherwise. The path
* consists of a leading segment separator character (a slash), if the
* path is absolute, followed by the slash-separated path segments. If
* this URI has a separate device
* component, it is not included in the path.
*/
public String path()
{
return null;
}
/**
* If this is a hierarchical URI with a path, returns a string
* representation of the path, including the authority and the
* device component;
* null otherwise.
*
*
If there is no authority, the format of this string is:
*
*/
public String devicePath()
{
return null;
}
/**
* If this is a hierarchical URI with a query component, returns it;
* null otherwise.
*/
public String query()
{
return null;
}
/**
* Returns the URI formed from this URI and the given query.
*
* @exception java.lang.IllegalArgumentException if
* query is not a valid query (portion) according
* to {@link #validQuery validQuery}.
*/
public URI appendQuery(String query)
{
if (!validQuery(query))
{
throw new IllegalArgumentException("invalid query portion: " + query);
}
return this;
}
/**
* If this URI has a non-null {@link #query query}, returns the URI
* formed by removing it; this URI unchanged, otherwise.
*/
public URI trimQuery()
{
return this;
}
/**
* If this URI has a fragment component, returns it; null otherwise.
*/
public String fragment()
{
return null;
}
/**
* A weak reference for the external queue that when cleared will
*/
/**
* Returns the URI formed from this URI and the given fragment.
*
* @exception java.lang.IllegalArgumentException if
* fragment is not a valid fragment (portion) according
* to {@link #validFragment validFragment}.
*/
public URI appendFragment(String fragment)
{
if (fragment == null)
{
return this;
}
else
{
if (POOL.externalQueue != null)
{
return new Fragment(0, this, fragment);
}
else
{
int hashCode = ((this.hashCode * 31) + FRAGMENT_SEPARATOR) * CommonUtil.powerOf31(fragment.length()) + fragment.hashCode();
return new Fragment(hashCode, this, splitInternFragment(fragment));
}
}
}
// Returns the URI formed from this uri and the already properly interned fragment representation.
//
protected URI rawAppendFragment(CharSequence fragment)
{
if (fragment == null)
{
return this;
}
else
{
int hashCode = ((this.hashCode * 31) + FRAGMENT_SEPARATOR) * CommonUtil.powerOf31(fragment.length()) + fragment.hashCode();
return new Fragment(hashCode, this, fragment);
}
}
/**
* If this URI has a non-null {@link #fragment fragment}, returns the URI
* formed by removing it; this URI unchanged, otherwise.
*/
public URI trimFragment()
{
return this;
}
/**
* Resolves this URI reference against a base absolute
* hierarchical URI, returning the resulting absolute URI. If already
* absolute, the URI itself is returned. URI resolution is described in
* detail in section 5.2 of RFC
* 2396, "Resolving Relative References to Absolute Form."
*
*
During resolution, empty segments, self references ("."), and parent
* references ("..") are interpreted, so that they can be removed from the
* path. Step 6(g) gives a choice of how to handle the case where parent
* references point to a path above the root: the offending segments can
* be preserved or discarded. This method preserves them. To have them
* discarded, please use the two-parameter form of {@link
* #resolve(URI, boolean) resolve}.
*
* @exception java.lang.IllegalArgumentException if base is
* non-hierarchical or is relative.
*/
public URI resolve(URI base)
{
return resolve(base, true);
}
/**
* Resolves this URI reference against a base absolute
* hierarchical URI, returning the resulting absolute URI. If already
* absolute, the URI itself is returned. URI resolution is described in
* detail in section 5.2 of RFC
* 2396, "Resolving Relative References to Absolute Form."
*
*
During resolution, empty segments, self references ("."), and parent
* references ("..") are interpreted, so that they can be removed from the
* path. Step 6(g) gives a choice of how to handle the case where parent
* references point to a path above the root: the offending segments can
* be preserved or discarded. This method can do either.
*
* @param preserveRootParents true if segments referring to the
* parent of the root path are to be preserved; false if they
* are to be discarded.
*
* @exception java.lang.IllegalArgumentException if base is
* non-hierarchical or is relative.
*/
public URI resolve(URI base, boolean preserveRootParents)
{
if (!base.isHierarchical() || base.isRelative())
{
throw new IllegalArgumentException(
"resolve against non-hierarchical or relative base");
}
return this;
}
/**
* Finds the shortest relative or, if necessary, the absolute URI that,
* when resolved against the given base absolute hierarchical
* URI using {@link #resolve(URI) resolve}, will yield this absolute URI.
* If base is non-hierarchical or is relative,
* or this is non-hierarchical or is relative,
* this will be returned.
*/
public URI deresolve(URI base)
{
return deresolve(base, true, false, true);
}
/**
* Finds an absolute URI that, when resolved against the given
* base absolute hierarchical URI using {@link
* #resolve(URI, boolean) resolve}, will yield this absolute URI.
* If base is non-hierarchical or is relative,
* or this is non-hierarchical or is relative,
* this will be returned.
*
* @param preserveRootParents the boolean argument to resolve(URI,
* boolean) for which the returned URI should resolve to this URI.
* @param anyRelPath if true, the returned URI's path (if
* any) will be relative, if possible. If false, the form of
* the result's path will depend upon the next parameter.
* @param shorterRelPath if anyRelPath is false
* and this parameter is true, the returned URI's path (if
* any) will be relative, if one can be found that is no longer (by number
* of segments) than the absolute path. If both anyRelPath
* and this parameter are false, it will be absolute.
*/
public URI deresolve(URI base, boolean preserveRootParents, boolean anyRelPath, boolean shorterRelPath)
{
return this;
}
protected String[] collapseSegments(boolean preserveRootParents)
{
return NO_SEGMENTS;
}
// Returns whether the string representation of the URI fully matches the given string.
//
protected boolean matches(String string)
{
return false;
}
// Used to match a URI against the specified components.
//
protected boolean matches(int validate, boolean hierarchical, String scheme, String authority, String device, boolean absolutePath, String[] segments, String query)
{
return false;
}
// Used to match a platform URI composed from these two components.
//
protected boolean matches(String base, String path)
{
return false;
}
/**
* If this URI may refer directly to a locally accessible file, as
* determined by {@link #isFile isFile}, {@link #decode decodes} and formats
* the URI as a pathname to that file; returns null otherwise.
*
*
If there is no authority, the format of this string is:
*
* device/pathSegment1/pathSegment2...
*
*
If there is an authority, it is:
*
* //authority/device/pathSegment1/pathSegment2...
*
*
However, the character used as a separator is system-dependent and
* obtained from {@link java.io.File#separatorChar}.
*/
public String toFileString()
{
return null;
}
/**
* If this is a platform URI, as determined by {@link #isPlatform}, returns
* the workspace-relative or plug-in-based path to the resource, optionally
* {@link #decode decoding} the segments in the process.
* @see #createPlatformResourceURI(String, boolean)
* @see #createPlatformPluginURI
* @since org.eclipse.emf.common 2.3
*/
public String toPlatformString(boolean decode)
{
return null;
}
/**
* Returns the URI formed by appending the specified segment on to the end
* of the path of this URI, if hierarchical; this URI unchanged,
* otherwise. If this URI has an authority and/or device, but no path,
* the segment becomes the first under the root in an absolute path.
*
* @exception java.lang.IllegalArgumentException if segment
* is not a valid segment according to {@link #validSegment}.
*/
public URI appendSegment(String segment)
{
if (!validSegment(segment))
{
throw new IllegalArgumentException("invalid segment: " + segment);
}
return this;
}
/**
* Returns the URI formed by appending the specified segments on to the
* end of the path of this URI, if hierarchical; this URI unchanged,
* otherwise. If this URI has an authority and/or device, but no path,
* the segments are made to form an absolute path.
*
* @param segments an array of non-null strings, each representing one
* segment of the path. If desired, a trailing separator should be
* represented by an empty-string segment as the last element of the
* array.
*
* @exception java.lang.IllegalArgumentException if segments
* is not a valid segment array according to {@link #validSegments}.
*/
public URI appendSegments(String[] segments)
{
if (!validSegments(segments))
{
String s = segments == null ? "invalid segments: null" : "invalid segment: " + firstInvalidSegment(segments);
throw new IllegalArgumentException(s);
}
return this;
}
/**
* Returns the URI formed by trimming the specified number of segments
* (including empty segments, such as one representing a trailing
* separator) from the end of the path of this URI, if hierarchical;
* otherwise, this URI is returned unchanged.
*
*
Note that if all segments are trimmed from an absolute path, the
* root absolute path remains.
*
* @param i the number of segments to be trimmed in the returned URI. If
* less than 1, this URI is returned unchanged; if equal to or greater
* than the number of segments in this URI's path, all segments are
* trimmed.
*/
public URI trimSegments(int i)
{
return this;
}
/**
* Returns true if this is a hierarchical URI that has a path
* that ends with a trailing separator; false otherwise.
*
*
A trailing separator is represented as an empty segment as the
* last segment in the path; note that this definition does not
* include the lone separator in the root absolute path.
*/
public boolean hasTrailingPathSeparator()
{
return false;
}
/**
* If this is a hierarchical URI whose path includes a file extension,
* that file extension is returned; null otherwise. We define a file
* extension as any string following the last period (".") in the final
* path segment. If there is no path, the path ends in a trailing
* separator, or the final segment contains no period, then we consider
* there to be no file extension. If the final segment ends in a period,
* then the file extension is an empty string.
*/
public String fileExtension()
{
return null;
}
/**
* Returns the URI formed by appending a period (".") followed by the
* specified file extension to the last path segment of this URI, if it is
* hierarchical with a non-empty path ending in a non-empty segment;
* otherwise, this URI is returned unchanged.
*
The extension is appended regardless of whether the segment already
* contains an extension.
*
* @exception java.lang.IllegalArgumentException if
* fileExtension is not a valid segment (portion) according
* to {@link #validSegment}.
*/
public URI appendFileExtension(String fileExtension)
{
if (!validSegment(fileExtension))
{
throw new IllegalArgumentException("invalid segment portion: " + fileExtension);
}
return this;
}
/**
* If this URI has a non-null {@link #fileExtension fileExtension},
* returns the URI formed by removing it; this URI unchanged, otherwise.
*/
public URI trimFileExtension()
{
return this;
}
/**
* Returns true if this is a hierarchical URI that ends in a
* slash; that is, it has a trailing path separator or is the root
* absolute path, and has no query and no fragment; false
* is returned otherwise.
*/
public boolean isPrefix()
{
return false;
}
/**
* If this is a hierarchical URI reference and oldPrefix is a
* prefix of it, this returns the URI formed by replacing it by
* newPrefix; null otherwise.
*
*
In order to be a prefix, the oldPrefix's
* {@link #isPrefix isPrefix} must return true, and it must
* match this URI's scheme, authority, and device. Also, the paths must
* match, up to prefix's end.
*
* @exception java.lang.IllegalArgumentException if either
* oldPrefix or newPrefix is not a prefix URI
* according to {@link #isPrefix}.
*/
public URI replacePrefix(URI oldPrefix, URI newPrefix)
{
if (!oldPrefix.isPrefix() || !newPrefix.isPrefix())
{
String which = oldPrefix.isPrefix() ? "new" : "old";
throw new IllegalArgumentException("non-prefix " + which + " value");
}
return null;
}
/**
* Encodes a string so as to produce a valid opaque part value, as defined
* by the RFC. All excluded characters, such as space and #,
* are escaped, as is / if it is the first character.
*
* @param ignoreEscaped true to leave % characters
* unescaped if they already begin a valid three-character escape sequence;
* false to encode all % characters. Note that
* if a % is not followed by 2 hex digits, it will always be
* escaped.
*/
public static String encodeOpaquePart(String value, boolean ignoreEscaped)
{
String result = encode(value, URIC_HI, URIC_LO, ignoreEscaped);
return
result != null && result.length() > 0 && result.charAt(0) == SEGMENT_SEPARATOR ? "%2F" + result.substring(1) : result;
}
/**
* Encodes a string so as to produce a valid authority, as defined by the
* RFC. All excluded characters, such as space and #,
* are escaped, as are / and ?
*
* @param ignoreEscaped true to leave % characters
* unescaped if they already begin a valid three-character escape sequence;
* false to encode all % characters. Note that
* if a % is not followed by 2 hex digits, it will always be
* escaped.
*/
public static String encodeAuthority(String value, boolean ignoreEscaped)
{
return encode(value, SEGMENT_CHAR_HI, SEGMENT_CHAR_LO, ignoreEscaped);
}
/**
* Encodes a string so as to produce a valid segment, as defined by the
* RFC. All excluded characters, such as space and #,
* are escaped, as are / and ?
*
* @param ignoreEscaped true to leave % characters
* unescaped if they already begin a valid three-character escape sequence;
* false to encode all % characters. Note that
* if a % is not followed by 2 hex digits, it will always be
* escaped.
*/
public static String encodeSegment(String value, boolean ignoreEscaped)
{
return encode(value, SEGMENT_CHAR_HI, SEGMENT_CHAR_LO, ignoreEscaped);
}
/**
* Encodes a string so as to produce a valid query, as defined by the RFC.
* Only excluded characters, such as space and #, are escaped.
*
* @param ignoreEscaped true to leave % characters
* unescaped if they already begin a valid three-character escape sequence;
* false to encode all % characters. Note that
* if a % is not followed by 2 hex digits, it will always be
* escaped.
*/
public static String encodeQuery(String value, boolean ignoreEscaped)
{
return encode(value, URIC_HI, URIC_LO, ignoreEscaped);
}
/**
* Encodes a string so as to produce a valid fragment, as defined by the
* RFC. Only excluded characters, such as space and #, are
* escaped.
*
* @param ignoreEscaped true to leave % characters
* unescaped if they already begin a valid three-character escape sequence;
* false to encode all % characters. Note that
* if a % is not followed by 2 hex digits, it will always be
* escaped.
*/
public static String encodeFragment(String value, boolean ignoreEscaped)
{
return encode(value, URIC_HI, URIC_LO, ignoreEscaped);
}
// Encodes a complete URI, optionally leaving % characters unescaped when
// beginning a valid three-character escape sequence. We can either treat
// the first or # as a fragment separator, or encode them all.
protected static String encodeURI(String uri, boolean ignoreEscaped, int fragmentLocationStyle)
{
if (uri == null) return null;
StringBuilder result = new StringBuilder();
int i = uri.indexOf(SCHEME_SEPARATOR);
if (i != -1)
{
String scheme = uri.substring(0, i);
result.append(scheme);
result.append(SCHEME_SEPARATOR);
}
int j =
fragmentLocationStyle == FRAGMENT_FIRST_SEPARATOR ?
uri.indexOf(FRAGMENT_SEPARATOR) :
fragmentLocationStyle == FRAGMENT_LAST_SEPARATOR ?
uri.lastIndexOf(FRAGMENT_SEPARATOR) :
-1;
if (j != -1)
{
String sspart = uri.substring(++i, j);
result.append(encode(sspart, URIC_HI, URIC_LO, ignoreEscaped));
result.append(FRAGMENT_SEPARATOR);
String fragment = uri.substring(++j);
result.append(encode(fragment, URIC_HI, URIC_LO, ignoreEscaped));
}
else
{
String sspart = uri.substring(++i);
result.append(encode(sspart, URIC_HI, URIC_LO, ignoreEscaped));
}
return result.toString();
}
// Encodes the given string, replacing each ASCII character that is not in
// the set specified by the 128-bit bitmask and each non-ASCII character
// below 0xA0 by an escape sequence of % followed by two hex digits. If
// % is not in the set but ignoreEscaped is true, then % will not be encoded
// iff it already begins a valid escape sequence.
protected static String encode(String value, long highBitmask, long lowBitmask, boolean ignoreEscaped)
{
if (value == null) return null;
StringBuffer result = null;
for (int i = 0, len = value.length(); i < len; i++)
{
char c = value.charAt(i);
if (!matches(c, highBitmask, lowBitmask) && c < 160 && (!ignoreEscaped || !isEscaped(value, i)))
{
if (result == null)
{
result = new StringBuffer(value.substring(0, i));
}
appendEscaped(result, (byte)c);
}
else if (result != null)
{
result.append(c);
}
}
return result == null ? value : result.toString();
}
// Tests whether an escape occurs in the given string, starting at index i.
// An escape sequence is a % followed by two hex digits.
protected static boolean isEscaped(String s, int i)
{
return s.charAt(i) == ESCAPE && s.length() > i + 2 && matches(s.charAt(i + 1), HEX_HI, HEX_LO) && matches(s.charAt(i + 2), HEX_HI, HEX_LO);
}
// Computes a three-character escape sequence for the byte, appending
// it to the StringBuffer. Only characters up to 0xFF should be escaped;
// all but the least significant byte will be ignored.
protected static void appendEscaped(StringBuffer result, byte b)
{
result.append(ESCAPE);
// The byte is automatically widened into an int, with sign extension,
// for shifting. This can introduce 1's to the left of the byte, which
// must be cleared by masking before looking up the hex digit.
//
result.append(HEX_DIGITS[(b >> 4) & 0x0F]);
result.append(HEX_DIGITS[b & 0x0F]);
}
/**
* Decodes the given string by interpreting three-digit escape sequences as the bytes of a UTF-8 encoded character
* and replacing them with the characters they represent.
* Incomplete escape sequences are ignored and invalid UTF-8 encoded bytes are treated as extended ASCII characters.
*/
public static String decode(String value)
{
if (value == null) return null;
int i = value.indexOf('%');
if (i < 0)
{
return value;
}
else
{
StringBuilder result = new StringBuilder(value.substring(0, i));
byte [] bytes = new byte[4];
int receivedBytes = 0;
int expectedBytes = 0;
for (int len = value.length(); i < len; i++)
{
if (isEscaped(value, i))
{
char character = unescape(value.charAt(i + 1), value.charAt(i + 2));
i += 2;
if (expectedBytes > 0)
{
if ((character & 0xC0) == 0x80)
{
bytes[receivedBytes++] = (byte)character;
}
else
{
expectedBytes = 0;
}
}
else if (character >= 0x80)
{
if ((character & 0xE0) == 0xC0)
{
bytes[receivedBytes++] = (byte)character;
expectedBytes = 2;
}
else if ((character & 0xF0) == 0xE0)
{
bytes[receivedBytes++] = (byte)character;
expectedBytes = 3;
}
else if ((character & 0xF8) == 0xF0)
{
bytes[receivedBytes++] = (byte)character;
expectedBytes = 4;
}
}
if (expectedBytes > 0)
{
if (receivedBytes == expectedBytes)
{
switch (receivedBytes)
{
case 2:
{
result.append((char)((bytes[0] & 0x1F) << 6 | bytes[1] & 0x3F));
break;
}
case 3:
{
result.append((char)((bytes[0] & 0xF) << 12 | (bytes[1] & 0X3F) << 6 | bytes[2] & 0x3F));
break;
}
case 4:
{
result.appendCodePoint(((bytes[0] & 0x7) << 18 | (bytes[1] & 0X3F) << 12 | (bytes[2] & 0X3F) << 6 | bytes[3] & 0x3F));
break;
}
}
receivedBytes = 0;
expectedBytes = 0;
}
}
else
{
for (int j = 0; j < receivedBytes; ++j)
{
result.append((char)bytes[j]);
}
receivedBytes = 0;
result.append(character);
}
}
else
{
for (int j = 0; j < receivedBytes; ++j)
{
result.append((char)bytes[j]);
}
receivedBytes = 0;
result.append(value.charAt(i));
}
}
return result.toString();
}
}
// Returns the character encoded by % followed by the two given hex digits,
// which is always 0xFF or less, so can safely be casted to a byte. If
// either character is not a hex digit, a bogus result will be returned.
protected static char unescape(char highHexDigit, char lowHexDigit)
{
return (char)((valueOf(highHexDigit) << 4) | valueOf(lowHexDigit));
}
// Returns the int value of the given hex digit.
protected static int valueOf(char hexDigit)
{
if (hexDigit <= '9')
{
if (hexDigit >= '0')
{
return hexDigit - '0';
}
}
else if (hexDigit <= 'F')
{
if (hexDigit >= 'A')
{
return hexDigit - 'A' + 10;
}
}
else if (hexDigit >= 'a' && hexDigit <= 'f')
{
return hexDigit - 'a' + 10;
}
return 0;
}
public static void main(String[] args)
{
System.out.println("###" + URI.createURI("d:a/b"));
System.out.println("###" + URI.createURI("d:a/b").isFile());
System.out.println("###" + URI.createURI("d:a/b").toFileString());
File file = new File("a");
System.out.println(new File(URI.createFileURI(file.getPath()).toFileString()).equals(new File("a").getAbsoluteFile()));
}
}