blob: b5fc522444533ae118b7ffe25c6017d809cc5ec2 [file] [log] [blame]
/**
* Copyright (c) 2004-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.test.core.common.util;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertSame;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
import java.io.File;
import java.lang.ref.WeakReference;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.ConcurrentModificationException;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Random;
import java.util.Set;
import java.util.StringTokenizer;
import java.util.UUID;
import org.eclipse.emf.common.util.InterningSet;
import org.eclipse.emf.common.util.URI;
import org.junit.Test;
public class URITest
{
protected static final String URN = "mailto:me@yahoo.com";
protected static final String[] ABSOLUTE_URLS = {
"file:/",
"file:/bar",
"file:/bar/",
"file:/bar/baz",
"file:/bar/baz/",
"file:/c:",
"file:/c:/",
"file:/c:/bar",
"file:/c:/bar/",
"file:/c:/bar/baz",
"file:/c:/bar/baz/",
"file://foo",
"file://foo/",
"file://foo/bar",
"file://foo/bar/",
"file://foo/bar/baz",
"file://foo/bar/baz/",
"file://foo/c:",
"file://foo/c:/",
"file://foo/c:/bar",
"file://foo/c:/bar/",
"file://foo/c:/bar/baz",
"file://foo/c:/bar/baz/"
};
protected static final String[] RELATIVE_URLS = {
"",
"nif",
"nif/",
"nif/phi",
"nif/phi/",
"/",
"/nif",
"/nif/",
"/nif/phi",
"/nif/phi/",
"/d:",
"/d:/nif",
"/d:/nif/",
"/d:/nif/phi",
"/d:/nif/phi/",
"//sig",
"//sig/",
"//sig/nif",
"//sig/nif/",
"//sig/nif/phi",
"//sig/nif/phi/",
"//sig/d:",
"//sig/d:/",
"//sig/d:/nif",
"//sig/d:/nif/",
"//sig/d:/nif/phi",
"//sig/d:/nif/phi/"
};
protected static final String[] QUERIES = { "", "?q=huh" };
protected static final String[] FRAGMENTS = { "", "#toc", "#/a/b/c/d/e/f/g/h/i/j/k/l/m/n/o/p" };
protected static final String BASE_URI = "http://a/b/c/d;p?q";
protected static final String[] UNRESOLVED_URIS = {
"g:h",
"g",
"./g",
"g/",
"/g",
"//g",
"?y",
"g?y",
"#s",
"g#s",
"g?y#s",
";x",
"g;x",
"g;x?y#s",
".",
"./",
"..",
"../",
"../g",
"../..",
"../../",
"../../g",
"",
"/./g",
"/../g",
"g.",
".g",
"g..",
"..g",
"./../g",
"./g/.",
"g/./h",
"g/../h",
"g;x=1/./y",
"g;x=1/../y",
"g?y/./x",
"g?y/../x",
"g#s/./x",
"g#s/../x",
"http:g"
};
protected static final String[] RESOLVED_URIS = {
"g:h",
"http://a/b/c/g",
"http://a/b/c/g",
"http://a/b/c/g/",
"http://a/g",
"http://g",
"http://a/b/c/?y",
"http://a/b/c/g?y",
"http://a/b/c/d;p?q#s",
"http://a/b/c/g#s",
"http://a/b/c/g?y#s",
"http://a/b/c/;x",
"http://a/b/c/g;x",
"http://a/b/c/g;x?y#s",
"http://a/b/c/",
"http://a/b/c/",
"http://a/b/",
"http://a/b/",
"http://a/b/g",
"http://a/",
"http://a/",
"http://a/g",
"http://a/b/c/d;p?q",
"http://a/./g",
"http://a/../g",
"http://a/b/c/g.",
"http://a/b/c/.g",
"http://a/b/c/g..",
"http://a/b/c/..g",
"http://a/b/g",
"http://a/b/c/g/",
"http://a/b/c/g/h",
"http://a/b/c/h",
"http://a/b/c/g;x=1/y",
"http://a/b/c/y",
"http://a/b/c/g?y/./x",
"http://a/b/c/g?y/../x",
"http://a/b/c/g#s/./x",
"http://a/b/c/g#s/../x",
"http:g"
};
protected static final String[] UNRESOLVED_ABOVE_ROOT_URIS = { "../../../g", "../../../../g" };
protected static final String[] RESOLVED_PRESERVE_ABOVE_ROOT_URIS = { "http://a/../g", "http://a/../../g" };
protected static final String[] RESOLVED_NO_PRESERVE_ABOVE_ROOT_URIS = { "http://a/g", "http://a/g" };
protected static final String[] NON_CANONICAL_UNRESOLVED_URIS = {
"./../g",
"./g/.",
"g/./h",
"g/../h",
"g;x=1/./y",
"g;x=1/../y"
};
protected static final String[] NON_CANONICAL_PRESERVE_ABOVE_ROOT_UNRESOLVED_URIS = { };
protected static final String[] NON_CANONICAL_NO_PRESERVE_ABOVE_ROOT_UNRESOLVED_URIS = { "../../../g", "../../../../g" };
protected static final String[] AUTHORITY_PARSE_URIS = {
"not/here",
"//myhost/",
"//me@myhost/",
"//myhost:1234/",
"//me@myhost:1234",
"//me@:1234",
"//@:"
};
protected static final String[] AUTHORITY_PARSE_USER_INFOS = {
null,
null,
"me",
null,
"me",
"me",
""
};
protected static final String[] AUTHORITY_PARSE_HOSTS = {
null,
"myhost",
"myhost",
"myhost",
"myhost",
"",
""
};
protected static final String[] AUTHORITY_PARSE_PORTS = {
null,
null,
null,
"1234",
"1234",
"1234",
""
};
protected static final String[] JAR_URIS = {
"jar:file:/home/dave/myapp.jar!/",
"jar:file:/dave/myapp.jar!/schema.xsd",
"jar:file:/dave/myapp.jar!/support/schema.xsd",
"jar:file:/dave/myapp.jar!/support/xml/schema.xsd",
"jar:http://www.eclipse.org/myapp.jar!/schema.xsd",
"jar:http://www.eclipse.org/jar-server?some-jar!/support/xml/schema.xsd",
"jar:http://www.eclipse.org/jar-server?some-jar!/support/xml/schema?myquery#top",
"jar:dave/myapp.jar!/schema.xsd",
"jar:/home/dave/myapp.jar!/schema.xsd",
"zip://capilano/home/dave/myapp.jar!/schema.xsd",
};
protected static final String[] BAD_JAR_URIS = {
"jar:",
"jar:file:/dave/myapp.jar",
"jar:file:/dave/myapp.jar!",
"jar:http://www.eclipse.org/jar-server?some-jar#foo!/schema?myquery"
};
protected static final String[] UNENCODED_URIS = {
"http://www.eclipse.org/foo",
"http://server#1.eclipse.org/foo bar/baz#toc",
"myscheme:my name",
"file:/C:/My Documents/me/50%+1.txt",
"My Documents/me/50%50.txt"
};
protected static final String[] ENCODED_URIS = {
"http://www.eclipse.org/foo",
"http://server%231.eclipse.org/foo%20bar/baz#toc",
"myscheme:my%20name",
"file:/C:/My%20Documents/me/50%25+1.txt",
"My%20Documents/me/50%2550.txt"
};
protected static final String[] ENCODED_URIS_IGNORE_ESCAPED = {
"http://www.eclipse.org/foo",
"http://server%231.eclipse.org/foo%20bar/baz#toc",
"myscheme:my%20name",
"file:/C:/My%20Documents/me/50%25+1.txt",
"My%20Documents/me/50%50.txt"
};
protected static final String[] UNENCODED_PLATFORM_PATHS = {
"/project/myfile.txt",
"My Project #1/My File.txt",
"are you there?"
};
protected static final String[] ENCODED_PLATFORM_PATH_URIS = {
"platform:/resource/project/myfile.txt",
"platform:/resource/My%20Project%20%231/My%20File.txt",
"platform:/resource/are%20you%20there%3F"
};
protected static final String[] FILE_EXTENSION_URIS = {
"",
"foo.txt",
"path/foo.txt"
};
protected static final String[] FILE_EXTENSION_TRIMMED_URIS = {
"",
"foo",
"path/foo"
};
protected static final String[] FILE_EXTENSION_APPENDED_URIS = {
"",
"foo.bar",
"path/foo.bar"
};
protected static final String[] PREFIX_URIS = {
"http://foo/a/b/c/d",
"http://foo/a/b/c/d",
"http://foo/a/b/c/d/",
"http://foo/a/b/c/d/",
"http://foo/",
"http://foo/a/",
"/a/b/c",
"/a/b/c"
};
protected static final String[] REPLACED_PREFIX_URIS = {
"http://foo/a/b/c/",
"http://foo/",
"http://foo/a/b/c/d/",
"http://foo/a/b/c/d/",
"http://foo/",
"http://foo/a/",
"/",
"/a/"
};
protected static final String[] REPLACEMNT_PREFIX_URIS = {
"http://bar/a/b/c/",
"ftp://bar/",
"http://bar/a/b/c/d/",
"http://bar/",
"http://bar/",
"http://bar/a/",
"http://foo/",
"http://foo/A/"
};
protected static final String[] PREFIX_REPLACEMENT_RESULT_URIs = {
"http://bar/a/b/c/d",
"ftp://bar/a/b/c/d",
"http://bar/a/b/c/d/",
"http://bar/",
"http://bar/",
"http://bar/a/",
"http://foo/a/b/c",
"http://foo/A/b/c"
};
protected String[] getURNs()
{
return new String[] { URN + FRAGMENTS[0], URN + FRAGMENTS[1] };
}
protected String[] getAbsoluteURLs()
{
String[] result = new String[ABSOLUTE_URLS.length * QUERIES.length * FRAGMENTS.length];
for (int i = 0, x = 0; x < FRAGMENTS.length; x++)
for (int y = 0; y < QUERIES.length; y++)
for (int z = 0; z < ABSOLUTE_URLS.length; z++)
result[i++] = ABSOLUTE_URLS[z] + QUERIES[y] + FRAGMENTS[x];
return result;
}
protected String[] getRelativeURLs()
{
String[] result = new String[RELATIVE_URLS.length * QUERIES.length * FRAGMENTS.length];
for (int i = 0, x = 0; x < FRAGMENTS.length; x++)
for (int y = 0; y < QUERIES.length; y++)
for (int z = 0; z < RELATIVE_URLS.length; z++)
result[i++] = RELATIVE_URLS[z] + QUERIES[y] + FRAGMENTS[x];
return result;
}
protected String[] getAllURLs()
{
String[] result = new String[(ABSOLUTE_URLS.length + RELATIVE_URLS.length) * QUERIES.length * FRAGMENTS.length];
int i = 0;
for (int x = 0; x < FRAGMENTS.length; x++)
for (int y = 0; y < QUERIES.length; y++)
for (int z = 0; z < ABSOLUTE_URLS.length; z++)
result[i++] = ABSOLUTE_URLS[z] + QUERIES[y] + FRAGMENTS[x];
for (int x = 0; x < FRAGMENTS.length; x++)
for (int y = 0; y < QUERIES.length; y++)
for (int z = 0; z < RELATIVE_URLS.length; z++)
result[i++] = RELATIVE_URLS[z] + QUERIES[y] + FRAGMENTS[x];
return result;
}
protected String[] getUnresolvedURIs()
{
String[] result = new String[UNRESOLVED_URIS.length + UNRESOLVED_ABOVE_ROOT_URIS.length];
System.arraycopy(UNRESOLVED_URIS, 0, result, 0, UNRESOLVED_URIS.length);
System.arraycopy(UNRESOLVED_ABOVE_ROOT_URIS, 0, result, RESOLVED_URIS.length, UNRESOLVED_ABOVE_ROOT_URIS.length);
return result;
}
protected String[] getResolvedURIs(boolean preserve)
{
String[] aboveRoot = preserve ? RESOLVED_PRESERVE_ABOVE_ROOT_URIS : RESOLVED_NO_PRESERVE_ABOVE_ROOT_URIS;
String[] result = new String[RESOLVED_URIS.length + aboveRoot.length];
System.arraycopy(RESOLVED_URIS, 0, result, 0, RESOLVED_URIS.length);
System.arraycopy(aboveRoot, 0, result, RESOLVED_URIS.length, aboveRoot.length);
return result;
}
protected String[] getNonCanonicalUnresolvedURIs(boolean preserve)
{
String[] aboveRoot = preserve ? NON_CANONICAL_PRESERVE_ABOVE_ROOT_UNRESOLVED_URIS : NON_CANONICAL_NO_PRESERVE_ABOVE_ROOT_UNRESOLVED_URIS;
String[] result = new String[NON_CANONICAL_UNRESOLVED_URIS.length + aboveRoot.length];
System.arraycopy(NON_CANONICAL_UNRESOLVED_URIS, 0, result, 0, NON_CANONICAL_UNRESOLVED_URIS.length);
System.arraycopy(aboveRoot, 0, result, NON_CANONICAL_UNRESOLVED_URIS.length, aboveRoot.length);
return result;
}
@Test
public void testAppendLeadingEmptyOrDeviceSegment()
{
URI empty = URI.createURI("");
URI slash = URI.createURI("/");
URI slashSlash = URI.createURI("//");
URI slashSlashSlash = URI.createURI("///");
URI slashSlashSlashDriveRelative = URI.createURI("///C:");
URI slashSlashSlashDriveAbsolute = URI.createURI("///C:/");
URI deviceRelative = URI.createURI("/C:");
URI deviceAbsolute = URI.createURI("/C:/");
URI fileRoot = URI.createURI("file:/");
URI fileRootWithDeviceRelative = URI.createURI("file:/C:");
URI fileRootWithDeviceAbsolute = URI.createURI("file:/C:/");
{
URI uri = empty.appendSegment("");
assertSame(uri, slash);
}
{
URI uri = empty.appendSegment("C:");
assertSame(uri, deviceRelative);
}
{
// Appending an empty segment won't turn it into an authority, it's ignored.
URI uri = slash.appendSegment("");
assertSame(uri, slash);
}
{
// If it's the first segment, it will be recognized as the device.
URI uri = slash.appendSegment("C:");
assertSame(uri, deviceRelative);
}
{
// If it's the first segment, it will be recognized as the device.
URI uri = slash.appendSegment("C:");
assertSame(uri, deviceRelative);
}
{
URI uri = slashSlash.appendSegment("");
assertSame(uri, slashSlashSlash);
}
{
URI uri = slashSlash.appendSegment("C:");
assertSame(uri, slashSlashSlashDriveRelative);
}
{
URI uri = slashSlashSlashDriveRelative.appendSegment("");
assertSame(uri, slashSlashSlashDriveAbsolute);
}
{
URI uri = deviceRelative.appendSegment("");
assertSame(uri, deviceAbsolute);
}
{
// Ignored.
URI uri = fileRoot.appendSegment("");
assertSame(uri, fileRoot);
}
{
URI uri = fileRoot.appendSegment("C:");
assertSame(uri, fileRootWithDeviceRelative);
}
{
URI uri = fileRootWithDeviceRelative.appendSegment("");
assertSame(uri, fileRootWithDeviceAbsolute);
}
}
@Test
public void testAppendLeadingEmptyAndDeviceSegments()
{
URI empty = URI.createURI("");
URI slash = URI.createURI("/");
URI slashSlash = URI.createURI("//");
URI slashSlashSlash = URI.createURI("///");
URI slashSlashSlashDriveRelative = URI.createURI("///C:");
URI slashSlashSlashDriveAbsolute = URI.createURI("///C:/");
URI deviceRelative = URI.createURI("/C:");
URI deviceAbsolute = URI.createURI("/C:/");
URI fileRoot = URI.createURI("file:/");
URI fileRootWithDeviceRelative = URI.createURI("file:/C:");
URI fileRootWithDeviceAbsolute = URI.createURI("file:/C:/");
{
URI uri = empty.appendSegments(new String[] {""});
assertSame(uri, slash);
}
{
URI uri = empty.appendSegments(new String[] {"C:"});
assertSame(uri, deviceRelative);
}
{
// Appending an empty segment won't turn it into an authority, it's ignored.
URI uri = slash.appendSegments(new String[] {""});
assertSame(uri, slash);
}
{
// If it's the first segment, it will be recognized as the device.
URI uri = slash.appendSegments(new String[] {"C:"});
assertSame(uri, deviceRelative);
}
{
// If it's the first segment, it will be recognized as the device.
URI uri = slash.appendSegments(new String[] {"C:"});
assertSame(uri, deviceRelative);
}
{
URI uri = slashSlash.appendSegments(new String[] {""});
assertSame(uri, slashSlashSlash);
}
{
URI uri = slashSlash.appendSegments(new String[] {"C:"});
assertSame(uri, slashSlashSlashDriveRelative);
}
{
URI uri = slashSlashSlashDriveRelative.appendSegments(new String[] {""});
assertSame(uri, slashSlashSlashDriveAbsolute);
}
{
URI uri = deviceRelative.appendSegments(new String[] {""});
assertSame(uri, deviceAbsolute);
}
{
// Ignored.
URI uri = fileRoot.appendSegments(new String[] {""});
assertSame(uri, fileRoot);
}
{
URI uri = fileRoot.appendSegments(new String[] {"C:"});
assertSame(uri, fileRootWithDeviceRelative);
}
{
URI uri = fileRootWithDeviceRelative.appendSegments(new String[] {""});
assertSame(uri, fileRootWithDeviceAbsolute);
}
}
@Test
public void testUserInfo()
{
{
URI uri = URI.createURI("http://user:password@www.example.org:8080");
assertEquals("www.example.org", uri.host());
assertEquals("user:password", uri.userInfo());
assertEquals("8080", uri.port());
}
{
URI uri = URI.createURI("http://user@www.example.org:8080");
assertEquals("www.example.org", uri.host());
assertEquals("user", uri.userInfo());
assertEquals("8080", uri.port());
}
{
URI uri = URI.createURI("http://www.example.org:8080");
assertEquals("www.example.org", uri.host());
assertEquals(null, uri.userInfo());
assertEquals("8080", uri.port());
}
{
URI uri = URI.createURI("http://www.example.org");
assertEquals("www.example.org", uri.host());
assertEquals(null, uri.userInfo());
assertEquals(null, uri.port());
}
}
@Test
public void testAppendMultipleLeadingEmptyAndDeviceSegments()
{
URI empty = URI.createURI("");
URI slash = URI.createURI("/");
URI slashSlash = URI.createURI("//");
URI slashSlashSlash = URI.createURI("///");
URI slashSlashSlashDriveRelative = URI.createURI("///C:");
URI slashSlashSlashDriveAbsolute = URI.createURI("///C:/");
URI deviceRelative = URI.createURI("/C:");
URI deviceAbsolute = URI.createURI("/C:/");
URI fileRoot = URI.createURI("file:/");
URI fileRootWithDeviceRelative = URI.createURI("file:/C:");
URI fileRootWithDeviceAbsolute = URI.createURI("file:/C:/");
{
URI uri = empty.appendSegments(new String[] {"", "", ""});
assertSame(uri, slash);
}
{
URI uri = empty.appendSegments(new String[] {"", "C:"});
assertSame(uri, deviceRelative);
}
{
// Appending an empty segment won't turn it into an authority, it's ignored.
URI uri = slash.appendSegments(new String[] {"", "", ""});
assertSame(uri, slash);
}
{
// If it's the first segment, it will be recognized as the device.
URI uri = slash.appendSegments(new String[] {"", "C:"});
assertSame(uri, deviceRelative);
}
{
// If it's the first segment, it will be recognized as the device.
URI uri = slash.appendSegments(new String[] {"", "", "", "C:"});
assertSame(uri, deviceRelative);
}
{
URI uri = slashSlash.appendSegments(new String[] {""});
assertSame(uri, slashSlashSlash);
}
{
URI uri = slashSlash.appendSegments(new String[] {"C:"});
assertSame(uri, slashSlashSlashDriveRelative);
}
{
URI uri = slashSlashSlashDriveRelative.appendSegments(new String[] {""});
assertSame(uri, slashSlashSlashDriveAbsolute);
}
{
URI uri = deviceRelative.appendSegments(new String[] {""});
assertSame(uri, deviceAbsolute);
}
{
// Ignored.
URI uri = fileRoot.appendSegments(new String[] {""});
assertSame(uri, fileRoot);
}
{
URI uri = fileRoot.appendSegments(new String[] {"", "", "C:"});
assertSame(uri, fileRootWithDeviceRelative);
}
{
URI uri = fileRootWithDeviceRelative.appendSegments(new String[] {""});
assertSame(uri, fileRootWithDeviceAbsolute);
}
}
/**
* Parses URIs and converts them back to strings, comparing with the originals.
*
*/
@Test
public void testParse()
{
String[] uriStrings = getAllURLs();
for (int i = 0, len = uriStrings.length; i < len; i++)
{
String s = uriStrings[i];
URI u = URI.createURI(s);
assertEquals("Bad URL parse", s, u.toString());
}
uriStrings = getURNs();
for (int i = 0, len = uriStrings.length; i < len; i++)
{
String s = uriStrings[i];
URI u = URI.createURI(s);
assertEquals("Bad URN parse", s, u.toString());
}
}
/**
* Resolves URIs against a base, comparing with the known correct results.
* This tests both preserving and not preserving path segments above root.
*/
@Test
public void testResolve()
{
URI base = URI.createURI(BASE_URI);
for (int i = 0; i < 2; i++)
{
boolean preserve = i == 0;
String[] uriStrings = getUnresolvedURIs();
String[] resolvedStrings = getResolvedURIs(preserve);
for (int j = 0, len = uriStrings.length; j < len; j++)
{
URI uri = URI.createURI(uriStrings[j]);
URI resolved = URI.createURI(resolvedStrings[j]);
URI myResolved = uri.resolve(base, preserve);
assertEquals("Bad resolve: " + uri, resolved, myResolved);
}
}
}
/**
* Deresolves URIs against a base, comparing with the known correct results.
* This tests both preserving and no preserving path segments above roots, and skips cases where the unresolved URI
* is non-canonical.
*/
@Test
public void testDeresolve()
{
URI base = URI.createURI(BASE_URI);
for (int i = 0; i < 2; i++)
{
boolean preserve = i == 0;
String[] uriStrings = getResolvedURIs(preserve);
String[] deresolvedStrings = getUnresolvedURIs();
List<String> skipStrings = Arrays.asList(getNonCanonicalUnresolvedURIs(preserve));
for (int j = 0, len = uriStrings.length; j < len; j++)
{
URI uri = URI.createURI(uriStrings[j]);
if ((j > 0 && uriStrings[j].equals(uriStrings[j - 1])) ||
skipStrings.contains(deresolvedStrings[j]))
continue;
URI deresolved = URI.createURI(deresolvedStrings[j]);
URI myDeresolved = uri.deresolve(base, preserve, deresolved.hasRelativePath(), false);
assertEquals("Bad deresolve: " + uri, deresolved, myDeresolved);
}
}
}
/**
* Parses URIs and calls the authority sub-part accessors, comparing with known results.
*/
@Test
public void testAuthorityParse()
{
String[] uriStrings = AUTHORITY_PARSE_URIS;
String[] userInfos = AUTHORITY_PARSE_USER_INFOS;
String[] hosts = AUTHORITY_PARSE_HOSTS;
String[] ports = AUTHORITY_PARSE_PORTS;
for (int i = 0, len = uriStrings.length; i < len; i++)
{
URI uri = URI.createURI(uriStrings[i]);
assertEquals("Bad user info parse: " + uriStrings[i], userInfos[i], uri.userInfo());
assertEquals("Bad host parse: " + uriStrings[i], hosts[i], uri.host());
assertEquals("Bad port parse: " + uriStrings[i], ports[i], uri.port());
}
}
/*
* Parses URIs with JAR scheme and converts them back to strings, comparing with the originals. Parses invalid
* JAR-scheme URIs, checking to ensure that the correct exceptions are thrown.
*/
@Test
public void testJARParse()
{
String[] uriStrings = JAR_URIS;
for (int i = 0, len = uriStrings.length; i < len; i++)
{
String s = uriStrings[i];
URI u = URI.createURI(s);
assertEquals("Bad JAR-scheme URI parse", s, u.toString());
}
uriStrings = BAD_JAR_URIS;
for (int i = 0, len = uriStrings.length; i < len; i++)
{
String s = uriStrings[i];
try
{
URI.createURI(s);
fail("Parse of bad JAR-scheme URI failed to throw IllegalArgumentException: " + s);
}
catch (IllegalArgumentException e)
{
// Ignore
}
}
}
/**
* Parses a URI with a fragment, appends a fragment to a URI, replaces that fragment with another, then trims the
* three fragments, comparing the results to the base.
*/
@Test
public void testFragmentAppendAndTrim()
{
String base = "http://download.eclipse.org/tools/emf/scripts/home.php";
String fragment1 = "top";
String fragment2 = "quicknav";
String fragment3 = "over2";
URI fragment1URI = URI.createURI(base + "#" + fragment1);
assertEquals("Bad URI parse", base + "#" + fragment1, fragment1URI.toString());
URI baseURI = URI.createURI(base);
URI fragment2URI = baseURI.appendFragment(fragment2);
assertEquals("Bad fragment append: " + fragment2, base + "#" + fragment2, fragment2URI.toString());
URI fragment3URI = fragment2URI.appendFragment(fragment3);
assertEquals("Bad fragment replace: " + fragment3, base + "#" + fragment3, fragment3URI.toString());
URI trimmedFragment1URI = fragment1URI.trimFragment();
assertEquals("Bad parsed fragment trim: " + fragment1URI, base, trimmedFragment1URI.toString());
URI trimmedFragment2URI = fragment2URI.trimFragment();
assertEquals("Bad appended fragment trim: " + fragment2URI, base, trimmedFragment2URI.toString());
URI trimmedFragment3URI = fragment3URI.trimFragment();
assertEquals("Bad replaced fragment trim: " + fragment3URI, base, trimmedFragment3URI.toString());
}
/**
* Performs automatic encoding of general URIs and platform resource URIs, and decodes the former back, comparing the
* result to known encoded versions.
*/
@Test
public void testEncodeAndDecode()
{
String[] unencodedURIStrings = UNENCODED_URIS;
String[] encodedURIStrings = ENCODED_URIS;
for (int i = 0, len = unencodedURIStrings.length; i < len; i++)
{
String unencoded = unencodedURIStrings[i];
URI encodedURI = URI.createURI(unencoded, false);
assertEquals("Bad URI encode: " + unencoded, URI.createURI(encodedURIStrings[i]), encodedURI);
assertEquals("Bad URI decode: " + encodedURI, unencoded, URI.decode(encodedURI.toString()));
}
encodedURIStrings = ENCODED_URIS_IGNORE_ESCAPED;
for (int i = 0, len = unencodedURIStrings.length; i < len; i++)
{
String unencoded = unencodedURIStrings[i];
URI encodedURI = URI.createURI(unencoded, true);
assertEquals("Bad URI encode: " + unencoded, URI.createURI(encodedURIStrings[i]), encodedURI);
}
//As of Bugzilla 72731, this behaviour requires a system property to be set.
//
//String[] paths = UNENCODED_PLATFORM_PATHS;
//encodedURIStrings = ENCODED_PLATFORM_PATH_URIS;
//
//for (int i = 0, len = paths.length; i < len; i++)
//{
// String path = paths[i];
// URI uri = URI.createPlatformResourceURI(path);
// assertEquals("Bad platform resource encode: " + path, encodedURIStrings[i], uri.toString());
//}
// Bugzilla 116074
String unencoded = "platform://resource/a#b/c#d#e";
String encodedWithNoFragment = "platform://resource/a%23b/c%23d%23e";
String encodedWithFragmentFirst = "platform://resource/a#b/c%23d%23e";
String encodedWithFragmentLast = "platform://resource/a%23b/c%23d#e";
assertEquals("Bad URI encode: " + unencoded, encodedWithNoFragment, URI.createURI(unencoded, false, URI.FRAGMENT_NONE).toString());
assertEquals("Bad URI decode: " + encodedWithNoFragment, unencoded, URI.decode(encodedWithNoFragment.toString()));
assertEquals("Bad URI encode: " + unencoded, encodedWithFragmentFirst, URI.createURI(unencoded, false, URI.FRAGMENT_FIRST_SEPARATOR).toString());
assertEquals("Bad URI decode: " + encodedWithFragmentFirst, unencoded, URI.decode(encodedWithFragmentFirst.toString()));
assertEquals("Bad URI encode: " + unencoded, encodedWithFragmentLast, URI.createURI(unencoded, false, URI.FRAGMENT_LAST_SEPARATOR).toString());
assertEquals("Bad URI decode: " + encodedWithFragmentLast, unencoded, URI.decode(encodedWithFragmentLast.toString()));
}
@Test
public void testPlatformURI() throws Exception
{
{
URI uri = URI.createURI("platform:/d:/resource/foo?bar");
assertFalse(uri.isPlatformResource());
}
{
String resource = "platform:/resource/myProject/foo.txt";
URI uri = URI.createURI(resource);
assertTrue(uri.isPlatform());
assertEquals("platform:/resource/myProject/foo.txt", uri.toString());
assertEquals("/myProject/foo.txt", uri.toPlatformString(true));
}
{
String resource = "myProject/foo.txt";
URI uri = URI.createPlatformResourceURI(resource, true);
assertTrue(uri.isPlatform());
assertFalse(uri.isFile());
assertEquals("platform:/resource/myProject/foo.txt", uri.toString());
assertEquals("/myProject/foo.txt", uri.toPlatformString(true));
}
{
String resource = "platform:/resource/myProject/foo.txt";
URI uri = URI.createPlatformResourceURI(resource, true);
assertTrue(uri.isPlatform());
assertFalse(uri.isFile());
assertEquals("platform:/resource/platform:/resource/myProject/foo.txt", uri.toString());
assertEquals("/platform:/resource/myProject/foo.txt", uri.toPlatformString(true));
}
{
String resource = new File("myProject/foo.txt").getAbsolutePath();
URI uri = URI.createFileURI(resource);
assertFalse(uri.isPlatform());
assertTrue(uri.isFile());
resource = resource.replace('\\', '/');
if (resource.charAt(0) != '/') resource = "/" + resource;
assertEquals("file:" + resource, uri.toString());
assertNull(uri.toPlatformString(true));
}
{
String resource = "myProject/foo.txt";
URI uri = URI.createFileURI(resource);
assertFalse(uri.isPlatform());
assertTrue(uri.isFile());
assertEquals("myProject/foo.txt", uri.toString());
assertNull(uri.toPlatformString(true));
}
String[] paths = UNENCODED_PLATFORM_PATHS;
String[] encodedURIStrings = ENCODED_PLATFORM_PATH_URIS;
for (int i = 0, len = paths.length; i < len; i++)
{
String path = paths[i];
URI uri = URI.createPlatformResourceURI(path, true);
assertEquals("Bad platform resource encode: " + path, encodedURIStrings[i], uri.toString());
assertEquals(encodedURIStrings[i].substring("platform:/resource".length()), uri.toPlatformString(false));
}
}
@Test
public void testFileExtensions()
{
for (int i = 0; i < FILE_EXTENSION_URIS.length; ++i)
{
String s = FILE_EXTENSION_URIS[i];
URI uri = URI.createURI(s);
assertEquals("Bad trim fragment", uri.trimFileExtension().toString(), URI.createURI(FILE_EXTENSION_TRIMMED_URIS[i]).toString());
assertEquals("Bad append fragment", uri.trimFileExtension().appendFileExtension("bar").toString(), URI.createURI(FILE_EXTENSION_APPENDED_URIS[i]).toString());
}
}
@Test
public void testPrefixReplacement()
{
for (int i = 0; i < PREFIX_URIS.length; ++i)
{
String s = PREFIX_URIS[i];
URI uri = URI.createURI(s);
assertEquals("Bad replace prefix", PREFIX_REPLACEMENT_RESULT_URIs[i],uri.replacePrefix(URI.createURI(REPLACED_PREFIX_URIS[i]), URI.createURI(REPLACEMNT_PREFIX_URIS[i])).toString());
}
}
@Test
public void testIdentity()
{
for (String s : getAllURLs())
{
URI uri = URI.createURI(s);
if (uri.hasFragment())
{
assertSame("Non-unique trimmed fragments" + s, uri.trimFragment(), URI.createURI(s).trimFragment());
assertSame("Non-unique fragments" + s, uri.fragment(), URI.createURI(s).fragment());
}
else
{
assertSame("Non-unique " + s, uri, URI.createURI(s));
}
String toString = uri.toString();
assertSame("Non-unique strings " + s, toString, URI.createURI(s).toString());
}
}
@Test
public void testGenericURI()
{
{
URI uri1 = URI.createURI("foo:bar");
URI uri2 = URI.createGenericURI("foo", "bar", null);
assertSame("Non-unique generic URI foo:bar", uri1, uri2);
}
{
URI uri1 = URI.createURI("foo:bar/");
URI uri2 = URI.createGenericURI("foo", "bar/", null);
assertSame("Non-unique generic URI foo:bar/", uri1, uri2);
}
{
try
{
URI uri = URI.createGenericURI("foo", "/bar", null);
fail("Expecting an IllegalArgumentException for " + uri);
}
catch (IllegalArgumentException exception)
{
// Expected failure.
}
}
}
@Test
public void testFileURINormalizations()
{
assertSame(URI.createURI("HTTP://test"), URI.createURI("http://test"));
// We can only run this part of the test on Windows, where empty segment normalization at the start of the path is done.
if (File.separatorChar == '\\')
{
assertSame(URI.createFileURI("\\\\x\\y"), URI.createFileURI("\\\\x\\\\y"));
assertSame(URI.createFileURI("c:\\\\"), URI.createFileURI("c:\\"));
assertSame(URI.createFileURI("c:\\\\x"), URI.createFileURI("c:\\x"));
assertSame(URI.createFileURI("c://x"), URI.createFileURI("c:/x"));
assertSame(URI.createFileURI("c:\\\\x"), URI.createFileURI("c:/x"));
assertEquals("\\\\authority\\path", URI.createFileURI("\\\\authority\\path").toFileString());
}
}
@Test
public void testThreadSafety()
{
testThreadSafety(100000, 10);
}
public void testThreadSafety(final int count, int stringSize)
{
// Create random strings.
//
final int stringCount = 10000;
final String[] data = new String[stringCount];
final Random random = new Random(0);
for (int i = 0; i < stringCount; ++i)
{
char[] characters = new char[stringSize];
for (int j = 0; j < stringSize; ++j)
{
characters[j] = (char)(((0x7FFFFFFF & random.nextInt()) % 26) + 'a');
}
data[i] = new String(characters);
}
final Set<String> allStrings = new HashSet<String>();
final String[] uriData = new String[count];
for (int i = 0; i < count; ++i)
{
StringBuilder builder = new StringBuilder();
boolean isArchive = false;
switch ((0x7FFFFFFF & random.nextInt()) % 7)
{
case 0:
{
builder.append("http://");
break;
}
case 1:
{
builder.append("file:/");
if ((0x7FFFFFFF & random.nextInt()) % 5 != 0)
{
builder.append("c:/");
}
break;
}
case 2:
{
builder.append("platform:/");
break;
}
case 3:
{
builder.append("archive://");
isArchive = true;
break;
}
case 4:
{
builder.append("nrn:");
}
case 5:
{
builder.append("/");
}
}
builder.append(data[(0x7FFFFFFF & random.nextInt()) % stringCount]);
if ((0x7FFFFFFF & random.nextInt()) % 10 != 0)
{
builder.append("/");
for (int j = 0, length = (0x7FFFFFFF & random.nextInt()) % 5; j < length; ++j)
{
builder.append(data[(0x7FFFFFFF & random.nextInt()) % stringCount]);
builder.append("/");
}
if ((0x7FFFFFFF & random.nextInt()) % 3 != 0)
{
builder.append(data[(0x7FFFFFFF & random.nextInt()) % stringCount]);
}
}
if (isArchive)
{
builder.append("!/");
for (int j = 0, length = (0x7FFFFFFF & random.nextInt()) % 5; j < length; ++j)
{
builder.append(data[(0x7FFFFFFF & random.nextInt()) % stringCount]);
builder.append("/");
}
if ((0x7FFFFFFF & random.nextInt()) % 3 != 0)
{
builder.append(data[(0x7FFFFFFF & random.nextInt()) % stringCount]);
}
}
allStrings.add(builder.toString());
if ((0x7FFFFFFF & random.nextInt()) % 3 != 0)
{
builder.append("#");
if ((0x7FFFFFFF & random.nextInt()) % 3 != 0)
{
builder.append("/");
}
for (int j = 0, length = (0x7FFFFFFF & random.nextInt()) % 3; j < length; ++j)
{
if (j != 0)
{
builder.append("/");
}
builder.append(data[(0x7FFFFFFF & random.nextInt()) % stringCount]);
}
}
uriData[i] = builder.toString();
}
final InterningSet<URI> uris = URI_POOL;
Set<String> initialURIs = new HashSet<String>();
for (URI uri : uris)
{
initialURIs.add(uri.toString());
}
int expectedSize = allStrings.size();
// Record the interned results to avoid them being garbage collected.
//
final URI[] dataCopy = new URI[count];
// Spawn this many threads.
//
int threadCount = 10;
Thread[] threads = new Thread[threadCount];
for (int i = 0; i < threadCount; ++i)
{
threads[i] =
new Thread()
{
@Override
public void run()
{
// Intern all the random strings and store them.
//
for (int j = 0; j < count; ++j)
{
String value = uriData[j];
dataCopy[j] = URI.createURI(value);
}
}
};
}
// Start all the threads.
//
for (int i = 0; i < threadCount; ++i)
{
threads[i].start();
}
// Wait for all threads to finish.
//
for (int i = 0; i < threadCount; ++i)
{
try
{
threads[i].join();
}
catch (InterruptedException exception)
{
exception.printStackTrace();
fail("Thread interupted");
}
}
// Do a garbage collection and wait for the cleaner thread to complete.
// This is to allow the archive URI's authorities to be garbage collected.
//
System.gc();
try
{
Thread.sleep(10000);
}
catch (InterruptedException e)
{
// Expected.
}
System.gc();
Set<String> uriStrings = new HashSet<String>();
// This is kind of ugly, but I'm seeing exceptions indicating garbage collection is happening during this loop.
// It's just generally impossible to be sure when garbage collection is actually completely finished.
//
for (int j = 0; j < 10; ++j)
{
try
{
for (Iterator<URI> i = uris.iterator(); i.hasNext(); )
{
String value = i.next().toString();
if (!uriStrings.add(value))
{
fail("Duplicate URI" + value);
}
}
// If we get this far, then we didn't get an exception, so terminate the loop now.
//
break;
}
catch (ConcurrentModificationException exception)
{
// If garbage collection was not completed, then we can get this exception.
// So try again with a clean set of strings.
//
uriStrings.clear();
}
}
uriStrings.removeAll(initialURIs);
// Test that all the strings are added.
//
assertEquals(expectedSize, uriStrings.size());
// Clean up references to the strings so they can be garbage collected.
//
allStrings.clear();
for (int i = 0; i < count; ++i)
{
dataCopy[i] = null;
}
// Do a garbage collection and wait for the cleaner thread to complete.
//
System.gc();
try
{
Thread.sleep(10000);
}
catch (InterruptedException e)
{
// Expected.
}
System.gc();
uriStrings = new HashSet<String>();
for (Iterator<URI> i = uris.iterator(); i.hasNext(); )
{
String value = i.next().toString();
if (!uriStrings.add(value))
{
fail("Duplicate URI" + value);
}
}
uriStrings.removeAll(initialURIs);
assertEquals(0, uriStrings.size());
}
private static final InterningSet<URI> URI_POOL;
static
{
InterningSet<URI> uriPool = null;
try
{
@SuppressWarnings("unchecked")
Class<InterningSet<String>> uriClass = (Class<InterningSet<String>>)Class.forName("org.eclipse.emf.common.util.URI");
Field pool = uriClass.getDeclaredField("POOL");
pool.setAccessible(true);
@SuppressWarnings("unchecked")
InterningSet<URI> result = (InterningSet<URI>)pool.get(null);
uriPool = result;
}
catch (Throwable throwable)
{
fail();
}
URI_POOL = uriPool;
}
@SuppressWarnings("unused")
public static void main(String[] args) throws Exception
{
System.out.println(URI.createPlatformPluginURI("org.eclipse.m2m.tests.qvt.oml/parserTestData/externlib/FooLib.qvto?ns=.", true));
System.out.println(URI.createURI(URI.createPlatformPluginURI("org.eclipse.m2m.tests.qvt.oml/parserTestData/externlib/FooLib.qvto?ns=.", false).toString()).query());
System.out.println(URI.createURI("platform:/resource/org.eclipse.m2m.tests.qvt.oml/parserTestData/externlib/FooLib.qvto?ns=.").query());
Collection<? super URI> collection = new ArrayList<URI>();
URI urix = null;
collection.add(urix);
System.out.println("###" + URI.createURI("http:///dasfasfsa#/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a"));
System.out.println("###" + URI.createURI("http:///abcdef"));
URI.createURI("ABC:xxx");
URI.createURI(": ");
URI.createURI(" : /");
// URI.createHierarchicalURI(new String[] { null }, null, null);
// URI.createURI("foo").appendSegments(new String[] {"a", "b", "/"});
URI2 xxxx = URI2.createURI("platform:/d:/resource/foo?bar");
if (xxxx.isPlatformResource())
{
System.err.println("Bug");
}
URI2 yyy = URI2.createURI("file:/c://");
URI.createURI("file:/c://");
URI.createURI("platform:/d:/resource/foo?bar");
URI.createURI("foo");
URI x = URI.createURI("a:b?c");
URI y = URI.createURI("A:b?c");
System.out.println(x);
System.out.println(y);
System.out.println(x == y);
/*
System.out.println("" + URI2.createURI("http://foo/bar/").replacePrefix(URI2.createURI("http://foo/bar/"), URI2.createURI("http://boo/bar/")));
System.out.println("" + URI2.createURI("http://foo/").replacePrefix(URI2.createURI("http://foo/"), URI2.createURI("http://boo/")));
System.out.println("" + URI.createURI("http://foo/bar/").replacePrefix(URI.createURI("http://foo/bar/"), URI.createURI("http://boo/bar/")));
System.out.println("" + URI.createURI("http://foo/").replacePrefix(URI.createURI("http://foo/"), URI.createURI("http://boo/")));
System.out.println("is Prefix " + URI.createURI(".").isPrefix());
System.out.println(URI.createPlatformResourceURI("", true).hashCode() + "...");
System.out.println(URI.createPlatformResourceURI("/", true).hashCode() + "...");
System.out.println(URI.createPlatformResourceURI("\\", true).hashCode() + "...");
System.out.println(URI.createPlatformResourceURI("a", true).hashCode() + "...");
System.out.println(URI.createPlatformResourceURI("/a", true).hashCode() + "...");
System.out.println(URI.createPlatformResourceURI("\\a", true).hashCode() + "...");
URI file = URI.createFileURI("\\stuff");
URI file2 = URI.createFileURI("\\stuff");
URI file4 = URI.createURI("file:/c:/");
URI file3 = URI.createFileURI("c:\\");
URI file5 = URI.createURI("file:/c:y/");
URI file6 = URI.createFileURI("c:y\\");
URI file7 = URI.createFileURI("cc:\\");
URI file8 = URI.createFileURI("//a/b//");
URI file9 = URI.createFileURI("a//b//");
URI file10 = URI.createFileURI("\\\\foo\\b\\");
URI2 file11 = URI2.createFileURI("\\\\foo\\b\\");
URI file12 = URI.createFileURI("\\\\foo\\b:\\");
URI2 file13 = URI2.createFileURI("\\\\foo\\b:\\");
URI file14 = URI.createURI("file://foo/b:/");
URI2 file15 = URI2.createURI("file://foo/b:/");
URI file16 = URI.createFileURI("\\\\foo");
URI2 file17 = URI2.createFileURI("\\\\foo");
URI file18 = URI.createFileURI("\\\\foo\\");
URI2 file19 = URI2.createFileURI("\\\\foo\\");
URI file20 = URI.createFileURI("\\\\foo\\b");
URI2 file21 = URI2.createFileURI("\\\\foo\\b");
URI file22 = URI.createFileURI("\\\\foo\\b:");
URI2 file23 = URI2.createFileURI("\\\\foo\\b:");
URI file24 = URI.createFileURI("\\\\foo\\b:\\");
URI2 file25 = URI2.createFileURI("\\\\foo\\b:\\");
URI file26 = URI.createFileURI("\\\\foo\\b:\\x");
URI2 file27 = URI2.createFileURI("\\\\foo\\b:\\x");
*/
// URI archive = URI.createURI("archive:file:///c:/temp/example.zip!/org/example?/nested.zip!/org/example/deeply-nested.html");
// POOL.grow(10000000);
// CommonUtil.STRING_POOL.grow(10000000);
// SegmentSequence.STRING_ARRAY_POOL.grow(10000000);
final int count = 500000;
final int repetitions = 10;
final URI[][] uris = new URI[repetitions][];
uris[0] = new URI[count];
final URI2[][] uri2s = new URI2[repetitions][];
uri2s[0] = new URI2[count];
System.gc();
// POOL.cleanup();
System.out.println("Hit enter to continue");
System.in.read();
System.in.read();
for (int i = 0; i < 10000; ++i)
{
//uris[0][i] = URI.createFileURI("c:/bar/" + UUID.randomUUID() + "#" + UUID.randomUUID());
//uri2s[0][i] = URI2.createFileURI("c:/bar/" + UUID.randomUUID() + "#" + UUID.randomUUID());
uris[0][i] = URI.createURI("http://bar/foo/" + UUID.randomUUID() + "#" + UUID.randomUUID());
uri2s[0][i] = URI2.createURI("http://bar/foo/" + UUID.randomUUID() + "#" + UUID.randomUUID());
}
System.gc();
boolean stop = true;
System.out.println("Hit enter to continue");
System.in.read();
System.in.read();
// if (stop) return;
for (int i = 0; i < 50; ++i)
{
uris[0][i] = URI.createURI("foo://bar/" + UUID.randomUUID() + "#" + UUID.randomUUID());
}
// CommonUtil.STRING_POOL.cleanup();
// CommonUtil.STRING_POOL.dump();
URI x0 = URI.createPlatformResourceURI("/", true);
URI x1 = URI.createPlatformResourceURI("\\", true);
URI x2 = URI.createPlatformResourceURI("", true);
URI x3 = URI.createPlatformResourceURI("", true);
URI xxx = URI.createPlatformResourceURI("/a c/", true);
URI ykl = URI.createPlatformResourceURI("/a c/", true);
URI z = URI.createURI("platform:/resource/a%20c/", true);
URI xx = URI.createPlatformResourceURI("//////////////////aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////", true);
/*
System.gc();
POOL.cleanup();
POOL.dump();
System.gc();
*/
// if (stop) return;
for (int repeat = 0; repeat < repetitions; ++repeat)
{
final int rep = repeat;
/*
URI[] array = POOL.toArray(new URI[0]);
HashSet<String> strings = new HashSet();
for (URI uri : array)
{
if (!strings.add(uri.toString()))
{
System.err.println("################");
}
}
strings.clear();
*/
final String[] values = new String[count];
for (int i = 0; i < count; ++i)
{
String value = "http://bar/a/b/c/d/e/f/g/h/i/j" + UUID.randomUUID(); // + "#/" + UUID.randomUUID();
values[i] = value;
}
System.gc();
uri2s[repeat] = new URI2[count];
new Runnable()
{
public void run()
{
testURI2Create();
}
public void testURI2Create()
{
long start = System.currentTimeMillis();
int dummy = 0;
for (int i = 0; i < count; ++i)
{
dummy += (uri2s[rep][i] = URI2.createURI(values[i])).hashCode();
// dummy += (uri2s[repeat][i] = URI2.createPlatformResourceURI("\bar /a b/c d/" + UUID.randomUUID(), true)).hashCode();
// dummy += (uri2s[repeat][i] = URI2.createFileURI("c:\\bar /a#b/d%e/" + UUID.randomUUID())).hashCode();
}
long end = System.currentTimeMillis();
System.out.println("URI2 {" + dummy + "} elapsed time: " + (end - start));
}
}.run();
System.gc();
// uri2s[repeat] = new URI2[count];
new Runnable()
{
public void run()
{
testURI2Lookup();
}
public void testURI2Lookup()
{
long start = System.currentTimeMillis();
int dummy = 0;
for (int i = 0; i < count; ++i)
{
dummy += (uri2s[rep][i] = URI2.createURI(values[i])).hashCode();
// dummy += (uri2s[repeat][i] = URI2.createPlatformResourceURI("\bar /a b/c d/" + UUID.randomUUID(), true)).hashCode();
// dummy += (uri2s[repeat][i] = URI2.createFileURI("c:\\bar /a#b/d%e/" + UUID.randomUUID())).hashCode();
}
long end = System.currentTimeMillis();
System.out.println("URI2+ {" + dummy + "} elapsed time: " + (end - start));
}
}.run();
System.gc();
uris[repeat] = new URI[count];
new Runnable()
{
public void run()
{
testURICreate();
}
public void testURICreate()
{
long start = System.currentTimeMillis();
int dummy = 0;
for (int i = 0; i < count; ++i)
{
dummy += (uris[rep][i] = URI.createURI(values[i])).hashCode();
// dummy += (uris[repeat][i] = URI.createPlatformResourceURI("\bar /a b/c d/" + UUID.randomUUID(), true)).hashCode();
// dummy += (uris[repeat][i] = URI.createFileURI("c:\\bar /a#b/d%e/" + UUID.randomUUID())).hashCode();
}
long end = System.currentTimeMillis();
System.out.println("URI {" + dummy + "} elapsed time: " + (end - start));
}
}.run();
// System.out.println("POOL Capacity " + URI_POOL.capacityIndex);
// System.out.println("STRING_POOL Capacity " + URI_POOL.STRING_POOL.capacityIndex);
// System.out.println("STRING_ARRAY_POOL Capacity " + URI_POOL.STRING_ARRAY_POOL.capacityIndex);
System.gc();
// uris[repeat] = new URI[count];
new Runnable()
{
public void run()
{
testURILookup();
}
public void testURILookup()
{
long start = System.currentTimeMillis();
int dummy = 0;
for (int i = 0; i < count; ++i)
{
URI value = URI.createURI(values[i]);
if (value != uris[rep][i])
{
System.out.println("###");
}
dummy += (uris[rep][i] = value).hashCode();
// dummy += (uris[repeat][i] = URI.createPlatformResourceURI("\bar /a b/c d/" + UUID.randomUUID(), true)).hashCode();
// dummy += (uris[repeat][i] = URI.createFileURI("c:\\bar /a#b/d%e/" + UUID.randomUUID())).hashCode();
}
long end = System.currentTimeMillis();
System.out.println("URI+ {" + dummy + "} elapsed time: " + (end - start));
}
}.run();
// System.out.println("POOL Capacity " + URI_POOL.capacityIndex);
// System.out.println("STRING_POOL Capacity " + URI_POOL.STRING_POOL.capacityIndex);
// System.out.println("STRING_ARRAY_POOL Capacity " + URI_POOL.STRING_ARRAY_POOL.capacityIndex);
System.gc();
/*
System.gc();
{
long start = System.currentTimeMillis();
int dummy = 0;
for (int i = 0; i < count; ++i)
{
dummy += (uris[repeat][i] = uris[repeat][i].appendFragment("foo")).hashCode();
}
long end = System.currentTimeMillis();
System.out.println("URI.appendFrag {" + dummy + "} elapsed time: " + (end - start));
}
System.gc();
{
long start = System.currentTimeMillis();
int dummy = 0;
for (int i = 0; i < count; ++i)
{
dummy += (uri2s[repeat][i] = uri2s[repeat][i].appendFragment("foo")).hashCode();
}
long end = System.currentTimeMillis();
System.out.println("URI2.appendFrag {" + dummy + "} elapsed time: " + (end - start));
}
System.gc();
*/
// Let them be garbage collected now
//
uris[repeat] = null;
uri2s[repeat] = null;
System.gc();
}
System.gc();
System.out.println("Hit enter to continue");
System.in.read();
for (int i = 0; i < 10000; ++i)
{
uris[0][i] = URI.createURI("foo://bar/" + UUID.randomUUID() + "#" + UUID.randomUUID());
}
if (stop)
{
return;
}
String string = "PLATFORM:/plugin/org.eclipse.e4.ui.model.workbench/model/ModelFragment.genmodel";
/*
for (int i = 0; i < 10000000; ++i)
{
URI uri = parseIntoURI(string.toCharArray(), string.length());
}
if (true)
return;
*/
URI uri1 = URI.createURI(string);
URI uri2 = URI.createURI("platform:/plugin/org.eclipse.e4.ui.model.workbench/model/ModelFragment.genmodel");
URI uri3 = URI.createURI(string);
URI uri4 = URI.createURI(string);
URI uri5 = URI.createURI("platform:/plugin/org.eclipse.e4.ui.model.workbench/model/ModelFragment.genmodel");
URI uri6 = URI.createURI("platform:/plugin/org.eclipse.e4.ui.model.workbench/model/ModelFragment.genmodel");
URI uri7 = URI.createURI(string);
System.out.println("? " + (uri1 == uri2));
System.out.println("? " + (uri2 == uri3));
System.out.println("? " + (uri1 == uri3));
System.out.println("? " + (uri1 == uri4));
System.out.println("? " + (uri1 == uri5));
System.out.println("? " + (uri1 == uri6));
System.out.println("? " + (uri1 == uri7));
}
/**
* A representation of a Uniform Resource Identifier (URI), as specified by
* <a href="http://www.ietf.org/rfc/rfc2396.txt">RFC 2396</a>, with certain
* enhancements. A <code>URI</code> 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.
*
* <p>Like <code>String</code>, <code>URI</code> is an immutable class;
* a <code>URI</code> instance offers several by-value methods that return a
* new <code>URI</code> object based on its current state. Most useful,
* a relative <code>URI</code> can be {@link #resolve(URI2) resolve}d against
* a base absolute <code>URI</code> -- the latter typically identifies the
* document in which the former appears. The inverse to this is {@link
* #deresolve(URI2) deresolve}, which answers the question, "what relative
* URI will resolve, against the given base, to this absolute URI?"
*
* <p>In the <a href="http://www.ietf.org/rfc/rfc2396.txt">RFC</a>, 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}.
*
* <p><a name="device_explanation">
* The primary enhancement beyond the RFC description is an optional
* device component. Instead of treating the device as just another segment
* in the path, it can be stored as a separate component (almost a
* sub-authority), with the root below it. For example, resolving
* <code>/bar</code> against <code>file:///c:/foo</code> would result in
* <code>file:///c:/bar</code> being returned. Also, you cannot take
* the parent of a device, so resolving <code>..</code> against
* <code>file:///c:/</code> would not yield <code>file:///</code>, as you
* might expect. This feature is useful when working with file-scheme
* URIs, as devices do not typically occur in protocol-based ones. A
* device-enabled <code>URI</code> is created by parsing a string with
* {@link #createURI(String) createURI}; if the first segment of the path
* ends with the <code>:</code> character, it is stored (including the colon)
* as the device, instead. Alternately, either the {@link
* #createHierarchicalURI(String, String, String, String, String) no-path}
* or the {@link #createHierarchicalURI(String, String, String, String[],
* String, String) absolute-path} form of <code>createHierarchicalURI()</code>
* can be used, in which a non-null <code>device</code> parameter can be
* specified.
*
* <p><a name="archive_explanation">
* The other enhancement provides support for the almost-hierarchical
* form used for files within archives, such as the JAR scheme, defined
* for the Java Platform in the documentation for {@link
* java.net.JarURLConnection}. By default, this support is enabled for
* absolute URIs with scheme equal to "jar", "zip", or "archive" (ignoring case), and
* is implemented by a hierarchical URI, whose authority includes the
* entire URI of the archive, up to and including the <code>!</code>
* character. The URI of the archive must have no fragment. The whole
* archive URI must have no device and an absolute path. Special handling
* is supported for {@link #createURI(String) creating}, {@link
* #validArchiveAuthority validating}, {@link #devicePath getting the path}
* from, and {@link #toString() displaying} archive URIs. In all other
* operations, including {@link #resolve(URI2) resolving} and {@link
* #deresolve(URI2) deresolving}, they are handled like any ordinary URI.
* The schemes that identify archive URIs can be changed from their default
* by setting the <code>org.eclipse.emf.common.util.URI.archiveSchemes</code>
* system property. Multiple schemes should be space separated, and the test
* of whether a URI's scheme matches is always case-insensitive.
*
* <p>This implementation does not impose all of the restrictions on
* character validity that are specified in the RFC. Static methods whose
* names begin with "valid" are used to test whether a given string is valid
* value for the various URI components. Presently, these tests place no
* restrictions beyond what would have been required in order for {@link
* #createURI(String) createURI} to have parsed them correctly from a single
* URI string. If necessary in the future, these tests may be made more
* strict, to better conform to the RFC.
*
* <p>Another group of static methods, whose names begin with "encode", use
* percent escaping to encode any characters that are not permitted in the
* various URI components. Another static method is provided to {@link
* #decode decode} encoded strings. An escaped character is represented as
* a percent symbol (<code>%</code>), followed by two hex digits that specify
* the character code. These encoding methods are more strict than the
* validation methods described above. They ensure validity according to the
* RFC, with one exception: non-ASCII characters.
*
* <p>The RFC allows only characters that can be mapped to 7-bit US-ASCII
* representations. Non-ASCII, single-byte characters can be used only via
* percent escaping, as described above. This implementation uses Java's
* Unicode <code>char</code> and <code>String</code> representations, and
* makes no attempt to encode characters 0xA0 and above. Characters in the
* range 0x80-0x9F are still escaped. In this respect, EMF's notion of a URI
* is actually more like an IRI (Internationalized Resource Identifier), for
* which an RFC is now in <href="http://www.w3.org/International/iri-edit/draft-duerst-iri-09.txt">draft
* form</a>.
*
* <p>Finally, note the difference between a <code>null</code> 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: <code>/bar</code> (with no
* authority) and <code>///bar</code> (with a blank authority). Imagine
* resolving them against a base with an authority, such as
* <code>http://www.eclipse.org/</code>. The former case will yield
* <code>http://www.eclipse.org/bar</code>, as the base authority will be
* preserved. In the latter case, the empty authority will override the
* base authority, resulting in <code>http:///bar</code>!
*/
public static final class URI2
{
// Common to all URI types.
private final int hashCode;
private static final int HIERARICHICAL_FLAG = 0x0100;
private final String scheme; // null -> relative URI reference
private final String authority;
private final String fragment;
private URI2 cachedTrimFragment;
private String cachedToString;
//private final boolean iri;
//private URI cachedASCIIURI;
// Applicable only to a hierarchical URI.
private final String device;
private static final int ABSOLUTE_PATH_FLAG = 0x0010;
private final String[] segments; // empty last segment -> trailing separator
private final String query;
// A cache of URIs, keyed by the strings from which they were created.
// The fragment of any URI is removed before caching it here, to minimize
// the size of the cache in the usual case where most URIs only differ by
// the fragment.
private static final URICache uriCache = new URICache();
private static class URICache extends HashMap<String,WeakReference<URI2>>
{
private static final long serialVersionUID = 1L;
static final int MIN_LIMIT = 1000;
int count;
int limit = MIN_LIMIT;
public synchronized URI2 get(String key)
{
WeakReference<URI2> reference = super.get(key);
return reference == null ? null : reference.get();
}
public synchronized void put(String key, URI2 value)
{
super.put(key, new WeakReference<URI2>(value));
if (++count > limit)
{
cleanGCedValues();
}
}
private void cleanGCedValues()
{
for (Iterator<Map.Entry<String,WeakReference<URI2>>> i = entrySet().iterator(); i.hasNext(); )
{
Map.Entry<String,WeakReference<URI2>> entry = i.next();
WeakReference<URI2> reference = entry.getValue();
if (reference.get() == null)
{
i.remove();
}
}
count = 0;
limit = Math.max(MIN_LIMIT, size() / 2);
}
}
// The lower-cased schemes that will be used to identify archive URIs.
private static final Set<String> archiveSchemes;
// Identifies a file-type absolute URI.
private static final String SCHEME_FILE = "file";
private static final String SCHEME_JAR = "jar";
private static final String SCHEME_ZIP = "zip";
private static final String SCHEME_ARCHIVE = "archive";
private static final String SCHEME_PLATFORM = "platform";
// Special segment values interpreted at resolve and resolve time.
private static final String SEGMENT_EMPTY = "";
private static final String SEGMENT_SELF = ".";
private static final String SEGMENT_PARENT = "..";
private static final String[] NO_SEGMENTS = new String[0];
// Separators for parsing a URI string.
private static final char SCHEME_SEPARATOR = ':';
private static final String AUTHORITY_SEPARATOR = "//";
private static final char DEVICE_IDENTIFIER = ':';
private static final char SEGMENT_SEPARATOR = '/';
private static final char QUERY_SEPARATOR = '?';
private static final char FRAGMENT_SEPARATOR = '#';
private static final char USER_INFO_SEPARATOR = '@';
private static final char PORT_SEPARATOR = ':';
private static final char FILE_EXTENSION_SEPARATOR = '.';
private static final char ARCHIVE_IDENTIFIER = '!';
private static final String ARCHIVE_SEPARATOR = "!/";
// Characters to use in escaping.
private static final char ESCAPE = '%';
private static final char[] HEX_DIGITS = {
'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F' };
// Some character classes, as defined in RFC 2396's BNF for URI.
// These are 128-bit bitmasks, stored as two longs, where the Nth bit is set
// iff the ASCII character with value N is included in the set. These are
// created with the highBitmask() and lowBitmask() methods defined below,
// and a character is tested against them using matches().
//
private static final long ALPHA_HI = highBitmask('a', 'z') | highBitmask('A', 'Z');
private static final long ALPHA_LO = lowBitmask('a', 'z') | lowBitmask('A', 'Z');
private static final long DIGIT_HI = highBitmask('0', '9');
private static final long DIGIT_LO = lowBitmask('0', '9');
private static final long ALPHANUM_HI = ALPHA_HI | DIGIT_HI;
private static final long ALPHANUM_LO = ALPHA_LO | DIGIT_LO;
private static final long HEX_HI = DIGIT_HI | highBitmask('A', 'F') | highBitmask('a', 'f');
private static final long HEX_LO = DIGIT_LO | lowBitmask('A', 'F') | lowBitmask('a', 'f');
private static final long UNRESERVED_HI = ALPHANUM_HI | highBitmask("-_.!~*'()");
private static final long UNRESERVED_LO = ALPHANUM_LO | lowBitmask("-_.!~*'()");
private static final long RESERVED_HI = highBitmask(";/?:@&=+$,");
private static final long RESERVED_LO = lowBitmask(";/?:@&=+$,");
private static final long URIC_HI = RESERVED_HI | UNRESERVED_HI; // | ucschar | escaped
private static final long URIC_LO = RESERVED_LO | UNRESERVED_LO;
// Additional useful character classes, including characters valid in certain
// URI components and separators used in parsing them out of a string.
//
private static final long SEGMENT_CHAR_HI = UNRESERVED_HI | highBitmask(";:@&=+$,"); // | ucschar | escaped
private static final long SEGMENT_CHAR_LO = UNRESERVED_LO | lowBitmask(";:@&=+$,");
private static final long PATH_CHAR_HI = SEGMENT_CHAR_HI | highBitmask('/'); // | ucschar | escaped
private static final long PATH_CHAR_LO = SEGMENT_CHAR_LO | lowBitmask('/');
// private static final long SCHEME_CHAR_HI = ALPHANUM_HI | highBitmask("+-.");
// private static final long SCHEME_CHAR_LO = ALPHANUM_LO | lowBitmask("+-.");
private static final long MAJOR_SEPARATOR_HI = highBitmask(":/?#");
private static final long MAJOR_SEPARATOR_LO = lowBitmask(":/?#");
private static final long SEGMENT_END_HI = highBitmask("/?#");
private static final long SEGMENT_END_LO = lowBitmask("/?#");
// The intent of this was to switch over to encoding platform resource URIs
// by default, but allow people to use a system property to avoid this.
// However, that caused problems for people and we had to go back to not
// encoding and introduce yet another factory method that explicitly enables
// encoding.
//
private static final boolean ENCODE_PLATFORM_RESOURCE_URIS =
System.getProperty("org.eclipse.emf.common.util.URI.encodePlatformResourceURIs") != null &&
!"false".equalsIgnoreCase(System.getProperty("org.eclipse.emf.common.util.URI.encodePlatformResourceURIs"));
// Static initializer for archiveSchemes.
static
{
Set<String> set = new HashSet<String>();
String propertyValue = System.getProperty("org.eclipse.emf.common.util.URI.archiveSchemes");
if (propertyValue == null)
{
set.add(SCHEME_JAR);
set.add(SCHEME_ZIP);
set.add(SCHEME_ARCHIVE);
}
else
{
for (StringTokenizer t = new StringTokenizer(propertyValue); t.hasMoreTokens(); )
{
set.add(t.nextToken().toLowerCase());
}
}
archiveSchemes = Collections.unmodifiableSet(set);
}
// Returns the lower half bitmask for the given ASCII character.
private static long lowBitmask(char c)
{
return c < 64 ? 1L << c : 0L;
}
// Returns the upper half bitmask for the given ACSII character.
private static long highBitmask(char c)
{
return c >= 64 && c < 128 ? 1L << (c - 64) : 0L;
}
// Returns the lower half bitmask for all ASCII characters between the two
// given characters, inclusive.
private static long lowBitmask(char from, char to)
{
long result = 0L;
if (from < 64 && from <= to)
{
to = to < 64 ? to : 63;
for (char c = from; c <= to; c++)
{
result |= (1L << c);
}
}
return result;
}
// Returns the upper half bitmask for all AsCII characters between the two
// given characters, inclusive.
private static long highBitmask(char from, char to)
{
return to < 64 ? 0 : lowBitmask((char)(from < 64 ? 0 : from - 64), (char)(to - 64));
}
// Returns the lower half bitmask for all the ASCII characters in the given
// string.
private static long lowBitmask(String chars)
{
long result = 0L;
for (int i = 0, len = chars.length(); i < len; i++)
{
char c = chars.charAt(i);
if (c < 64) result |= (1L << c);
}
return result;
}
// Returns the upper half bitmask for all the ASCII characters in the given
// string.
private static long highBitmask(String chars)
{
long result = 0L;
for (int i = 0, len = chars.length(); i < len; i++)
{
char c = chars.charAt(i);
if (c >= 64 && c < 128) result |= (1L << (c - 64));
}
return result;
}
// Returns whether the given character is in the set specified by the given
// bitmask.
private static boolean matches(char c, long highBitmask, long lowBitmask)
{
if (c >= 128) return false;
return c < 64 ?
((1L << c) & lowBitmask) != 0 :
((1L << (c - 64)) & highBitmask) != 0;
}
// Debugging method: converts the given long to a string of binary digits.
/*
private static String toBits(long l)
{
StringBuffer result = new StringBuffer();
for (int i = 0; i < 64; i++)
{
boolean b = (l & 1L) != 0;
result.insert(0, b ? '1' : '0');
l >>= 1;
}
return result.toString();
}
*/
/**
* Static factory method for a generic, non-hierarchical URI. There is no
* concept of a relative non-hierarchical URI; such an object cannot be
* created.
*
* @exception java.lang.IllegalArgumentException if <code>scheme</code> is
* null, if <code>scheme</code> is an <a href="#archive_explanation">archive
* URI</a> scheme, or if <code>scheme</code>, <code>opaquePart</code>, or
* <code>fragment</code> is not valid according to {@link #validScheme
* validScheme}, {@link #validOpaquePart validOpaquePart}, or {@link
* #validFragment validFragment}, respectively.
*/
public static URI2 createGenericURI(String scheme, String opaquePart,
String fragment)
{
if (scheme == null)
{
throw new IllegalArgumentException("relative non-hierarchical URI");
}
if (isArchiveScheme(scheme))
{
throw new IllegalArgumentException("non-hierarchical archive URI");
}
validateURI(false, scheme, opaquePart, null, false, NO_SEGMENTS, null, fragment);
return new URI2(false, scheme, opaquePart, null, false, NO_SEGMENTS, null, fragment);
}
/**
* Static factory method for a hierarchical URI with no path. The
* URI will be relative if <code>scheme</code> is non-null, and absolute
* otherwise. An absolute URI with no path requires a non-null
* <code>authority</code> and/or <code>device</code>.
*
* @exception java.lang.IllegalArgumentException if <code>scheme</code> is
* non-null while <code>authority</code> and <code>device</code> are null,
* if <code>scheme</code> is an <a href="#archive_explanation">archive
* URI</a> scheme, or if <code>scheme</code>, <code>authority</code>,
* <code>device</code>, <code>query</code>, or <code>fragment</code> is not
* valid according to {@link #validScheme validSheme}, {@link
* #validAuthority validAuthority}, {@link #validDevice validDevice},
* {@link #validQuery validQuery}, or {@link #validFragment validFragment},
* respectively.
*/
public static URI2 createHierarchicalURI(String scheme, String authority,
String device, String query,
String fragment)
{
if (scheme != null && authority == null && device == null)
{
throw new IllegalArgumentException(
"absolute hierarchical URI without authority, device, path");
}
if (isArchiveScheme(scheme))
{
throw new IllegalArgumentException("archive URI with no path");
}
validateURI(true, scheme, authority, device, false, NO_SEGMENTS, query, fragment);
return new URI2(true, scheme, authority, device, false, NO_SEGMENTS, query, fragment);
}
/**
* Static factory method for a hierarchical URI with absolute path.
* The URI will be relative if <code>scheme</code> is non-null, and
* absolute otherwise.
*
* @param segments an array of non-null strings, each representing one
* segment of the path. As an absolute path, it is automatically
* preceded by a <code>/</code> separator. 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 <code>scheme</code> is
* an <a href="#archive_explanation">archive URI</a> scheme and
* <code>device</code> is non-null, or if <code>scheme</code>,
* <code>authority</code>, <code>device</code>, <code>segments</code>,
* <code>query</code>, or <code>fragment</code> is not valid according to
* {@link #validScheme validScheme}, {@link #validAuthority validAuthority}
* or {@link #validArchiveAuthority validArchiveAuthority}, {@link
* #validDevice validDevice}, {@link #validSegments validSegments}, {@link
* #validQuery validQuery}, or {@link #validFragment validFragment}, as
* appropriate.
*/
public static URI2 createHierarchicalURI(String scheme, String authority,
String device, String[] segments,
String query, String fragment)
{
if (isArchiveScheme(scheme) && device != null)
{
throw new IllegalArgumentException("archive URI with device");
}
segments = fix(segments);
validateURI(true, scheme, authority, device, true, segments, query, fragment);
return new URI2(true, scheme, authority, device, true, segments, query, fragment);
}
/**
* Static factory method for a relative hierarchical URI with relative
* path.
*
* @param segments an array of non-null strings, each representing one
* segment of the path. A trailing separator is represented by an
* empty-string segment at the end of the array.
*
* @exception java.lang.IllegalArgumentException if <code>segments</code>,
* <code>query</code>, or <code>fragment</code> is not valid according to
* {@link #validSegments validSegments}, {@link #validQuery validQuery}, or
* {@link #validFragment validFragment}, respectively.
*/
public static URI2 createHierarchicalURI(String[] segments, String query,
String fragment)
{
segments = fix(segments);
validateURI(true, null, null, null, false, segments, query, fragment);
return new URI2(true, null, null, null, false, segments, query, fragment);
}
// Converts null to length-zero array, and clones array to ensure
// immutability.
private static String[] fix(String[] segments)
{
return segments == null ? NO_SEGMENTS : (String[])segments.clone();
}
/**
* Static factory method based on parsing a URI string, with
* <a href="#device_explanation">explicit device support</a> and handling
* for <a href="#archive_explanation">archive URIs</a> enabled. The
* specified string is parsed as described in <a
* href="http://www.ietf.org/rfc/rfc2396.txt">RFC 2396</a>, and an
* appropriate <code>URI</code> is created and returned. Note that
* validity testing is not as strict as in the RFC; essentially, only
* separator characters are considered. This method also does not perform
* encoding of invalid characters, so it should only be used when the URI
* string is known to have already been encoded, so as to avoid double
* encoding.
*
* @exception java.lang.IllegalArgumentException if any component parsed
* from <code>uri</code> is not valid according to {@link #validScheme
* validScheme}, {@link #validOpaquePart validOpaquePart}, {@link
* #validAuthority validAuthority}, {@link #validArchiveAuthority
* validArchiveAuthority}, {@link #validDevice validDevice}, {@link
* #validSegments validSegments}, {@link #validQuery validQuery}, or {@link
* #validFragment validFragment}, as appropriate.
*/
public static URI2 createURI(String uri)
{
return createURIWithCache(uri);
}
/**
* Static factory method that encodes and parses the given URI string.
* Appropriate encoding is performed for each component of the URI.
* If more than one <code>#</code> is in the string, the last one is
* assumed to be the fragment's separator, and any others are encoded.
* This method is the simplest way to safely parse an arbitrary URI string.
*
* @param ignoreEscaped <code>true</code> to leave <code>%</code> characters
* unescaped if they already begin a valid three-character escape sequence;
* <code>false</code> to encode all <code>%</code> characters. This
* capability is provided to allow partially encoded URIs to be "fixed",
* while avoiding adding double encoding; however, it is usual just to
* specify <code>false</code> to perform ordinary encoding.
*
* @exception java.lang.IllegalArgumentException if any component parsed
* from <code>uri</code> is not valid according to {@link #validScheme
* validScheme}, {@link #validOpaquePart validOpaquePart}, {@link
* #validAuthority validAuthority}, {@link #validArchiveAuthority
* validArchiveAuthority}, {@link #validDevice validDevice}, {@link
* #validSegments validSegments}, {@link #validQuery validQuery}, or {@link
* #validFragment validFragment}, as appropriate.
*/
public static URI2 createURI(String uri, boolean ignoreEscaped)
{
return createURIWithCache(encodeURI(uri, ignoreEscaped, FRAGMENT_LAST_SEPARATOR));
}
/**
* When specified as the last argument to {@link #createURI(String, boolean, int)
* createURI}, indicates that there is no fragment, so any <code>#</code> characters
* should be encoded.
* @see #createURI(String, boolean, int)
*/
public static final int FRAGMENT_NONE = 0;
/**
* When specified as the last argument to {@link #createURI(String, boolean, int)
* createURI}, indicates that the first <code>#</code> character should be taken as
* the fragment separator, and any others should be encoded.
* @see #createURI(String, boolean, int)
*/
public static final int FRAGMENT_FIRST_SEPARATOR = 1;
/**
* When specified as the last argument to {@link #createURI(String, boolean, int)
* createURI}, indicates that the last <code>#</code> character should be taken as
* the fragment separator, and any others should be encoded.
* @see #createURI(String, boolean, int)
*/
public static final int FRAGMENT_LAST_SEPARATOR = 2;
/**
* Static factory method that encodes and parses the given URI string.
* Appropriate encoding is performed for each component of the URI.
* Control is provided over which, if any, <code>#</code> should be
* taken as the fragment separator and which should be encoded.
* This method is the preferred way to safely parse an arbitrary URI string
* that is known to contain <code>#</code> characters in the fragment or to
* have no fragment at all.
*
* @param ignoreEscaped <code>true</code> to leave <code>%</code> characters
* unescaped if they already begin a valid three-character escape sequence;
* <code>false</code> to encode all <code>%</code> characters. This
* capability is provided to allow partially encoded URIs to be "fixed",
* while avoiding adding double encoding; however, it is usual just to
* specify <code>false</code> to perform ordinary encoding.
*
* @param fragmentLocationStyle one of {@link #FRAGMENT_NONE},
* {@link #FRAGMENT_FIRST_SEPARATOR}, or {@link #FRAGMENT_LAST_SEPARATOR},
* indicating which, if any, of the <code>#</code> characters should be
* considered the fragment separator. Any others will be encoded.
*
* @exception java.lang.IllegalArgumentException if any component parsed
* from <code>uri</code> is not valid according to {@link #validScheme
* validScheme}, {@link #validOpaquePart validOpaquePart}, {@link
* #validAuthority validAuthority}, {@link #validArchiveAuthority
* validArchiveAuthority}, {@link #validDevice validDevice}, {@link
* #validSegments validSegments}, {@link #validQuery validQuery}, or {@link
* #validFragment validFragment}, as appropriate.
*/
public static URI2 createURI(String uri, boolean ignoreEscaped, int fragmentLocationStyle)
{
return createURIWithCache(encodeURI(uri, ignoreEscaped, fragmentLocationStyle));
}
/**
* Static factory method based on parsing a URI string, with
* <a href="#device_explanation">explicit device support</a> enabled.
* Note that validity testing is not a strict as in the RFC; essentially,
* only separator characters are considered. So, for example, non-Latin
* alphabet characters appearing in the scheme would not be considered an
* error.
*
* @exception java.lang.IllegalArgumentException if any component parsed
* from <code>uri</code> is not valid according to {@link #validScheme
* validScheme}, {@link #validOpaquePart validOpaquePart}, {@link
* #validAuthority validAuthority}, {@link #validArchiveAuthority
* validArchiveAuthority}, {@link #validDevice validDevice}, {@link
* #validSegments validSegments}, {@link #validQuery validQuery}, or {@link
* #validFragment validFragment}, as appropriate.
*
* @deprecated Use {@link #createURI(String) createURI}, which now has explicit
* device support enabled. The two methods now operate identically.
*/
@Deprecated
public static URI2 createDeviceURI(String uri)
{
return createURIWithCache(uri);
}
// Uses a cache to speed up creation of a URI from a string. The cache
// is consulted to see if the URI, less any fragment, has already been
// created. If needed, the fragment is re-appended to the cached URI,
// which is considerably more efficient than creating the whole URI from
// scratch. If the URI wasn't found in the cache, it is created using
// parseIntoURI() and then cached. This method should always be used
// by string-parsing factory methods, instead of parseIntoURI() directly.
/**
* This method was included in the public API by mistake.
*
* @deprecated Please use {@link #createURI(String) createURI} instead.
*/
@Deprecated
public static URI2 createURIWithCache(String uri)
{
int i = uri.indexOf(FRAGMENT_SEPARATOR);
String base = i == -1 ? uri : uri.substring(0, i);
String fragment = i == -1 ? null : uri.substring(i + 1);
URI2 result = uriCache.get(base);
if (result == null)
{
result = parseIntoURI(base);
uriCache.put(base, result);
if (fragment != null)
{
result = result.appendFragment(fragment);
}
}
else if (fragment != null)
{
result = result.appendFragment(new String(fragment));
}
return result;
}
// String-parsing implementation.
private static URI2 parseIntoURI(String uri)
{
boolean hierarchical = true;
String scheme = null;
String authority = null;
String device = null;
boolean absolutePath = false;
String[] segments = NO_SEGMENTS;
String query = null;
String fragment = null;
int i = 0;
int j = find(uri, i, MAJOR_SEPARATOR_HI, MAJOR_SEPARATOR_LO);
if (j < uri.length() && uri.charAt(j) == SCHEME_SEPARATOR)
{
scheme = uri.substring(i, j);
i = j + 1;
}
boolean archiveScheme = isArchiveScheme(scheme);
if (archiveScheme)
{
j = uri.lastIndexOf(ARCHIVE_SEPARATOR);
if (j == -1)
{
throw new IllegalArgumentException("no archive separator");
}
hierarchical = true;
authority = uri.substring(i, ++j);
i = j;
}
else if (uri.startsWith(AUTHORITY_SEPARATOR, i))
{
i += AUTHORITY_SEPARATOR.length();
j = find(uri, i, SEGMENT_END_HI, SEGMENT_END_LO);
authority = uri.substring(i, j);
i = j;
}
else if (scheme != null &&
(i == uri.length() || uri.charAt(i) != SEGMENT_SEPARATOR))
{
hierarchical = false;
j = uri.indexOf(FRAGMENT_SEPARATOR, i);
if (j == -1) j = uri.length();
authority = uri.substring(i, j);
i = j;
}
if (!archiveScheme && i < uri.length() && uri.charAt(i) == SEGMENT_SEPARATOR)
{
j = find(uri, i + 1, SEGMENT_END_HI, SEGMENT_END_LO);
String s = uri.substring(i + 1, j);
if (s.length() > 0 && s.charAt(s.length() - 1) == DEVICE_IDENTIFIER)
{
device = s;
i = j;
}
}
if (i < uri.length() && uri.charAt(i) == SEGMENT_SEPARATOR)
{
i++;
absolutePath = true;
}
if (segmentsRemain(uri, i))
{
List<String> segmentList = new ArrayList<String>();
while (segmentsRemain(uri, i))
{
j = find(uri, i, SEGMENT_END_HI, SEGMENT_END_LO);
segmentList.add(uri.substring(i, j));
i = j;
if (i < uri.length() && uri.charAt(i) == SEGMENT_SEPARATOR)
{
if (!segmentsRemain(uri, ++i)) segmentList.add(SEGMENT_EMPTY);
}
}
segments = new String[segmentList.size()];
segmentList.toArray(segments);
}
if (i < uri.length() && uri.charAt(i) == QUERY_SEPARATOR)
{
j = uri.indexOf(FRAGMENT_SEPARATOR, ++i);
if (j == -1) j = uri.length();
query = uri.substring(i, j);
i = j;
}
if (i < uri.length()) // && uri.charAt(i) == FRAGMENT_SEPARATOR (implied)
{
fragment = uri.substring(++i);
}
validateURI(hierarchical, scheme, authority, device, absolutePath, segments, query, fragment);
return new URI2(hierarchical, scheme, authority, device, absolutePath, segments, query, fragment);
}
// Checks whether the string contains any more segments after the one that
// starts at position i.
private static boolean segmentsRemain(String uri, int i)
{
return i < uri.length() && uri.charAt(i) != QUERY_SEPARATOR &&
uri.charAt(i) != FRAGMENT_SEPARATOR;
}
// Finds the next occurrence of one of the characters in the set represented
// by the given bitmask in the given string, beginning at index i. The index
// of the first found character, or s.length() if there is none, is
// returned. Before searching, i is limited to the range [0, s.length()].
//
private static int find(String s, int i, long highBitmask, long lowBitmask)
{
int len = s.length();
if (i >= len) return len;
for (i = i > 0 ? i : 0; i < len; i++)
{
if (matches(s.charAt(i), highBitmask, lowBitmask)) break;
}
return i;
}
/**
* Static factory method based on parsing a {@link java.io.File} path
* string. The <code>pathName</code> is converted into an appropriate
* form, as follows: platform specific path separators are converted to
* <code>/<code>; the path is encoded; and a "file" scheme and, if missing,
* a leading <code>/</code>, are added to an absolute path. The result
* is then parsed using {@link #createURI(String) createURI}.
*
* <p>The encoding step escapes all spaces, <code>#</code> characters, and
* other characters disallowed in URIs, as well as <code>?</code>, which
* would delimit a path from a query. Decoding is automatically performed
* by {@link #toFileString toFileString}, and can be applied to the values
* returned by other accessors by via the static {@link #decode(String)
* decode} method.
*
* <p>A relative path with a specified device (something like
* <code>C:myfile.txt</code>) cannot be expressed as a valid URI.
*
* @exception java.lang.IllegalArgumentException if <code>pathName</code>
* 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 URI2 createFileURI(String pathName)
{
File file = new File(pathName);
String uri = File.separatorChar != '/' ? pathName.replace(File.separatorChar, SEGMENT_SEPARATOR) : pathName;
uri = encode(uri, PATH_CHAR_HI, PATH_CHAR_LO, false);
if (file.isAbsolute())
{
URI2 result = createURI((uri.charAt(0) == SEGMENT_SEPARATOR ? "file:" : "file:/") + uri);
return result;
}
else
{
URI2 result = createURI(uri);
if (result.scheme() != null)
{
throw new IllegalArgumentException("invalid relative pathName: " + pathName);
}
return result;
}
}
/**
* Static factory method based on parsing a workspace-relative path string.
*
* <p>The <code>pathName</code> must be of the form:
* <pre>
* /project-name/path</pre>
*
* <p>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}:
* <pre>
* platform:/resource/project-name/path</pre>
*
* <p>This scheme supports relocatable projects in Eclipse and in
* stand-alone EMF.
*
* <p>Path encoding is performed only if the
* <code>org.eclipse.emf.common.util.URI.encodePlatformResourceURIs</code>
* 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 URI2 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.
*
* <p>The <code>pathName</code> must be of the form:
* <pre>
* /project-name/path</pre>
*
* <p>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}:
* <pre>
* platform:/resource/project-name/path</pre>
*
* <p>This scheme supports relocatable projects in Eclipse and in
* stand-alone EMF.
*
* <p>Depending on the <code>encode</code> argument, the path may be
* automatically encoded to escape all spaces, <code>#</code> characters,
* and other characters disallowed in URIs, as well as <code>?</code>,
* 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 <code>true</code> 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 URI2 createPlatformResourceURI(String pathName, boolean encode)
{
return createPlatformURI("platform:/resource", "platform:/resource/", pathName, encode);
}
/**
* Static factory method based on parsing a plug-in-based path string,
* with an option to encode the created URI.
*
* <p>The <code>pathName</code> must be of the form:
* <pre>
* /plugin-id/path</pre>
*
* <p>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}:
* <pre>
* platform:/plugin/plugin-id/path</pre>
*
* <p>This scheme supports relocatable plug-in content in Eclipse.
*
* <p>Depending on the <code>encode</code> argument, the path may be
* automatically encoded to escape all spaces, <code>#</code> characters,
* and other characters disallowed in URIs, as well as <code>?</code>,
* 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 <code>true</code> 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 URI2 createPlatformPluginURI(String pathName, boolean encode)
{
return createPlatformURI("platform:/plugin", "platform:/plugin/", pathName, encode);
}
// Private constructor for use of platform factory methods.
private static URI2 createPlatformURI(String unrootedBase, String rootedBase, String pathName, boolean encode)
{
if (File.separatorChar != SEGMENT_SEPARATOR)
{
pathName = pathName.replace(File.separatorChar, SEGMENT_SEPARATOR);
}
if (encode)
{
pathName = encode(pathName, PATH_CHAR_HI, PATH_CHAR_LO, false);
}
URI2 result = createURI((pathName.charAt(0) == SEGMENT_SEPARATOR ? unrootedBase : rootedBase) + pathName);
return result;
}
// Private constructor for use of static factory methods.
private URI2(boolean hierarchical, String scheme, String authority,
String device, boolean absolutePath, String[] segments,
String query, String fragment)
{
int hashCode = 0;
//boolean iri = false;
if (scheme != null)
{
hashCode ^= scheme.toLowerCase().hashCode();
}
if (authority != null)
{
hashCode ^= authority.hashCode();
//iri = iri || containsNonASCII(authority);
}
if (device != null)
{
hashCode ^= device.hashCode();
//iri = iri || containsNonASCII(device);
}
if (query != null)
{
hashCode ^= query.hashCode();
//iri = iri || containsNonASCII(query);
}
if (fragment != null)
{
hashCode ^= fragment.hashCode();
//iri = iri || containsNonASCII(fragment);
}
for (int i = 0, len = segments.length; i < len; i++)
{
hashCode ^= segments[i].hashCode();
//iri = iri || containsNonASCII(segments[i]);
}
if (hierarchical)
{
hashCode |= HIERARICHICAL_FLAG;
}
else
{
hashCode &= ~HIERARICHICAL_FLAG;
}
if (absolutePath)
{
hashCode |= ABSOLUTE_PATH_FLAG;
}
else
{
hashCode &= ~ABSOLUTE_PATH_FLAG;
}
this.hashCode = hashCode;
//this.iri = iri;
this.scheme = scheme == null ? null : scheme.intern();
this.authority = authority;
this.device = device;
this.segments = segments;
this.query = query;
this.fragment = fragment;
}
// 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.
private static void 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 && !validOpaquePart(authority))
{
throw new IllegalArgumentException("invalid opaquePart: " + authority);
}
if (hierarchical && !isArchiveScheme(scheme) && !validAuthority(authority))
{
throw new IllegalArgumentException("invalid authority: " + authority);
}
if (hierarchical && isArchiveScheme(scheme) && !validArchiveAuthority(authority))
{
throw new IllegalArgumentException("invalid authority: " + authority);
}
if (!validDevice(device))
{
throw new IllegalArgumentException("invalid device: " + device);
}
if (!validSegments(segments))
{
String s = segments == null ? "invalid segments: null" :
"invalid segment: " + firstInvalidSegment(segments);
throw new IllegalArgumentException(s);
}
if (!validQuery(query))
{
throw new IllegalArgumentException("invalid query: " + query);
}
if (!validFragment(fragment))
{
throw new IllegalArgumentException("invalid fragment: " + fragment);
}
}
// Alternate, stricter implementations of the following validation methods
// are provided, commented out, for possible future use...
/**
* Returns <code>true</code> if the specified <code>value</code> would be
* valid as the scheme component of a URI; <code>false</code> otherwise.
*
* <p>A valid scheme may be null or contain any characters except for the
* following: <code>: / ? #</code>
*/
public static boolean validScheme(String value)
{
return value == null || !contains(value, MAJOR_SEPARATOR_HI, MAJOR_SEPARATOR_LO);
// <p>A valid scheme may be null, or consist of a single letter followed
// by any number of letters, numbers, and the following characters:
// <code>+ - .</code>
//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 <code>true</code> if the specified <code>value</code> would be
* valid as the opaque part component of a URI; <code>false</code>
* otherwise.
*
* <p>A valid opaque part must be non-null, non-empty, and not contain the
* <code>#</code> character. In addition, its first character must not be
* <code>/</code>
*/
public static boolean validOpaquePart(String value)
{
return value != null && value.indexOf(FRAGMENT_SEPARATOR) == -1 &&
value.length() > 0 && value.charAt(0) != SEGMENT_SEPARATOR;
// <p>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
// <code>/</code>
//return value != null && value.length() != 0 &&
// value.charAt(0) != SEGMENT_SEPARATOR &&
// validate(value, URIC_HI, URIC_LO, true, true);
}
/**
* Returns <code>true</code> if the specified <code>value</code> would be
* valid as the authority component of a URI; <code>false</code> otherwise.
*
* <p>A valid authority may be null or contain any characters except for
* the following: <code>/ ? #</code>
*/
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: <code>/ ?</code>
//return value == null || validate(value, SEGMENT_CHAR_HI, SEGMENT_CHAR_LO, true, true);
}
/**
* Returns <code>true</code> if the specified <code>value</code> would be
* valid as the authority component of an <a
* href="#archive_explanation">archive URI</a>; <code>false</code>
* otherwise.
*
* <p>To be valid, the authority, itself, must be a URI with no fragment,
* followed by the character <code>!</code>.
*/
public static boolean validArchiveAuthority(String value)
{
if (value != null && value.length() > 0 &&
value.charAt(value.length() - 1) == ARCHIVE_IDENTIFIER)
{
try
{
URI2 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 <code>value</code> would be valid as the
* authority component of an <a href="#archive_explanation">archive
* URI</a>. 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 <code>true</code> if the specified <code>value</code> would be
* valid as the device component of a URI; <code>false</code> otherwise.
*
* <p>A valid device may be null or non-empty, containing any characters
* except for the following: <code>/ ? #</code> In addition, its last
* character must be <code>:</code>
*/
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 <code>true</code> if the specified <code>value</code> would be
* a valid path segment of a URI; <code>false</code> otherwise.
*
* <p>A valid path segment must be non-null and not contain any of the
* following characters: <code>/ ? #</code>
*/
public static boolean validSegment(String value)
{
return value != null && !contains(value, SEGMENT_END_HI, SEGMENT_END_LO);
// <p>A valid path segment must be non-null and may contain any allowed URI
// characters except for the following: <code>/ ?</code>
//return value != null && validate(value, SEGMENT_CHAR_HI, SEGMENT_CHAR_LO, true, true);
}
/**
* Returns <code>true</code> if the specified <code>value</code> would be
* a valid path segment array of a URI; <code>false</code> otherwise.
*
* <p>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.
private 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 <code>true</code> if the specified <code>value</code> would be
* valid as the query component of a URI; <code>false</code> otherwise.
*
* <p>A valid query may be null or contain any characters except for
* <code>#</code>
*/
public static boolean validQuery(String value)
{
return value == null || value.indexOf(FRAGMENT_SEPARATOR) == -1;
// <p>A valid query may be null or contain any allowed URI characters.
//return value == null || validate(value, URIC_HI, URIC_LO, true, true);
}
/**
* Returns <code>true</code> if the specified <code>value</code> would be
* valid as the fragment component of a URI; <code>false</code> otherwise.
*
* <p>A fragment is taken to be unconditionally valid.
*/
public static boolean validFragment(String value)
{
return true;
// <p>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.
private 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;
}
// Tests the non-null string value to see if it contains only ASCII
// characters in the set represented by the specified 128-bit bitmask,
// as well as, optionally, non-ASCII characters 0xA0 and above, and,
// also optionally, escape sequences of % followed by two hex digits.
// This method is used for the new, strict URI validation that is not
// not currently in place.
/*
private static boolean validate(String value, long highBitmask, long lowBitmask,
boolean allowNonASCII, boolean allowEscaped)
{
for (int i = 0, length = value.length(); i < length; i++)
{
char c = value.charAt(i);
if (matches(c, highBitmask, lowBitmask)) continue;
if (allowNonASCII && c >= 160) continue;
if (allowEscaped && isEscaped(value, i))
{
i += 2;
continue;
}
return false;
}
return true;
}
*/
/**
* Returns <code>true</code> if this is a relative URI, or
* <code>false</code> if it is an absolute URI.
*/
public boolean isRelative()
{
return scheme == null;
}
/**
* Returns <code>true</code> if this a a hierarchical URI, or
* <code>false</code> if it is of the generic form.
*/
public boolean isHierarchical()
{
return (hashCode & HIERARICHICAL_FLAG) != 0;
}
/**
* Returns <code>true</code> if this is a hierarchical URI with an authority
* component; <code>false</code> otherwise.
*/
public boolean hasAuthority()
{
return isHierarchical() && authority != null;
}
/**
* Returns <code>true</code> if this is a non-hierarchical URI with an
* opaque part component; <code>false</code> otherwise.
*/
public boolean hasOpaquePart()
{
// note: hierarchical -> authority != null
return !isHierarchical();
}
/**
* Returns <code>true</code> if this is a hierarchical URI with a device
* component; <code>false</code> otherwise.
*/
public boolean hasDevice()
{
// note: device != null -> hierarchical
return device != null;
}
/**
* Returns <code>true</code> if this is a hierarchical URI with an
* absolute or relative path; <code>false</code> otherwise.
*/
public boolean hasPath()
{
// note: (absolutePath || authority == null) -> hierarchical
// (authority == null && device == null && !absolutePath) -> scheme == null
return hasAbsolutePath() || (authority == null && device == null);
}
/**
* Returns <code>true</code> if this is a hierarchical URI with an
* absolute path, or <code>false</code> if it is non-hierarchical, has no
* path, or has a relative path.
*/
public boolean hasAbsolutePath()
{
// note: absolutePath -> hierarchical
return (hashCode & ABSOLUTE_PATH_FLAG) != 0;
}
/**
* Returns <code>true</code> if this is a hierarchical URI with a relative
* path, or <code>false</code> 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 authority == null && device == null && !hasAbsolutePath();
}
/**
* Returns <code>true</code> if this is a hierarchical URI with an empty
* relative path; <code>false</code> otherwise.
*
* <p>Note that <code>!hasEmpty()</code> does <em>not</em> imply that this
* URI has any path segments; however, <code>hasRelativePath &&
* !hasEmptyPath()</code> does.
*/
public boolean hasEmptyPath()
{
// note: authority == null -> hierarchical
// (authority == null && device == null && !absolutePath) -> scheme == null
return authority == null && device == null && !hasAbsolutePath() &&
segments.length == 0;
}
/**
* Returns <code>true</code> if this is a hierarchical URI with a query
* component; <code>false</code> otherwise.
*/
public boolean hasQuery()
{
// note: query != null -> hierarchical
return query != null;
}
/**
* Returns <code>true</code> if this URI has a fragment component;
* <code>false</code> otherwise.
*/
public boolean hasFragment()
{
return fragment != null;
}
/**
* Returns <code>true</code> 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; <code>false</code> is returned
* otherwise.
*/
public boolean isCurrentDocumentReference()
{
// note: authority == null -> hierarchical
// (authority == null && device == null && !absolutePath) -> scheme == null
return authority == null && device == null && !hasAbsolutePath() &&
segments.length == 0 && query == null;
}
/**
* Returns <code>true</code> if this is a {@link
* #isCurrentDocumentReference() current document reference} with no
* fragment component; <code>false</code> otherwise.
*
* @see #isCurrentDocumentReference()
*/
public boolean isEmpty()
{
// note: authority == null -> hierarchical
// (authority == null && device == null && !absolutePath) -> scheme == null
return authority == null && device == null && !hasAbsolutePath() &&
segments.length == 0 && query == null && fragment == null;
}
/**
* Returns <code>true</code> 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;
* <code>false</code> is returned otherwise.
*/
public boolean isFile()
{
return isHierarchical() &&
((isRelative() && !hasQuery()) || SCHEME_FILE.equalsIgnoreCase(scheme));
}
/**
* Returns <code>true</code> if this is a platform URI, that is, an absolute,
* hierarchical URI, with "platform" scheme, no authority, and at least two
* segments; <code>false</code> is returned otherwise.
* @since org.eclipse.emf.common 2.3
*/
public boolean isPlatform()
{
return isHierarchical() && !hasAuthority() && segmentCount() >= 2 &&
SCHEME_PLATFORM.equalsIgnoreCase(scheme);
}
/**
* Returns <code>true</code> if this is a platform resource URI, that is,
* a {@link #isPlatform platform URI} whose first segment is "resource";
* <code>false</code> is returned otherwise.
* @see #isPlatform
* @since org.eclipse.emf.common 2.3
*/
public boolean isPlatformResource()
{
return isPlatform() && "resource".equals(segments[0]);
}
/**
* Returns <code>true</code> if this is a platform plug-in URI, that is,
* a {@link #isPlatform platform URI} whose first segment is "plugin";
* <code>false</code> is returned otherwise.
* @see #isPlatform
* @since org.eclipse.emf.common 2.3
*/
public boolean isPlatformPlugin()
{
return isPlatform() && "plugin".equals(segments[0]);
}
/**
* Returns <code>true</code> 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 isArchiveScheme(scheme);
}
/**
* Returns <code>true</code> if the specified <code>value</code> would be
* valid as the scheme of an <a
* href="#archive_explanation">archive URI</a>; <code>false</code>
* 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.
return value != null && archiveSchemes.contains(value.toLowerCase());
}
/**
* Returns the hash code.
*/
@Override
public int hashCode()
{
return hashCode;
}
/**
* Returns <code>true</code> if <code>object</code> is an instance of
* <code>URI</code> equal to this one; <code>false</code> otherwise.
*
* <p>Equality is determined strictly by comparing components, not by
* attempting to interpret what resource is being identified. The
* comparison of schemes is case-insensitive.
*/
@Override
public boolean equals(Object object)
{
if (this == object) return true;
if (!(object instanceof URI2)) return false;
URI2 uri = (URI2) object;
return hashCode == uri.hashCode() &&
equals(scheme, uri.scheme(), true) &&
equals(authority, isHierarchical() ? uri.authority() : uri.opaquePart()) &&
equals(device, uri.device()) &&
equals(query, uri.query()) &&
equals(fragment, uri.fragment()) &&
segmentsEqual(uri);
}
// Tests whether this URI's path segment array is equal to that of the
// given uri.
private boolean segmentsEqual(URI2 uri)
{
if (segments.length != uri.segmentCount()) return false;
for (int i = 0, len = segments.length; i < len; i++)
{
if (!segments[i].equals(uri.segment(i))) return false;
}
return true;
}
// Tests two objects for equality, tolerating nulls; null is considered
// to be a valid value that is only equal to itself.
private static boolean equals(Object o1, Object o2)
{
return o1 == null ? o2 == null : o1.equals(o2);
}
// Tests two strings for equality, tolerating nulls and optionally
// ignoring case.
private static boolean equals(String s1, String s2, boolean ignoreCase)
{
return s1 == null ? s2 == null :
ignoreCase ? s1.equalsIgnoreCase(s2) : s1.equals(s2);
}
/**
* If this is an absolute URI, returns the scheme component;
* <code>null</code> otherwise.
*/
public String scheme()
{
return scheme;
}
/**
* If this is a non-hierarchical URI, returns the opaque part component;
* <code>null</code> otherwise.
*/
public String opaquePart()
{
return isHierarchical() ? null : authority;
}
/**
* If this is a hierarchical URI with an authority component, returns it;
* <code>null</code> otherwise.
*/
public String authority()
{
return isHierarchical() ? authority : null;
}
/**
* If this is a hierarchical URI with an authority component that has a
* user info portion, returns it; <code>null</code> otherwise.
*/
public String userInfo()
{
if (!hasAuthority()) return null;
int i = authority.indexOf(USER_INFO_SEPARATOR);
return i < 0 ? null : authority.substring(0, i);
}
/**
* If this is a hierarchical URI with an authority component that has a
* host portion, returns it; <code>null</code> otherwise.
*/
public String host()
{
if (!hasAuthority()) return null;
int i = authority.indexOf(USER_INFO_SEPARATOR);
int j = authority.indexOf(PORT_SEPARATOR);
return j < 0 ? authority.substring(i + 1) : authority.substring(i + 1, j);
}
/**
* If this is a hierarchical URI with an authority component that has a
* port portion, returns it; <code>null</code> otherwise.
*/
public String port()
{
if (!hasAuthority()) return null;
int i = authority.indexOf(PORT_SEPARATOR);
return i < 0 ? null : authority.substring(i + 1);
}
/**
* If this is a hierarchical URI with a device component, returns it;
* <code>null</code> otherwise.
*/
public String device()
{
return device;
}
/**
* 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 segments.clone();
}
/**
* Returns an unmodifiable list containing the same segments as the array
* returned by {@link #segments segments}.
*/
public List<String> segmentsList()
{
return Collections.unmodifiableList(Arrays.asList(segments));
}
/**
* Returns the number of elements in the segment array that would be
* returned by {@link #segments segments}.
*/
public int segmentCount()
{
return segments.length;
}
/**
* Provides fast, indexed access to individual segments in the path
* segment array.
*
* @exception java.lang.IndexOutOfBoundsException if <code>i < 0</code> or
* <code>i >= segmentCount()</code>.
*/
public String segment(int i)
{
return segments[i];
}
/**
* Returns the last segment in the segment array, or <code>null</code>.
*/
public String lastSegment()
{
int len = segments.length;
if (len == 0) return null;
return segments[len - 1];
}
/**
* If this is a hierarchical URI with a path, returns a string
* representation of the path; <code>null</code> 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 <a href="#device_explanation">device
* component</a>, it is <em>not</em> included in the path.
*/
public String path()
{
if (!hasPath()) return null;
StringBuffer result = new StringBuffer();
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]);
}
return result.toString();
}
/**
* If this is a hierarchical URI with a path, returns a string
* representation of the path, including the authority and the
* <a href="#device_explanation">device component</a>;
* <code>null</code> otherwise.
*
* <p>If there is no authority, the format of this string is:
* <pre>
* device/pathSegment1/pathSegment2...</pre>
*
* <p>If there is an authority, it is:
* <pre>
* //authority/device/pathSegment1/pathSegment2...</pre>
*
* <p>For an <a href="#archive_explanation">archive URI</a>, it's just:
* <pre>
* authority/pathSegment1/pathSegment2...</pre>
*/
public String devicePath()
{
if (!hasPath()) return null;
StringBuffer result = new StringBuffer();
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);
for (int i = 0, len = segments.length; i < len; i++)
{
if (i != 0) result.append(SEGMENT_SEPARATOR);
result.append(segments[i]);
}
return result.toString();
}
/**
* If this is a hierarchical URI with a query component, returns it;
* <code>null</code> otherwise.
*/
public String query()
{
return query;
}
/**
* Returns the URI formed from this URI and the given query.
*
* @exception java.lang.IllegalArgumentException if
* <code>query</code> is not a valid query (portion) according
* to {@link #validQuery validQuery}.
*/
public URI2 appendQuery(String query)
{
if (!validQuery(query))
{
throw new IllegalArgumentException(
"invalid query portion: " + query);
}
return new URI2(isHierarchical(), scheme, authority, device, hasAbsolutePath(), segments, query, fragment);
}
/**
* If this URI has a non-null {@link #query query}, returns the URI
* formed by removing it; this URI unchanged, otherwise.
*/
public URI2 trimQuery()
{
if (query == null)
{
return this;
}
else
{
return new URI2(isHierarchical(), scheme, authority, device, hasAbsolutePath(), segments, null, fragment);
}
}
/**
* If this URI has a fragment component, returns it; <code>null</code>
* otherwise.
*/
public String fragment()
{
return fragment;
}
/**
* Returns the URI formed from this URI and the given fragment.
*
* @exception java.lang.IllegalArgumentException if
* <code>fragment</code> is not a valid fragment (portion) according
* to {@link #validFragment validFragment}.
*/
public URI2 appendFragment(String fragment)
{
if (!validFragment(fragment))
{
throw new IllegalArgumentException(
"invalid fragment portion: " + fragment);
}
URI2 result = new URI2(isHierarchical(), scheme, authority, device, hasAbsolutePath(), segments, query, fragment);
if (!hasFragment())
{
result.cachedTrimFragment = this;
}
return result;
}
/**
* If this URI has a non-null {@link #fragment fragment}, returns the URI
* formed by removing it; this URI unchanged, otherwise.
*/
public URI2 trimFragment()
{
if (fragment == null)
{
return this;
}
else if (cachedTrimFragment == null)
{
cachedTrimFragment = new URI2(isHierarchical(), scheme, authority, device, hasAbsolutePath(), segments, query, null);
}
return cachedTrimFragment;
}
/**
* Resolves this URI reference against a <code>base</code> 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 <a href="http://www.ietf.org/rfc/rfc2396.txt">RFC
* 2396</a>, "Resolving Relative References to Absolute Form."
*
* <p>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(URI2, boolean) resolve}.
*
* @exception java.lang.IllegalArgumentException if <code>base</code> is
* non-hierarchical or is relative.
*/
public URI2 resolve(URI2 base)
{
return resolve(base, true);
}
/**
* Resolves this URI reference against a <code>base</code> 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 <a href="http://www.ietf.org/rfc/rfc2396.txt">RFC
* 2396</a>, "Resolving Relative References to Absolute Form."
*
* <p>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 <code>true</code> if segments referring to the
* parent of the root path are to be preserved; <code>false</code> if they
* are to be discarded.
*
* @exception java.lang.IllegalArgumentException if <code>base</code> is
* non-hierarchical or is relative.
*/
public URI2 resolve(URI2 base, boolean preserveRootParents)
{
if (!base.isHierarchical() || base.isRelative())
{
throw new IllegalArgumentException(
"resolve against non-hierarchical or relative base");
}
// an absolute URI needs no resolving
if (!isRelative()) return this;
// note: isRelative() -> hierarchical
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.segments();
newQuery = base.query();
}
else if (hasRelativePath())
{
// relative path: merge with base and keep query (note: 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
// always keep fragment, even if null, and use scheme from base;
// no validation needed since all components are from existing URIs
return new URI2(true, base.scheme(), newAuthority, newDevice,
newAbsolutePath, newSegments, newQuery, fragment);
}
// 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.
private String[] mergePath(URI2 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 && (segmentCount == 0 ||
SEGMENT_EMPTY.equals(segments[segmentCount - 1]) ||
SEGMENT_PARENT.equals(segments[segmentCount - 1]) ||
SEGMENT_SELF.equals(segments[segmentCount - 1])))
{
stack[sp++] = SEGMENT_EMPTY;
}
// return a correctly sized result
String[] result = new String[sp];
System.arraycopy(stack, 0, result, 0, sp);
return result;
}
// Adds a segment to a stack, skipping empty segments and self references,
// and interpreting parent references.
private static int accumulate(String[] stack, int sp, String segment,
boolean preserveRootParents)
{
if (SEGMENT_PARENT.equals(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.equals(stack[sp - 1])) stack[sp++] = segment;
else sp--;
}
}
else if (!SEGMENT_EMPTY.equals(segment) && !SEGMENT_SELF.equals(segment))
{
// skip empty segments and self references; push everything else
stack[sp++] = segment;
}
return sp;
}
/**
* Finds the shortest relative or, if necessary, the absolute URI that,
* when resolved against the given <code>base</code> absolute hierarchical
* URI using {@link #resolve(URI2) resolve}, will yield this absolute URI.
* If <code>base</code> is non-hierarchical or is relative,
* or <code>this</code> is non-hierarchical or is relative,
* <code>this</code> will be returned.
*/
public URI2 deresolve(URI2 base)
{
return deresolve(base, true, false, true);
}
/**
* Finds an absolute URI that, when resolved against the given
* <code>base</code> absolute hierarchical URI using {@link
* #resolve(URI2, boolean) resolve}, will yield this absolute URI.
* If <code>base</code> is non-hierarchical or is relative,
* or <code>this</code> is non-hierarchical or is relative,
* <code>this</code> will be returned.
*
* @param preserveRootParents the boolean argument to <code>resolve(URI,
* boolean)</code> for which the returned URI should resolve to this URI.
* @param anyRelPath if <code>true</code>, the returned URI's path (if
* any) will be relative, if possible. If <code>false</code>, the form of
* the result's path will depend upon the next parameter.
* @param shorterRelPath if <code>anyRelPath</code> is <code>false</code>
* and this parameter is <code>true</code>, 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 <code>anyRelPath</code>
* and this parameter are <code>false</code>, it will be absolute.
*/
public URI2 deresolve(URI2 base, boolean preserveRootParents,
boolean anyRelPath, boolean shorterRelPath)
{
if (!base.isHierarchical() || base.isRelative()) return this;
if (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.equalsIgnoreCase(base.scheme())) return this;
// since base must be hierarchical, and since a non-hierarchical URI
// must have both scheme and opaque part, the complete absolute URI is
// needed to resolve to a non-hierarchical URI
if (!isHierarchical()) return this;
String newAuthority = authority;
String newDevice = device;
boolean newAbsolutePath = hasAbsolutePath();
String[] newSegments = segments;
String newQuery = query;
if (equals(authority, base.authority()) &&
(hasDevice() || hasPath() || (!base.hasDevice() && !base.hasPath())))
{
// matching authorities and no device or path removal
newAuthority = null;
if (equals(device, base.device()) && (hasPath() || !base.hasPath()))
{
// 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() == base.hasPath() && segmentsEqual(base) &&
equals(query, base.query()))
{
// current document reference: keep no path or query
newAbsolutePath = false;
newSegments = NO_SEGMENTS;
newQuery = null;
}
else if (!hasPath() && !base.hasPath())
{
// 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 new URI2(true, null, newAuthority, newDevice, newAbsolutePath,
newSegments, newQuery, fragment);
}
// 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.
private boolean hasCollapsableSegments(boolean preserveRootParents)
{
if (hasRelativePath())
{
throw new IllegalStateException("test collapsability of relative path");
}
for (int i = 0, len = segments.length; i < len; i++)
{
String segment = segments[i];
if ((i < len - 1 && SEGMENT_EMPTY.equals(segment)) ||
SEGMENT_SELF.equals(segment) ||
SEGMENT_PARENT.equals(segment) && (
!preserveRootParents || (
i != 0 && !SEGMENT_PARENT.equals(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.
private String[] findRelativePath(URI2 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].equals(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.equals(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
if (upCount + downCount == 0)
{
if (query == null) return new String[] { SEGMENT_SELF };
return NO_SEGMENTS;
}
// return a correctly sized result
String[] result = new String[upCount + downCount];
Arrays.fill(result, 0, upCount, SEGMENT_PARENT);
System.arraycopy(endPath, diff, result, upCount, downCount);
return result;
}
// Collapses non-ending empty segments, parent references, and self
// references in a non-relative path, returning the same path that would
// be produced from the base hierarchical URI as part of a resolve.
String[] collapseSegments(boolean preserveRootParents)
{
if (hasRelativePath())
{
throw new IllegalStateException("collapse relative path");
}
if (!hasCollapsableSegments(preserveRootParents)) return segments();
// use a stack to accumulate 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 && (SEGMENT_EMPTY.equals(segments[segmentCount - 1]) ||
SEGMENT_PARENT.equals(segments[segmentCount - 1]) ||
SEGMENT_SELF.equals(segments[segmentCount - 1])))
{
stack[sp++] = SEGMENT_EMPTY;
}
// return a correctly sized result
String[] result = new String[sp];
System.arraycopy(stack, 0, result, 0, sp);
return result;
}
/**
* Returns the string representation of this URI. For a generic,
* non-hierarchical URI, this looks like:
* <pre>
* scheme:opaquePart#fragment</pre>
*
* <p>For a hierarchical URI, it looks like:
* <pre>
* scheme://authority/device/pathSegment1/pathSegment2...?query#fragment</pre>
*
* <p>For an <a href="#archive_explanation">archive URI</a>, it's just:
* <pre>
* scheme:authority/pathSegment1/pathSegment2...?query#fragment</pre>
* <p>Of course, absent components and their separators will be omitted.
*/
@Override
public String toString()
{
if (cachedToString == null)
{
StringBuffer result = new StringBuffer();
if (!isRelative())
{
result.append(scheme);
result.append(SCHEME_SEPARATOR);
}
if (isHierarchical())
{
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);
}
}
else
{
result.append(authority);
}
if (hasFragment())
{
result.append(FRAGMENT_SEPARATOR);
result.append(fragment);
}
cachedToString = result.toString();
}
return cachedToString;
}
// Returns a string representation of this URI for debugging, explicitly
// showing each of the components.
String toString(boolean includeSimpleForm)
{
StringBuffer result = new StringBuffer();
if (includeSimpleForm) result.append(toString());
result.append("\n hierarchical: ");
result.append(isHierarchical());
result.append("\n scheme: ");
result.append(scheme);
result.append("\n authority: ");
result.append(authority);
result.append("\n device: ");
result.append(device);
result.append("\n absolutePath: ");
result.append(hasAbsolutePath());
result.append("\n segments: ");
if (segments.length == 0) result.append("<empty>");
for (int i = 0, len = segments.length; i < len; i++)
{
if (i > 0) result.append("\n ");
result.append(segments[i]);
}
result.append("\n query: ");
result.append(query);
result.append("\n fragment: ");
result.append(fragment);
return result.toString();
}
/**
* 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.
*
* <p>If there is no authority, the format of this string is:
* <pre>
* device/pathSegment1/pathSegment2...</pre>
*
* <p>If there is an authority, it is:
* <pre>
* //authority/device/pathSegment1/pathSegment2...</pre>
*
* <p>However, the character used as a separator is system-dependent and
* obtained from {@link java.io.File#separatorChar}.
*/
public String toFileString()
{
if (!isFile()) return null;
StringBuffer result = new StringBuffer();
char separator = File.separatorChar;
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);
for (int i = 0, len = segments.length; i < len; i++)
{
if (i != 0) result.append(separator);
result.append(segments[i]);
}
return decode(result.toString());
}
/**
* 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)
{
if (isPlatform())
{
StringBuffer result = new StringBuffer();
for (int i = 1, len = segments.length; i < len; i++)
{
result.append('/').append(decode ? URI2.decode(segments[i]) : segments[i]);
}
return result.toString();
}
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 <code>segment</code>
* is not a valid segment according to {@link #validSegment}.
*/
public URI2 appendSegment(String segment)
{
if (!validSegment(segment))
{
throw new IllegalArgumentException("invalid segment: " + segment);
}
if (!isHierarchical()) return this;
// absolute path or no path -> absolute path
boolean newAbsolutePath = !hasRelativePath();
int len = segments.length;
String[] newSegments = new String[len + 1];
System.arraycopy(segments, 0, newSegments, 0, len);
newSegments[len] = segment;
return new URI2(true, scheme, authority, device, newAbsolutePath,
newSegments, query, fragment);
}
/**
* 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 <code>segments</code>
* is not a valid segment array according to {@link #validSegments}.
*/
public URI2 appendSegments(String[] segments)
{
if (!validSegments(segments))
{
String s = segments == null ? "invalid segments: null" :
"invalid segment: " + firstInvalidSegment(segments);
throw new IllegalArgumentException(s);
}
if (!isHierarchical()) return this;
// absolute path or no path -> absolute path
boolean newAbsolutePath = !hasRelativePath();
int len = this.segments.length;
int segmentsCount = segments.length;
String[] newSegments = new String[len + segmentsCount];
System.arraycopy(this.segments, 0, newSegments, 0, len);
System.arraycopy(segments, 0, newSegments, len, segmentsCount);
return new URI2(true, scheme, authority, device, newAbsolutePath,
newSegments, query, fragment);
}
/**
* 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.
*
* <p>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 URI2 trimSegments(int i)
{
if (!isHierarchical() || i < 1) return this;
String[] newSegments = NO_SEGMENTS;
int len = segments.length - i;
if (len > 0)
{
newSegments = new String[len];
System.arraycopy(segments, 0, newSegments, 0, len);
}
return new URI2(true, scheme, authority, device, hasAbsolutePath(),
newSegments, query, fragment);
}
/**
* Returns <code>true</code> if this is a hierarchical URI that has a path
* that ends with a trailing separator; <code>false</code> otherwise.
*
* <p>A trailing separator is represented as an empty segment as the
* last segment in the path; note that this definition does <em>not</em>
* include the lone separator in the root absolute path.
*/
public boolean hasTrailingPathSeparator()
{
return segments.length > 0 &&
SEGMENT_EMPTY.equals(segments[segments.length - 1]);
}
/**
* 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()
{
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);
}
/**
* 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.
* <p>The extension is appended regardless of whether the segment already
* contains an extension.
*
* @exception java.lang.IllegalArgumentException if
* <code>fileExtension</code> is not a valid segment (portion) according
* to {@link #validSegment}.
*/
public URI2 appendFileExtension(String fileExtension)
{
if (!validSegment(fileExtension))
{
throw new IllegalArgumentException(
"invalid segment portion: " + fileExtension);
}
int len = segments.length;
if (len == 0) return this;
String lastSegment = segments[len - 1];
if (SEGMENT_EMPTY.equals(lastSegment)) return this;
StringBuffer newLastSegment = new StringBuffer(lastSegment);
newLastSegment.append(FILE_EXTENSION_SEPARATOR);
newLastSegment.append(fileExtension);
String[] newSegments = new String[len];
System.arraycopy(segments, 0, newSegments, 0, len - 1);
newSegments[len - 1] = newLastSegment.toString();
// note: segments.length > 0 -> hierarchical
return new URI2(true, scheme, authority, device, hasAbsolutePath(),
newSegments, query, fragment);
}
/**
* If this URI has a non-null {@link #fileExtension fileExtension},
* returns the URI formed by removing it; this URI unchanged, otherwise.
*/
public URI2 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 = new String[len];
System.arraycopy(segments, 0, newSegments, 0, len - 1);
newSegments[len - 1] = newLastSegment;
// note: segments.length > 0 -> hierarchical
return new URI2(true, scheme, authority, device, hasAbsolutePath(),
newSegments, query, fragment);
}
/**
* Returns <code>true</code> 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; <code>false</code>
* is returned otherwise.
*/
public boolean isPrefix()
{
return isHierarchical() && query == null && fragment == null &&
(hasTrailingPathSeparator() || (hasAbsolutePath() && segments.length == 0));
}
/**
* If this is a hierarchical URI reference and <code>oldPrefix</code> is a
* prefix of it, this returns the URI formed by replacing it by
* <code>newPrefix</code>; <code>null</code> otherwise.
*
* <p>In order to be a prefix, the <code>oldPrefix</code>'s
* {@link #isPrefix isPrefix} must return <code>true</code>, 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
* <code>oldPrefix</code> or <code>newPrefix</code> is not a prefix URI
* according to {@link #isPrefix}.
*/
public URI2 replacePrefix(URI2 oldPrefix, URI2 newPrefix)
{
if (!oldPrefix.isPrefix() || !newPrefix.isPrefix())
{
String which = oldPrefix.isPrefix() ? "new" : "old";
throw new IllegalArgumentException("non-prefix " + which + " value");
}
// Get what's left of the segments after trimming the prefix.
String[] tailSegments = getTailSegments(oldPrefix);
if (tailSegments == null) return null;
// 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[] mergedSegments = tailSegments;
if (newPrefix.segmentCount() != 0)
{
int segmentsToKeep = newPrefix.segmentCount() - 1;
mergedSegments = new String[segmentsToKeep + tailSegments.length];
System.arraycopy(newPrefix.segments(), 0, mergedSegments, 0,
segmentsToKeep);
if (tailSegments.length != 0)
{
System.arraycopy(tailSegments, 0, mergedSegments, segmentsToKeep,
tailSegments.length);
}
}
// no validation needed since all components are from existing URIs
return new URI2(true, newPrefix.scheme(), newPrefix.authority(),
newPrefix.device(), newPrefix.hasAbsolutePath(),
mergedSegments, query, fragment);
}
// If this is a hierarchical URI reference and prefix is a prefix of it,
// returns the portion of the path remaining after that prefix has been
// trimmed; null otherwise.
private String[] getTailSegments(URI2 prefix)
{
if (!prefix.isPrefix())
{
throw new IllegalArgumentException("non-prefix trim");
}
// Don't even consider it unless this is hierarchical and has scheme,
// authority, device and path absoluteness equal to those of the prefix.
if (!isHierarchical() ||
!equals(scheme, prefix.scheme(), true) ||
!equals(authority, prefix.authority()) ||
!equals(device, prefix.device()) ||
hasAbsolutePath() != prefix.hasAbsolutePath())
{
return null;
}
// If the prefix has no segments, then it is the root absolute path, and
// we know this is an absolute path, too.
if (prefix.segmentCount() == 0) return segments;
// 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 = prefix.segmentCount() - 1;
if (segments.length <= segmentsToCompare) return null;
for (; i < segmentsToCompare; i++)
{
if (!segments[i].equals(prefix.segment(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 == segments.length - 1 && SEGMENT_EMPTY.equals(segments[i]))
{
return NO_SEGMENTS;
}
// Otherwise, the path needs only the remaining segments.
String[] newSegments = new String[segments.length - i];
System.arraycopy(segments, i, newSegments, 0, newSegments.length);
return newSegments;
}
/**
* Encodes a string so as to produce a valid opaque part value, as defined
* by the RFC. All excluded characters, such as space and <code>#</code>,
* are escaped, as is <code>/</code> if it is the first character.
*
* @param ignoreEscaped <code>true</code> to leave <code>%</code> characters
* unescaped if they already begin a valid three-character escape sequence;
* <code>false</code> to encode all <code>%</code> characters. Note that
* if a <code>%</code> 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 <code>#</code>,
* are escaped, as are <code>/</code> and <code>?</code>
*
* @param ignoreEscaped <code>true</code> to leave <code>%</code> characters
* unescaped if they already begin a valid three-character escape sequence;
* <code>false</code> to encode all <code>%</code> characters. Note that
* if a <code>%</code> 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 <code>#</code>,
* are escaped, as are <code>/</code> and <code>?</code>
*
* @param ignoreEscaped <code>true</code> to leave <code>%</code> characters
* unescaped if they already begin a valid three-character escape sequence;
* <code>false</code> to encode all <code>%</code> characters. Note that
* if a <code>%</code> 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 <code>#</code>, are escaped.
*
* @param ignoreEscaped <code>true</code> to leave <code>%</code> characters
* unescaped if they already begin a valid three-character escape sequence;
* <code>false</code> to encode all <code>%</code> characters. Note that
* if a <code>%</code> 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 <code>#</code>, are
* escaped.
*
* @param ignoreEscaped <code>true</code> to leave <code>%</code> characters
* unescaped if they already begin a valid three-character escape sequence;
* <code>false</code> to encode all <code>%</code> characters. Note that
* if a <code>%</code> 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.
private static String encodeURI(String uri, boolean ignoreEscaped, int fragmentLocationStyle)
{
if (uri == null) return null;
StringBuffer result = new StringBuffer();
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.
private 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.
private 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.
private 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.
private static char unescape(char highHexDigit, char lowHexDigit)
{
return (char)((valueOf(highHexDigit) << 4) | valueOf(lowHexDigit));
}
// Returns the int value of the given hex digit.
private static int valueOf(char hexDigit)
{
if (hexDigit >= 'A' && hexDigit <= 'F')
{
return hexDigit - 'A' + 10;
}
if (hexDigit >= 'a' && hexDigit <= 'f')
{
return hexDigit - 'a' + 10;
}
if (hexDigit >= '0' && hexDigit <= '9')
{
return hexDigit - '0';
}
return 0;
}
/*
* Returns <code>true</code> if this URI contains non-ASCII characters;
* <code>false</code> otherwise.
*
* This unused code is included for possible future use...
*/
/*
public boolean isIRI()
{
return iri;
}
// Returns true if the given string contains any non-ASCII characters;
// false otherwise.
private static boolean containsNonASCII(String value)
{
for (int i = 0, length = value.length(); i < length; i++)
{
if (value.charAt(i) > 127) return true;
}
return false;
}
*/
/*
* If this is an {@link #isIRI IRI}, converts it to a strict ASCII URI,
* using the procedure described in Section 3.1 of the
* <a href="http://www.w3.org/International/iri-edit/draft-duerst-iri-09.txt">IRI
* Draft RFC</a>. Otherwise, this URI, itself, is returned.
*
* This unused code is included for possible future use...
*/
/*
public URI toASCIIURI()
{
if (!iri) return this;
if (cachedASCIIURI == null)
{
String eAuthority = encodeAsASCII(authority);
String eDevice = encodeAsASCII(device);
String eQuery = encodeAsASCII(query);
String eFragment = encodeAsASCII(fragment);
String[] eSegments = new String[segments.length];
for (int i = 0; i < segments.length; i++)
{
eSegments[i] = encodeAsASCII(segments[i]);
}
cachedASCIIURI = new URI(hierarchical, scheme, eAuthority, eDevice, absolutePath, eSegments, eQuery, eFragment);
}
return cachedASCIIURI;
}
// Returns a strict ASCII encoding of the given value. Each non-ASCII
// character is converted to bytes using UTF-8 encoding, which are then
// represented using % escaping.
private String encodeAsASCII(String value)
{
if (value == null) return null;
StringBuffer result = null;
for (int i = 0, length = value.length(); i < length; i++)
{
char c = value.charAt(i);
if (c >= 128)
{
if (result == null)
{
result = new StringBuffer(value.substring(0, i));
}
try
{
byte[] encoded = (new String(new char[] { c })).getBytes("UTF-8");
for (int j = 0, encLen = encoded.length; j < encLen; j++)
{
appendEscaped(result, encoded[j]);
}
}
catch (UnsupportedEncodingException e)
{
throw new WrappedException(e);
}
}
else if (result != null)
{
result.append(c);
}
}
return result == null ? value : result.toString();
}
// Returns the number of valid, consecutive, three-character escape
// sequences in the given string, starting at index i.
private static int countEscaped(String s, int i)
{
int result = 0;
for (int length = s.length(); i < length; i += 3)
{
if (isEscaped(s, i)) result++;
}
return result;
}
*/
}
}