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 2016/08/09 19:54:01 UTC
[28/51] [partial] incubator-juneau git commit: Rename project
directories.
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 (james.bognar@salesforce.com)
+ */
+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 (james.bognar@salesforce.com)
+ */
+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 (james.bognar@salesforce.com)
+ */
+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><=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 (james.bognar@salesforce.com)
+ */
+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 (james.bognar@salesforce.com)
+ */
+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 (james.bognar@salesforce.com)
+ */
+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 (james.bognar@salesforce.com)
+ */
+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 (james.bognar@salesforce.com)
+ * @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 (james.bognar@salesforce.com)
+ */
+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 (james.bognar@salesforce.com)
+ */
+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