You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@juneau.apache.org by ja...@apache.org on 2017/09/02 14:10:44 UTC

[22/51] [partial] incubator-juneau git commit: Add project hierarchies, part 1

http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/75b0d8ee/juneau-core/juneau-core-test/src/test/java/org/apache/juneau/utils/StringUtilsTest.java
----------------------------------------------------------------------
diff --git a/juneau-core/juneau-core-test/src/test/java/org/apache/juneau/utils/StringUtilsTest.java b/juneau-core/juneau-core-test/src/test/java/org/apache/juneau/utils/StringUtilsTest.java
new file mode 100755
index 0000000..7143b97
--- /dev/null
+++ b/juneau-core/juneau-core-test/src/test/java/org/apache/juneau/utils/StringUtilsTest.java
@@ -0,0 +1,748 @@
+// ***************************************************************************************************************************
+// * Licensed to the Apache Software Foundation (ASF) under one or more contributor license agreements.  See the NOTICE file *
+// * distributed with this work for additional information regarding copyright ownership.  The ASF licenses this file        *
+// * to you under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance            *
+// * with the License.  You may obtain a copy of the License at                                                              *
+// *                                                                                                                         *
+// *  http://www.apache.org/licenses/LICENSE-2.0                                                                             *
+// *                                                                                                                         *
+// * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an  *
+// * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  See the License for the        *
+// * specific language governing permissions and limitations under the License.                                              *
+// ***************************************************************************************************************************
+package org.apache.juneau.utils;
+
+import static org.apache.juneau.TestUtils.*;
+import static org.apache.juneau.internal.StringUtils.*;
+import static org.junit.Assert.*;
+
+import java.math.*;
+import java.util.*;
+import java.util.concurrent.atomic.*;
+
+import org.apache.juneau.*;
+import org.apache.juneau.json.*;
+import org.apache.juneau.parser.*;
+import org.apache.juneau.serializer.*;
+import org.apache.juneau.transforms.*;
+import org.junit.*;
+
+@SuppressWarnings("javadoc")
+public class StringUtilsTest {
+
+	//====================================================================================================
+	// isNumeric(String,Class)
+	// parseNumber(String,Class)
+	//====================================================================================================
+	@Test
+	public void testParseNumber() throws Exception {
+
+		// Integers
+		assertTrue(isNumeric("123"));
+		assertEquals(123, parseNumber("123", null));
+		assertEquals(123, parseNumber("123", Integer.class));
+		assertEquals((short)123, parseNumber("123", Short.class));
+		assertEquals((long)123, parseNumber("123", Long.class));
+
+		assertTrue(isNumeric("0123"));
+		assertEquals(0123, parseNumber("0123", null));
+
+		assertTrue(isNumeric("-0123"));
+		assertEquals(-0123, parseNumber("-0123", null));
+
+		// Hexadecimal
+		assertTrue(isNumeric("0x123"));
+		assertEquals(0x123, parseNumber("0x123", null));
+
+		assertTrue(isNumeric("-0x123"));
+		assertEquals(-0x123, parseNumber("-0x123", null));
+
+		assertTrue(isNumeric("0X123"));
+		assertEquals(0X123, parseNumber("0X123", null));
+
+		assertTrue(isNumeric("-0X123"));
+		assertEquals(-0X123, parseNumber("-0X123", null));
+
+		assertTrue(isNumeric("#123"));
+		assertEquals(0x123, parseNumber("#123", null));
+
+		assertTrue(isNumeric("-#123"));
+		assertEquals(-0x123, parseNumber("-#123", null));
+
+		assertFalse(isNumeric("x123"));
+		assertFalse(isNumeric("0x123x"));
+
+		// Decimal
+		assertTrue(isNumeric("0.123"));
+		assertEquals(0.123f, parseNumber("0.123", null));
+
+		assertTrue(isNumeric("-0.123"));
+		assertEquals(-0.123f, parseNumber("-0.123", null));
+
+		assertTrue(isNumeric(".123"));
+		assertEquals(.123f, parseNumber(".123", null));
+
+		assertTrue(isNumeric("-.123"));
+		assertEquals(-.123f, parseNumber("-.123", null));
+
+		assertFalse(isNumeric("0.123.4"));
+
+		assertTrue(isNumeric("0.84370821629078d"));
+		assertEquals(0.84370821629078d, parseNumber("0.84370821629078d", null));
+
+		assertTrue(isNumeric("84370821629078.8437d"));
+		assertEquals(84370821629078.8437d, parseNumber("84370821629078.8437d", null));
+
+		assertTrue(isNumeric("0.16666666666666666d"));
+		assertEquals(0.16666666666666666d, parseNumber("0.16666666666666666d", null));
+
+		assertTrue(isNumeric("0.16666666f"));
+		assertEquals(0.16666666f, parseNumber("0.16666666f", null));
+
+		assertTrue(isNumeric("0.16666666d"));
+		assertEquals(0.16666666f, parseNumber("0.16666666d", null));
+
+		assertTrue(isNumeric("3.140000000000000124344978758017532527446746826171875d"));
+		assertEquals(3.14f, parseNumber("3.140000000000000124344978758017532527446746826171875d", null));
+
+		assertTrue(isNumeric("12345.678f"));
+		assertEquals(1.2345678e4f, parseNumber("12345.678f", null));
+
+		// Scientific notation
+		assertTrue(isNumeric("1e1"));
+		assertEquals(1e1f, parseNumber("1e1", null));
+
+		assertTrue(isNumeric("1e+1"));
+		assertEquals(1e+1f, parseNumber("1e+1", null));
+
+		assertTrue(isNumeric("1e-1"));
+		assertEquals(1e-1f, parseNumber("1e-1", null));
+
+		assertTrue(isNumeric("1.1e1"));
+		assertEquals(1.1e1f, parseNumber("1.1e1", null));
+
+		assertTrue(isNumeric("1.1e+1"));
+		assertEquals(1.1e+1f, parseNumber("1.1e+1", null));
+
+		assertTrue(isNumeric("1.1e-1"));
+		assertEquals(1.1e-1f, parseNumber("1.1e-1", null));
+
+		assertTrue(isNumeric(".1e1"));
+		assertEquals(.1e1f, parseNumber(".1e1", null));
+
+		assertTrue(isNumeric(".1e+1"));
+		assertEquals(.1e+1f, parseNumber(".1e+1", null));
+
+		assertTrue(isNumeric(".1e-1"));
+		assertEquals(.1e-1f, parseNumber(".1e-1", null));
+
+		// Hexadecimal + scientific
+		assertTrue(isNumeric("0x123e1"));
+		assertEquals(0x123e1, parseNumber("0x123e1", null));
+
+		try {
+			parseNumber("x", Number.class);
+			fail();
+		} catch (ParseException e) {
+			assertTrue(e.getCause() instanceof NumberFormatException);
+		}
+
+		try {
+			parseNumber("x", null);
+			fail();
+		} catch (ParseException e) {
+			assertTrue(e.getCause() instanceof NumberFormatException);
+		}
+
+		try {
+			parseNumber("x", BadNumber.class);
+			fail();
+		} catch (ParseException e) {
+			assertTrue(e.getLocalizedMessage().startsWith("Unsupported Number type"));
+		}
+	}
+
+	@SuppressWarnings("serial")
+	private abstract static class BadNumber extends Number {}
+
+	//====================================================================================================
+	// parseNumber(ParserReader,Class)
+	//====================================================================================================
+	@SuppressWarnings({ "rawtypes", "unchecked" })
+	@Test
+	public void testParseNumberFromReader() throws Exception {
+		ParserReader in;
+		Number n;
+
+		for (Class c : new Class[]{ Integer.class, Double.class, Float.class, Long.class, Short.class, Byte.class, BigInteger.class, BigDecimal.class, Number.class, AtomicInteger.class, AtomicLong.class}) {
+			in = new ParserReader(new ParserPipe("123'"));
+			n = parseNumber(in, c);
+			assertTrue(c.isInstance(n));
+			assertEquals(123, n.intValue());
+		}
+	}
+
+	//====================================================================================================
+	// test - Basic tests
+	//====================================================================================================
+	@Test
+	public void testNumberRanges() throws Exception {
+		String s;
+
+		// An integer range is -2,147,483,648 to 2,147,483,647
+
+		assertFalse(isNumeric(null));
+		assertFalse(isNumeric(""));
+		assertFalse(isNumeric("x"));
+
+		s = "-2147483648";
+		assertTrue(isNumeric(s));
+		assertTrue(parseNumber(s, null) instanceof Integer);
+		assertEquals(-2147483648, parseNumber(s, null));
+
+		s = "2147483647";
+		assertTrue(isNumeric(s));
+		assertTrue(parseNumber(s, null) instanceof Integer);
+		assertEquals(2147483647, parseNumber(s, null));
+
+		s = "-2147483649";
+		assertTrue(isNumeric(s));
+		assertTrue(parseNumber(s, null) instanceof Long);
+		assertEquals(-2147483649L, parseNumber(s, null));
+
+		s = "2147483648";
+		assertTrue(isNumeric(s));
+		assertTrue(parseNumber(s, null) instanceof Long);
+		assertEquals(2147483648L, parseNumber(s, null));
+
+		// An long range is -9,223,372,036,854,775,808 to +9,223,372,036,854,775,807
+
+		s = "-9223372036854775808";
+		assertTrue(isNumeric(s));
+		assertTrue(parseNumber(s, null) instanceof Long);
+		assertEquals(-9223372036854775808L, parseNumber(s, null));
+
+		s = "9223372036854775807";
+		assertTrue(isNumeric(s));
+		assertTrue(parseNumber(s, null) instanceof Long);
+		assertEquals(9223372036854775807L, parseNumber(s, null));
+
+		// Anything that falls outside this range should be a double.
+
+		s = "-9223372036854775809";
+		assertTrue(isNumeric(s));
+		assertTrue(parseNumber(s, null) instanceof Double);
+		assertEquals(-9223372036854775808L, parseNumber(s, null).longValue());
+		assertEquals(-9.223372036854776E18, parseNumber(s, null));
+
+		s = "9223372036854775808";
+		assertTrue(isNumeric(s));
+		assertTrue(parseNumber(s, null) instanceof Double);
+		assertEquals(9223372036854775807L, parseNumber(s, null).longValue());
+		assertEquals(9.223372036854776E18, parseNumber(s, null));
+
+		// Check case where string is longer than 20 characters since it's a different code path.
+
+		s = "-123456789012345678901";
+		assertTrue(isNumeric(s));
+		assertTrue(parseNumber(s, null) instanceof Double);
+		assertEquals(-9223372036854775808L, parseNumber(s, null).longValue());
+		assertEquals(-1.2345678901234568E20, parseNumber(s, null));
+
+		s = "123456789012345678901";
+		assertTrue(isNumeric(s));
+		assertTrue(parseNumber(s, null) instanceof Double);
+		assertEquals(9223372036854775807L, parseNumber(s, null).longValue());
+		assertEquals(1.2345678901234568E20, parseNumber(s, null));
+
+		// Autodetected floating point numbers smaller than Float.MAX_VALUE
+		s = String.valueOf(Float.MAX_VALUE / 2);
+		assertTrue(isNumeric(s));
+		assertTrue(parseNumber(s, null) instanceof Float);
+		assertEquals(1.7014117E38f, parseNumber(s, null));
+
+		s = String.valueOf((-Float.MAX_VALUE) / 2);
+		assertTrue(isNumeric(s));
+		assertTrue(parseNumber(s, null) instanceof Float);
+		assertEquals(-1.7014117E38f, parseNumber(s, null));
+
+		// Autodetected floating point numbers larger than Float.MAX_VALUE
+		s = String.valueOf((double)Float.MAX_VALUE * 2);
+		assertTrue(isNumeric(s));
+		assertTrue(parseNumber(s, null) instanceof Double);
+		assertEquals("6.805646932770577E38", parseNumber(s, null).toString());
+
+		s = String.valueOf((double)Float.MAX_VALUE * -2);
+		assertTrue(isNumeric(s));
+		assertTrue(parseNumber(s, null) instanceof Double);
+		assertEquals("-6.805646932770577E38", parseNumber(s, null).toString());
+
+		s = String.valueOf("214748364x");
+		assertFalse(isNumeric(s));
+		try {
+			parseNumber(s, Number.class);
+		} catch (ParseException e) {}
+
+		s = String.valueOf("2147483640x");
+		assertFalse(isNumeric(s));
+		try {
+			parseNumber(s, Long.class);
+		} catch (ParseException e) {}
+
+
+	}
+
+	//====================================================================================================
+	// testReplaceVars
+	//====================================================================================================
+	@Test
+	public void testReplaceVars() throws Exception {
+		ObjectMap m = new ObjectMap("{a:'A',b:1,c:true,d:'{e}',e:'E{f}E',f:'F',g:'{a}',h:'a',i:null}");
+
+		String s = "xxx";
+		assertEquals("xxx", replaceVars(s, m));
+
+		s = "{a}";
+		assertEquals("A", replaceVars(s, m));
+		s = "{a}{a}";
+		assertEquals("AA", replaceVars(s, m));
+		s = "x{a}x";
+		assertEquals("xAx", replaceVars(s, m));
+		s = "x{a}x{a}x";
+		assertEquals("xAxAx", replaceVars(s, m));
+
+		s = "{b}";
+		assertEquals("1", replaceVars(s, m));
+		s = "{b}{b}";
+		assertEquals("11", replaceVars(s, m));
+		s = "x{b}x";
+		assertEquals("x1x", replaceVars(s, m));
+		s = "x{b}x{b}x";
+		assertEquals("x1x1x", replaceVars(s, m));
+
+		s = "{c}";
+		assertEquals("true", replaceVars(s, m));
+		s = "{c}{c}";
+		assertEquals("truetrue", replaceVars(s, m));
+		s = "x{c}x{c}x";
+		assertEquals("xtruextruex", replaceVars(s, m));
+
+		s = "{d}";
+		assertEquals("EFE", replaceVars(s, m));
+		s = "{d}{d}";
+		assertEquals("EFEEFE", replaceVars(s, m));
+		s = "x{d}x";
+		assertEquals("xEFEx", replaceVars(s, m));
+		s = "x{d}x{d}x";
+		assertEquals("xEFExEFEx", replaceVars(s, m));
+
+		s = "{g}";
+		assertEquals("A", replaceVars(s, m));
+		s = "{g}{g}";
+		assertEquals("AA", replaceVars(s, m));
+		s = "x{g}x";
+		assertEquals("xAx", replaceVars(s, m));
+		s = "x{g}x{g}x";
+		assertEquals("xAxAx", replaceVars(s, m));
+
+		s = "{x}";
+		assertEquals("{x}", replaceVars(s, m));
+		s = "{x}{x}";
+		assertEquals("{x}{x}", replaceVars(s, m));
+		s = "x{x}x{x}x";
+		assertEquals("x{x}x{x}x", replaceVars(s, m));
+
+		s = "{{g}}";
+		assertEquals("{A}", replaceVars(s, m));
+		s = "{{h}}";
+		assertEquals("A", replaceVars(s, m));
+
+		s = "{{i}}";
+		assertEquals("{}", replaceVars(s, m));
+		s = "{}";
+		assertEquals("{}", replaceVars(s, m));
+	}
+
+	//====================================================================================================
+	// isFloat(String)
+	//====================================================================================================
+	@Test
+	public void testisFloat() throws Exception {
+		String[] valid = {
+			"+1.0",
+			"-1.0",
+			".0",
+			"NaN",
+			"Infinity",
+			"1e1",
+			"-1e-1",
+			"+1e+1",
+			"-1.1e-1",
+			"+1.1e+1",
+			"1.1f",
+			"1.1F",
+			"1.1d",
+			"1.1D",
+			"0x1.fffffffffffffp1023",
+			"0x1.FFFFFFFFFFFFFP1023",
+		};
+		for (String s : valid)
+			assertTrue(isFloat(s));
+
+		String[] invalid = {
+			null,
+			"",
+			"a",
+			"+",
+			"-",
+			".",
+			"a",
+			"+a",
+			"11a",
+		};
+		for (String s : invalid)
+			assertFalse(isFloat(s));
+	}
+
+	//====================================================================================================
+	// isDecimal(String)
+	//====================================================================================================
+	@Test
+	public void testisDecimal() throws Exception {
+		String[] valid = {
+			"+1",
+			"-1",
+			"0x123",
+			"0X123",
+			"0xdef",
+			"0XDEF",
+			"#def",
+			"#DEF",
+			"0123",
+		};
+		for (String s : valid)
+			assertTrue(isDecimal(s));
+
+		String[] invalid = {
+			null,
+			"",
+			"a",
+			"+",
+			"-",
+			".",
+			"0xdeg",
+			"0XDEG",
+			"#deg",
+			"#DEG",
+			"0128",
+			"012A",
+		};
+		for (String s : invalid)
+			assertFalse(isDecimal(s));
+	}
+
+	//====================================================================================================
+	// join(Object[],String)
+	// join(int[],String)
+	// join(Collection,String)
+	// join(Object[],char)
+	// join(int[],char)
+	// join(Collection,char)
+	//====================================================================================================
+	@Test
+	public void testJoin() throws Exception {
+		assertNull(join((Object[])null, ","));
+		assertEquals("1", join(new Object[]{1}, ","));
+		assertEquals("1,2", join(new Object[]{1,2}, ","));
+
+		assertNull(join((int[])null, ","));
+		assertEquals("1", join(new int[]{1}, ","));
+		assertEquals("1,2", join(new int[]{1,2}, ","));
+
+		assertNull(join((Collection<?>)null, ","));
+		assertEquals("1", join(Arrays.asList(new Integer[]{1}), ","));
+		assertEquals("1,2", join(Arrays.asList(new Integer[]{1,2}), ","));
+
+		assertNull(join((Object[])null, ','));
+		assertEquals("1", join(new Object[]{1}, ','));
+		assertEquals("1,2", join(new Object[]{1,2}, ','));
+
+		assertNull(join((int[])null, ','));
+		assertEquals("1", join(new int[]{1}, ','));
+		assertEquals("1,2", join(new int[]{1,2}, ','));
+
+		assertNull(join((Collection<?>)null, ','));
+		assertEquals("1", join(Arrays.asList(new Integer[]{1}), ','));
+		assertEquals("1,2", join(Arrays.asList(new Integer[]{1,2}), ','));
+	}
+
+	//====================================================================================================
+	// split(String,char)
+	//====================================================================================================
+	@Test
+	public void testSplit() throws Exception {
+		String[] r;
+
+		assertNull(split((String)null));
+		assertObjectEquals("[]", split(""));
+		assertObjectEquals("['1']", split("1"));
+		assertObjectEquals("['1','2']", split("1,2"));
+		assertObjectEquals("['1,2']", split("1\\,2"));
+
+		r = split("1\\\\,2");
+		assertEquals("1\\", r[0]);
+		assertEquals("2", r[1]);
+
+		r = split("1\\\\\\,2");
+		assertEquals(1, r.length);
+		assertEquals("1\\,2", r[0]);
+
+		r = split("1,2\\");
+		assertEquals("2\\", r[1]);
+
+		r = split("1,2\\\\");
+		assertEquals("2\\", r[1]);
+
+		r = split("1,2\\,");
+		assertEquals("2,", r[1]);
+
+		r = split("1,2\\\\,");
+		assertEquals("2\\", r[1]);
+		assertEquals("", r[2]);
+	}
+
+	//====================================================================================================
+	// nullIfEmpty(String)
+	//====================================================================================================
+	@Test
+	public void testNullIfEmpty() throws Exception {
+		assertNull(nullIfEmpty(null));
+		assertNull(nullIfEmpty(""));
+		assertNotNull(nullIfEmpty("x"));
+	}
+
+	//====================================================================================================
+	// unescapeChars(String,char[],char)
+	//====================================================================================================
+	@Test
+	public void testUnescapeChars() throws Exception {
+		char[] toEscape = {'\\',',','|'};
+		char escape = '\\';
+
+		assertNull(unEscapeChars(null, toEscape, escape));
+		assertEquals("xxx", unEscapeChars("xxx", new char[0], escape));
+		assertEquals("xxx", unEscapeChars("xxx", null, escape));
+		assertEquals("xxx", unEscapeChars("xxx", toEscape, (char)0));
+
+		assertEquals("xxx", unEscapeChars("xxx", toEscape, escape));
+		assertEquals("x,xx", unEscapeChars("x\\,xx", toEscape, escape));
+		assertEquals("x\\xx", unEscapeChars("x\\xx", toEscape, escape));
+		assertEquals("x\\,xx", unEscapeChars("x\\\\,xx", toEscape, escape));
+		assertEquals("x\\,xx", unEscapeChars("x\\\\\\,xx", toEscape, escape));
+		assertEquals("\\", unEscapeChars("\\", toEscape, escape));
+		assertEquals(",", unEscapeChars("\\,", toEscape, escape));
+		assertEquals("|", unEscapeChars("\\|", toEscape, escape));
+
+		toEscape = new char[] {',','|'};
+		assertEquals("x\\\\xx", unEscapeChars("x\\\\xx", toEscape, escape));
+	}
+
+	//====================================================================================================
+	// decodeHex(String)
+	//====================================================================================================
+	@Test
+	public void testDecodeHex() throws Exception {
+		assertNull(decodeHex(null));
+		assertEquals("19azAZ", decodeHex("19azAZ"));
+		assertEquals("[0][1][ffff]", decodeHex("\u0000\u0001\uFFFF"));
+	}
+
+	//====================================================================================================
+	// startsWith(String,char)
+	//====================================================================================================
+	@Test
+	public void testStartsWith() throws Exception {
+		assertFalse(startsWith(null, 'a'));
+		assertFalse(startsWith("", 'a'));
+		assertTrue(startsWith("a", 'a'));
+		assertTrue(startsWith("ab", 'a'));
+	}
+
+	//====================================================================================================
+	// endsWith(String,char)
+	//====================================================================================================
+	@Test
+	public void testEndsWith() throws Exception {
+		assertFalse(endsWith(null, 'a'));
+		assertFalse(endsWith("", 'a'));
+		assertTrue(endsWith("a", 'a'));
+		assertTrue(endsWith("ba", 'a'));
+	}
+
+	//====================================================================================================
+	// base64EncodeToString(String)
+	// base64DecodeToString(String)
+	//====================================================================================================
+	@Test
+	public void testBase64EncodeToString() throws Exception {
+		String s = null;
+
+		assertEquals(s, base64DecodeToString(base64EncodeToString(s)));
+		s = "";
+		assertEquals(s, base64DecodeToString(base64EncodeToString(s)));
+		s = "foobar";
+		assertEquals(s, base64DecodeToString(base64EncodeToString(s)));
+		s = "\u0000\uffff";
+		assertEquals(s, base64DecodeToString(base64EncodeToString(s)));
+
+		try {
+			base64Decode("a");
+			fail();
+		} catch (IllegalArgumentException e) {
+			assertEquals("Invalid BASE64 string length.  Must be multiple of 4.", e.getLocalizedMessage());
+			// OK.
+		}
+		try {
+			base64Decode("aaa");
+			fail();
+		} catch (IllegalArgumentException e) {
+			// OK.
+		}
+	}
+
+	//====================================================================================================
+	// generateUUID(String)
+	//====================================================================================================
+	@Test
+	public void testGenerateUUID() throws Exception {
+		for (int i = 0; i < 10; i++) {
+			String s = generateUUID(i);
+			assertEquals(i, s.length());
+			for (char c : s.toCharArray())
+				assertTrue(Character.isLowerCase(c) || Character.isDigit(c));
+		}
+	}
+
+	//====================================================================================================
+	// trim(String)
+	//====================================================================================================
+	@Test
+	public void testTrim() throws Exception {
+		assertNull(trim(null));
+		assertEquals("", trim(""));
+		assertEquals("", trim("  "));
+	}
+
+	//====================================================================================================
+	// parseISO8601Date(String)
+	//====================================================================================================
+	@Test
+	public void testParseISO8601Date() throws Exception {
+		WriterSerializer s = new JsonSerializerBuilder().simple().pojoSwaps(DateSwap.ISO8601DTPZ.class).timeZone(TimeZone.getTimeZone("GMT")).build();
+
+		assertNull(parseISO8601Date(null));
+		assertNull(parseISO8601Date(""));
+
+		TestUtils.setTimeZone("GMT");
+		try {
+
+			assertEquals("'2000-01-01T00:00:00.000Z'", s.serialize(parseISO8601Date("2000")));
+			assertEquals("'2000-02-01T00:00:00.000Z'", s.serialize(parseISO8601Date("2000-02")));
+			assertEquals("'2000-02-03T00:00:00.000Z'", s.serialize(parseISO8601Date("2000-02-03")));
+			assertEquals("'2000-02-03T04:00:00.000Z'", s.serialize(parseISO8601Date("2000-02-03T04")));
+			assertEquals("'2000-02-03T04:05:00.000Z'", s.serialize(parseISO8601Date("2000-02-03T04:05")));
+			assertEquals("'2000-02-03T04:05:06.000Z'", s.serialize(parseISO8601Date("2000-02-03T04:05:06")));
+			assertEquals("'2000-02-03T04:00:00.000Z'", s.serialize(parseISO8601Date("2000-02-03 04")));
+			assertEquals("'2000-02-03T04:05:00.000Z'", s.serialize(parseISO8601Date("2000-02-03 04:05")));
+			assertEquals("'2000-02-03T04:05:06.000Z'", s.serialize(parseISO8601Date("2000-02-03 04:05:06")));
+
+			// ISO8601 doesn't support milliseconds, so it gets trimmed.
+			assertEquals("'2000-02-03T04:05:06.000Z'", s.serialize(parseISO8601Date("2000-02-03 04:05:06,789")));
+		} finally {
+			TestUtils.unsetTimeZone();
+		}
+	}
+
+	//====================================================================================================
+	// pathStartsWith(String, String)
+	//====================================================================================================
+	@Test
+	public void testPathStartsWith() throws Exception {
+		assertTrue(pathStartsWith("foo", "foo"));
+		assertTrue(pathStartsWith("foo/bar", "foo"));
+		assertFalse(pathStartsWith("foo2/bar", "foo"));
+		assertFalse(pathStartsWith("foo2", "foo"));
+		assertFalse(pathStartsWith("foo2", ""));
+	}
+
+	//====================================================================================================
+	// getField(int, String, char)
+	//====================================================================================================
+	@Test
+	public void testGetField() {
+		String in = "0,1,2";
+		assertEquals("0", getField(0, in, ','));
+		assertEquals("1", getField(1, in, ','));
+		assertEquals("2", getField(2, in, ','));
+		assertEquals("", getField(3, in, ','));
+
+		in = ",1,,3,";
+		assertEquals("", getField(0, in, ','));
+		assertEquals("1", getField(1, in, ','));
+		assertEquals("", getField(2, in, ','));
+		assertEquals("3", getField(3, in, ','));
+		assertEquals("", getField(4, in, ','));
+		assertEquals("", getField(5, in, ','));
+
+		in = "";
+		assertEquals("", getField(0, in, ','));
+
+		in = null;
+		assertEquals("", getField(0, in, ','));
+	}
+
+	//====================================================================================================
+	// parseMap(String,char,char,boolean)
+	//====================================================================================================
+	@Test
+	public void testSplitMap() {
+		assertObjectEquals("{a:'1'}", splitMap("a=1", ',', '=', true));
+		assertObjectEquals("{a:'1',b:'2'}", splitMap("a=1,b=2", ',', '=', true));
+		assertObjectEquals("{a:'1',b:'2'}", splitMap(" a = 1 , b = 2 ", ',', '=', true));
+		assertObjectEquals("{' a ':' 1 ',' b ':' 2 '}", splitMap(" a = 1 , b = 2 ", ',', '=', false));
+		assertObjectEquals("{a:''}", splitMap("a", ',', '=', true));
+		assertObjectEquals("{a:'',b:''}", splitMap("a,b", ',', '=', true));
+		assertObjectEquals("{a:'1',b:''}", splitMap("a=1,b", ',', '=', true));
+		assertObjectEquals("{a:'',b:'1'}", splitMap("a,b=1", ',', '=', true));
+		assertObjectEquals("{'a=':'1'}", splitMap("a\\==1", ',', '=', true));
+		assertObjectEquals("{'a\\\\':'1'}", splitMap("a\\\\=1", ',', '=', true));
+	}
+
+	//====================================================================================================
+	// isAbsoluteUri(String)
+	//====================================================================================================
+	@Test
+	public void testIsAbsoluteUri() {
+		assertFalse(isAbsoluteUri(null));
+		assertFalse(isAbsoluteUri(""));
+		assertTrue(isAbsoluteUri("http://foo"));
+		assertTrue(isAbsoluteUri("x://x"));
+		assertFalse(isAbsoluteUri("xX://x"));
+		assertFalse(isAbsoluteUri("x ://x"));
+		assertFalse(isAbsoluteUri("x: //x"));
+		assertFalse(isAbsoluteUri("x:/ /x"));
+		assertFalse(isAbsoluteUri("x:x//x"));
+		assertFalse(isAbsoluteUri("x:/x/x"));
+	}
+
+	//====================================================================================================
+	// getAuthorityUri(String)
+	//====================================================================================================
+	@Test
+	public void testGetAuthorityUri() {
+		assertEquals("http://foo", getAuthorityUri("http://foo"));
+		assertEquals("http://foo:123", getAuthorityUri("http://foo:123"));
+		assertEquals("http://foo:123", getAuthorityUri("http://foo:123/"));
+		assertEquals("http://foo:123", getAuthorityUri("http://foo:123/bar"));
+	}
+}

