http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/e6bf97a8/juneau-core/src/main/java/org/apache/juneau/internal/StringUtils.java
----------------------------------------------------------------------
diff --git 
a/juneau-core/src/main/java/org/apache/juneau/internal/StringUtils.java 
b/juneau-core/src/main/java/org/apache/juneau/internal/StringUtils.java
new file mode 100644
index 0000000..6ed4d55
--- /dev/null
+++ b/juneau-core/src/main/java/org/apache/juneau/internal/StringUtils.java
@@ -0,0 +1,979 @@
+/***************************************************************************************************************************
+ * 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.internal;
+
+import static org.apache.juneau.internal.ThrowableUtils.*;
+
+import java.io.*;
+import java.math.*;
+import java.util.*;
+import java.util.concurrent.atomic.*;
+import java.util.regex.*;
+
+import javax.xml.bind.*;
+
+import org.apache.juneau.parser.*;
+
+/**
+ * Reusable string utility methods.
+ */
+public final class StringUtils {
+
+       private static final AsciiSet numberChars = new 
AsciiSet("-xX.+-#pP0123456789abcdefABCDEF");
+       private static final AsciiSet firstNumberChars = new 
AsciiSet("+-.#0123456789");
+       private static final AsciiSet octChars = new AsciiSet("01234567");
+       private static final AsciiSet decChars = new AsciiSet("0123456789");
+       private static final AsciiSet hexChars = new 
AsciiSet("0123456789abcdefABCDEF");
+
+       // Maps 6-bit nibbles to BASE64 characters.
+       private static final char[] base64m1 = 
"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/".toCharArray();
+
+       // Maps BASE64 characters to 6-bit nibbles.
+       private static final byte[] base64m2 = new byte[128];
+       static {
+               for (int i = 0; i < 64; i++)
+                       base64m2[base64m1[i]] = (byte)i;
+       }
+
+       /**
+        * Parses a number from the specified reader stream.
+        *
+        * @param r The reader to parse the string from.
+        * @param type The number type to created. <br>
+        *      Can be any of the following:
+        *      <ul>
+        *              <li> Integer
+        *              <li> Double
+        *              <li> Float
+        *              <li> Long
+        *              <li> Short
+        *              <li> Byte
+        *              <li> BigInteger
+        *              <li> BigDecimal
+        *      </ul>
+        *      If <jk>null</jk>, uses the best guess.
+        * @throws IOException If a problem occurred trying to read from the 
reader.
+        * @return The parsed number.
+        * @throws Exception
+        */
+       public static Number parseNumber(ParserReader r, Class<? extends 
Number> type) throws Exception {
+               return parseNumber(parseNumberString(r), type);
+       }
+
+       /**
+        * Reads a numeric string from the specified reader.
+        *
+        * @param r The reader to read form.
+        * @return The parsed number string.
+        * @throws Exception
+        */
+       public static String parseNumberString(ParserReader r) throws Exception 
{
+               r.mark();
+               int c = 0;
+               while (true) {
+                       c = r.read();
+                       if (c == -1)
+                               break;
+                       if (! numberChars.contains((char)c)) {
+                               r.unread();
+                               break;
+                       }
+               }
+               return r.getMarked();
+       }
+
+       /**
+        * Parses a number from the specified string.
+        *
+        * @param s The string to parse the number from.
+        * @param type The number type to created. <br>
+        *      Can be any of the following:
+        *      <ul>
+        *              <li> Integer
+        *              <li> Double
+        *              <li> Float
+        *              <li> Long
+        *              <li> Short
+        *              <li> Byte
+        *              <li> BigInteger
+        *              <li> BigDecimal
+        *      </ul>
+        *      If <jk>null</jk>, uses the best guess.
+        * @return The parsed number.
+        * @throws ParseException
+        */
+       public static Number parseNumber(String s, Class<? extends Number> 
type) throws ParseException {
+
+               if (s.isEmpty())
+                       s = "0";
+               if (type == null)
+                       type = Number.class;
+
+               try {
+                       // Determine the data type if it wasn't specified.
+                       boolean isAutoDetect = (type == Number.class);
+                       boolean isDecimal = false;
+                       if (isAutoDetect) {
+                               // If we're auto-detecting, then we use either 
an Integer, Long, or Double depending on how
+                               // long the string is.
+                               // An integer range is -2,147,483,648 to 
2,147,483,647
+                               // An long range is -9,223,372,036,854,775,808 
to +9,223,372,036,854,775,807
+                               isDecimal = isDecimal(s);
+                               if (isDecimal) {
+                                       if (s.length() > 20)
+                                               type = Double.class;
+                                       else if (s.length() >= 10)
+                                               type = Long.class;
+                                       else
+                                               type = Integer.class;
+                               }
+                               else if (isFloat(s))
+                                       type = Double.class;
+                               else
+                                       throw new NumberFormatException(s);
+                       }
+
+                       if (type == Double.class || type == Double.TYPE) {
+                               Double d = Double.valueOf(s);
+                               if (isAutoDetect && (! isDecimal) && d >= 
-Float.MAX_VALUE && d <= Float.MAX_VALUE)
+                                       return d.floatValue();
+                               return d;
+                       }
+                       if (type == Float.class || type == Float.TYPE)
+                               return Float.valueOf(s);
+                       if (type == BigDecimal.class)
+                               return new BigDecimal(s);
+                       if (type == Long.class || type == Long.TYPE || type == 
AtomicLong.class) {
+                               try {
+                                       Long l = Long.decode(s);
+                                       if (type == AtomicLong.class)
+                                               return new AtomicLong(l);
+                                       if (isAutoDetect && l >= 
Integer.MIN_VALUE && l <= Integer.MAX_VALUE) {
+                                               // This occurs if the string is 
10 characters long but is still a valid integer value.
+                                               return l.intValue();
+                                       }
+                                       return l;
+                               } catch (NumberFormatException e) {
+                                       if (isAutoDetect) {
+                                               // This occurs if the string is 
20 characters long but still falls outside the range of a valid long.
+                                               return Double.valueOf(s);
+                                       }
+                                       throw e;
+                               }
+                       }
+                       if (type == Integer.class || type == Integer.TYPE)
+                               return Integer.decode(s);
+                       if (type == Short.class || type == Short.TYPE)
+                               return Short.decode(s);
+                       if (type == Byte.class || type == Byte.TYPE)
+                               return Byte.decode(s);
+                       if (type == BigInteger.class)
+                               return new BigInteger(s);
+                       if (type == AtomicInteger.class)
+                               return new AtomicInteger(Integer.decode(s));
+                       throw new ParseException("Unsupported Number type: 
{0}", type.getName());
+               } catch (NumberFormatException e) {
+                       throw new ParseException("Could not convert string 
''{0}'' to class ''{1}''", s, type.getName()).initCause(e);
+               }
+       }
+
+   private final static Pattern fpRegex = Pattern.compile(
+      
"[+-]?(NaN|Infinity|((((\\p{Digit}+)(\\.)?((\\p{Digit}+)?)([eE][+-]?(\\p{Digit}+))?)|(\\.((\\p{Digit}+))([eE][+-]?(\\p{Digit}+))?)|(((0[xX](\\p{XDigit}+)(\\.)?)|(0[xX](\\p{XDigit}+)?(\\.)(\\p{XDigit}+)))[pP][+-]?(\\p{Digit}+)))[fFdD]?))[\\x00-\\x20]*"
+   );
+
+       /**
+        * Returns <jk>true</jk> if this string can be parsed by {@link 
#parseNumber(String, Class)}.
+        *
+        * @param s The string to check.
+        * @return <jk>true</jk> if this string can be parsed without causing 
an exception.
+        */
+       public static boolean isNumeric(String s) {
+               if (s == null || s.isEmpty())
+                       return false;
+               if (! isFirstNumberChar(s.charAt(0)))
+                       return false;
+               return isDecimal(s) || isFloat(s);
+       }
+
+       /**
+        * Returns <jk>true</jk> if the specified character is a valid first 
character for a number.
+        *
+        * @param c The character to test.
+        * @return <jk>true</jk> if the specified character is a valid first 
character for a number.
+        */
+       public static boolean isFirstNumberChar(char c) {
+               return firstNumberChars.contains(c);
+       }
+
+       /**
+        * Returns <jk>true</jk> if the specified string is a floating point 
number.
+        *
+        * @param s The string to check.
+        * @return <jk>true</jk> if the specified string is a floating point 
number.
+        */
+       public static boolean isFloat(String s) {
+               if (s == null || s.isEmpty())
+                       return false;
+               if (! firstNumberChars.contains(s.charAt(0)))
+                       return (s.equals("NaN") || s.equals("Infinity"));
+               int i = 0;
+               int length = s.length();
+               char c = s.charAt(0);
+               if (c == '+' || c == '-')
+                       i++;
+               if (i == length)
+                       return false;
+               c = s.charAt(i++);
+               if (c == '.' || decChars.contains(c)) {
+                       return fpRegex.matcher(s).matches();
+               }
+               return false;
+       }
+
+       /**
+        * Returns <jk>true</jk> if the specified string is numeric.
+        *
+        * @param s The string to check.
+        * @return <jk>true</jk> if the specified string is numeric.
+        */
+       public static boolean isDecimal(String s) {
+               if (s == null || s.isEmpty())
+                       return false;
+               if (! firstNumberChars.contains(s.charAt(0)))
+                       return false;
+               int i = 0;
+               int length = s.length();
+               char c = s.charAt(0);
+               boolean isPrefixed = false;
+               if (c == '+' || c == '-') {
+                       isPrefixed = true;
+                       i++;
+               }
+               if (i == length)
+                       return false;
+               c = s.charAt(i++);
+               if (c == '0' && length > (isPrefixed ? 2 : 1)) {
+                       c = s.charAt(i++);
+                       if (c == 'x' || c == 'X') {
+                               for (int j = i; j < length; j++) {
+                                       if (! hexChars.contains(s.charAt(j)))
+                                               return false;
+                               }
+                       } else if (octChars.contains(c)) {
+                               for (int j = i; j < length; j++)
+                                       if (! octChars.contains(s.charAt(j)))
+                                               return false;
+                       } else {
+                               return false;
+                       }
+               } else if (c == '#') {
+                       for (int j = i; j < length; j++) {
+                               if (! hexChars.contains(s.charAt(j)))
+                                       return false;
+                       }
+               } else if (decChars.contains(c)) {
+                       for (int j = i; j < length; j++)
+                               if (! decChars.contains(s.charAt(j)))
+                                       return false;
+               } else {
+                       return false;
+               }
+               return true;
+       }
+
+       /**
+        * Convenience method for getting a stack trace as a string.
+        *
+        * @param t The throwable to get the stack trace from.
+        * @return The same content that would normally be rendered via 
<code>t.printStackTrace()</code>
+        */
+       public static String getStackTrace(Throwable t) {
+               StringWriter sw = new StringWriter();
+               PrintWriter pw = new PrintWriter(sw);
+               t.printStackTrace(pw);
+               pw.flush();
+               pw.close();
+               return sw.toString();
+       }
+
+       /**
+        * Join the specified tokens into a delimited string.
+        *
+        * @param tokens The tokens to join.
+        * @param separator The delimiter.
+        * @return The delimited string.  If <code>tokens</code> is 
<jk>null</jk>, returns <jk>null</jk>.
+        */
+       public static String join(Object[] tokens, String separator) {
+               if (tokens == null)
+                       return null;
+               StringBuilder sb = new StringBuilder();
+               for (int i = 0; i < tokens.length; i++) {
+                       if (i > 0)
+                               sb.append(separator);
+                       sb.append(tokens[i]);
+               }
+               return sb.toString();
+       }
+
+       /**
+        * Join the specified tokens into a delimited string.
+        *
+        * @param tokens The tokens to join.
+        * @param d The delimiter.
+        * @return The delimited string.  If <code>tokens</code> is 
<jk>null</jk>, returns <jk>null</jk>.
+        */
+       public static String join(int[] tokens, String d) {
+               if (tokens == null)
+                       return null;
+               StringBuilder sb = new StringBuilder();
+               for (int i = 0; i < tokens.length; i++) {
+                       if (i > 0)
+                               sb.append(d);
+                       sb.append(tokens[i]);
+               }
+               return sb.toString();
+       }
+
+       /**
+        * Join the specified tokens into a delimited string.
+        *
+        * @param tokens The tokens to join.
+        * @param d The delimiter.
+        * @return The delimited string.  If <code>tokens</code> is 
<jk>null</jk>, returns <jk>null</jk>.
+        */
+       public static String join(Collection<?> tokens, String d) {
+               if (tokens == null)
+                       return null;
+               return join(tokens, d, new StringBuilder()).toString();
+       }
+
+       /**
+        * Joins the specified tokens into a delimited string and writes the 
output to the specified string builder.
+        *
+        * @param tokens The tokens to join.
+        * @param d The delimiter.
+        * @param sb The string builder to append the response to.
+        * @return The same string builder passed in as <code>sb</code>.
+        */
+       public static StringBuilder join(Collection<?> tokens, String d, 
StringBuilder sb) {
+               if (tokens == null)
+                       return sb;
+               for (Iterator<?> iter = tokens.iterator(); iter.hasNext();) {
+                       sb.append(iter.next());
+                       if (iter.hasNext())
+                               sb.append(d);
+               }
+               return sb;
+       }
+
+       /**
+        * Joins the specified tokens into a delimited string.
+        *
+        * @param tokens The tokens to join.
+        * @param d The delimiter.
+        * @return The delimited string.  If <code>tokens</code> is 
<jk>null</jk>, returns <jk>null</jk>.
+        */
+       public static String join(Object[] tokens, char d) {
+               if (tokens == null)
+                       return null;
+               return join(tokens, d, new StringBuilder()).toString();
+       }
+
+       /**
+        * Join the specified tokens into a delimited string and writes the 
output to the specified string builder.
+        *
+        * @param tokens The tokens to join.
+        * @param d The delimiter.
+        * @param sb The string builder to append the response to.
+        * @return The same string builder passed in as <code>sb</code>.
+        */
+       public static StringBuilder join(Object[] tokens, char d, StringBuilder 
sb) {
+               if (tokens == null)
+                       return sb;
+               for (int i = 0; i < tokens.length; i++) {
+                       if (i > 0)
+                               sb.append(d);
+                       sb.append(tokens[i]);
+               }
+               return sb;
+       }
+
+       /**
+        * Join the specified tokens into a delimited string.
+        *
+        * @param tokens The tokens to join.
+        * @param d The delimiter.
+        * @return The delimited string.  If <code>tokens</code> is 
<jk>null</jk>, returns <jk>null</jk>.
+        */
+       public static String join(int[] tokens, char d) {
+               if (tokens == null)
+                       return null;
+               StringBuilder sb = new StringBuilder();
+               for (int i = 0; i < tokens.length; i++) {
+                       if (i > 0)
+                               sb.append(d);
+                       sb.append(tokens[i]);
+               }
+               return sb.toString();
+       }
+
+       /**
+        * Join the specified tokens into a delimited string.
+        *
+        * @param tokens The tokens to join.
+        * @param d The delimiter.
+        * @return The delimited string.  If <code>tokens</code> is 
<jk>null</jk>, returns <jk>null</jk>.
+        */
+       public static String join(Collection<?> tokens, char d) {
+               if (tokens == null)
+                       return null;
+               StringBuilder sb = new StringBuilder();
+               for (Iterator<?> iter = tokens.iterator(); iter.hasNext();) {
+                       sb.append(iter.next());
+                       if (iter.hasNext())
+                               sb.append(d);
+               }
+               return sb.toString();
+       }
+
+       /**
+        * Splits a character-delimited string into a string array.
+        * Does not split on escaped-delimiters (e.g. "\,");
+        * Resulting tokens are trimmed of whitespace.
+        * NOTE:  This behavior is different than the Jakarta equivalent.
+        * split("a,b,c",',') -> {"a","b","c"}
+        * split("a, b ,c ",',') -> {"a","b","c"}
+        * split("a,,c",',') -> {"a","","c"}
+        * split(",,",',') -> {"","",""}
+        * split("",',') -> {}
+        * split(null,',') -> null
+        * split("a,b\,c,d", ',', false) -> {"a","b\,c","d"}
+        * split("a,b\\,c,d", ',', false) -> {"a","b\","c","d"}
+        * split("a,b\,c,d", ',', true) -> {"a","b,c","d"}
+        *
+        * @param s The string to split.  Can be <jk>null</jk>.
+        * @param c The character to split on.
+        * @return The tokens.
+        */
+       public static String[] split(String s, char c) {
+
+               char[] unEscapeChars = new char[]{'\\', c};
+
+               if (s == null)
+                       return null;
+               if (isEmpty(s))
+                       return new String[0];
+
+               List<String> l = new LinkedList<String>();
+               char[] sArray = s.toCharArray();
+               int x1 = 0, escapeCount = 0;
+               for (int i = 0; i < sArray.length; i++) {
+                       if (sArray[i] == '\\') escapeCount++;
+                       else if (sArray[i]==c && escapeCount % 2 == 0) {
+                               String s2 = new String(sArray, x1, i-x1);
+                               String s3 = unEscapeChars(s2, unEscapeChars);
+                               l.add(s3.trim());
+                               x1 = i+1;
+                       }
+                       if (sArray[i] != '\\') escapeCount = 0;
+               }
+               String s2 = new String(sArray, x1, sArray.length-x1);
+               String s3 = unEscapeChars(s2, unEscapeChars);
+               l.add(s3.trim());
+
+               return l.toArray(new String[l.size()]);
+       }
+
+       /**
+        * Returns <jk>true</jk> if specified string is <jk>null</jk> or empty.
+        *
+        * @param s The string to check.
+        * @return <jk>true</jk> if specified string is <jk>null</jk> or empty.
+        */
+       public static boolean isEmpty(String s) {
+               return s == null || s.isEmpty();
+       }
+
+       /**
+        * Returns <jk>true</jk> if specified string is <jk>null</jk> or it's 
{@link #toString()} method returns an empty string.
+        *
+        * @param s The string to check.
+        * @return <jk>true</jk> if specified string is <jk>null</jk> or it's 
{@link #toString()} method returns an empty string.
+        */
+       public static boolean isEmpty(Object s) {
+               return s == null || s.toString().isEmpty();
+       }
+
+       /**
+        * Returns <jk>null</jk> if the specified string is <jk>null</jk> or 
empty.
+        *
+        * @param s The string to check.
+        * @return <jk>null</jk> if the specified string is <jk>null</jk> or 
empty, or the same string if not.
+        */
+       public static String nullIfEmpty(String s) {
+               if (s == null || s.isEmpty())
+                       return null;
+               return s;
+       }
+
+       /**
+        * Removes escape characters (\) from the specified characters.
+        *
+        * @param s The string to remove escape characters from.
+        * @param toEscape The characters escaped.
+        * @return A new string if characters were removed, or the same string 
if not or if the input was <jk>null</jk>.
+        */
+       public static String unEscapeChars(String s, char[] toEscape) {
+               return unEscapeChars(s, toEscape, '\\');
+       }
+
+       /**
+        * Removes escape characters (specified by escapeChar) from the 
specified characters.
+        *
+        * @param s The string to remove escape characters from.
+        * @param toEscape The characters escaped.
+        * @param escapeChar The escape character.
+        * @return A new string if characters were removed, or the same string 
if not or if the input was <jk>null</jk>.
+        */
+       public static String unEscapeChars(String s, char[] toEscape, char 
escapeChar) {
+               if (s == null) return null;
+               if (s.length() == 0 || toEscape == null || toEscape.length == 0 
|| escapeChar == 0) return s;
+               StringBuffer sb = new StringBuffer(s.length());
+               char[] sArray = s.toCharArray();
+               for (int i = 0; i < sArray.length; i++) {
+                       char c = sArray[i];
+
+                       if (c == escapeChar) {
+                               if (i+1 != sArray.length) {
+                                       char c2 = sArray[i+1];
+                                       boolean isOneOf = false;
+                                       for (int j = 0; j < toEscape.length && 
! isOneOf; j++)
+                                               isOneOf = (c2 == toEscape[j]);
+                                       if (isOneOf) {
+                                               i++;
+                                       } else if (c2 == escapeChar) {
+                                               sb.append(escapeChar);
+                                               i++;
+                                       }
+                               }
+                       }
+                       sb.append(sArray[i]);
+               }
+               return sb.toString();
+       }
+
+       /**
+        * Debug method for rendering non-ASCII character sequences.
+        *
+        * @param s The string to decode.
+        * @return A string with non-ASCII characters converted to 
<js>"[hex]"</js> sequences.
+        */
+       public static String decodeHex(String s) {
+               if (s == null)
+                       return null;
+               StringBuilder sb = new StringBuilder();
+               for (char c : s.toCharArray()) {
+                       if (c < ' ' || c > '~')
+                               sb.append("["+Integer.toHexString(c)+"]");
+                       else
+                               sb.append(c);
+               }
+               return sb.toString();
+       }
+
+       /**
+        * An efficient method for checking if a string starts with a character.
+        *
+        * @param s The string to check.  Can be <jk>null</jk>.
+        * @param c The character to check for.
+        * @return <jk>true</jk> if the specified string is not <jk>null</jk> 
and starts with the specified character.
+        */
+       public static boolean startsWith(String s, char c) {
+               if (s != null) {
+                       int i = s.length();
+                       if (i > 0)
+                               return s.charAt(0) == c;
+               }
+               return false;
+       }
+
+       /**
+        * An efficient method for checking if a string ends with a character.
+        *
+        * @param s The string to check.  Can be <jk>null</jk>.
+        * @param c The character to check for.
+        * @return <jk>true</jk> if the specified string is not <jk>null</jk> 
and ends with the specified character.
+        */
+       public static boolean endsWith(String s, char c) {
+               if (s != null) {
+                       int i = s.length();
+                       if (i > 0)
+                               return s.charAt(i-1) == c;
+               }
+               return false;
+       }
+
+       /**
+        * Tests two strings for equality, but gracefully handles nulls.
+        *
+        * @param s1 String 1.
+        * @param s2 String 2.
+        * @return <jk>true</jk> if the strings are equal.
+        */
+       public static boolean isEquals(String s1, String s2) {
+               if (s1 == null)
+                       return s2 == null;
+               if (s2 == null)
+                       return false;
+               return s1.equals(s2);
+       }
+
+       /**
+        * Shortcut for calling 
<code>base64Encode(in.getBytes(<js>"UTF-8"</js>))</code>
+        *
+        * @param in The input string to convert.
+        * @return The string converted to BASE-64 encoding.
+        */
+       public static String base64EncodeToString(String in) {
+               if (in == null)
+                       return null;
+               return base64Encode(in.getBytes(IOUtils.UTF8));
+       }
+
+       /**
+        * BASE64-encodes the specified byte array.
+        *
+        * @param in The input byte array to convert.
+        * @return The byte array converted to a BASE-64 encoded string.
+        */
+       public static String base64Encode(byte[] in) {
+               int outLength = (in.length * 4 + 2) / 3;   // Output length 
without padding
+               char[] out = new char[((in.length + 2) / 3) * 4];  // Length 
includes padding.
+               int iIn = 0;
+               int iOut = 0;
+               while (iIn < in.length) {
+                       int i0 = in[iIn++] & 0xff;
+                       int i1 = iIn < in.length ? in[iIn++] & 0xff : 0;
+                       int i2 = iIn < in.length ? in[iIn++] & 0xff : 0;
+                       int o0 = i0 >>> 2;
+                       int o1 = ((i0 & 3) << 4) | (i1 >>> 4);
+                       int o2 = ((i1 & 0xf) << 2) | (i2 >>> 6);
+                       int o3 = i2 & 0x3F;
+                       out[iOut++] = base64m1[o0];
+                       out[iOut++] = base64m1[o1];
+                       out[iOut] = iOut < outLength ? base64m1[o2] : '=';
+                       iOut++;
+                       out[iOut] = iOut < outLength ? base64m1[o3] : '=';
+                       iOut++;
+               }
+               return new String(out);
+       }
+
+       /**
+        * Shortcut for calling <code>base64Decode(String)</code> and 
converting the
+        *      result to a UTF-8 encoded string.
+        *
+        * @param in The BASE-64 encoded string to decode.
+        * @return The decoded string.
+        */
+       public static String base64DecodeToString(String in) {
+               byte[] b = base64Decode(in);
+               if (b == null)
+                       return null;
+               return new String(b, IOUtils.UTF8);
+       }
+
+       /**
+        * BASE64-decodes the specified string.
+        *
+        * @param in The BASE-64 encoded string.
+        * @return The decoded byte array.
+        */
+       public static byte[] base64Decode(String in) {
+               if (in == null)
+                       return null;
+
+               byte bIn[] = in.getBytes(IOUtils.UTF8);
+
+               if (bIn.length % 4 != 0)
+                       illegalArg("Invalid BASE64 string length.  Must be 
multiple of 4.");
+
+               // Strip out any trailing '=' filler characters.
+               int inLength = bIn.length;
+               while (inLength > 0 && bIn[inLength - 1] == '=')
+                       inLength--;
+
+               int outLength = (inLength * 3) / 4;
+               byte[] out = new byte[outLength];
+               int iIn = 0;
+               int iOut = 0;
+               while (iIn < inLength) {
+                       int i0 = bIn[iIn++];
+                       int i1 = bIn[iIn++];
+                       int i2 = iIn < inLength ? bIn[iIn++] : 'A';
+                       int i3 = iIn < inLength ? bIn[iIn++] : 'A';
+                       int b0 = base64m2[i0];
+                       int b1 = base64m2[i1];
+                       int b2 = base64m2[i2];
+                       int b3 = base64m2[i3];
+                       int o0 = (b0 << 2) | (b1 >>> 4);
+                       int o1 = ((b1 & 0xf) << 4) | (b2 >>> 2);
+                       int o2 = ((b2 & 3) << 6) | b3;
+                       out[iOut++] = (byte)o0;
+                       if (iOut < outLength)
+                               out[iOut++] = (byte)o1;
+                       if (iOut < outLength)
+                               out[iOut++] = (byte)o2;
+               }
+               return out;
+       }
+
+       /**
+        * Generated a random UUID with the specified number of characters.
+        * Characters are composed of lower-case ASCII letters and numbers only.
+        * This method conforms to the restrictions for hostnames as specified 
in <a href='https://tools.ietf.org/html/rfc952'>RFC 952</a>
+        * Since each character has 36 possible values, the square 
approximation formula for
+        *      the number of generated IDs that would produce a 50% chance of 
collision is:
+        * <code>sqrt(36^N)</code>.
+        * Dividing this number by 10 gives you an approximation of the number 
of generated IDs
+        *      needed to produce a <1% chance of collision.
+        * For example, given 5 characters, the number of generated IDs need to 
produce a <1% chance of
+        *      collision would be:
+        * <code>sqrt(36^5)/10=777</code>
+        *
+        * @param numchars The number of characters in the generated UUID.
+        * @return A new random UUID.
+        */
+       public static String generateUUID(int numchars) {
+               Random r = new Random();
+               StringBuilder sb = new StringBuilder(numchars);
+               for (int i = 0; i < numchars; i++) {
+                       int c = r.nextInt(36) + 97;
+                       if (c > 'z')
+                               c -= ('z'-'0'+1);
+                       sb.append((char)c);
+               }
+               return sb.toString();
+       }
+
+       /**
+        * Same as {@link String#trim()} but prevents 
<code>NullPointerExceptions</code>.
+        *
+        * @param s The string to trim.
+        * @return The trimmed string, or <jk>null</jk> if the string was 
<jk>null</jk>.
+        */
+       public static String trim(String s) {
+               if (s == null)
+                       return null;
+               return s.trim();
+       }
+
+       /**
+        * Parses an ISO8601 string into a date.
+        *
+        * @param date The date string.
+        * @return The parsed date.
+        * @throws IllegalArgumentException
+        */
+       @SuppressWarnings("nls")
+       public static Date parseISO8601Date(String date) throws 
IllegalArgumentException {
+               if (isEmpty(date))
+                       return null;
+               date = date.trim().replace(' ', 'T');  // Convert to 'standard' 
ISO8601
+               if (date.indexOf(',') != -1)  // Trim milliseconds
+                       date = date.substring(0, date.indexOf(','));
+               if (date.matches("\\d{4}"))
+                       date += "-01-01T00:00:00";
+               else if (date.matches("\\d{4}\\-\\d{2}"))
+                       date += "-01T00:00:00";
+               else if (date.matches("\\d{4}\\-\\d{2}\\-\\d{2}"))
+                       date += "T00:00:00";
+               else if (date.matches("\\d{4}\\-\\d{2}\\-\\d{2}T\\d{2}"))
+                       date += ":00:00";
+               else if 
(date.matches("\\d{4}\\-\\d{2}\\-\\d{2}T\\d{2}\\:\\d{2}"))
+                       date += ":00";
+               return DatatypeConverter.parseDateTime(date).getTime();
+       }
+
+       /**
+        * Simple utility for replacing variables of the form <js>"{key}"</js> 
with values
+        *      in the specified map.
+        * <p>
+        * Nested variables are supported in both the input string and map 
values.
+        * <p>
+        * If the map does not contain the specified value, the variable is not 
replaced.
+        *      <p>
+        *      <jk>null</jk> values in the map are treated as blank strings.
+        *
+        * @param s The string containing variables to replace.
+        * @param m The map containing the variable values.
+        * @return The new string with variables replaced, or the original 
string if it didn't have variables in it.
+        */
+       public static String replaceVars(String s, Map<String,Object> m) {
+
+               if (s.indexOf('{') == -1)
+                       return s;
+
+               int S1 = 1;        // Not in variable, looking for {
+               int S2 = 2;    // Found {, Looking for }
+
+               int state = S1;
+               boolean hasInternalVar = false;
+               int x = 0;
+               int depth = 0;
+               int length = s.length();
+               StringBuilder out = new StringBuilder();
+               for (int i = 0; i < length; i++) {
+                       char c = s.charAt(i);
+                       if (state == S1) {
+                               if (c == '{') {
+                                       state = S2;
+                                       x = i;
+                               } else {
+                                       out.append(c);
+                               }
+                       } else /* state == S2 */ {
+                               if (c == '{') {
+                                       depth++;
+                                       hasInternalVar = true;
+                               } else if (c == '}') {
+                                       if (depth > 0) {
+                                               depth--;
+                                       } else {
+                                               String key = s.substring(x+1, 
i);
+                                               key = (hasInternalVar ? 
replaceVars(key, m) : key);
+                                               hasInternalVar = false;
+                                               if (! m.containsKey(key))
+                                                       
out.append('{').append(key).append('}');
+                                               else {
+                                                       Object val = m.get(key);
+                                                       if (val == null)
+                                                               val = "";
+                                                       String v = 
val.toString();
+                                                       // If the replacement 
also contains variables, replace them now.
+                                                       if (v.indexOf('{') != 
-1)
+                                                               v = 
replaceVars(v, m);
+                                                       out.append(v);
+                                               }
+                                               state = 1;
+                                       }
+                               }
+                       }
+               }
+               return out.toString();
+       }
+
+       /**
+        * Returns <jk>true</jk> if the specified path string is prefixed with 
the specified prefix.
+        * <p>
+        * Examples:
+        * <p class='bcode'>
+        *      pathStartsWith(<js>"foo"</js>, <js>"foo"</js>);  <jc>// 
true</jc>
+        *      pathStartsWith(<js>"foo/bar"</js>, <js>"foo"</js>);  <jc>// 
true</jc>
+        *      pathStartsWith(<js>"foo2"</js>, <js>"foo"</js>);  <jc>// 
false</jc>
+        *      pathStartsWith(<js>"foo2"</js>, <js>""</js>);  <jc>// false</jc>
+        * </p>
+        *
+        * @param path The path to check.
+        * @param pathPrefix The prefix.
+        * @return <jk>true</jk> if the specified path string is prefixed with 
the specified prefix.
+        */
+       public static boolean pathStartsWith(String path, String pathPrefix) {
+               if (path == null || pathPrefix == null)
+                       return false;
+               if (path.startsWith(pathPrefix))
+                       return path.length() == pathPrefix.length() || 
path.charAt(pathPrefix.length()) == '/';
+               return false;
+       }
+
+       /**
+        * Same as {@link #pathStartsWith(String, String)} but returns 
<jk>true</jk> if at least one prefix matches.
+        * <p>
+        *
+        * @param path The path to check.
+        * @param pathPrefixes The prefixes.
+        * @return <jk>true</jk> if the specified path string is prefixed with 
any of the specified prefixes.
+        */
+       public static boolean pathStartsWith(String path, String[] 
pathPrefixes) {
+               for (String p : pathPrefixes)
+                       if (pathStartsWith(path, p))
+                               return true;
+               return false;
+       }
+
+       /**
+        * Replaces <js>"\\uXXXX"</js> character sequences with their unicode 
characters.
+        *
+        * @param s The string to replace unicode sequences in.
+        * @return A string with unicode sequences replaced.
+        */
+       public static String replaceUnicodeSequences(String s) {
+               if (s.indexOf('\\') == -1)
+                       return s;
+               Pattern p = Pattern.compile("\\\\u(\\p{XDigit}{4})");
+               Matcher m = p.matcher(s);
+               StringBuffer sb = new StringBuffer(s.length());
+               while (m.find()) {
+                       String ch = String.valueOf((char) 
Integer.parseInt(m.group(1), 16));
+                       m.appendReplacement(sb, Matcher.quoteReplacement(ch));
+               }
+               m.appendTail(sb);
+               return sb.toString();
+       }
+
+       /**
+        * Returns the specified field in a delimited string without splitting 
the string.
+        * <p>
+        * Equivalent to the following:
+        * <p class='bcode'>
+        *      String in = <js>"0,1,2"</js>;
+        *      String[] parts = in.split(<js>","</js>);
+        *      String p1 = (parts.<jk>length</jk> > 1 ? parts[1] : 
<js>""</js>);
+        *
+        * @param fieldNum The field number.  Zero-indexed.
+        * @param s The input string.
+        * @param delim The delimiter character.
+        * @return The field entry in the string, or a blank string if it 
doesn't exist or the string is null.
+        */
+       public static String getField(int fieldNum, String s, char delim) {
+               return getField(fieldNum, s, delim, "");
+       }
+
+       /**
+        * Same as {@link #getField(int, String, char)} except allows you to 
specify the default value.
+        *
+        * @param fieldNum The field number.  Zero-indexed.
+        * @param s The input string.
+        * @param delim The delimiter character.
+        * @param def The default value if the field does not exist.
+        * @return The field entry in the string, or the default value if it 
doesn't exist or the string is null.
+        */
+       public static String getField(int fieldNum, String s, char delim, 
String def) {
+               if (s == null || fieldNum < 0)
+                       return def;
+               int start = 0;
+               for (int i = 0; i < s.length(); i++) {
+                       char c = s.charAt(i);
+                       if (c == delim) {
+                               fieldNum--;
+                               if (fieldNum == 0)
+                                       start = i+1;
+                       }
+                       if (fieldNum < 0)
+                               return s.substring(start, i);
+               }
+               if (start == 0)
+                       return def;
+               return s.substring(start);
+       }
+}

http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/e6bf97a8/juneau-core/src/main/java/org/apache/juneau/internal/TeeOutputStream.java
----------------------------------------------------------------------
diff --git 
a/juneau-core/src/main/java/org/apache/juneau/internal/TeeOutputStream.java 
b/juneau-core/src/main/java/org/apache/juneau/internal/TeeOutputStream.java
new file mode 100644
index 0000000..0dc6b7d
--- /dev/null
+++ b/juneau-core/src/main/java/org/apache/juneau/internal/TeeOutputStream.java
@@ -0,0 +1,163 @@
+/***************************************************************************************************************************
+ * 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.internal;
+
+import java.io.*;
+import java.util.*;
+
+/**
+ * Output stream that can send output to multiple output streams.
+ *
+ * @author James Bognar ([email protected])
+ */
+public class TeeOutputStream extends OutputStream {
+       private OutputStream[] outputStreams = new OutputStream[0];
+       private Map<String,OutputStream> outputStreamMap;
+
+       /**
+        * Constructor.
+        *
+        * @param outputStreams The list of output streams.
+        */
+       public TeeOutputStream(OutputStream...outputStreams) {
+               this.outputStreams = outputStreams;
+       }
+
+       /**
+        * Constructor.
+        *
+        * @param outputStreams The list of output streams.
+        */
+       public TeeOutputStream(Collection<OutputStream> outputStreams) {
+               this.outputStreams = outputStreams.toArray(new 
OutputStream[outputStreams.size()]);
+       }
+
+       /**
+        * Adds an output stream to this tee output stream.
+        *
+        * @param os The output stream to add to this tee output stream.
+        * @param close If <jk>false</jk>, then calling {@link #close()} on 
this stream
+        *      will not filter to the specified output stream.
+        * @return This object (for method chaining).
+        */
+       public TeeOutputStream add(OutputStream os, boolean close) {
+               if (os == null)
+                       return this;
+               if (! close)
+                       os = new NoCloseOutputStream(os);
+               if (os == this)
+                       throw new RuntimeException("Cannot add this output 
stream to itself.");
+               for (OutputStream os2 : outputStreams)
+                       if (os2 == os)
+                               throw new RuntimeException("Cannot add this 
output stream again.");
+               if (os instanceof TeeOutputStream) {
+                       for (OutputStream os2 : 
((TeeOutputStream)os).outputStreams)
+                               add(os2, true);
+               } else {
+                       outputStreams = ArrayUtils.append(outputStreams, os);
+               }
+               return this;
+       }
+
+       /**
+        * Returns the output stream identified through the <code>id</code> 
parameter
+        * passed in through the {@link #add(String, OutputStream, boolean)} 
method.
+        *
+        * @param id The ID associated with the output stream.
+        * @return The output stream, or <jk>null</jk> if no identifier was 
specified when the output stream was added.
+        */
+       public OutputStream getOutputStream(String id) {
+               if (outputStreamMap != null)
+                       return outputStreamMap.get(id);
+               return null;
+       }
+
+       /**
+        * Same as {@link #add(OutputStream, boolean)} but associates the 
stream with an identifier
+        * so the stream can be retrieved through {@link 
#getOutputStream(String)}.
+        *
+        * @param id The ID to associate the output stream with.
+        * @param os The output stream to add.
+        * @param close Close the specified stream afterwards.
+        * @return This object (for method chaining).
+        */
+       public TeeOutputStream add(String id, OutputStream os, boolean close) {
+               if (id != null) {
+                       if (outputStreamMap == null)
+                               outputStreamMap = new 
TreeMap<String,OutputStream>();
+                       outputStreamMap.put(id, os);
+               }
+               return add(os, close);
+       }
+
+       /**
+        * Returns the number of inner streams in this tee stream.
+        *
+        * @return The number of streams in this tee stream.
+        */
+       public int size() {
+               return outputStreams.length;
+       }
+
+       @Override /* OutputStream */
+       public void write(int b) throws IOException {
+               for (OutputStream os : outputStreams)
+                       os.write(b);
+       }
+
+       @Override /* OutputStream */
+       public void write(byte b[], int off, int len) throws IOException {
+               for (OutputStream os : outputStreams)
+                       os.write(b, off, len);
+       }
+
+       @Override /* OutputStream */
+       public void flush() throws IOException {
+               for (OutputStream os : outputStreams)
+                       os.flush();
+       }
+
+       @Override /* OutputStream */
+       public void close() throws IOException {
+               for (OutputStream os : outputStreams)
+                       os.close();
+       }
+
+       private static class NoCloseOutputStream extends OutputStream {
+               private OutputStream os;
+
+               private NoCloseOutputStream(OutputStream os) {
+                       this.os = os;
+               }
+
+               @Override /* OutputStream */
+               public void write(int b) throws IOException {
+                       os.write(b);
+               }
+
+               @Override /* OutputStream */
+               public void write(byte b[], int off, int len) throws 
IOException {
+                       os.write(b, off, len);
+               }
+
+               @Override /* OutputStream */
+               public void flush() throws IOException {
+                       os.flush();
+               }
+
+               @Override /* OutputStream */
+               public void close() throws IOException {
+                       // Do nothing.
+               }
+       }
+}

http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/e6bf97a8/juneau-core/src/main/java/org/apache/juneau/internal/TeeWriter.java
----------------------------------------------------------------------
diff --git 
a/juneau-core/src/main/java/org/apache/juneau/internal/TeeWriter.java 
b/juneau-core/src/main/java/org/apache/juneau/internal/TeeWriter.java
new file mode 100644
index 0000000..2e9dffa
--- /dev/null
+++ b/juneau-core/src/main/java/org/apache/juneau/internal/TeeWriter.java
@@ -0,0 +1,165 @@
+/***************************************************************************************************************************
+ * 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.internal;
+
+import java.io.*;
+import java.util.*;
+
+
+/**
+ * Writer that can send output to multiple writers.
+ *
+ * @author James Bognar ([email protected])
+ */
+public class TeeWriter extends Writer {
+       private Writer[] writers = new Writer[0];
+       private Map<String,Writer> writerMap;
+
+       /**
+        * Constructor.
+        *
+        * @param writers The list of writers.
+        */
+       public TeeWriter(Writer...writers) {
+               this.writers = writers;
+       }
+
+       /**
+        * Constructor.
+        *
+        * @param writers The list of writers.
+        */
+       public TeeWriter(Collection<Writer> writers) {
+               this.writers = writers.toArray(new Writer[writers.size()]);
+       }
+
+       /**
+        * Adds a writer to this tee writer.
+        *
+        * @param w The writer to add to this tee writer.
+        * @param close If <jk>false</jk>, then calling {@link #close()} on 
this tee writer
+        *      will not filter to the specified writer.
+        * @return This object (for method chaining).
+        */
+       public TeeWriter add(Writer w, boolean close) {
+               if (w == null)
+                       return this;
+               if (! close)
+                       w = new NoCloseWriter(w);
+               if (w == this)
+                       throw new RuntimeException("Cannot add this writer to 
itself.");
+               for (Writer w2 : writers)
+                       if (w2 == w)
+                               throw new RuntimeException("Cannot add this 
writer again.");
+               if (w instanceof TeeWriter) {
+                       for (Writer w2 : ((TeeWriter)w).writers)
+                               add(w2, true);
+               } else {
+                       writers = ArrayUtils.append(writers, w);
+               }
+               return this;
+       }
+
+       /**
+        * Same as {@link #add(Writer, boolean)} but associates the writer with 
an identifier
+        * so the writer can be retrieved through {@link #getWriter(String)}.
+        *
+        * @param id The ID to associate the writer with.
+        * @param w The writer to add.
+        * @param close Close the specified writer afterwards.
+        * @return This object (for method chaining).
+        */
+       public TeeWriter add(String id, Writer w, boolean close) {
+               if (id != null) {
+                       if (writerMap == null)
+                               writerMap = new TreeMap<String,Writer>();
+                       writerMap.put(id, w);
+               }
+               return add(w, close);
+       }
+
+       /**
+        * Returns the number of inner writers in this tee writer.
+        *
+        * @return The number of writers.
+        */
+       public int size() {
+               return writers.length;
+       }
+
+       /**
+        * Returns the writer identified through the <code>id</code> parameter
+        * passed in through the {@link #add(String, Writer, boolean)} method.
+        *
+        * @param id The ID associated with the writer.
+        * @return The writer, or <jk>null</jk> if no identifier was specified 
when the writer was added.
+        */
+       public Writer getWriter(String id) {
+               if (writerMap != null)
+                       return writerMap.get(id);
+               return null;
+       }
+
+       @Override /* Writer */
+       public void write(char[] cbuf, int off, int len) throws IOException {
+               for (Writer w : writers)
+                       if (w != null)
+                       w.write(cbuf, off, len);
+       }
+
+       @Override /* Writer */
+       public void flush() throws IOException {
+               for (Writer w : writers)
+                       if (w != null)
+                       w.flush();
+       }
+
+       @Override /* Writer */
+       public void close() throws IOException {
+               IOException e = null;
+               for (Writer w : writers) {
+                       if (w != null) {
+                               try {
+                       w.close();
+                               } catch (IOException e2) {
+                                       e = e2;
+                               }
+                       }
+               }
+               if (e != null)
+                       throw e;
+       }
+
+       private static class NoCloseWriter extends Writer {
+               private Writer writer;
+
+               private NoCloseWriter(Writer writer) {
+                       this.writer = writer;
+               }
+
+               @Override /* Writer */
+               public void write(char[] cbuf, int off, int len) throws 
IOException {
+                       writer.write(cbuf, off, len);
+               }
+
+               @Override /* Writer */
+               public void flush() throws IOException {
+                       writer.flush();
+               }
+
+               @Override /* Writer */
+               public void close() throws IOException {
+                       // Do nothing.
+               }
+       }
+}

http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/e6bf97a8/juneau-core/src/main/java/org/apache/juneau/internal/ThrowableUtils.java
----------------------------------------------------------------------
diff --git 
a/juneau-core/src/main/java/org/apache/juneau/internal/ThrowableUtils.java 
b/juneau-core/src/main/java/org/apache/juneau/internal/ThrowableUtils.java
new file mode 100644
index 0000000..469dd2a
--- /dev/null
+++ b/juneau-core/src/main/java/org/apache/juneau/internal/ThrowableUtils.java
@@ -0,0 +1,84 @@
+/***************************************************************************************************************************
+ * 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.internal;
+
+import java.text.*;
+
+/**
+ * Various utility methods for creating and working with throwables.
+ *
+ * @author James Bognar ([email protected])
+ */
+public class ThrowableUtils {
+
+       /**
+        * Throws an {@link IllegalArgumentException} if the specified object 
is <jk>null</jk>.
+        *
+        * @param o The object to check.
+        * @param msg The message of the IllegalArgumentException.
+        * @param args {@link MessageFormat}-style arguments in the message.
+        * @throws IllegalArgumentException
+        */
+       public static void assertNotNull(Object o, String msg, Object...args) 
throws IllegalArgumentException {
+               if (o == null)
+                       throw new 
IllegalArgumentException(MessageFormat.format(msg, args));
+       }
+
+       /**
+        * Throws an {@link IllegalArgumentException} if the specified field is 
<jk>null</jk>.
+        *
+        * @param fieldValue The object to check.
+        * @param fieldName The name of the field.
+        * @throws IllegalArgumentException
+        */
+       public static void assertFieldNotNull(Object fieldValue, String 
fieldName) throws IllegalArgumentException {
+               if (fieldValue == null)
+                       throw new IllegalArgumentException("Field '" + 
fieldName + "' cannot be null.");
+       }
+
+       /**
+        * Throws an {@link IllegalArgumentException} if the specified field is 
<code>&lt;=0</code>.
+        *
+        * @param fieldValue The object to check.
+        * @param fieldName The name of the field.
+        * @throws IllegalArgumentException
+        */
+       public static void assertFieldPositive(int fieldValue, String 
fieldName) throws IllegalArgumentException {
+               if (fieldValue <= 0)
+                       throw new IllegalArgumentException("Field '" + 
fieldName + "' must be a positive integer.");
+       }
+
+       /**
+        * Shortcut for calling <code><jk>new</jk> 
IllegalArgumentException(MessageFormat.<jsm>format</jsm>(msg, args));</code>
+        *
+        * @param msg The message of the IllegalArgumentException.
+        * @param args {@link MessageFormat}-style arguments in the message.
+        * @throws IllegalArgumentException
+        */
+       public static void illegalArg(String msg, Object...args) throws 
IllegalArgumentException {
+               throw new IllegalArgumentException(MessageFormat.format(msg, 
args));
+       }
+
+       /**
+        * Throws an exception if the specified thread ID is not the same as 
the current thread.
+        *
+        * @param threadId The ID of the thread to compare against.
+        * @param msg The message of the IllegalStateException.
+        * @param args {@link IllegalStateException}-style arguments in the 
message.
+        * @throws IllegalStateException
+        */
+       public static void assertSameThread(long threadId, String msg, 
Object...args) throws IllegalStateException {
+               if (Thread.currentThread().getId() != threadId)
+                       throw new 
IllegalArgumentException(MessageFormat.format(msg, args));
+       }
+}

http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/e6bf97a8/juneau-core/src/main/java/org/apache/juneau/internal/Utils.java
----------------------------------------------------------------------
diff --git a/juneau-core/src/main/java/org/apache/juneau/internal/Utils.java 
b/juneau-core/src/main/java/org/apache/juneau/internal/Utils.java
new file mode 100644
index 0000000..ddfe685
--- /dev/null
+++ b/juneau-core/src/main/java/org/apache/juneau/internal/Utils.java
@@ -0,0 +1,38 @@
+/***************************************************************************************************************************
+ * 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.internal;
+
+/**
+ * Various utility methods.
+ *
+ * @author James Bognar ([email protected])
+ */
+public class Utils {
+
+       /**
+        * Compare two integers numerically.
+        *
+        * @param i1 Integer #1
+        * @param i2 Integer #2
+     * @return the value <code>0</code> if Integer #1 is
+     *                 equal to Integer #2; a value less than
+     *                 <code>0</code> if Integer #1 numerically less
+     *                 than Integer #2; and a value greater
+     *                 than <code>0</code> if Integer #1 is numerically
+     *                  greater than Integer #2 (signed
+     *                  comparison).
+        */
+       public static final int compare(int i1, int i2) {
+               return (i1<i2 ? -1 : (i1==i2 ? 0 : 1));
+       }
+}

http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/e6bf97a8/juneau-core/src/main/java/org/apache/juneau/internal/Version.java
----------------------------------------------------------------------
diff --git a/juneau-core/src/main/java/org/apache/juneau/internal/Version.java 
b/juneau-core/src/main/java/org/apache/juneau/internal/Version.java
new file mode 100644
index 0000000..8d16e46
--- /dev/null
+++ b/juneau-core/src/main/java/org/apache/juneau/internal/Version.java
@@ -0,0 +1,122 @@
+/***************************************************************************************************************************
+ * 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.internal;
+
+import static org.apache.juneau.internal.StringUtils.*;
+
+/**
+ * Represents a version string such as <js>"1.2"</js> or <js>"1.2.3"</js>
+ * <p>
+ * Used to compare version numbers.
+ *
+ * @author James Bognar ([email protected])
+ */
+public class Version {
+
+       private int[] parts;
+
+       /**
+        * Constructor
+        *
+        * @param versionString - A string of the form <js>"#.#..."</js> where 
there can be any number of parts.
+        *      <br>Valid values:
+        *      <ul>
+        *              <li><js>"1.2"</js>
+        *              <li><js>"1.2.3"</js>
+        *              <li><js>"0.1"</js>
+        *              <li><js>".1"</js>
+        *      </ul>
+        *      Any parts that are not numeric are interpreted as {@link 
Integer#MAX_VALUE}
+        */
+       public Version(String versionString) {
+               if (isEmpty(versionString))
+                       versionString = "0";
+               String[] sParts = split(versionString, '.');
+               parts = new int[sParts.length];
+               for (int i = 0; i < sParts.length; i++) {
+                       try {
+                               parts[i] = sParts[i].isEmpty() ? 0 : 
Integer.parseInt(sParts[i]);
+                       } catch (NumberFormatException e) {
+                               parts[i] = Integer.MAX_VALUE;
+                       }
+               }
+       }
+
+       /**
+        * Returns <jk>true</jk> if the specified version is at least this 
version.
+        * <p>
+        * Note that the following is true:
+        * <p class='bcode'>
+        *      boolean b;
+        *      b = <jk>new</jk> Version(<js>"1.2"</js>).isAtLeast(<jk>new</jk> 
Version(<js>"1.2.3"</js>)); <jc>// == true </jc>
+        *      b = <jk>new</jk> 
Version(<js>"1.2.0"</js>).isAtLeast(<jk>new</jk> Version(<js>"1.2.3"</js>)); 
<jc>// == false</jc>
+        * </p>
+        *
+        * @param v The version to compare to.
+        * @param exclusive Match down-to-version but not including.
+        * @return <jk>true</jk> if the specified version is at least this 
version.
+        */
+       public boolean isAtLeast(Version v, boolean exclusive) {
+               for (int i = 0; i < Math.min(parts.length, v.parts.length); 
i++) {
+                       int c = v.parts[i] - parts[i];
+                       if (c > 0)
+                               return false;
+                       else if (c < 0)
+                               return true;
+               }
+               return ! exclusive;
+       }
+
+       /**
+        * Returns <jk>true</jk> if the specified version is at most this 
version.
+        * <p>
+        * Note that the following is true:
+        * <p class='bcode'>
+        *      boolean b;
+        *      b = <jk>new</jk> 
Version(<js>"1.2.3"</js>).isAtMost(<jk>new</jk> Version(<js>"1.2"</js>)); 
<jc>// == true </jc>
+        *      b = <jk>new</jk> 
Version(<js>"1.2.3"</js>).isAtMost(<jk>new</jk> Version(<js>"1.2.0"</js>)); 
<jc>// == false</jc>
+        * </p>
+        *
+        * @param v The version to compare to.
+        * @param exclusive Match up-to-version but not including.
+        * @return <jk>true</jk> if the specified version is at most this 
version.
+        */
+       public boolean isAtMost(Version v, boolean exclusive) {
+               return v.isAtLeast(this, exclusive);
+       }
+
+       /**
+        * Returns <jk>true</jk> if the specified version is equal to this 
version.
+        * <p>
+        * Note that the following is true:
+        * <p class='bcode'>
+        *      boolean b;
+        *      b = <jk>new</jk> Version(<js>"1.2.3"</js>).equals(<jk>new</jk> 
Version(<js>"1.2"</js>)); <jc>// == true </jc>
+        *      b = <jk>new</jk> Version(<js>"1.2"</js>).equals(<jk>new</jk> 
Version(<js>"1.2.3"</js>)); <jc>// == true</jc>
+        * </p>
+        *
+        * @param v The version to compare to.
+        * @return <jk>true</jk> if the specified version is equal to this 
version.
+        */
+       public boolean equals(Version v) {
+               for (int i = 0; i < Math.min(parts.length, v.parts.length); i++)
+                       if (v.parts[i] - parts[i] != 0)
+                               return false;
+               return true;
+       }
+
+       @Override /* Object */
+       public String toString() {
+               return join(parts, '.');
+       }
+}

http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/e6bf97a8/juneau-core/src/main/java/org/apache/juneau/internal/VersionRange.java
----------------------------------------------------------------------
diff --git 
a/juneau-core/src/main/java/org/apache/juneau/internal/VersionRange.java 
b/juneau-core/src/main/java/org/apache/juneau/internal/VersionRange.java
new file mode 100644
index 0000000..1caad9e
--- /dev/null
+++ b/juneau-core/src/main/java/org/apache/juneau/internal/VersionRange.java
@@ -0,0 +1,81 @@
+/***************************************************************************************************************************
+ * 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.internal;
+
+/**
+ * Represents an OSGi-style version range like <js>"1.2"</js> or 
<js>"[1.0,2.0)"</js>.
+ * <p>
+ *     The range can be any of the following formats:
+ * <ul>
+ *     <li><js>"[0,1.0)"</js> = Less than 1.0.  1.0 and 1.0.0 does not match.
+ *     <li><js>"[0,1.0]"</js> = Less than or equal to 1.0.  Note that 1.0.1 
will match.
+ *     <li><js>"1.0"</js> = At least 1.0.  1.0 and 2.0 will match.
+ * </ul>
+ *
+ * @author James Bognar ([email protected])
+ */
+public class VersionRange {
+
+       private final Version minVersion, maxVersion;
+       private final boolean minExclusive, maxExclusive;
+
+       /**
+        * Constructor.
+        *
+        * @param range The range string to parse.
+        */
+       public VersionRange(String range) {
+               range = range.trim();
+               if (! range.isEmpty()) {
+                       char c1 = range.charAt(0), c2 = 
range.charAt(range.length()-1);
+                       int c = range.indexOf(',');
+                       if (c > -1 && (c1 == '[' || c1 == '(') && (c2 == ']' || 
c2 == ')')) {
+                               String v1 = range.substring(1, c), v2 = 
range.substring(c+1, range.length()-1);
+                               //System.err.println("v1=["+v1+"], 
v2=["+v2+"]");
+                               minVersion = new Version(v1);
+                               maxVersion = new Version(v2);
+                               minExclusive = c1 == '(';
+                               maxExclusive = c2 == ')';
+                       } else {
+                               minVersion = new Version(range);
+                               maxVersion = null;
+                               minExclusive = maxExclusive = false;
+                       }
+               } else {
+                       minVersion = maxVersion = null;
+                       minExclusive = maxExclusive = false;
+               }
+       }
+
+       /**
+        * Returns <jk>true</jk> if the specified version string matches this 
version range.
+        *
+        * @param v The version string (e.g. <js>"1.2.3"</js>)
+        * @return <jk>true</jk> if the specified version string matches this 
version range.
+        */
+       public boolean matches(String v) {
+               if (StringUtils.isEmpty(v))
+                       return (minVersion == null && maxVersion == null);
+               Version ver = new Version(v);
+               if (minVersion != null && ! ver.isAtLeast(minVersion, 
minExclusive))
+                       return false;
+               if (maxVersion != null && ! ver.isAtMost(maxVersion, 
maxExclusive))
+                       return false;
+               return true;
+       }
+
+       @Override /* Object */
+       public String toString() {
+               return (minExclusive ? "(" : "[") + minVersion + ',' + 
maxVersion + (maxExclusive ? ")" : "]");
+       }
+}

http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/e6bf97a8/juneau-core/src/main/java/org/apache/juneau/jena/Constants.java
----------------------------------------------------------------------
diff --git a/juneau-core/src/main/java/org/apache/juneau/jena/Constants.java 
b/juneau-core/src/main/java/org/apache/juneau/jena/Constants.java
new file mode 100644
index 0000000..cd32372
--- /dev/null
+++ b/juneau-core/src/main/java/org/apache/juneau/jena/Constants.java
@@ -0,0 +1,95 @@
+/***************************************************************************************************************************
+ * 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.jena;
+
+import org.apache.juneau.serializer.*;
+
+/**
+ * Constants used by the {@link RdfSerializer} and {@link RdfParser} classes.
+ *
+ * @author James Bognar ([email protected])
+ */
+public final class Constants {
+
+       
//--------------------------------------------------------------------------------
+       // Built-in Jena languages.
+       
//--------------------------------------------------------------------------------
+
+       /** Jena language support: <js>"RDF/XML"</js>.*/
+       public static final String LANG_RDF_XML = "RDF/XML";
+
+       /** Jena language support: <js>"RDF/XML-ABBREV"</js>.*/
+       public static final String LANG_RDF_XML_ABBREV = "RDF/XML-ABBREV";
+
+       /** Jena language support: <js>"N-TRIPLE"</js>.*/
+       public static final String LANG_NTRIPLE = "N-TRIPLE";
+
+       /** Jena language support: <js>"TURTLE"</js>.*/
+       public static final String LANG_TURTLE = "TURTLE";
+
+       /** Jena language support: <js>"N3"</js>.*/
+       public static final String LANG_N3 = "N3";
+
+
+       
//--------------------------------------------------------------------------------
+       // Built-in Juneau properties.
+       
//--------------------------------------------------------------------------------
+
+       /**
+        * RDF property identifier <js>"items"</js>.
+        * <p>
+        * For resources that are collections, this property identifies the RDF 
Sequence
+        *      container for the items in the collection.
+        */
+       public static final String RDF_juneauNs_ITEMS = "items";
+
+       /**
+        * RDF property identifier <js>"root"</js>.
+        * <p>
+        * Property added to root nodes to help identify them as root elements 
during parsing.
+        * <p>
+        * Added if {@link RdfSerializerContext#RDF_addRootProperty} setting is 
enabled.
+        */
+       public static final String RDF_juneauNs_ROOT = "root";
+
+       /**
+        * RDF property identifier <js>"class"</js>.
+        * <p>
+        * Property added to bean resources to identify the class type.
+        * <p>
+        * Added if {@link SerializerContext#SERIALIZER_addClassAttrs} setting 
is enabled.
+        */
+       public static final String RDF_juneauNs_CLASS = "class";
+
+       /**
+        * RDF property identifier <js>"value"</js>.
+        * <p>
+        * Property added to nodes to identify a simple value.
+        */
+       public static final String RDF_juneauNs_VALUE = "value";
+
+       /**
+        * RDF resource that identifies a <jk>null</jk> value.
+        */
+       public static final String RDF_NIL = 
"http://www.w3.org/1999/02/22-rdf-syntax-ns#nil";;
+
+       /**
+        * RDF resource that identifies a <code>Seq</code> value.
+        */
+       public static final String RDF_SEQ = 
"http://www.w3.org/1999/02/22-rdf-syntax-ns#Seq";;
+
+       /**
+        * RDF resource that identifies a <code>Bag</code> value.
+        */
+       public static final String RDF_BAG = 
"http://www.w3.org/1999/02/22-rdf-syntax-ns#Bag";;
+}

http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/e6bf97a8/juneau-core/src/main/java/org/apache/juneau/jena/RdfBeanPropertyMeta.java
----------------------------------------------------------------------
diff --git 
a/juneau-core/src/main/java/org/apache/juneau/jena/RdfBeanPropertyMeta.java 
b/juneau-core/src/main/java/org/apache/juneau/jena/RdfBeanPropertyMeta.java
new file mode 100644
index 0000000..f3548d9
--- /dev/null
+++ b/juneau-core/src/main/java/org/apache/juneau/jena/RdfBeanPropertyMeta.java
@@ -0,0 +1,82 @@
+/***************************************************************************************************************************
+ * 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.jena;
+
+import static org.apache.juneau.jena.RdfCollectionFormat.*;
+
+import java.util.*;
+
+import org.apache.juneau.*;
+import org.apache.juneau.jena.annotation.*;
+import org.apache.juneau.xml.*;
+
+/**
+ * Metadata on bean properties specific to the RDF serializers and parsers 
pulled from the {@link Rdf @Rdf} annotation on the bean property.
+ *
+ * @author James Bognar ([email protected])
+ * @param <T> The bean class.
+ */
+public class RdfBeanPropertyMeta<T> {
+
+       private RdfCollectionFormat collectionFormat = DEFAULT;
+       private Namespace namespace = null;
+
+       /**
+        * Constructor.
+        *
+        * @param bpMeta The metadata of the bean property of this additional 
metadata.
+        */
+       public RdfBeanPropertyMeta(BeanPropertyMeta<T> bpMeta) {
+
+               List<Rdf> rdfs = bpMeta.findAnnotations(Rdf.class);
+               List<RdfSchema> schemas = 
bpMeta.findAnnotations(RdfSchema.class);
+
+               for (Rdf rdf : rdfs)
+                       if (collectionFormat == DEFAULT)
+                               collectionFormat = rdf.collectionFormat();
+
+               namespace = RdfUtils.findNamespace(rdfs, schemas);
+       }
+
+       /**
+        * Returns the RDF collection format of this property from the {@link 
Rdf#collectionFormat} annotation on this bean property.
+        *
+        * @return The RDF collection format, or {@link 
RdfCollectionFormat#DEFAULT} if annotation not specified.
+        */
+       protected RdfCollectionFormat getCollectionFormat() {
+               return collectionFormat;
+       }
+
+       /**
+        * Returns the RDF namespace associated with this bean property.
+        * <p>
+        *      Namespace is determined in the following order:
+        * <ol>
+        *      <li>{@link Rdf#prefix()} annotation defined on bean property 
field.
+        *      <li>{@link Rdf#prefix()} annotation defined on bean getter.
+        *      <li>{@link Rdf#prefix()} annotation defined on bean setter.
+        *      <li>{@link Rdf#prefix()} annotation defined on bean.
+        *      <li>{@link Rdf#prefix()} annotation defined on bean package.
+        *      <li>{@link Rdf#prefix()} annotation defined on bean 
superclasses.
+        *      <li>{@link Rdf#prefix()} annotation defined on bean superclass 
packages.
+        *      <li>{@link Rdf#prefix()} annotation defined on bean interfaces.
+        *      <li>{@link Rdf#prefix()} annotation defined on bean interface 
packages.
+        * </ol>
+        *
+        * @return The namespace associated with this bean property, or 
<jk>null</jk> if no namespace is
+        *      associated with it.
+        */
+       public Namespace getNamespace() {
+               return namespace;
+       }
+}

http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/e6bf97a8/juneau-core/src/main/java/org/apache/juneau/jena/RdfClassMeta.java
----------------------------------------------------------------------
diff --git a/juneau-core/src/main/java/org/apache/juneau/jena/RdfClassMeta.java 
b/juneau-core/src/main/java/org/apache/juneau/jena/RdfClassMeta.java
new file mode 100644
index 0000000..fcfcfc9
--- /dev/null
+++ b/juneau-core/src/main/java/org/apache/juneau/jena/RdfClassMeta.java
@@ -0,0 +1,86 @@
+/***************************************************************************************************************************
+ * 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.jena;
+
+import java.util.*;
+
+import org.apache.juneau.internal.*;
+import org.apache.juneau.jena.annotation.*;
+import org.apache.juneau.xml.*;
+
+/**
+ * Metadata on classes specific to the RDF serializers and parsers pulled from 
the {@link Rdf @Rdf} annotation on the class.
+ *
+ * @author James Bognar ([email protected])
+ */
+public class RdfClassMeta {
+
+       private final Rdf rdf;
+       private final RdfCollectionFormat collectionFormat;
+       private final Namespace namespace;
+
+       /**
+        * Constructor.
+        *
+        * @param c The class that this annotation is defined on.
+        */
+       public RdfClassMeta(Class<?> c) {
+               this.rdf = ReflectionUtils.getAnnotation(Rdf.class, c);
+               if (rdf != null) {
+                       collectionFormat = rdf.collectionFormat();
+               } else {
+                       collectionFormat = RdfCollectionFormat.DEFAULT;
+               }
+               List<Rdf> rdfs = ReflectionUtils.findAnnotations(Rdf.class, c);
+               List<RdfSchema> schemas = 
ReflectionUtils.findAnnotations(RdfSchema.class, c);
+               this.namespace = RdfUtils.findNamespace(rdfs, schemas);
+       }
+
+       /**
+        * Returns the {@link Rdf} annotation defined on the class.
+        *
+        * @return The value of the {@link Rdf} annotation, or <jk>null</jk> if 
annotation is not specified.
+        */
+       protected Rdf getAnnotation() {
+               return rdf;
+       }
+
+       /**
+        * Returns the {@link Rdf#collectionFormat()} annotation defined on the 
class.
+        *
+        * @return The value of the {@link Rdf#collectionFormat()} annotation, 
or <jk>null</jk> if annotation is not specified.
+        */
+       protected RdfCollectionFormat getCollectionFormat() {
+               return collectionFormat;
+       }
+
+       /**
+        * Returns the RDF namespace associated with this class.
+        * <p>
+        *      Namespace is determined in the following order:
+        * <ol>
+        *      <li>{@link Rdf#prefix()} annotation defined on class.
+        *      <li>{@link Rdf#prefix()} annotation defined on package.
+        *      <li>{@link Rdf#prefix()} annotation defined on superclasses.
+        *      <li>{@link Rdf#prefix()} annotation defined on superclass 
packages.
+        *      <li>{@link Rdf#prefix()} annotation defined on interfaces.
+        *      <li>{@link Rdf#prefix()} annotation defined on interface 
packages.
+        * </ol>
+        *
+        * @return The namespace associated with this class, or <jk>null</jk> 
if no namespace is
+        *      associated with it.
+        */
+       protected Namespace getNamespace() {
+               return namespace;
+       }
+}

http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/e6bf97a8/juneau-core/src/main/java/org/apache/juneau/jena/RdfCollectionFormat.java
----------------------------------------------------------------------
diff --git 
a/juneau-core/src/main/java/org/apache/juneau/jena/RdfCollectionFormat.java 
b/juneau-core/src/main/java/org/apache/juneau/jena/RdfCollectionFormat.java
new file mode 100644
index 0000000..5ce4842
--- /dev/null
+++ b/juneau-core/src/main/java/org/apache/juneau/jena/RdfCollectionFormat.java
@@ -0,0 +1,56 @@
+/***************************************************************************************************************************
+ * 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.jena;
+
+import org.apache.juneau.jena.annotation.*;
+
+/**
+ * Used in conjunction with the {@link Rdf#collectionFormat() 
@Rdf.collectionFormat()} annotation to fine-tune how
+ *     classes, beans, and bean properties are serialized, particularly 
collections.
+ * <p>
+ *
+ * @author James Bognar ([email protected])
+ */
+public enum RdfCollectionFormat {
+
+       /**
+        * Default formatting (default).
+        * <p>
+        * Inherit formatting from parent class or parent package.
+        *      If no formatting specified at any level, default is {@link 
#SEQ}.
+        */
+       DEFAULT,
+
+       /**
+        * Causes collections and arrays to be rendered as RDF sequences.
+        */
+       SEQ,
+
+       /**
+        * Causes collections and arrays to be rendered as RDF bags.
+        */
+       BAG,
+
+       /**
+        * Causes collections and arrays to be rendered as RDF lists.
+        */
+       LIST,
+
+       /**
+        * Causes collections and arrays to be rendered as multi-valued RDF 
properties instead of sequences.
+        * <p>
+        *      Note that enabling this setting will cause order of elements in 
the collection to be lost.
+        */
+       MULTI_VALUED;
+
+}
\ No newline at end of file

Reply via email to