http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/75b0d8ee/juneau-core/juneau-core-test/src/test/java/org/apache/juneau/utils/StringVarResolverTest.java
----------------------------------------------------------------------
diff --git a/juneau-core/juneau-core-test/src/test/java/org/apache/juneau/utils/StringVarResolverTest.java b/juneau-core/juneau-core-test/src/test/java/org/apache/juneau/utils/StringVarResolverTest.java
new file mode 100755
index 0000000..364e233
--- /dev/null
+++ b/juneau-core/juneau-core-test/src/test/java/org/apache/juneau/utils/StringVarResolverTest.java
@@ -0,0 +1,337 @@
+// ***************************************************************************************************************************
+// * Licensed to the Apache Software Foundation (ASF) under one or more contributor license agreements.  See the NOTICE file *
+// * distributed with this work for additional information regarding copyright ownership.  The ASF licenses this file        *
+// * to you under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance            *
+// * with the License.  You may obtain a copy of the License at                                                              *
+// *                                                                                                                         *
+// *  http://www.apache.org/licenses/LICENSE-2.0                                                                             *
+// *                                                                                                                         *
+// * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an  *
+// * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  See the License for the        *
+// * specific language governing permissions and limitations under the License.                                              *
+// ***************************************************************************************************************************
+package org.apache.juneau.utils;
+
+import static org.junit.Assert.*;
+import static org.apache.juneau.internal.StringUtils.*;
+
+import org.apache.juneau.svl.*;
+import org.junit.*;
+
+@SuppressWarnings("javadoc")
+public class StringVarResolverTest {
+
+	//====================================================================================================
+	// test - Basic tests
+	//====================================================================================================
+	@Test
+	public void test() throws Exception {
+		VarResolver vr = new VarResolverBuilder().vars(XVar.class).build();
+		String t;
+
+		t = null;
+		assertEquals("", vr.resolve(t));
+
+		t = "";
+		assertEquals("", vr.resolve(t));
+
+		t = "foo";
+		assertEquals("foo", vr.resolve(t));
+
+		t = "$X{y}";
+		assertEquals("xyx", vr.resolve(t));
+
+		t = "$X{y}x";
+		assertEquals("xyxx", vr.resolve(t));
+
+		t = "x$X{y}";
+		assertEquals("xxyx", vr.resolve(t));
+
+		t = "$X{y}$X{y}";
+		assertEquals("xyxxyx", vr.resolve(t));
+
+		t = "z$X{y}z$X{y}z";
+		assertEquals("zxyxzxyxz", vr.resolve(t));
+
+		t = "$X{$X{y}}";
+		assertEquals("xxyxx", vr.resolve(t));
+
+		t = "$X{z$X{y}z}";
+		assertEquals("xzxyxzx", vr.resolve(t));
+
+		t = "$X.{y}";
+		assertEquals("$X.{y}", vr.resolve(t));
+
+		t = "z$X.{y}z";
+		assertEquals("z$X.{y}z", vr.resolve(t));
+
+		t = "z$X.{$X.{z}}z";
+		assertEquals("z$X.{$X.{z}}z", vr.resolve(t));
+
+		// Non-existent vars
+		t = "$Y{y}";
+		assertEquals("$Y{y}", vr.resolve(t));
+
+		t = "$Y{y}x";
+		assertEquals("$Y{y}x", vr.resolve(t));
+
+		t = "x$Y{y}";
+		assertEquals("x$Y{y}", vr.resolve(t));
+
+		// Incomplete vars
+		// TODO - fix
+//		t = "x$Y{y";
+//		assertEquals("x$Y{y", vr.resolve(t));
+	}
+
+	public static class XVar extends SimpleVar {
+		public XVar() {
+			super("X");
+		}
+		@Override
+		public String resolve(VarResolverSession session, String arg) {
+			return "x" + arg + "x";
+		}
+	}
+
+	//====================================================================================================
+	// test - No-name variables
+	//====================================================================================================
+	@Test
+	public void test2() throws Exception {
+		VarResolver vr = new VarResolverBuilder().vars(BlankVar.class).build();
+		String t;
+
+		t = "${y}";
+		assertEquals("xyx", vr.resolve(t));
+
+		t = "${${y}}";
+		assertEquals("xxyxx", vr.resolve(t));
+
+		t = "${${y}${y}}";
+		assertEquals("xxyxxyxx", vr.resolve(t));
+
+		t = "z${z${y}z}z";
+		assertEquals("zxzxyxzxz", vr.resolve(t));
+	}
+
+	public static class BlankVar extends SimpleVar {
+		public BlankVar() {
+			super("");
+		}
+		@Override
+		public String resolve(VarResolverSession session, String arg) {
+			return "x" + arg + "x";
+		}
+	}
+
+	//====================================================================================================
+	// test - No-name variables
+	//====================================================================================================
+	@Test
+	public void testEscaped$() throws Exception {
+		VarResolver vr = new VarResolverBuilder().vars(BlankVar.class).build();
+		String t;
+
+		t = "${y}";
+		assertEquals("xyx", vr.resolve(t));
+		t = "\\${y}";
+		assertEquals("${y}", vr.resolve(t));
+
+		t = "foo\\${y}foo";
+		assertEquals("foo${y}foo", vr.resolve(t));
+
+		// TODO - This doesn't work.
+		//t = "${\\${y}}";
+		//assertEquals("x${y}x", vr.resolve(t));
+	}
+
+	//====================================================================================================
+	// test - Escape sequences.
+	//====================================================================================================
+	@Test
+	public void testEscapedSequences() throws Exception {
+		VarResolver vr = new VarResolverBuilder().vars(XVar.class).build();
+		String t;
+		char b = '\\';
+
+		t = "A|A".replace('|',b);
+		assertEquals("A|A".replace('|',b), vr.resolve(t));
+		t = "A||A".replace('|',b);
+		assertEquals("A|A".replace('|',b), vr.resolve(t));
+		t = "A|A$X{B}".replace('|',b);
+		assertEquals("A|AxBx".replace('|',b), vr.resolve(t));
+		t = "A||A$X{B}".replace('|',b);
+		assertEquals("A|AxBx".replace('|',b), vr.resolve(t));
+		t = "A|$X{B}".replace('|',b);
+		assertEquals("A$X{B}".replace('|',b), vr.resolve(t));
+		t = "A||$X{B}".replace('|',b);
+		assertEquals("A|xBx".replace('|',b), vr.resolve(t));
+		t = "A$X|{B}".replace('|',b);
+		assertEquals("A$X{B}".replace('|',b), vr.resolve(t));
+		t = "A$X{B|}".replace('|',b);
+		assertEquals("A$X{B}".replace('|',b), vr.resolve(t));
+		t = "A$X{B}|".replace('|',b);
+		assertEquals("AxBx|".replace('|',b), vr.resolve(t));
+	}
+
+	//====================================================================================================
+	// Test $E variables
+	//====================================================================================================
+	@Test
+	public void test$E() throws Exception {
+		String t;
+
+		t = "$E{PATH}";
+		assertFalse(isEmpty(VarResolver.DEFAULT.resolve(t)));
+	}
+
+	//====================================================================================================
+	// Test that StringResolver(parent) works as expected.
+	//====================================================================================================
+	@Test
+	public void testParent() throws Exception {
+		VarResolver vr = new VarResolverBuilder().defaultVars().vars(XMultipartVar.class).build();
+		String t;
+		System.setProperty("a", "a1");
+		System.setProperty("b", "b1");
+
+		t = "$X{$S{a},$S{b}}";
+		assertEquals("a1+b1", vr.resolve(t));
+		t = "$X{$S{a}}";
+		assertEquals("a1", vr.resolve(t));
+	}
+
+	public static class XMultipartVar extends MultipartVar {
+		public XMultipartVar() {
+			super("X");
+		}
+		@Override /* MultipartVar */
+		public String resolve(VarResolverSession session, String[] args) {
+			return join(args, '+');
+		}
+	}
+
+	//====================================================================================================
+	// Test false triggers.
+	//====================================================================================================
+	@Test
+	public void testFalseTriggers() throws Exception {
+		VarResolverBuilder vrb = new VarResolverBuilder().defaultVars();
+		String in = null;
+
+		// Should reject names with characters outside A-Za-z
+		for (Class<?> c : new Class[]{InvalidVar1.class, InvalidVar2.class, InvalidVar3.class, InvalidVar4.class, InvalidVar5.class}) {
+			try {
+				vrb.vars(c);
+				fail();
+			} catch (RuntimeException e) {
+				// OK.
+			}
+		}
+
+		VarResolver vr = vrb.build();
+
+		// These should all be unchanged.
+		in = "$@{foobar}";
+		assertEquals(in, vr.resolve(in));
+		in = "$[{foobar}";
+		assertEquals(in, vr.resolve(in));
+		in = "$`{foobar}";
+		assertEquals(in, vr.resolve(in));
+		in = "$|{foobar}";
+		assertEquals(in, vr.resolve(in));
+		in = "${{foobar}";
+		assertEquals(in, vr.resolve(in));
+		in = "${$foobar}";
+		assertEquals(in, vr.resolve(in));
+
+		System.setProperty("foobar", "baz");
+
+		in = "$";
+		assertEquals(in, vr.resolve(in));
+
+		in = "$S";
+		assertEquals(in, vr.resolve(in));
+
+		in = "$S{";
+		assertEquals(in, vr.resolve(in));
+
+		in = "$S{foobar";
+
+		assertEquals(in, vr.resolve(in));
+		in = "$S{foobar}$";
+		assertEquals("baz$", vr.resolve(in));
+
+		in = "$S{foobar}$S";
+		assertEquals("baz$S", vr.resolve(in));
+
+		in = "$S{foobar}$S{";
+		assertEquals("baz$S{", vr.resolve(in));
+
+		in = "$S{foobar}$S{foobar";
+		assertEquals("baz$S{foobar", vr.resolve(in));
+
+		System.clearProperty("foobar");
+		in = "$S{foobar}";
+
+		// Test nulls returned by StringVar.
+		// Should be converted to blanks.
+		vrb.vars(AlwaysNullVar.class);
+
+		vr = vrb.build();
+
+		in = "$A{xxx}";
+		assertEquals("", vr.resolve(in));
+		in = "x$A{xxx}";
+		assertEquals("x", vr.resolve(in));
+		in = "$A{xxx}x";
+		assertEquals("x", vr.resolve(in));
+	}
+
+	public static class AlwaysNullVar extends SimpleVar {
+		public AlwaysNullVar() {
+			super("A");
+		}
+		@Override
+		public String resolve(VarResolverSession session, String key) {
+			return null;
+		}
+	}
+
+	public static class InvalidVar extends SimpleVar {
+		public InvalidVar(String c) {
+			super(c);
+		}
+		@Override
+		public String resolve(VarResolverSession session, String key) {
+			return null;
+		}
+	}
+
+	public static class InvalidVar1 extends InvalidVar {
+		public InvalidVar1() {
+			super(null);
+		}
+	}
+	public static class InvalidVar2 extends InvalidVar {
+		public InvalidVar2() {
+			super("@");
+		}
+	}
+	public static class InvalidVar3 extends InvalidVar {
+		public InvalidVar3() {
+			super("[");
+		}
+	}
+	public static class InvalidVar4 extends InvalidVar {
+		public InvalidVar4() {
+			super("`");
+		}
+	}
+	public static class InvalidVar5 extends InvalidVar {
+		public InvalidVar5() {
+			super("|");
+		}
+	}
+}

http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/75b0d8ee/juneau-core/juneau-core-test/src/test/java/org/apache/juneau/utils/UriContextResolutionComboTest.java
----------------------------------------------------------------------
diff --git a/juneau-core/juneau-core-test/src/test/java/org/apache/juneau/utils/UriContextResolutionComboTest.java b/juneau-core/juneau-core-test/src/test/java/org/apache/juneau/utils/UriContextResolutionComboTest.java
new file mode 100644
index 0000000..8e3b43b
--- /dev/null
+++ b/juneau-core/juneau-core-test/src/test/java/org/apache/juneau/utils/UriContextResolutionComboTest.java
@@ -0,0 +1,1054 @@
+// ***************************************************************************************************************************
+// * Licensed to the Apache Software Foundation (ASF) under one or more contributor license agreements.  See the NOTICE file *
+// * distributed with this work for additional information regarding copyright ownership.  The ASF licenses this file        *
+// * to you under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance            *
+// * with the License.  You may obtain a copy of the License at                                                              *
+// *                                                                                                                         *
+// *  http://www.apache.org/licenses/LICENSE-2.0                                                                             *
+// *                                                                                                                         *
+// * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an  *
+// * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  See the License for the        *
+// * specific language governing permissions and limitations under the License.                                              *
+// ***************************************************************************************************************************
+package org.apache.juneau.utils;
+
+import static org.apache.juneau.TestUtils.*;
+import static org.apache.juneau.UriRelativity.*;
+import static org.apache.juneau.UriResolution.*;
+
+import java.util.*;
+
+import org.apache.juneau.*;
+import org.junit.*;
+import org.junit.runner.*;
+import org.junit.runners.*;
+
+/**
+ * Verifies that the resolveUri() methods in UriContext work correctly.
+ */
+@RunWith(Parameterized.class)
+@FixMethodOrder(MethodSorters.NAME_ASCENDING)
+public class UriContextResolutionComboTest {
+
+	@Parameterized.Parameters
+	public static Collection<Object[]> getInput() {
+		return Arrays.asList(new Object[][] {
+
+			// Happy cases - All URL parts known.
+			{	/* 0 */
+				"Happy-1a",
+				input(
+					"http://host:port","/context","/resource","/path",
+					"http://foo.com:123/foobar"
+				),
+				results(
+					"http://foo.com:123/foobar",
+					"http://foo.com:123/foobar",
+					"http://foo.com:123/foobar",
+					"http://foo.com:123/foobar",
+					"http://foo.com:123/foobar",
+					"http://foo.com:123/foobar"
+				)
+			},
+			{	/* 1 */
+				"Happy-2",
+				input(
+					"http://host:port","/context","/resource","/path",
+					"http://foo.com:123"
+				),
+				results(
+					"http://foo.com:123",
+					"http://foo.com:123",
+					"http://foo.com:123",
+					"http://foo.com:123",
+					"http://foo.com:123",
+					"http://foo.com:123"
+				)
+			},
+			{	/* 2 */
+				"Happy-3",
+				input(
+					"http://host:port","/context","/resource","/path",
+					"/foobar"
+				),
+				results(
+					"http://host:port/foobar",
+					"http://host:port/foobar",
+					"/foobar",
+					"/foobar",
+					"/foobar",
+					"/foobar"
+				)
+			},
+			{	/* 3 */
+				"Happy-4",
+				input(
+					"http://host:port","/context","/resource","/path",
+					"/"
+				),
+				results(
+					"http://host:port",
+					"http://host:port",
+					"/",
+					"/",
+					"/",
+					"/"
+				)
+			},
+			{	/* 4 */
+				"Happy-5",
+				input(
+					"http://host:port","/context","/resource","/path",
+					"foobar"
+				),
+				results(
+					"http://host:port/context/resource/foobar",
+					"http://host:port/context/resource/foobar",
+					"/context/resource/foobar",
+					"/context/resource/foobar",
+					"foobar",
+					"foobar"
+				)
+			},
+			{	/* 5 */
+				"Happy-6",
+				input(
+					"http://host:port","/context","/resource","/path",
+					""
+				),
+				results(
+					"http://host:port/context/resource",
+					"http://host:port/context/resource/path",
+					"/context/resource",
+					"/context/resource/path",
+					"",
+					""
+				)
+			},
+			{	/* 6 */
+				"Happy-7",
+				input(
+					"http://host:port","/context","/resource","/path",
+					"context:/foo"
+				),
+				results(
+					"http://host:port/context/foo",
+					"http://host:port/context/foo",
+					"/context/foo",
+					"/context/foo",
+					"/context/foo",
+					"/context/foo"
+				)
+			},
+			{	/* 7 */
+				"Happy-8",
+				input(
+					"http://host:port","/context","/resource","/path",
+					"context:/"
+				),
+				results(
+					"http://host:port/context",
+					"http://host:port/context",
+					"/context",
+					"/context",
+					"/context",
+					"/context"
+				)
+			},
+			{	/* 8 */
+				"Happy-9",
+				input(
+					"http://host:port","/context","/resource","/path",
+					"servlet:/foo"
+				),
+				results(
+					"http://host:port/context/resource/foo",
+					"http://host:port/context/resource/foo",
+					"/context/resource/foo",
+					"/context/resource/foo",
+					"/context/resource/foo",
+					"/context/resource/foo"
+				)
+			},
+			{	/* 9 */
+				"Happy-10",
+				input(
+					"http://host:port","/context","/resource","/path",
+					"servlet:/"
+				),
+				results(
+					"http://host:port/context/resource",
+					"http://host:port/context/resource",
+					"/context/resource",
+					"/context/resource",
+					"/context/resource",
+					"/context/resource"
+				)
+			},
+
+			// Multiple context and resource parts
+			{	/* 10 */
+				"MultiContextResource-1",
+				input(
+					"http://host:port","/c1/c2","/r1/r2","/p1/p2",
+					"http://foo.com:123/foobar"
+				),
+				results(
+					"http://foo.com:123/foobar",
+					"http://foo.com:123/foobar",
+					"http://foo.com:123/foobar",
+					"http://foo.com:123/foobar",
+					"http://foo.com:123/foobar",
+					"http://foo.com:123/foobar"
+				)
+			},
+			{	/* 11 */
+				"MultiContextResource-2",
+				input(
+					"http://host:port","/c1/c2","/r1/r2","/p1/p2",
+					"http://foo.com:123"
+				),
+				results(
+					"http://foo.com:123",
+					"http://foo.com:123",
+					"http://foo.com:123",
+					"http://foo.com:123",
+					"http://foo.com:123",
+					"http://foo.com:123"
+				)
+			},
+			{	/* 12 */
+				"MultiContextResource-3",
+				input(
+					"http://host:port","/c1/c2","/r1/r2","/p1/p2",
+					"/foobar"
+				),
+				results(
+					"http://host:port/foobar",
+					"http://host:port/foobar",
+					"/foobar",
+					"/foobar",
+					"/foobar",
+					"/foobar"
+				)
+			},
+			{	/* 13 */
+				"MultiContextResource-4",
+				input(
+					"http://host:port","/c1/c2","/r1/r2","/p1/p2",
+					"/"
+				),
+				results(
+					"http://host:port",
+					"http://host:port",
+					"/",
+					"/",
+					"/",
+					"/"
+				)
+			},
+			{	/* 14 */
+				"MultiContextResource-5",
+				input(
+					"http://host:port","/c1/c2","/r1/r2","/p1/p2",
+					"foobar"
+				),
+				results(
+					"http://host:port/c1/c2/r1/r2/foobar",
+					"http://host:port/c1/c2/r1/r2/p1/foobar",
+					"/c1/c2/r1/r2/foobar",
+					"/c1/c2/r1/r2/p1/foobar",
+					"foobar",
+					"foobar"
+				)
+			},
+			{	/* 15 */
+				"MultiContextResource-6",
+				input(
+					"http://host:port","/c1/c2","/r1/r2","/p1/p2",
+					""
+				),
+				results(
+					"http://host:port/c1/c2/r1/r2",
+					"http://host:port/c1/c2/r1/r2/p1/p2",
+					"/c1/c2/r1/r2",
+					"/c1/c2/r1/r2/p1/p2",
+					"",
+					""
+				)
+			},
+			{	/* 16 */
+				"MultiContextResource-7",
+				input(
+					"http://host:port","/c1/c2","/r1/r2","/p1/p2",
+					"context:/foo"
+				),
+				results(
+					"http://host:port/c1/c2/foo",
+					"http://host:port/c1/c2/foo",
+					"/c1/c2/foo",
+					"/c1/c2/foo",
+					"/c1/c2/foo",
+					"/c1/c2/foo"
+				)
+			},
+			{	/* 17 */
+				"MultiContextResource-8",
+				input(
+					"http://host:port","/c1/c2","/r1/r2","/p1/p2",
+					"context:/"
+				),
+				results(
+					"http://host:port/c1/c2",
+					"http://host:port/c1/c2",
+					"/c1/c2",
+					"/c1/c2",
+					"/c1/c2",
+					"/c1/c2"
+				)
+			},
+			{	/* 18 */
+				"MultiContextResource-9",
+				input(
+					"http://host:port","/c1/c2","/r1/r2","/p1/p2",
+					"servlet:/foo"
+				),
+				results(
+					"http://host:port/c1/c2/r1/r2/foo",
+					"http://host:port/c1/c2/r1/r2/foo",
+					"/c1/c2/r1/r2/foo",
+					"/c1/c2/r1/r2/foo",
+					"/c1/c2/r1/r2/foo",
+					"/c1/c2/r1/r2/foo"
+				)
+			},
+			{	/* 19 */
+				"MultiContextResource-10",
+				input(
+					"http://host:port","/c1/c2","/r1/r2","/p1/p2",
+					"servlet:/"
+				),
+				results(
+					"http://host:port/c1/c2/r1/r2",
+					"http://host:port/c1/c2/r1/r2",
+					"/c1/c2/r1/r2",
+					"/c1/c2/r1/r2",
+					"/c1/c2/r1/r2",
+					"/c1/c2/r1/r2"
+				)
+			},
+
+			// No authority given
+			{	/* 20 */
+				"NoAuthority-1",
+				input(
+					"","/context","/resource","/path",
+					"http://foo.com:123/foobar"
+				),
+				results(
+					"http://foo.com:123/foobar",
+					"http://foo.com:123/foobar",
+					"http://foo.com:123/foobar",
+					"http://foo.com:123/foobar",
+					"http://foo.com:123/foobar",
+					"http://foo.com:123/foobar"
+				)
+			},
+			{	/* 21 */
+				"NoAuthority-2",
+				input(
+					"","/context","/resource","/path",
+					"http://foo.com:123"
+				),
+				results(
+					"http://foo.com:123",
+					"http://foo.com:123",
+					"http://foo.com:123",
+					"http://foo.com:123",
+					"http://foo.com:123",
+					"http://foo.com:123"
+				)
+			},
+			{	/* 22 */
+				"NoAuthority-3",
+				input(
+					"","/context","/resource","/path",
+					"/foobar"
+				),
+				results(
+					"/foobar",
+					"/foobar",
+					"/foobar",
+					"/foobar",
+					"/foobar",
+					"/foobar"
+				)
+			},
+			{	/* 23 */
+				"NoAuthority-4",
+				input(
+					"","/context","/resource","/path",
+					"/"
+				),
+				results(
+					"/",
+					"/",
+					"/",
+					"/",
+					"/",
+					"/"
+				)
+			},
+			{	/* 24 */
+				"NoAuthority-5",
+				input(
+					"","/context","/resource","/path",
+					"foobar"
+				),
+				results(
+					"/context/resource/foobar",
+					"/context/resource/foobar",
+					"/context/resource/foobar",
+					"/context/resource/foobar",
+					"foobar",
+					"foobar"
+				)
+			},
+			{	/* 25 */
+				"NoAuthority-6",
+				input(
+					"","/context","/resource","/path",
+					""
+				),
+				results(
+					"/context/resource",
+					"/context/resource/path",
+					"/context/resource",
+					"/context/resource/path",
+					"",
+					""
+				)
+			},
+			{	/* 26 */
+				"NoAuthority-7",
+				input(
+					"","/context","/resource","/path",
+					"context:/foo"
+				),
+				results(
+					"/context/foo",
+					"/context/foo",
+					"/context/foo",
+					"/context/foo",
+					"/context/foo",
+					"/context/foo"
+				)
+			},
+			{	/* 27 */
+				"NoAuthority-8",
+				input(
+					"","/context","/resource","/path",
+					"context:/"
+				),
+				results(
+					"/context",
+					"/context",
+					"/context",
+					"/context",
+					"/context",
+					"/context"
+				)
+			},
+			{	/* 28 */
+				"NoAuthority-9",
+				input(
+					"","/context","/resource","/path",
+					"servlet:/foo"
+				),
+				results(
+					"/context/resource/foo",
+					"/context/resource/foo",
+					"/context/resource/foo",
+					"/context/resource/foo",
+					"/context/resource/foo",
+					"/context/resource/foo"
+				)
+			},
+			{	/* 29 */
+				"NoAuthority-10",
+				input(
+					"","/context","/resource","/path",
+					"servlet:/"
+				),
+				results(
+					"/context/resource",
+					"/context/resource",
+					"/context/resource",
+					"/context/resource",
+					"/context/resource",
+					"/context/resource"
+				)
+			},
+
+			// No authority or context given
+			{	/* 30 */
+				"NoAuthorityOrContext-1",
+				input(
+					"","","/resource","/path",
+					"http://foo.com:123/foobar"
+				),
+				results(
+					"http://foo.com:123/foobar",
+					"http://foo.com:123/foobar",
+					"http://foo.com:123/foobar",
+					"http://foo.com:123/foobar",
+					"http://foo.com:123/foobar",
+					"http://foo.com:123/foobar"
+				)
+			},
+			{	/* 31 */
+				"NoAuthorityOrContext-2",
+				input(
+					"","","/resource","/path",
+					"http://foo.com:123"
+				),
+				results(
+					"http://foo.com:123",
+					"http://foo.com:123",
+					"http://foo.com:123",
+					"http://foo.com:123",
+					"http://foo.com:123",
+					"http://foo.com:123"
+				)
+			},
+			{	/* 32 */
+				"NoAuthorityOrContext-3",
+				input(
+					"","","/resource","/path",
+					"/foobar"
+				),
+				results(
+					"/foobar",
+					"/foobar",
+					"/foobar",
+					"/foobar",
+					"/foobar",
+					"/foobar"
+				)
+			},
+			{	/* 33 */
+				"NoAuthorityOrContext-4",
+				input(
+					"","","/resource","/path",
+					"/"
+				),
+				results(
+					"/",
+					"/",
+					"/",
+					"/",
+					"/",
+					"/"
+				)
+			},
+			{	/* 34 */
+				"NoAuthorityOrContext-5",
+				input(
+					"","","/resource","/path",
+					"foobar"
+				),
+				results(
+					"/resource/foobar",
+					"/resource/foobar",
+					"/resource/foobar",
+					"/resource/foobar",
+					"foobar",
+					"foobar"
+				)
+			},
+			{	/* 35 */
+				"NoAuthorityOrContext-6",
+				input(
+					"","","/resource","/path",
+					""
+				),
+				results(
+					"/resource",
+					"/resource/path",
+					"/resource",
+					"/resource/path",
+					"",
+					""
+				)
+			},
+			{	/* 36 */
+				"NoAuthorityOrContext-7",
+				input(
+					"","","/resource","/path",
+					"context:/foo"
+				),
+				results(
+					"/foo",
+					"/foo",
+					"/foo",
+					"/foo",
+					"/foo",
+					"/foo"
+				)
+			},
+			{	/* 37 */
+				"NoAuthorityOrContext-8",
+				input(
+					"","","/resource","/path",
+					"context:/"
+				),
+				results(
+					"/",
+					"/",
+					"/",
+					"/",
+					"/",
+					"/"
+				)
+			},
+			{	/* 38 */
+				"NoAuthorityOrContext-9",
+				input(
+					"","","/resource","/path",
+					"servlet:/foo"
+				),
+				results(
+					"/resource/foo",
+					"/resource/foo",
+					"/resource/foo",
+					"/resource/foo",
+					"/resource/foo",
+					"/resource/foo"
+				)
+			},
+			{	/* 39 */
+				"NoAuthorityOrContext-10",
+				input(
+					"","","/resource","/path",
+					"servlet:/"
+				),
+				results(
+					"/resource",
+					"/resource",
+					"/resource",
+					"/resource",
+					"/resource",
+					"/resource"
+				)
+			},
+
+			// No authority or context or resource given
+			{	/* 40 */
+				"NoAuthorityOrContextOrResource-1",
+				input(
+					"","","","/path",
+					"http://foo.com:123/foobar"
+				),
+				results(
+					"http://foo.com:123/foobar",
+					"http://foo.com:123/foobar",
+					"http://foo.com:123/foobar",
+					"http://foo.com:123/foobar",
+					"http://foo.com:123/foobar",
+					"http://foo.com:123/foobar"
+				)
+			},
+			{	/* 41 */
+				"NoAuthorityOrContextOrResource-2",
+				input(
+					"","","","/path",
+					"http://foo.com:123"
+				),
+				results(
+					"http://foo.com:123",
+					"http://foo.com:123",
+					"http://foo.com:123",
+					"http://foo.com:123",
+					"http://foo.com:123",
+					"http://foo.com:123"
+				)
+			},
+			{	/* 42 */
+				"NoAuthorityOrContextOrResource-3",
+				input(
+					"","","","/path",
+					"/foobar"
+				),
+				results(
+					"/foobar",
+					"/foobar",
+					"/foobar",
+					"/foobar",
+					"/foobar",
+					"/foobar"
+				)
+			},
+			{	/* 43 */
+				"NoAuthorityOrContextOrResource-4",
+				input(
+					"","","","/path",
+					"/"
+				),
+				results(
+					"/",
+					"/",
+					"/",
+					"/",
+					"/",
+					"/"
+				)
+			},
+			{	/* 44 */
+				"NoAuthorityOrContextOrResource-5",
+				input(
+					"","","","/path",
+					"foobar"
+				),
+				results(
+					"/foobar",
+					"/foobar",
+					"/foobar",
+					"/foobar",
+					"foobar",
+					"foobar"
+				)
+			},
+			{	/* 45 */
+				"NoAuthorityOrContextOrResource-6",
+				input(
+					"","","","/path",
+					""
+				),
+				results(
+					"/",
+					"/path",
+					"/",
+					"/path",
+					"",
+					""
+				)
+			},
+			{	/* 46 */
+				"NoAuthorityOrContextOrResource-7",
+				input(
+					"","","","/path",
+					"context:/foo"
+				),
+				results(
+					"/foo",
+					"/foo",
+					"/foo",
+					"/foo",
+					"/foo",
+					"/foo"
+				)
+			},
+			{	/* 47 */
+				"NoAuthorityOrContextOrResource-8",
+				input(
+					"","","","/path",
+					"context:/"
+				),
+				results(
+					"/",
+					"/",
+					"/",
+					"/",
+					"/",
+					"/"
+				)
+			},
+			{	/* 48 */
+				"NoAuthorityOrContextOrResource-9",
+				input(
+					"","","","/path",
+					"servlet:/foo"
+				),
+				results(
+					"/foo",
+					"/foo",
+					"/foo",
+					"/foo",
+					"/foo",
+					"/foo"
+				)
+			},
+			{	/* 49 */
+				"NoAuthorityOrContextOrResource-10",
+				input(
+					"","","","/path",
+					"servlet:/"
+				),
+				results(
+					"/",
+					"/",
+					"/",
+					"/",
+					"/",
+					"/"
+				)
+			},
+
+			// No context or resource given.
+			{	/* 50 */
+				"NoContextOrResource-1",
+				input(
+					"http://host:port","","","/path",
+					"http://foo.com:123/foobar"
+				),
+				results(
+					"http://foo.com:123/foobar",
+					"http://foo.com:123/foobar",
+					"http://foo.com:123/foobar",
+					"http://foo.com:123/foobar",
+					"http://foo.com:123/foobar",
+					"http://foo.com:123/foobar"
+				)
+			},
+			{	/* 51 */
+				"NoContextOrResource-2",
+				input(
+					"http://host:port","","","/path",
+					"http://foo.com:123"
+				),
+				results(
+					"http://foo.com:123",
+					"http://foo.com:123",
+					"http://foo.com:123",
+					"http://foo.com:123",
+					"http://foo.com:123",
+					"http://foo.com:123"
+				)
+			},
+			{	/* 52 */
+				"NoContextOrResource-3",
+				input(
+					"http://host:port","","","/path",
+					"/foobar"
+				),
+				results(
+					"http://host:port/foobar",
+					"http://host:port/foobar",
+					"/foobar",
+					"/foobar",
+					"/foobar",
+					"/foobar"
+				)
+			},
+			{	/* 53 */
+				"NoContextOrResource-4",
+				input(
+					"http://host:port","","","/path",
+					"/"
+				),
+				results(
+					"http://host:port",
+					"http://host:port",
+					"/",
+					"/",
+					"/",
+					"/"
+				)
+			},
+			{	/* 54 */
+				"NoContextOrResource-5",
+				input(
+					"http://host:port","","","/path",
+					"foobar"
+				),
+				results(
+					"http://host:port/foobar",
+					"http://host:port/foobar",
+					"/foobar",
+					"/foobar",
+					"foobar",
+					"foobar"
+				)
+			},
+			{	/* 55 */
+				"NoContextOrResource-6",
+				input(
+					"http://host:port","","","/path",
+					""
+				),
+				results(
+					"http://host:port",
+					"http://host:port/path",
+					"/",
+					"/path",
+					"",
+					""
+				)
+			},
+			{	/* 56 */
+				"NoContextOrResource-7",
+				input(
+					"http://host:port","","","/path",
+					"context:/foo"
+				),
+				results(
+					"http://host:port/foo",
+					"http://host:port/foo",
+					"/foo",
+					"/foo",
+					"/foo",
+					"/foo"
+				)
+			},
+			{	/* 57 */
+				"NoContextOrResource-8",
+				input(
+					"http://host:port","","","/path",
+					"context:/"
+				),
+				results(
+					"http://host:port",
+					"http://host:port",
+					"/",
+					"/",
+					"/",
+					"/"
+				)
+			},
+			{	/* 58 */
+				"NoContextOrResource-9",
+				input(
+					"http://host:port","","","/path",
+					"servlet:/foo"
+				),
+				results(
+					"http://host:port/foo",
+					"http://host:port/foo",
+					"/foo",
+					"/foo",
+					"/foo",
+					"/foo"
+				)
+			},
+			{	/* 59 */
+				"NoContextOrResource-10",
+				input(
+					"http://host:port","","","/path",
+					"servlet:/"
+				),
+				results(
+					"http://host:port",
+					"http://host:port",
+					"/",
+					"/",
+					"/",
+					"/"
+				)
+			},
+		});
+	}
+
+	public static Input input(String authority, String context, String resource, String path, String uri) {
+		return new Input(authority, context, resource, path, uri);
+	}
+
+	public static Results results(String eAbsResource, String eAbsPathInfo, String eRrResource, String eRrPathInfo, String eNoneResource, String eNonePathInfo) {
+		return new Results(eAbsResource, eAbsPathInfo, eRrResource, eRrPathInfo, eNoneResource, eNonePathInfo);
+	}
+
+	public static class Input {
+		private final String uri;
+		private final String authority, context, resource, path;
+
+		public Input(String authority, String context, String resource, String path, String uri) {
+			this.authority = authority;
+			this.context = context;
+			this.resource = resource;
+			this.path = path;
+			this.uri = uri;
+		}
+	}
+
+	public static class Results {
+		private final String aResource, aPathInfo, rrResource, rrPathInfo, nResource, nPathInfo;
+
+		public Results(String aResource, String aPathInfo, String rrResource, String rrPathInfo, String nResource, String nPathInfo) {
+			this.aResource = aResource;
+			this.aPathInfo = aPathInfo;
+			this.rrResource = rrResource;
+			this.rrPathInfo = rrPathInfo;
+			this.nResource = nResource;
+			this.nPathInfo = nPathInfo;
+		}
+	}
+
+	private String label;
+	private Input in;
+	private Results r;
+
+	public UriContextResolutionComboTest(String label, Input in, Results r) throws Exception {
+		this.label = label;
+		this.in = in;
+		this.r = r;
+	}
+
+	@Test
+	public void a01_testAbsoluteResource() {
+		assertEquals(r.aResource, new UriResolver(ABSOLUTE, RESOURCE, new UriContext(in.authority, in.context, in.resource, in.path)).resolve(in.uri), "{0}: testAbsolute() failed", label);
+	}
+
+	@Test
+	public void a02_testAppendAbsoluteResource() {
+		assertEquals(r.aResource, new UriResolver(ABSOLUTE, RESOURCE, new UriContext(in.authority, in.context, in.resource, in.path)).append(new StringBuilder(), in.uri).toString(), "{0}: testAbsolute() failed", label);
+	}
+
+	@Test
+	public void a03_testAbsolutePathInfo() {
+		assertEquals(r.aPathInfo, new UriResolver(ABSOLUTE, PATH_INFO, new UriContext(in.authority, in.context, in.resource, in.path)).resolve(in.uri), "{0}: testAbsolute() failed", label);
+	}
+
+	@Test
+	public void a04_testAppendAbsolutePathInfo() {
+		assertEquals(r.aPathInfo, new UriResolver(ABSOLUTE, PATH_INFO, new UriContext(in.authority, in.context, in.resource, in.path)).append(new StringBuilder(), in.uri).toString(), "{0}: testAbsolute() failed", label);
+	}
+
+	@Test
+	public void a05_testRootRelativeResource() {
+		assertEquals(r.rrResource, new UriResolver(ROOT_RELATIVE, RESOURCE, new UriContext(in.authority, in.context, in.resource, in.path)).resolve(in.uri), "{0}: testAbsolute() failed", label);
+	}
+
+	@Test
+	public void a06_testAppendRootRelativeResource() {
+		assertEquals(r.rrResource, new UriResolver(ROOT_RELATIVE, RESOURCE, new UriContext(in.authority, in.context, in.resource, in.path)).append(new StringBuilder(), in.uri).toString(), "{0}: testAbsolute() failed", label);
+	}
+
+	@Test
+	public void a07_testRootRelativePathInfo() {
+		assertEquals(r.rrPathInfo, new UriResolver(ROOT_RELATIVE, PATH_INFO, new UriContext(in.authority, in.context, in.resource, in.path)).resolve(in.uri), "{0}: testAbsolute() failed", label);
+	}
+
+	@Test
+	public void a08_testAppendRootRelativePathInfo() {
+		assertEquals(r.rrPathInfo, new UriResolver(ROOT_RELATIVE, PATH_INFO, new UriContext(in.authority, in.context, in.resource, in.path)).append(new StringBuilder(), in.uri).toString(), "{0}: testAbsolute() failed", label);
+	}
+
+	@Test
+	public void a09_testNoneResource() {
+		assertEquals(r.nResource, new UriResolver(NONE, RESOURCE, new UriContext(in.authority, in.context, in.resource, in.path)).resolve(in.uri), "{0}: testAbsolute() failed", label);
+	}
+
+	@Test
+	public void a10_testAppendNoneResource() {
+		assertEquals(r.nResource, new UriResolver(NONE, RESOURCE, new UriContext(in.authority, in.context, in.resource, in.path)).append(new StringBuilder(), in.uri).toString(), "{0}: testAbsolute() failed", label);
+	}
+
+	@Test
+	public void a11_testNonePathInfo() {
+		assertEquals(r.nPathInfo, new UriResolver(NONE, PATH_INFO, new UriContext(in.authority, in.context, in.resource, in.path)).resolve(in.uri), "{0}: testAbsolute() failed", label);
+	}
+
+	@Test
+	public void a12_testAppendNonePathInfo() {
+		assertEquals(r.nPathInfo, new UriResolver(NONE, PATH_INFO, new UriContext(in.authority, in.context, in.resource, in.path)).append(new StringBuilder(), in.uri).toString(), "{0}: testAbsolute() failed", label);
+	}
+}

http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/75b0d8ee/juneau-core/juneau-core-test/src/test/java/org/apache/juneau/utils/UriContextUriComboTest.java
----------------------------------------------------------------------
diff --git a/juneau-core/juneau-core-test/src/test/java/org/apache/juneau/utils/UriContextUriComboTest.java b/juneau-core/juneau-core-test/src/test/java/org/apache/juneau/utils/UriContextUriComboTest.java
new file mode 100644
index 0000000..408c594
--- /dev/null
+++ b/juneau-core/juneau-core-test/src/test/java/org/apache/juneau/utils/UriContextUriComboTest.java
@@ -0,0 +1,265 @@
+// ***************************************************************************************************************************
+// * Licensed to the Apache Software Foundation (ASF) under one or more contributor license agreements.  See the NOTICE file *
+// * distributed with this work for additional information regarding copyright ownership.  The ASF licenses this file        *
+// * to you under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance            *
+// * with the License.  You may obtain a copy of the License at                                                              *
+// *                                                                                                                         *
+// *  http://www.apache.org/licenses/LICENSE-2.0                                                                             *
+// *                                                                                                                         *
+// * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an  *
+// * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  See the License for the        *
+// * specific language governing permissions and limitations under the License.                                              *
+// ***************************************************************************************************************************
+package org.apache.juneau.utils;
+
+import static org.apache.juneau.TestUtils.*;
+
+import java.util.*;
+
+import org.apache.juneau.*;
+import org.junit.*;
+import org.junit.runner.*;
+import org.junit.runners.*;
+
+/**
+ * Verifies that the getUri() methods in UriContext work correctly.
+ */
+@RunWith(Parameterized.class)
+@FixMethodOrder(MethodSorters.NAME_ASCENDING)
+public class UriContextUriComboTest {
+
+	@Parameterized.Parameters
+	public static Collection<Object[]> getInput() {
+		return Arrays.asList(new Object[][] {
+
+			// Happy cases - All URL parts known.
+			{
+				"Happy-1",
+				input(
+					"http://foo.com:123","/context","/resource","/path"
+				),
+				results(
+					"http://foo.com:123",
+					"http://foo.com:123/context",
+					"http://foo.com:123/context/resource",
+					"http://foo.com:123/context/resource/path",
+					"/context",
+					"/context/resource",
+					"/context/resource/path"
+				)
+			},
+			{
+				"Happy-2",
+				input(
+					"http://foo.com:123","/c1/c2","/r1/r2","/p1/p2"
+				),
+				results(
+					"http://foo.com:123",
+					"http://foo.com:123/c1/c2",
+					"http://foo.com:123/c1/c2/r1/r2",
+					"http://foo.com:123/c1/c2/r1/r2/p1/p2",
+					"/c1/c2",
+					"/c1/c2/r1/r2",
+					"/c1/c2/r1/r2/p1/p2"
+				)
+			},
+			{
+				"NoAuthority-1",
+				input(
+					"","/context","/resource","/path"
+				),
+				results(
+					"/",
+					"/context",
+					"/context/resource",
+					"/context/resource/path",
+					"/context",
+					"/context/resource",
+					"/context/resource/path"
+				)
+			},
+			{
+				"NoContext-1",
+				input(
+					"http://foo.com:123","","/resource","/path"
+				),
+				results(
+					"http://foo.com:123",
+					"http://foo.com:123",
+					"http://foo.com:123/resource",
+					"http://foo.com:123/resource/path",
+					"/",
+					"/resource",
+					"/resource/path"
+				)
+			},
+			{
+				"NoResource-1",
+				input(
+					"http://foo.com:123","/context","","/path"
+				),
+				results(
+					"http://foo.com:123",
+					"http://foo.com:123/context",
+					"http://foo.com:123/context",
+					"http://foo.com:123/context/path",
+					"/context",
+					"/context",
+					"/context/path"
+				)
+			},
+			{
+				"NoPath-1",
+				input(
+					"http://foo.com:123","/context","/resource",""
+				),
+				results(
+					"http://foo.com:123",
+					"http://foo.com:123/context",
+					"http://foo.com:123/context/resource",
+					"http://foo.com:123/context/resource",
+					"/context",
+					"/context/resource",
+					"/context/resource"
+				)
+			},
+			{
+				"NoAuthorityNoContext-1",
+				input(
+					"","","/resource","/path"
+				),
+				results(
+					"/",
+					"/",
+					"/resource",
+					"/resource/path",
+					"/",
+					"/resource",
+					"/resource/path"
+				)
+			},
+			{
+				"NoContextNoResource-1",
+				input(
+					"http://foo.com:123","","","/path"
+				),
+				results(
+					"http://foo.com:123",
+					"http://foo.com:123",
+					"http://foo.com:123",
+					"http://foo.com:123/path",
+					"/",
+					"/",
+					"/path"
+				)
+			},
+			{
+				"NoAuthorityNoContextNoResource-1",
+				input(
+					"","","","/path"
+				),
+				results(
+					"/",
+					"/",
+					"/",
+					"/path",
+					"/",
+					"/",
+					"/path"
+				)
+			},
+			{
+				"Nothing-1",
+				input(
+					"","","",""
+				),
+				results(
+					"/",
+					"/",
+					"/",
+					"/",
+					"/",
+					"/",
+					"/"
+				)
+			},
+		});
+	}
+
+	public static Input input(String authority, String context, String resource, String path) {
+		return new Input(authority, context, resource, path);
+	}
+
+	public static Results results(String eAbsoluteAuthority, String eAbsoluteContext, String eAbsoluteResource, String eAbsolutePath,
+			String eRootRelativeContext, String eRootRelativeResource, String eRootRelativePath) {
+		return new Results(eAbsoluteAuthority, eAbsoluteContext, eAbsoluteResource, eAbsolutePath, eRootRelativeContext, eRootRelativeResource, eRootRelativePath);
+	}
+
+	public static class Input {
+		private final UriContext uriContext;
+
+		public Input(String authority, String context, String resource, String path) {
+			this.uriContext = new UriContext(authority, context, resource, path);
+		}
+	}
+
+	public static class Results {
+		private final String eAbsoluteAuthority, eAbsoluteContext, eAbsoluteResource, eAbsolutePath, eRootRelativeContext, eRootRelativeResource, eRootRelativePath;
+
+		public Results(String eAbsoluteAuthority, String eAbsoluteContext, String eAbsoluteResource, String eAbsolutePath,
+					String eRootRelativeContext, String eRootRelativeResource, String eRootRelativePath) {
+			this.eAbsoluteAuthority = eAbsoluteAuthority;
+			this.eAbsoluteContext = eAbsoluteContext;
+			this.eAbsoluteResource = eAbsoluteResource;
+			this.eAbsolutePath = eAbsolutePath;
+			this.eRootRelativeContext = eRootRelativeContext;
+			this.eRootRelativeResource = eRootRelativeResource;
+			this.eRootRelativePath = eRootRelativePath;
+		}
+	}
+
+	private String label;
+	private Input in;
+	private Results r;
+
+	public UriContextUriComboTest(String label, Input in, Results r) throws Exception {
+		this.label = label;
+		this.in = in;
+		this.r = r;
+	}
+
+	@Test
+	public void a1_testAbsoluteAuthority() {
+		assertEquals(r.eAbsoluteAuthority, in.uriContext.getAbsoluteAuthority(), "{0}: testAbsoluteAuthority() failed", label);
+	}
+
+	@Test
+	public void a2_testAbsoluteContext() {
+		assertEquals(r.eAbsoluteContext, in.uriContext.getAbsoluteContextRoot(), "{0}: testAbsoluteContext() failed", label);
+	}
+
+	@Test
+	public void a3_testAbsoluteResource() {
+		assertEquals(r.eAbsoluteResource, in.uriContext.getAbsoluteServletPath(), "{0}: testAbsoluteResource() failed", label);
+	}
+
+	@Test
+	public void a4_testAbsolutePath() {
+		assertEquals(r.eAbsolutePath, in.uriContext.getAbsolutePathInfo(), "{0}: testAbsolutePath() failed", label);
+	}
+
+	@Test
+	public void a5_testRootRelativeContext() {
+		assertEquals(r.eRootRelativeContext, in.uriContext.getRootRelativeContextRoot(), "{0}: testRootRelativeContext() failed", label);
+	}
+
+	@Test
+	public void a6_testRootRelativeResource() {
+		assertEquals(r.eRootRelativeResource, in.uriContext.getRootRelativeServletPath(), "{0}: testRootRelativeResource() failed", label);
+	}
+
+	@Test
+	public void a7_testRootRelativePath() {
+		assertEquals(r.eRootRelativePath, in.uriContext.getRootRelativePathInfo(), "{0}: testRootRelativePath() failed", label);
+	}
+}