();
+ int sizePlus1 = 1;
+ int i = 0, start = 0;
+ boolean match = false;
+ boolean lastMatch = false;
+ if (separatorChars == null) {
+ // Null separator means use whitespace
+ while (i < len) {
+ if (Character.isWhitespace(str.charAt(i))) {
+ if (match || preserveAllTokens) {
+ lastMatch = true;
+ if (sizePlus1++ == max) {
+ i = len;
+ lastMatch = false;
+ }
+ list.add(str.substring(start, i));
+ match = false;
+ }
+ start = ++i;
+ continue;
+ }
+ lastMatch = false;
+ match = true;
+ i++;
+ }
+ } else if (separatorChars.length() == 1) {
+ // Optimise 1 character case
+ char sep = separatorChars.charAt(0);
+ while (i < len) {
+ if (str.charAt(i) == sep) {
+ if (match || preserveAllTokens) {
+ lastMatch = true;
+ if (sizePlus1++ == max) {
+ i = len;
+ lastMatch = false;
+ }
+ list.add(str.substring(start, i));
+ match = false;
+ }
+ start = ++i;
+ continue;
+ }
+ lastMatch = false;
+ match = true;
+ i++;
+ }
+ } else {
+ // standard case
+ while (i < len) {
+ if (separatorChars.indexOf(str.charAt(i)) >= 0) {
+ if (match || preserveAllTokens) {
+ lastMatch = true;
+ if (sizePlus1++ == max) {
+ i = len;
+ lastMatch = false;
+ }
+ list.add(str.substring(start, i));
+ match = false;
+ }
+ start = ++i;
+ continue;
+ }
+ lastMatch = false;
+ match = true;
+ i++;
+ }
+ }
+ if (match || preserveAllTokens && lastMatch) {
+ list.add(str.substring(start, i));
+ }
+ return list.toArray(new String[list.size()]);
+ }
+
+ /**
+ * Splits a String by Character type as returned by
+ * {@code java.lang.Character.getType(char)}. Groups of contiguous
+ * characters of the same type are returned as complete tokens.
+ *
+ * StringUtils.splitByCharacterType(null) = null
+ * StringUtils.splitByCharacterType("") = []
+ * StringUtils.splitByCharacterType("ab de fg") = ["ab", " ", "de", " ", "fg"]
+ * StringUtils.splitByCharacterType("ab de fg") = ["ab", " ", "de", " ", "fg"]
+ * StringUtils.splitByCharacterType("ab:cd:ef") = ["ab", ":", "cd", ":", "ef"]
+ * StringUtils.splitByCharacterType("number5") = ["number", "5"]
+ * StringUtils.splitByCharacterType("fooBar") = ["foo", "B", "ar"]
+ * StringUtils.splitByCharacterType("foo200Bar") = ["foo", "200", "B", "ar"]
+ * StringUtils.splitByCharacterType("ASFRules") = ["ASFR", "ules"]
+ *
+ * @param str the String to split, may be {@code null}
+ * @return an array of parsed Strings, {@code null} if null String input
+ * @since 2.4
+ */
+ public static String[] splitByCharacterType(String str) {
+ return splitByCharacterType(str, false);
+ }
+
+ /**
+ * Splits a String by Character type as returned by
+ * {@code java.lang.Character.getType(char)}. Groups of contiguous
+ * characters of the same type are returned as complete tokens, with the
+ * following exception: the character of type
+ * {@code Character.UPPERCASE_LETTER}, if any, immediately
+ * preceding a token of type {@code Character.LOWERCASE_LETTER}
+ * will belong to the following token rather than to the preceding, if any,
+ * {@code Character.UPPERCASE_LETTER} token.
+ *
+ * StringUtils.splitByCharacterTypeCamelCase(null) = null
+ * StringUtils.splitByCharacterTypeCamelCase("") = []
+ * StringUtils.splitByCharacterTypeCamelCase("ab de fg") = ["ab", " ", "de", " ", "fg"]
+ * StringUtils.splitByCharacterTypeCamelCase("ab de fg") = ["ab", " ", "de", " ", "fg"]
+ * StringUtils.splitByCharacterTypeCamelCase("ab:cd:ef") = ["ab", ":", "cd", ":", "ef"]
+ * StringUtils.splitByCharacterTypeCamelCase("number5") = ["number", "5"]
+ * StringUtils.splitByCharacterTypeCamelCase("fooBar") = ["foo", "Bar"]
+ * StringUtils.splitByCharacterTypeCamelCase("foo200Bar") = ["foo", "200", "Bar"]
+ * StringUtils.splitByCharacterTypeCamelCase("ASFRules") = ["ASF", "Rules"]
+ *
+ * @param str the String to split, may be {@code null}
+ * @return an array of parsed Strings, {@code null} if null String input
+ * @since 2.4
+ */
+ public static String[] splitByCharacterTypeCamelCase(String str) {
+ return splitByCharacterType(str, true);
+ }
+
+ /**
+ * Splits a String by Character type as returned by
+ * {@code java.lang.Character.getType(char)}. Groups of contiguous
+ * characters of the same type are returned as complete tokens, with the
+ * following exception: if {@code camelCase} is {@code true},
+ * the character of type {@code Character.UPPERCASE_LETTER}, if any,
+ * immediately preceding a token of type {@code Character.LOWERCASE_LETTER}
+ * will belong to the following token rather than to the preceding, if any,
+ * {@code Character.UPPERCASE_LETTER} token.
+ * @param str the String to split, may be {@code null}
+ * @param camelCase whether to use so-called "camel-case" for letter types
+ * @return an array of parsed Strings, {@code null} if null String input
+ * @since 2.4
+ */
+ private static String[] splitByCharacterType(String str, boolean camelCase) {
+ if (str == null) {
+ return null;
+ }
+ if (str.length() == 0) {
+ return ArrayUtils.EMPTY_STRING_ARRAY;
+ }
+ char[] c = str.toCharArray();
+ List list = new ArrayList();
+ int tokenStart = 0;
+ int currentType = Character.getType(c[tokenStart]);
+ for (int pos = tokenStart + 1; pos < c.length; pos++) {
+ int type = Character.getType(c[pos]);
+ if (type == currentType) {
+ continue;
+ }
+ if (camelCase && type == Character.LOWERCASE_LETTER && currentType == Character.UPPERCASE_LETTER) {
+ int newTokenStart = pos - 1;
+ if (newTokenStart != tokenStart) {
+ list.add(new String(c, tokenStart, newTokenStart - tokenStart));
+ tokenStart = newTokenStart;
+ }
+ } else {
+ list.add(new String(c, tokenStart, pos - tokenStart));
+ tokenStart = pos;
+ }
+ currentType = type;
+ }
+ list.add(new String(c, tokenStart, c.length - tokenStart));
+ return list.toArray(new String[list.size()]);
+ }
+
+ // Joining
+ //-----------------------------------------------------------------------
+ /**
+ * Joins the elements of the provided array into a single String
+ * containing the provided list of elements.
+ *
+ * No separator is added to the joined String.
+ * Null objects or empty strings within the array are represented by
+ * empty strings.
+ *
+ *
+ * StringUtils.join(null) = null
+ * StringUtils.join([]) = ""
+ * StringUtils.join([null]) = ""
+ * StringUtils.join(["a", "b", "c"]) = "abc"
+ * StringUtils.join([null, "", "a"]) = "a"
+ *
+ *
+ * @param the specific type of values to join together
+ * @param elements the values to join together, may be null
+ * @return the joined String, {@code null} if null array input
+ * @since 2.0
+ * @since 3.0 Changed signature to use varargs
+ */
+ public static String join(T... elements) {
+ return join(elements, null);
+ }
+
+ /**
+ * Joins the elements of the provided array into a single String
+ * containing the provided list of elements.
+ *
+ * No delimiter is added before or after the list.
+ * Null objects or empty strings within the array are represented by
+ * empty strings.
+ *
+ *
+ * StringUtils.join(null, *) = null
+ * StringUtils.join([], *) = ""
+ * StringUtils.join([null], *) = ""
+ * StringUtils.join(["a", "b", "c"], ';') = "a;b;c"
+ * StringUtils.join(["a", "b", "c"], null) = "abc"
+ * StringUtils.join([null, "", "a"], ';') = ";;a"
+ *
+ *
+ * @param array the array of values to join together, may be null
+ * @param separator the separator character to use
+ * @return the joined String, {@code null} if null array input
+ * @since 2.0
+ */
+ public static String join(Object[] array, char separator) {
+ if (array == null) {
+ return null;
+ }
+
+ return join(array, separator, 0, array.length);
+ }
+
+ /**
+ * Joins the elements of the provided array into a single String
+ * containing the provided list of elements.
+ *
+ * No delimiter is added before or after the list.
+ * Null objects or empty strings within the array are represented by
+ * empty strings.
+ *
+ *
+ * StringUtils.join(null, *) = null
+ * StringUtils.join([], *) = ""
+ * StringUtils.join([null], *) = ""
+ * StringUtils.join(["a", "b", "c"], ';') = "a;b;c"
+ * StringUtils.join(["a", "b", "c"], null) = "abc"
+ * StringUtils.join([null, "", "a"], ';') = ";;a"
+ *
+ *
+ * @param array the array of values to join together, may be null
+ * @param separator the separator character to use
+ * @param startIndex the first index to start joining from. It is
+ * an error to pass in an end index past the end of the array
+ * @param endIndex the index to stop joining from (exclusive). It is
+ * an error to pass in an end index past the end of the array
+ * @return the joined String, {@code null} if null array input
+ * @since 2.0
+ */
+ public static String join(Object[] array, char separator, int startIndex, int endIndex) {
+ if (array == null) {
+ return null;
+ }
+ int noOfItems = endIndex - startIndex;
+ if (noOfItems <= 0) {
+ return EMPTY;
+ }
+
+ StringBuilder buf = new StringBuilder(noOfItems * 16);
+
+ for (int i = startIndex; i < endIndex; i++) {
+ if (i > startIndex) {
+ buf.append(separator);
+ }
+ if (array[i] != null) {
+ buf.append(array[i]);
+ }
+ }
+ return buf.toString();
+ }
+
+ /**
+ * Joins the elements of the provided array into a single String
+ * containing the provided list of elements.
+ *
+ * No delimiter is added before or after the list.
+ * A {@code null} separator is the same as an empty String ("").
+ * Null objects or empty strings within the array are represented by
+ * empty strings.
+ *
+ *
+ * StringUtils.join(null, *) = null
+ * StringUtils.join([], *) = ""
+ * StringUtils.join([null], *) = ""
+ * StringUtils.join(["a", "b", "c"], "--") = "a--b--c"
+ * StringUtils.join(["a", "b", "c"], null) = "abc"
+ * StringUtils.join(["a", "b", "c"], "") = "abc"
+ * StringUtils.join([null, "", "a"], ',') = ",,a"
+ *
+ *
+ * @param array the array of values to join together, may be null
+ * @param separator the separator character to use, null treated as ""
+ * @return the joined String, {@code null} if null array input
+ */
+ public static String join(Object[] array, String separator) {
+ if (array == null) {
+ return null;
+ }
+ return join(array, separator, 0, array.length);
+ }
+
+ /**
+ * Joins the elements of the provided array into a single String
+ * containing the provided list of elements.
+ *
+ * No delimiter is added before or after the list.
+ * A {@code null} separator is the same as an empty String ("").
+ * Null objects or empty strings within the array are represented by
+ * empty strings.
+ *
+ *
+ * StringUtils.join(null, *) = null
+ * StringUtils.join([], *) = ""
+ * StringUtils.join([null], *) = ""
+ * StringUtils.join(["a", "b", "c"], "--") = "a--b--c"
+ * StringUtils.join(["a", "b", "c"], null) = "abc"
+ * StringUtils.join(["a", "b", "c"], "") = "abc"
+ * StringUtils.join([null, "", "a"], ',') = ",,a"
+ *
+ *
+ * @param array the array of values to join together, may be null
+ * @param separator the separator character to use, null treated as ""
+ * @param startIndex the first index to start joining from. It is
+ * an error to pass in an end index past the end of the array
+ * @param endIndex the index to stop joining from (exclusive). It is
+ * an error to pass in an end index past the end of the array
+ * @return the joined String, {@code null} if null array input
+ */
+ public static String join(Object[] array, String separator, int startIndex, int endIndex) {
+ if (array == null) {
+ return null;
+ }
+ if (separator == null) {
+ separator = EMPTY;
+ }
+
+ // endIndex - startIndex > 0: Len = NofStrings *(len(firstString) + len(separator))
+ // (Assuming that all Strings are roughly equally long)
+ int noOfItems = endIndex - startIndex;
+ if (noOfItems <= 0) {
+ return EMPTY;
+ }
+
+ StringBuilder buf = new StringBuilder(noOfItems * 16);
+
+ for (int i = startIndex; i < endIndex; i++) {
+ if (i > startIndex) {
+ buf.append(separator);
+ }
+ if (array[i] != null) {
+ buf.append(array[i]);
+ }
+ }
+ return buf.toString();
+ }
+
+ /**
+ * Joins the elements of the provided {@code Iterator} into
+ * a single String containing the provided elements.
+ *
+ * No delimiter is added before or after the list. Null objects or empty
+ * strings within the iteration are represented by empty strings.
+ *
+ * See the examples here: {@link #join(Object[],char)}.
+ *
+ * @param iterator the {@code Iterator} of values to join together, may be null
+ * @param separator the separator character to use
+ * @return the joined String, {@code null} if null iterator input
+ * @since 2.0
+ */
+ public static String join(Iterator> iterator, char separator) {
+
+ // handle null, zero and one elements before building a buffer
+ if (iterator == null) {
+ return null;
+ }
+ if (!iterator.hasNext()) {
+ return EMPTY;
+ }
+ Object first = iterator.next();
+ if (!iterator.hasNext()) {
+ return ObjectUtils.toString(first);
+ }
+
+ // two or more elements
+ StringBuilder buf = new StringBuilder(256); // Java default is 16, probably too small
+ if (first != null) {
+ buf.append(first);
+ }
+
+ while (iterator.hasNext()) {
+ buf.append(separator);
+ Object obj = iterator.next();
+ if (obj != null) {
+ buf.append(obj);
+ }
+ }
+
+ return buf.toString();
+ }
+
+ /**
+ * Joins the elements of the provided {@code Iterator} into
+ * a single String containing the provided elements.
+ *
+ * No delimiter is added before or after the list.
+ * A {@code null} separator is the same as an empty String ("").
+ *
+ * See the examples here: {@link #join(Object[],String)}.
+ *
+ * @param iterator the {@code Iterator} of values to join together, may be null
+ * @param separator the separator character to use, null treated as ""
+ * @return the joined String, {@code null} if null iterator input
+ */
+ public static String join(Iterator> iterator, String separator) {
+
+ // handle null, zero and one elements before building a buffer
+ if (iterator == null) {
+ return null;
+ }
+ if (!iterator.hasNext()) {
+ return EMPTY;
+ }
+ Object first = iterator.next();
+ if (!iterator.hasNext()) {
+ return ObjectUtils.toString(first);
+ }
+
+ // two or more elements
+ StringBuilder buf = new StringBuilder(256); // Java default is 16, probably too small
+ if (first != null) {
+ buf.append(first);
+ }
+
+ while (iterator.hasNext()) {
+ if (separator != null) {
+ buf.append(separator);
+ }
+ Object obj = iterator.next();
+ if (obj != null) {
+ buf.append(obj);
+ }
+ }
+ return buf.toString();
+ }
+
+ /**
+ * Joins the elements of the provided {@code Iterable} into
+ * a single String containing the provided elements.
+ *
+ * No delimiter is added before or after the list. Null objects or empty
+ * strings within the iteration are represented by empty strings.
+ *
+ * See the examples here: {@link #join(Object[],char)}.
+ *
+ * @param iterable the {@code Iterable} providing the values to join together, may be null
+ * @param separator the separator character to use
+ * @return the joined String, {@code null} if null iterator input
+ * @since 2.3
+ */
+ public static String join(Iterable> iterable, char separator) {
+ if (iterable == null) {
+ return null;
+ }
+ return join(iterable.iterator(), separator);
+ }
+
+ /**
+ * Joins the elements of the provided {@code Iterable} into
+ * a single String containing the provided elements.
+ *
+ * No delimiter is added before or after the list.
+ * A {@code null} separator is the same as an empty String ("").
+ *
+ * See the examples here: {@link #join(Object[],String)}.
+ *
+ * @param iterable the {@code Iterable} providing the values to join together, may be null
+ * @param separator the separator character to use, null treated as ""
+ * @return the joined String, {@code null} if null iterator input
+ * @since 2.3
+ */
+ public static String join(Iterable> iterable, String separator) {
+ if (iterable == null) {
+ return null;
+ }
+ return join(iterable.iterator(), separator);
+ }
+
+ // Delete
+ //-----------------------------------------------------------------------
+ /**
+ * Deletes all whitespaces from a String as defined by
+ * {@link Character#isWhitespace(char)}.
+ *
+ *
+ * StringUtils.deleteWhitespace(null) = null
+ * StringUtils.deleteWhitespace("") = ""
+ * StringUtils.deleteWhitespace("abc") = "abc"
+ * StringUtils.deleteWhitespace(" ab c ") = "abc"
+ *
+ *
+ * @param str the String to delete whitespace from, may be null
+ * @return the String without whitespaces, {@code null} if null String input
+ */
+ public static String deleteWhitespace(String str) {
+ if (isEmpty(str)) {
+ return str;
+ }
+ int sz = str.length();
+ char[] chs = new char[sz];
+ int count = 0;
+ for (int i = 0; i < sz; i++) {
+ if (!Character.isWhitespace(str.charAt(i))) {
+ chs[count++] = str.charAt(i);
+ }
+ }
+ if (count == sz) {
+ return str;
+ }
+ return new String(chs, 0, count);
+ }
+
+ // Remove
+ //-----------------------------------------------------------------------
+ /**
+ * Removes a substring only if it is at the beginning of a source string,
+ * otherwise returns the source string.
+ *
+ * A {@code null} source string will return {@code null}.
+ * An empty ("") source string will return the empty string.
+ * A {@code null} search string will return the source string.
+ *
+ *
+ * StringUtils.removeStart(null, *) = null
+ * StringUtils.removeStart("", *) = ""
+ * StringUtils.removeStart(*, null) = *
+ * StringUtils.removeStart("www.domain.com", "www.") = "domain.com"
+ * StringUtils.removeStart("domain.com", "www.") = "domain.com"
+ * StringUtils.removeStart("www.domain.com", "domain") = "www.domain.com"
+ * StringUtils.removeStart("abc", "") = "abc"
+ *
+ *
+ * @param str the source String to search, may be null
+ * @param remove the String to search for and remove, may be null
+ * @return the substring with the string removed if found,
+ * {@code null} if null String input
+ * @since 2.1
+ */
+ public static String removeStart(String str, String remove) {
+ if (isEmpty(str) || isEmpty(remove)) {
+ return str;
+ }
+ if (str.startsWith(remove)){
+ return str.substring(remove.length());
+ }
+ return str;
+ }
+
+ /**
+ * Case insensitive removal of a substring if it is at the beginning of a source string,
+ * otherwise returns the source string.
+ *
+ * A {@code null} source string will return {@code null}.
+ * An empty ("") source string will return the empty string.
+ * A {@code null} search string will return the source string.
+ *
+ *
+ * StringUtils.removeStartIgnoreCase(null, *) = null
+ * StringUtils.removeStartIgnoreCase("", *) = ""
+ * StringUtils.removeStartIgnoreCase(*, null) = *
+ * StringUtils.removeStartIgnoreCase("www.domain.com", "www.") = "domain.com"
+ * StringUtils.removeStartIgnoreCase("www.domain.com", "WWW.") = "domain.com"
+ * StringUtils.removeStartIgnoreCase("domain.com", "www.") = "domain.com"
+ * StringUtils.removeStartIgnoreCase("www.domain.com", "domain") = "www.domain.com"
+ * StringUtils.removeStartIgnoreCase("abc", "") = "abc"
+ *
+ *
+ * @param str the source String to search, may be null
+ * @param remove the String to search for (case insensitive) and remove, may be null
+ * @return the substring with the string removed if found,
+ * {@code null} if null String input
+ * @since 2.4
+ */
+ public static String removeStartIgnoreCase(String str, String remove) {
+ if (isEmpty(str) || isEmpty(remove)) {
+ return str;
+ }
+ if (startsWithIgnoreCase(str, remove)) {
+ return str.substring(remove.length());
+ }
+ return str;
+ }
+
+ /**
+ * Removes a substring only if it is at the end of a source string,
+ * otherwise returns the source string.
+ *
+ * A {@code null} source string will return {@code null}.
+ * An empty ("") source string will return the empty string.
+ * A {@code null} search string will return the source string.
+ *
+ *
+ * StringUtils.removeEnd(null, *) = null
+ * StringUtils.removeEnd("", *) = ""
+ * StringUtils.removeEnd(*, null) = *
+ * StringUtils.removeEnd("www.domain.com", ".com.") = "www.domain.com"
+ * StringUtils.removeEnd("www.domain.com", ".com") = "www.domain"
+ * StringUtils.removeEnd("www.domain.com", "domain") = "www.domain.com"
+ * StringUtils.removeEnd("abc", "") = "abc"
+ *
+ *
+ * @param str the source String to search, may be null
+ * @param remove the String to search for and remove, may be null
+ * @return the substring with the string removed if found,
+ * {@code null} if null String input
+ * @since 2.1
+ */
+ public static String removeEnd(String str, String remove) {
+ if (isEmpty(str) || isEmpty(remove)) {
+ return str;
+ }
+ if (str.endsWith(remove)) {
+ return str.substring(0, str.length() - remove.length());
+ }
+ return str;
+ }
+
+ /**
+ * Case insensitive removal of a substring if it is at the end of a source string,
+ * otherwise returns the source string.
+ *
+ * A {@code null} source string will return {@code null}.
+ * An empty ("") source string will return the empty string.
+ * A {@code null} search string will return the source string.
+ *
+ *
+ * StringUtils.removeEndIgnoreCase(null, *) = null
+ * StringUtils.removeEndIgnoreCase("", *) = ""
+ * StringUtils.removeEndIgnoreCase(*, null) = *
+ * StringUtils.removeEndIgnoreCase("www.domain.com", ".com.") = "www.domain.com"
+ * StringUtils.removeEndIgnoreCase("www.domain.com", ".com") = "www.domain"
+ * StringUtils.removeEndIgnoreCase("www.domain.com", "domain") = "www.domain.com"
+ * StringUtils.removeEndIgnoreCase("abc", "") = "abc"
+ * StringUtils.removeEndIgnoreCase("www.domain.com", ".COM") = "www.domain")
+ * StringUtils.removeEndIgnoreCase("www.domain.COM", ".com") = "www.domain")
+ *
+ *
+ * @param str the source String to search, may be null
+ * @param remove the String to search for (case insensitive) and remove, may be null
+ * @return the substring with the string removed if found,
+ * {@code null} if null String input
+ * @since 2.4
+ */
+ public static String removeEndIgnoreCase(String str, String remove) {
+ if (isEmpty(str) || isEmpty(remove)) {
+ return str;
+ }
+ if (endsWithIgnoreCase(str, remove)) {
+ return str.substring(0, str.length() - remove.length());
+ }
+ return str;
+ }
+
+ /**
+ * Removes all occurrences of a substring from within the source string.
+ *
+ * A {@code null} source string will return {@code null}.
+ * An empty ("") source string will return the empty string.
+ * A {@code null} remove string will return the source string.
+ * An empty ("") remove string will return the source string.
+ *
+ *
+ * StringUtils.remove(null, *) = null
+ * StringUtils.remove("", *) = ""
+ * StringUtils.remove(*, null) = *
+ * StringUtils.remove(*, "") = *
+ * StringUtils.remove("queued", "ue") = "qd"
+ * StringUtils.remove("queued", "zz") = "queued"
+ *
+ *
+ * @param str the source String to search, may be null
+ * @param remove the String to search for and remove, may be null
+ * @return the substring with the string removed if found,
+ * {@code null} if null String input
+ * @since 2.1
+ */
+ public static String remove(String str, String remove) {
+ if (isEmpty(str) || isEmpty(remove)) {
+ return str;
+ }
+ return replace(str, remove, EMPTY, -1);
+ }
+
+ /**
+ * Removes all occurrences of a character from within the source string.
+ *
+ * A {@code null} source string will return {@code null}.
+ * An empty ("") source string will return the empty string.
+ *
+ *
+ * StringUtils.remove(null, *) = null
+ * StringUtils.remove("", *) = ""
+ * StringUtils.remove("queued", 'u') = "qeed"
+ * StringUtils.remove("queued", 'z') = "queued"
+ *
+ *
+ * @param str the source String to search, may be null
+ * @param remove the char to search for and remove, may be null
+ * @return the substring with the char removed if found,
+ * {@code null} if null String input
+ * @since 2.1
+ */
+ public static String remove(String str, char remove) {
+ if (isEmpty(str) || str.indexOf(remove) == INDEX_NOT_FOUND) {
+ return str;
+ }
+ char[] chars = str.toCharArray();
+ int pos = 0;
+ for (int i = 0; i < chars.length; i++) {
+ if (chars[i] != remove) {
+ chars[pos++] = chars[i];
+ }
+ }
+ return new String(chars, 0, pos);
+ }
+
+ // Replacing
+ //-----------------------------------------------------------------------
+ /**
+ * Replaces a String with another String inside a larger String, once.
+ *
+ * A {@code null} reference passed to this method is a no-op.
+ *
+ *
+ * StringUtils.replaceOnce(null, *, *) = null
+ * StringUtils.replaceOnce("", *, *) = ""
+ * StringUtils.replaceOnce("any", null, *) = "any"
+ * StringUtils.replaceOnce("any", *, null) = "any"
+ * StringUtils.replaceOnce("any", "", *) = "any"
+ * StringUtils.replaceOnce("aba", "a", null) = "aba"
+ * StringUtils.replaceOnce("aba", "a", "") = "ba"
+ * StringUtils.replaceOnce("aba", "a", "z") = "zba"
+ *
+ *
+ * @see #replace(String text, String searchString, String replacement, int max)
+ * @param text text to search and replace in, may be null
+ * @param searchString the String to search for, may be null
+ * @param replacement the String to replace with, may be null
+ * @return the text with any replacements processed,
+ * {@code null} if null String input
+ */
+ public static String replaceOnce(String text, String searchString, String replacement) {
+ return replace(text, searchString, replacement, 1);
+ }
+
+ /**
+ * Replaces all occurrences of a String within another String.
+ *
+ * A {@code null} reference passed to this method is a no-op.
+ *
+ *
+ * StringUtils.replace(null, *, *) = null
+ * StringUtils.replace("", *, *) = ""
+ * StringUtils.replace("any", null, *) = "any"
+ * StringUtils.replace("any", *, null) = "any"
+ * StringUtils.replace("any", "", *) = "any"
+ * StringUtils.replace("aba", "a", null) = "aba"
+ * StringUtils.replace("aba", "a", "") = "b"
+ * StringUtils.replace("aba", "a", "z") = "zbz"
+ *
+ *
+ * @see #replace(String text, String searchString, String replacement, int max)
+ * @param text text to search and replace in, may be null
+ * @param searchString the String to search for, may be null
+ * @param replacement the String to replace it with, may be null
+ * @return the text with any replacements processed,
+ * {@code null} if null String input
+ */
+ public static String replace(String text, String searchString, String replacement) {
+ return replace(text, searchString, replacement, -1);
+ }
+
+ /**
+ * Replaces a String with another String inside a larger String,
+ * for the first {@code max} values of the search String.
+ *
+ * A {@code null} reference passed to this method is a no-op.
+ *
+ *
+ * StringUtils.replace(null, *, *, *) = null
+ * StringUtils.replace("", *, *, *) = ""
+ * StringUtils.replace("any", null, *, *) = "any"
+ * StringUtils.replace("any", *, null, *) = "any"
+ * StringUtils.replace("any", "", *, *) = "any"
+ * StringUtils.replace("any", *, *, 0) = "any"
+ * StringUtils.replace("abaa", "a", null, -1) = "abaa"
+ * StringUtils.replace("abaa", "a", "", -1) = "b"
+ * StringUtils.replace("abaa", "a", "z", 0) = "abaa"
+ * StringUtils.replace("abaa", "a", "z", 1) = "zbaa"
+ * StringUtils.replace("abaa", "a", "z", 2) = "zbza"
+ * StringUtils.replace("abaa", "a", "z", -1) = "zbzz"
+ *
+ *
+ * @param text text to search and replace in, may be null
+ * @param searchString the String to search for, may be null
+ * @param replacement the String to replace it with, may be null
+ * @param max maximum number of values to replace, or {@code -1} if no maximum
+ * @return the text with any replacements processed,
+ * {@code null} if null String input
+ */
+ public static String replace(String text, String searchString, String replacement, int max) {
+ if (isEmpty(text) || isEmpty(searchString) || replacement == null || max == 0) {
+ return text;
+ }
+ int start = 0;
+ int end = text.indexOf(searchString, start);
+ if (end == INDEX_NOT_FOUND) {
+ return text;
+ }
+ int replLength = searchString.length();
+ int increase = replacement.length() - replLength;
+ increase = increase < 0 ? 0 : increase;
+ increase *= max < 0 ? 16 : max > 64 ? 64 : max;
+ StringBuilder buf = new StringBuilder(text.length() + increase);
+ while (end != INDEX_NOT_FOUND) {
+ buf.append(text.substring(start, end)).append(replacement);
+ start = end + replLength;
+ if (--max == 0) {
+ break;
+ }
+ end = text.indexOf(searchString, start);
+ }
+ buf.append(text.substring(start));
+ return buf.toString();
+ }
+
+ /**
+ *
+ * Replaces all occurrences of Strings within another String.
+ *
+ *
+ *
+ * A {@code null} reference passed to this method is a no-op, or if
+ * any "search string" or "string to replace" is null, that replace will be
+ * ignored. This will not repeat. For repeating replaces, call the
+ * overloaded method.
+ *
+ *
+ *
+ * StringUtils.replaceEach(null, *, *) = null
+ * StringUtils.replaceEach("", *, *) = ""
+ * StringUtils.replaceEach("aba", null, null) = "aba"
+ * StringUtils.replaceEach("aba", new String[0], null) = "aba"
+ * StringUtils.replaceEach("aba", null, new String[0]) = "aba"
+ * StringUtils.replaceEach("aba", new String[]{"a"}, null) = "aba"
+ * StringUtils.replaceEach("aba", new String[]{"a"}, new String[]{""}) = "b"
+ * StringUtils.replaceEach("aba", new String[]{null}, new String[]{"a"}) = "aba"
+ * StringUtils.replaceEach("abcde", new String[]{"ab", "d"}, new String[]{"w", "t"}) = "wcte"
+ * (example of how it does not repeat)
+ * StringUtils.replaceEach("abcde", new String[]{"ab", "d"}, new String[]{"d", "t"}) = "dcte"
+ *
+ *
+ * @param text
+ * text to search and replace in, no-op if null
+ * @param searchList
+ * the Strings to search for, no-op if null
+ * @param replacementList
+ * the Strings to replace them with, no-op if null
+ * @return the text with any replacements processed, {@code null} if
+ * null String input
+ * @throws IllegalArgumentException
+ * if the lengths of the arrays are not the same (null is ok,
+ * and/or size 0)
+ * @since 2.4
+ */
+ public static String replaceEach(String text, String[] searchList, String[] replacementList) {
+ return replaceEach(text, searchList, replacementList, false, 0);
+ }
+
+ /**
+ *
+ * Replaces all occurrences of Strings within another String.
+ *
+ *
+ *
+ * A {@code null} reference passed to this method is a no-op, or if
+ * any "search string" or "string to replace" is null, that replace will be
+ * ignored.
+ *
+ *
+ *
+ * StringUtils.replaceEach(null, *, *, *) = null
+ * StringUtils.replaceEach("", *, *, *) = ""
+ * StringUtils.replaceEach("aba", null, null, *) = "aba"
+ * StringUtils.replaceEach("aba", new String[0], null, *) = "aba"
+ * StringUtils.replaceEach("aba", null, new String[0], *) = "aba"
+ * StringUtils.replaceEach("aba", new String[]{"a"}, null, *) = "aba"
+ * StringUtils.replaceEach("aba", new String[]{"a"}, new String[]{""}, *) = "b"
+ * StringUtils.replaceEach("aba", new String[]{null}, new String[]{"a"}, *) = "aba"
+ * StringUtils.replaceEach("abcde", new String[]{"ab", "d"}, new String[]{"w", "t"}, *) = "wcte"
+ * (example of how it repeats)
+ * StringUtils.replaceEach("abcde", new String[]{"ab", "d"}, new String[]{"d", "t"}, false) = "dcte"
+ * StringUtils.replaceEach("abcde", new String[]{"ab", "d"}, new String[]{"d", "t"}, true) = "tcte"
+ * StringUtils.replaceEach("abcde", new String[]{"ab", "d"}, new String[]{"d", "ab"}, true) = IllegalStateException
+ * StringUtils.replaceEach("abcde", new String[]{"ab", "d"}, new String[]{"d", "ab"}, false) = "dcabe"
+ *
+ *
+ * @param text
+ * text to search and replace in, no-op if null
+ * @param searchList
+ * the Strings to search for, no-op if null
+ * @param replacementList
+ * the Strings to replace them with, no-op if null
+ * @return the text with any replacements processed, {@code null} if
+ * null String input
+ * @throws IllegalStateException
+ * if the search is repeating and there is an endless loop due
+ * to outputs of one being inputs to another
+ * @throws IllegalArgumentException
+ * if the lengths of the arrays are not the same (null is ok,
+ * and/or size 0)
+ * @since 2.4
+ */
+ public static String replaceEachRepeatedly(String text, String[] searchList, String[] replacementList) {
+ // timeToLive should be 0 if not used or nothing to replace, else it's
+ // the length of the replace array
+ int timeToLive = searchList == null ? 0 : searchList.length;
+ return replaceEach(text, searchList, replacementList, true, timeToLive);
+ }
+
+ /**
+ *
+ * Replaces all occurrences of Strings within another String.
+ *
+ *
+ *
+ * A {@code null} reference passed to this method is a no-op, or if
+ * any "search string" or "string to replace" is null, that replace will be
+ * ignored.
+ *
+ *
+ *
+ * StringUtils.replaceEach(null, *, *, *) = null
+ * StringUtils.replaceEach("", *, *, *) = ""
+ * StringUtils.replaceEach("aba", null, null, *) = "aba"
+ * StringUtils.replaceEach("aba", new String[0], null, *) = "aba"
+ * StringUtils.replaceEach("aba", null, new String[0], *) = "aba"
+ * StringUtils.replaceEach("aba", new String[]{"a"}, null, *) = "aba"
+ * StringUtils.replaceEach("aba", new String[]{"a"}, new String[]{""}, *) = "b"
+ * StringUtils.replaceEach("aba", new String[]{null}, new String[]{"a"}, *) = "aba"
+ * StringUtils.replaceEach("abcde", new String[]{"ab", "d"}, new String[]{"w", "t"}, *) = "wcte"
+ * (example of how it repeats)
+ * StringUtils.replaceEach("abcde", new String[]{"ab", "d"}, new String[]{"d", "t"}, false) = "dcte"
+ * StringUtils.replaceEach("abcde", new String[]{"ab", "d"}, new String[]{"d", "t"}, true) = "tcte"
+ * StringUtils.replaceEach("abcde", new String[]{"ab", "d"}, new String[]{"d", "ab"}, *) = IllegalStateException
+ *
+ *
+ * @param text
+ * text to search and replace in, no-op if null
+ * @param searchList
+ * the Strings to search for, no-op if null
+ * @param replacementList
+ * the Strings to replace them with, no-op if null
+ * @param repeat if true, then replace repeatedly
+ * until there are no more possible replacements or timeToLive < 0
+ * @param timeToLive
+ * if less than 0 then there is a circular reference and endless
+ * loop
+ * @return the text with any replacements processed, {@code null} if
+ * null String input
+ * @throws IllegalStateException
+ * if the search is repeating and there is an endless loop due
+ * to outputs of one being inputs to another
+ * @throws IllegalArgumentException
+ * if the lengths of the arrays are not the same (null is ok,
+ * and/or size 0)
+ * @since 2.4
+ */
+ private static String replaceEach(
+ String text, String[] searchList, String[] replacementList, boolean repeat, int timeToLive) {
+
+ // mchyzer Performance note: This creates very few new objects (one major goal)
+ // let me know if there are performance requests, we can create a harness to measure
+
+ if (text == null || text.length() == 0 || searchList == null ||
+ searchList.length == 0 || replacementList == null || replacementList.length == 0) {
+ return text;
+ }
+
+ // if recursing, this shouldn't be less than 0
+ if (timeToLive < 0) {
+ throw new IllegalStateException("Aborting to protect against StackOverflowError - " +
+ "output of one loop is the input of another");
+ }
+
+ int searchLength = searchList.length;
+ int replacementLength = replacementList.length;
+
+ // make sure lengths are ok, these need to be equal
+ if (searchLength != replacementLength) {
+ throw new IllegalArgumentException("Search and Replace array lengths don't match: "
+ + searchLength
+ + " vs "
+ + replacementLength);
+ }
+
+ // keep track of which still have matches
+ boolean[] noMoreMatchesForReplIndex = new boolean[searchLength];
+
+ // index on index that the match was found
+ int textIndex = -1;
+ int replaceIndex = -1;
+ int tempIndex = -1;
+
+ // index of replace array that will replace the search string found
+ // NOTE: logic duplicated below START
+ for (int i = 0; i < searchLength; i++) {
+ if (noMoreMatchesForReplIndex[i] || searchList[i] == null ||
+ searchList[i].length() == 0 || replacementList[i] == null) {
+ continue;
+ }
+ tempIndex = text.indexOf(searchList[i]);
+
+ // see if we need to keep searching for this
+ if (tempIndex == -1) {
+ noMoreMatchesForReplIndex[i] = true;
+ } else {
+ if (textIndex == -1 || tempIndex < textIndex) {
+ textIndex = tempIndex;
+ replaceIndex = i;
+ }
+ }
+ }
+ // NOTE: logic mostly below END
+
+ // no search strings found, we are done
+ if (textIndex == -1) {
+ return text;
+ }
+
+ int start = 0;
+
+ // get a good guess on the size of the result buffer so it doesn't have to double if it goes over a bit
+ int increase = 0;
+
+ // count the replacement text elements that are larger than their corresponding text being replaced
+ for (int i = 0; i < searchList.length; i++) {
+ if (searchList[i] == null || replacementList[i] == null) {
+ continue;
+ }
+ int greater = replacementList[i].length() - searchList[i].length();
+ if (greater > 0) {
+ increase += 3 * greater; // assume 3 matches
+ }
+ }
+ // have upper-bound at 20% increase, then let Java take over
+ increase = Math.min(increase, text.length() / 5);
+
+ StringBuilder buf = new StringBuilder(text.length() + increase);
+
+ while (textIndex != -1) {
+
+ for (int i = start; i < textIndex; i++) {
+ buf.append(text.charAt(i));
+ }
+ buf.append(replacementList[replaceIndex]);
+
+ start = textIndex + searchList[replaceIndex].length();
+
+ textIndex = -1;
+ replaceIndex = -1;
+ tempIndex = -1;
+ // find the next earliest match
+ // NOTE: logic mostly duplicated above START
+ for (int i = 0; i < searchLength; i++) {
+ if (noMoreMatchesForReplIndex[i] || searchList[i] == null ||
+ searchList[i].length() == 0 || replacementList[i] == null) {
+ continue;
+ }
+ tempIndex = text.indexOf(searchList[i], start);
+
+ // see if we need to keep searching for this
+ if (tempIndex == -1) {
+ noMoreMatchesForReplIndex[i] = true;
+ } else {
+ if (textIndex == -1 || tempIndex < textIndex) {
+ textIndex = tempIndex;
+ replaceIndex = i;
+ }
+ }
+ }
+ // NOTE: logic duplicated above END
+
+ }
+ int textLength = text.length();
+ for (int i = start; i < textLength; i++) {
+ buf.append(text.charAt(i));
+ }
+ String result = buf.toString();
+ if (!repeat) {
+ return result;
+ }
+
+ return replaceEach(result, searchList, replacementList, repeat, timeToLive - 1);
+ }
+
+ // Replace, character based
+ //-----------------------------------------------------------------------
+ /**
+ * Replaces all occurrences of a character in a String with another.
+ * This is a null-safe version of {@link String#replace(char, char)}.
+ *
+ * A {@code null} string input returns {@code null}.
+ * An empty ("") string input returns an empty string.
+ *
+ *
+ * StringUtils.replaceChars(null, *, *) = null
+ * StringUtils.replaceChars("", *, *) = ""
+ * StringUtils.replaceChars("abcba", 'b', 'y') = "aycya"
+ * StringUtils.replaceChars("abcba", 'z', 'y') = "abcba"
+ *
+ *
+ * @param str String to replace characters in, may be null
+ * @param searchChar the character to search for, may be null
+ * @param replaceChar the character to replace, may be null
+ * @return modified String, {@code null} if null string input
+ * @since 2.0
+ */
+ public static String replaceChars(String str, char searchChar, char replaceChar) {
+ if (str == null) {
+ return null;
+ }
+ return str.replace(searchChar, replaceChar);
+ }
+
+ /**
+ * Replaces multiple characters in a String in one go.
+ * This method can also be used to delete characters.
+ *
+ * For example:
+ * replaceChars("hello", "ho", "jy") = jelly.
+ *
+ * A {@code null} string input returns {@code null}.
+ * An empty ("") string input returns an empty string.
+ * A null or empty set of search characters returns the input string.
+ *
+ * The length of the search characters should normally equal the length
+ * of the replace characters.
+ * If the search characters is longer, then the extra search characters
+ * are deleted.
+ * If the search characters is shorter, then the extra replace characters
+ * are ignored.
+ *
+ *
+ * StringUtils.replaceChars(null, *, *) = null
+ * StringUtils.replaceChars("", *, *) = ""
+ * StringUtils.replaceChars("abc", null, *) = "abc"
+ * StringUtils.replaceChars("abc", "", *) = "abc"
+ * StringUtils.replaceChars("abc", "b", null) = "ac"
+ * StringUtils.replaceChars("abc", "b", "") = "ac"
+ * StringUtils.replaceChars("abcba", "bc", "yz") = "ayzya"
+ * StringUtils.replaceChars("abcba", "bc", "y") = "ayya"
+ * StringUtils.replaceChars("abcba", "bc", "yzx") = "ayzya"
+ *
+ *
+ * @param str String to replace characters in, may be null
+ * @param searchChars a set of characters to search for, may be null
+ * @param replaceChars a set of characters to replace, may be null
+ * @return modified String, {@code null} if null string input
+ * @since 2.0
+ */
+ public static String replaceChars(String str, String searchChars, String replaceChars) {
+ if (isEmpty(str) || isEmpty(searchChars)) {
+ return str;
+ }
+ if (replaceChars == null) {
+ replaceChars = EMPTY;
+ }
+ boolean modified = false;
+ int replaceCharsLength = replaceChars.length();
+ int strLength = str.length();
+ StringBuilder buf = new StringBuilder(strLength);
+ for (int i = 0; i < strLength; i++) {
+ char ch = str.charAt(i);
+ int index = searchChars.indexOf(ch);
+ if (index >= 0) {
+ modified = true;
+ if (index < replaceCharsLength) {
+ buf.append(replaceChars.charAt(index));
+ }
+ } else {
+ buf.append(ch);
+ }
+ }
+ if (modified) {
+ return buf.toString();
+ }
+ return str;
+ }
+
+ // Overlay
+ //-----------------------------------------------------------------------
+ /**
+ * Overlays part of a String with another String.
+ *
+ * A {@code null} string input returns {@code null}.
+ * A negative index is treated as zero.
+ * An index greater than the string length is treated as the string length.
+ * The start index is always the smaller of the two indices.
+ *
+ *
+ * StringUtils.overlay(null, *, *, *) = null
+ * StringUtils.overlay("", "abc", 0, 0) = "abc"
+ * StringUtils.overlay("abcdef", null, 2, 4) = "abef"
+ * StringUtils.overlay("abcdef", "", 2, 4) = "abef"
+ * StringUtils.overlay("abcdef", "", 4, 2) = "abef"
+ * StringUtils.overlay("abcdef", "zzzz", 2, 4) = "abzzzzef"
+ * StringUtils.overlay("abcdef", "zzzz", 4, 2) = "abzzzzef"
+ * StringUtils.overlay("abcdef", "zzzz", -1, 4) = "zzzzef"
+ * StringUtils.overlay("abcdef", "zzzz", 2, 8) = "abzzzz"
+ * StringUtils.overlay("abcdef", "zzzz", -2, -3) = "zzzzabcdef"
+ * StringUtils.overlay("abcdef", "zzzz", 8, 10) = "abcdefzzzz"
+ *
+ *
+ * @param str the String to do overlaying in, may be null
+ * @param overlay the String to overlay, may be null
+ * @param start the position to start overlaying at
+ * @param end the position to stop overlaying before
+ * @return overlayed String, {@code null} if null String input
+ * @since 2.0
+ */
+ public static String overlay(String str, String overlay, int start, int end) {
+ if (str == null) {
+ return null;
+ }
+ if (overlay == null) {
+ overlay = EMPTY;
+ }
+ int len = str.length();
+ if (start < 0) {
+ start = 0;
+ }
+ if (start > len) {
+ start = len;
+ }
+ if (end < 0) {
+ end = 0;
+ }
+ if (end > len) {
+ end = len;
+ }
+ if (start > end) {
+ int temp = start;
+ start = end;
+ end = temp;
+ }
+ return new StringBuilder(len + start - end + overlay.length() + 1)
+ .append(str.substring(0, start))
+ .append(overlay)
+ .append(str.substring(end))
+ .toString();
+ }
+
+ // Chomping
+ //-----------------------------------------------------------------------
+ /**
+ * Removes one newline from end of a String if it's there,
+ * otherwise leave it alone. A newline is "{@code \n}",
+ * "{@code \r}", or "{@code \r\n}".
+ *
+ * NOTE: This method changed in 2.0.
+ * It now more closely matches Perl chomp.
+ *
+ *
+ * StringUtils.chomp(null) = null
+ * StringUtils.chomp("") = ""
+ * StringUtils.chomp("abc \r") = "abc "
+ * StringUtils.chomp("abc\n") = "abc"
+ * StringUtils.chomp("abc\r\n") = "abc"
+ * StringUtils.chomp("abc\r\n\r\n") = "abc\r\n"
+ * StringUtils.chomp("abc\n\r") = "abc\n"
+ * StringUtils.chomp("abc\n\rabc") = "abc\n\rabc"
+ * StringUtils.chomp("\r") = ""
+ * StringUtils.chomp("\n") = ""
+ * StringUtils.chomp("\r\n") = ""
+ *
+ *
+ * @param str the String to chomp a newline from, may be null
+ * @return String without newline, {@code null} if null String input
+ */
+ public static String chomp(String str) {
+ if (isEmpty(str)) {
+ return str;
+ }
+
+ if (str.length() == 1) {
+ char ch = str.charAt(0);
+ if (ch == CharUtils.CR || ch == CharUtils.LF) {
+ return EMPTY;
+ }
+ return str;
+ }
+
+ int lastIdx = str.length() - 1;
+ char last = str.charAt(lastIdx);
+
+ if (last == CharUtils.LF) {
+ if (str.charAt(lastIdx - 1) == CharUtils.CR) {
+ lastIdx--;
+ }
+ } else if (last != CharUtils.CR) {
+ lastIdx++;
+ }
+ return str.substring(0, lastIdx);
+ }
+
+ /**
+ * Removes {@code separator} from the end of
+ * {@code str} if it's there, otherwise leave it alone.
+ *
+ * NOTE: This method changed in version 2.0.
+ * It now more closely matches Perl chomp.
+ * For the previous behavior, use {@link #substringBeforeLast(String, String)}.
+ * This method uses {@link String#endsWith(String)}.
+ *
+ *
+ * StringUtils.chomp(null, *) = null
+ * StringUtils.chomp("", *) = ""
+ * StringUtils.chomp("foobar", "bar") = "foo"
+ * StringUtils.chomp("foobar", "baz") = "foobar"
+ * StringUtils.chomp("foo", "foo") = ""
+ * StringUtils.chomp("foo ", "foo") = "foo "
+ * StringUtils.chomp(" foo", "foo") = " "
+ * StringUtils.chomp("foo", "foooo") = "foo"
+ * StringUtils.chomp("foo", "") = "foo"
+ * StringUtils.chomp("foo", null) = "foo"
+ *
+ *
+ * @param str the String to chomp from, may be null
+ * @param separator separator String, may be null
+ * @return String without trailing separator, {@code null} if null String input
+ * @deprecated This feature will be removed in Lang 4.0, use {@link StringUtils#removeEnd(String, String)} instead
+ */
+ @Deprecated
+ public static String chomp(String str, String separator) {
+ return removeEnd(str,separator);
+ }
+
+ // Chopping
+ //-----------------------------------------------------------------------
+ /**
+ * Remove the last character from a String.
+ *
+ * If the String ends in {@code \r\n}, then remove both
+ * of them.
+ *
+ *
+ * StringUtils.chop(null) = null
+ * StringUtils.chop("") = ""
+ * StringUtils.chop("abc \r") = "abc "
+ * StringUtils.chop("abc\n") = "abc"
+ * StringUtils.chop("abc\r\n") = "abc"
+ * StringUtils.chop("abc") = "ab"
+ * StringUtils.chop("abc\nabc") = "abc\nab"
+ * StringUtils.chop("a") = ""
+ * StringUtils.chop("\r") = ""
+ * StringUtils.chop("\n") = ""
+ * StringUtils.chop("\r\n") = ""
+ *
+ *
+ * @param str the String to chop last character from, may be null
+ * @return String without last character, {@code null} if null String input
+ */
+ public static String chop(String str) {
+ if (str == null) {
+ return null;
+ }
+ int strLen = str.length();
+ if (strLen < 2) {
+ return EMPTY;
+ }
+ int lastIdx = strLen - 1;
+ String ret = str.substring(0, lastIdx);
+ char last = str.charAt(lastIdx);
+ if (last == CharUtils.LF && ret.charAt(lastIdx - 1) == CharUtils.CR) {
+ return ret.substring(0, lastIdx - 1);
+ }
+ return ret;
+ }
+
+ // Conversion
+ //-----------------------------------------------------------------------
+
+ // Padding
+ //-----------------------------------------------------------------------
+ /**
+ * Repeat a String {@code repeat} times to form a
+ * new String.
+ *
+ *
+ * StringUtils.repeat(null, 2) = null
+ * StringUtils.repeat("", 0) = ""
+ * StringUtils.repeat("", 2) = ""
+ * StringUtils.repeat("a", 3) = "aaa"
+ * StringUtils.repeat("ab", 2) = "abab"
+ * StringUtils.repeat("a", -2) = ""
+ *
+ *
+ * @param str the String to repeat, may be null
+ * @param repeat number of times to repeat str, negative treated as zero
+ * @return a new String consisting of the original String repeated,
+ * {@code null} if null String input
+ */
+ public static String repeat(String str, int repeat) {
+ // Performance tuned for 2.0 (JDK1.4)
+
+ if (str == null) {
+ return null;
+ }
+ if (repeat <= 0) {
+ return EMPTY;
+ }
+ int inputLength = str.length();
+ if (repeat == 1 || inputLength == 0) {
+ return str;
+ }
+ if (inputLength == 1 && repeat <= PAD_LIMIT) {
+ return repeat(str.charAt(0), repeat);
+ }
+
+ int outputLength = inputLength * repeat;
+ switch (inputLength) {
+ case 1 :
+ return repeat(str.charAt(0), repeat);
+ case 2 :
+ char ch0 = str.charAt(0);
+ char ch1 = str.charAt(1);
+ char[] output2 = new char[outputLength];
+ for (int i = repeat * 2 - 2; i >= 0; i--, i--) {
+ output2[i] = ch0;
+ output2[i + 1] = ch1;
+ }
+ return new String(output2);
+ default :
+ StringBuilder buf = new StringBuilder(outputLength);
+ for (int i = 0; i < repeat; i++) {
+ buf.append(str);
+ }
+ return buf.toString();
+ }
+ }
+
+ /**
+ * Repeat a String {@code repeat} times to form a
+ * new String, with a String separator injected each time.
+ *
+ *
+ * StringUtils.repeat(null, null, 2) = null
+ * StringUtils.repeat(null, "x", 2) = null
+ * StringUtils.repeat("", null, 0) = ""
+ * StringUtils.repeat("", "", 2) = ""
+ * StringUtils.repeat("", "x", 3) = "xxx"
+ * StringUtils.repeat("?", ", ", 3) = "?, ?, ?"
+ *
+ *
+ * @param str the String to repeat, may be null
+ * @param separator the String to inject, may be null
+ * @param repeat number of times to repeat str, negative treated as zero
+ * @return a new String consisting of the original String repeated,
+ * {@code null} if null String input
+ * @since 2.5
+ */
+ public static String repeat(String str, String separator, int repeat) {
+ if(str == null || separator == null) {
+ return repeat(str, repeat);
+ } else {
+ // given that repeat(String, int) is quite optimized, better to rely on it than try and splice this into it
+ String result = repeat(str + separator, repeat);
+ return removeEnd(result, separator);
+ }
+ }
+
+ /**
+ * Returns padding using the specified delimiter repeated
+ * to a given length.
+ *
+ *
+ * StringUtils.repeat(0, 'e') = ""
+ * StringUtils.repeat(3, 'e') = "eee"
+ * StringUtils.repeat(-2, 'e') = ""
+ *
+ *
+ * Note: this method doesn't not support padding with
+ * Unicode Supplementary Characters
+ * as they require a pair of {@code char}s to be represented.
+ * If you are needing to support full I18N of your applications
+ * consider using {@link #repeat(String, int)} instead.
+ *
+ *
+ * @param ch character to repeat
+ * @param repeat number of times to repeat char, negative treated as zero
+ * @return String with repeated character
+ * @see #repeat(String, int)
+ */
+ public static String repeat(char ch, int repeat) {
+ char[] buf = new char[repeat];
+ for (int i = repeat - 1; i >= 0; i--) {
+ buf[i] = ch;
+ }
+ return new String(buf);
+ }
+
+ /**
+ * Right pad a String with spaces (' ').
+ *
+ * The String is padded to the size of {@code size}.
+ *
+ *
+ * StringUtils.rightPad(null, *) = null
+ * StringUtils.rightPad("", 3) = " "
+ * StringUtils.rightPad("bat", 3) = "bat"
+ * StringUtils.rightPad("bat", 5) = "bat "
+ * StringUtils.rightPad("bat", 1) = "bat"
+ * StringUtils.rightPad("bat", -1) = "bat"
+ *
+ *
+ * @param str the String to pad out, may be null
+ * @param size the size to pad to
+ * @return right padded String or original String if no padding is necessary,
+ * {@code null} if null String input
+ */
+ public static String rightPad(String str, int size) {
+ return rightPad(str, size, ' ');
+ }
+
+ /**
+ * Right pad a String with a specified character.
+ *
+ * The String is padded to the size of {@code size}.
+ *
+ *
+ * StringUtils.rightPad(null, *, *) = null
+ * StringUtils.rightPad("", 3, 'z') = "zzz"
+ * StringUtils.rightPad("bat", 3, 'z') = "bat"
+ * StringUtils.rightPad("bat", 5, 'z') = "batzz"
+ * StringUtils.rightPad("bat", 1, 'z') = "bat"
+ * StringUtils.rightPad("bat", -1, 'z') = "bat"
+ *
+ *
+ * @param str the String to pad out, may be null
+ * @param size the size to pad to
+ * @param padChar the character to pad with
+ * @return right padded String or original String if no padding is necessary,
+ * {@code null} if null String input
+ * @since 2.0
+ */
+ public static String rightPad(String str, int size, char padChar) {
+ if (str == null) {
+ return null;
+ }
+ int pads = size - str.length();
+ if (pads <= 0) {
+ return str; // returns original String when possible
+ }
+ if (pads > PAD_LIMIT) {
+ return rightPad(str, size, String.valueOf(padChar));
+ }
+ return str.concat(repeat(padChar, pads));
+ }
+
+ /**
+ * Right pad a String with a specified String.
+ *
+ * The String is padded to the size of {@code size}.
+ *
+ *
+ * StringUtils.rightPad(null, *, *) = null
+ * StringUtils.rightPad("", 3, "z") = "zzz"
+ * StringUtils.rightPad("bat", 3, "yz") = "bat"
+ * StringUtils.rightPad("bat", 5, "yz") = "batyz"
+ * StringUtils.rightPad("bat", 8, "yz") = "batyzyzy"
+ * StringUtils.rightPad("bat", 1, "yz") = "bat"
+ * StringUtils.rightPad("bat", -1, "yz") = "bat"
+ * StringUtils.rightPad("bat", 5, null) = "bat "
+ * StringUtils.rightPad("bat", 5, "") = "bat "
+ *
+ *
+ * @param str the String to pad out, may be null
+ * @param size the size to pad to
+ * @param padStr the String to pad with, null or empty treated as single space
+ * @return right padded String or original String if no padding is necessary,
+ * {@code null} if null String input
+ */
+ public static String rightPad(String str, int size, String padStr) {
+ if (str == null) {
+ return null;
+ }
+ if (isEmpty(padStr)) {
+ padStr = " ";
+ }
+ int padLen = padStr.length();
+ int strLen = str.length();
+ int pads = size - strLen;
+ if (pads <= 0) {
+ return str; // returns original String when possible
+ }
+ if (padLen == 1 && pads <= PAD_LIMIT) {
+ return rightPad(str, size, padStr.charAt(0));
+ }
+
+ if (pads == padLen) {
+ return str.concat(padStr);
+ } else if (pads < padLen) {
+ return str.concat(padStr.substring(0, pads));
+ } else {
+ char[] padding = new char[pads];
+ char[] padChars = padStr.toCharArray();
+ for (int i = 0; i < pads; i++) {
+ padding[i] = padChars[i % padLen];
+ }
+ return str.concat(new String(padding));
+ }
+ }
+
+ /**
+ * Left pad a String with spaces (' ').
+ *
+ * The String is padded to the size of {@code size}.
+ *
+ *
+ * StringUtils.leftPad(null, *) = null
+ * StringUtils.leftPad("", 3) = " "
+ * StringUtils.leftPad("bat", 3) = "bat"
+ * StringUtils.leftPad("bat", 5) = " bat"
+ * StringUtils.leftPad("bat", 1) = "bat"
+ * StringUtils.leftPad("bat", -1) = "bat"
+ *
+ *
+ * @param str the String to pad out, may be null
+ * @param size the size to pad to
+ * @return left padded String or original String if no padding is necessary,
+ * {@code null} if null String input
+ */
+ public static String leftPad(String str, int size) {
+ return leftPad(str, size, ' ');
+ }
+
+ /**
+ * Left pad a String with a specified character.
+ *
+ * Pad to a size of {@code size}.
+ *
+ *
+ * StringUtils.leftPad(null, *, *) = null
+ * StringUtils.leftPad("", 3, 'z') = "zzz"
+ * StringUtils.leftPad("bat", 3, 'z') = "bat"
+ * StringUtils.leftPad("bat", 5, 'z') = "zzbat"
+ * StringUtils.leftPad("bat", 1, 'z') = "bat"
+ * StringUtils.leftPad("bat", -1, 'z') = "bat"
+ *
+ *
+ * @param str the String to pad out, may be null
+ * @param size the size to pad to
+ * @param padChar the character to pad with
+ * @return left padded String or original String if no padding is necessary,
+ * {@code null} if null String input
+ * @since 2.0
+ */
+ public static String leftPad(String str, int size, char padChar) {
+ if (str == null) {
+ return null;
+ }
+ int pads = size - str.length();
+ if (pads <= 0) {
+ return str; // returns original String when possible
+ }
+ if (pads > PAD_LIMIT) {
+ return leftPad(str, size, String.valueOf(padChar));
+ }
+ return repeat(padChar, pads).concat(str);
+ }
+
+ /**
+ * Left pad a String with a specified String.
+ *
+ * Pad to a size of {@code size}.
+ *
+ *
+ * StringUtils.leftPad(null, *, *) = null
+ * StringUtils.leftPad("", 3, "z") = "zzz"
+ * StringUtils.leftPad("bat", 3, "yz") = "bat"
+ * StringUtils.leftPad("bat", 5, "yz") = "yzbat"
+ * StringUtils.leftPad("bat", 8, "yz") = "yzyzybat"
+ * StringUtils.leftPad("bat", 1, "yz") = "bat"
+ * StringUtils.leftPad("bat", -1, "yz") = "bat"
+ * StringUtils.leftPad("bat", 5, null) = " bat"
+ * StringUtils.leftPad("bat", 5, "") = " bat"
+ *
+ *
+ * @param str the String to pad out, may be null
+ * @param size the size to pad to
+ * @param padStr the String to pad with, null or empty treated as single space
+ * @return left padded String or original String if no padding is necessary,
+ * {@code null} if null String input
+ */
+ public static String leftPad(String str, int size, String padStr) {
+ if (str == null) {
+ return null;
+ }
+ if (isEmpty(padStr)) {
+ padStr = " ";
+ }
+ int padLen = padStr.length();
+ int strLen = str.length();
+ int pads = size - strLen;
+ if (pads <= 0) {
+ return str; // returns original String when possible
+ }
+ if (padLen == 1 && pads <= PAD_LIMIT) {
+ return leftPad(str, size, padStr.charAt(0));
+ }
+
+ if (pads == padLen) {
+ return padStr.concat(str);
+ } else if (pads < padLen) {
+ return padStr.substring(0, pads).concat(str);
+ } else {
+ char[] padding = new char[pads];
+ char[] padChars = padStr.toCharArray();
+ for (int i = 0; i < pads; i++) {
+ padding[i] = padChars[i % padLen];
+ }
+ return new String(padding).concat(str);
+ }
+ }
+
+ /**
+ * Gets a CharSequence length or {@code 0} if the CharSequence is
+ * {@code null}.
+ *
+ * @param cs
+ * a CharSequence or {@code null}
+ * @return CharSequence length or {@code 0} if the CharSequence is
+ * {@code null}.
+ * @since 2.4
+ * @since 3.0 Changed signature from length(String) to length(CharSequence)
+ */
+ public static int length(CharSequence cs) {
+ return cs == null ? 0 : cs.length();
+ }
+
+ // Centering
+ //-----------------------------------------------------------------------
+ /**
+ * Centers a String in a larger String of size {@code size}
+ * using the space character (' ').
+ *
+ *
If the size is less than the String length, the String is returned.
+ * A {@code null} String returns {@code null}.
+ * A negative size is treated as zero.
+ *
+ * Equivalent to {@code center(str, size, " ")}.
+ *
+ *
+ * StringUtils.center(null, *) = null
+ * StringUtils.center("", 4) = " "
+ * StringUtils.center("ab", -1) = "ab"
+ * StringUtils.center("ab", 4) = " ab "
+ * StringUtils.center("abcd", 2) = "abcd"
+ * StringUtils.center("a", 4) = " a "
+ *
+ *
+ * @param str the String to center, may be null
+ * @param size the int size of new String, negative treated as zero
+ * @return centered String, {@code null} if null String input
+ */
+ public static String center(String str, int size) {
+ return center(str, size, ' ');
+ }
+
+ /**
+ * Centers a String in a larger String of size {@code size}.
+ * Uses a supplied character as the value to pad the String with.
+ *
+ * If the size is less than the String length, the String is returned.
+ * A {@code null} String returns {@code null}.
+ * A negative size is treated as zero.
+ *
+ *
+ * StringUtils.center(null, *, *) = null
+ * StringUtils.center("", 4, ' ') = " "
+ * StringUtils.center("ab", -1, ' ') = "ab"
+ * StringUtils.center("ab", 4, ' ') = " ab"
+ * StringUtils.center("abcd", 2, ' ') = "abcd"
+ * StringUtils.center("a", 4, ' ') = " a "
+ * StringUtils.center("a", 4, 'y') = "yayy"
+ *
+ *
+ * @param str the String to center, may be null
+ * @param size the int size of new String, negative treated as zero
+ * @param padChar the character to pad the new String with
+ * @return centered String, {@code null} if null String input
+ * @since 2.0
+ */
+ public static String center(String str, int size, char padChar) {
+ if (str == null || size <= 0) {
+ return str;
+ }
+ int strLen = str.length();
+ int pads = size - strLen;
+ if (pads <= 0) {
+ return str;
+ }
+ str = leftPad(str, strLen + pads / 2, padChar);
+ str = rightPad(str, size, padChar);
+ return str;
+ }
+
+ /**
+ * Centers a String in a larger String of size {@code size}.
+ * Uses a supplied String as the value to pad the String with.
+ *
+ * If the size is less than the String length, the String is returned.
+ * A {@code null} String returns {@code null}.
+ * A negative size is treated as zero.
+ *
+ *
+ * StringUtils.center(null, *, *) = null
+ * StringUtils.center("", 4, " ") = " "
+ * StringUtils.center("ab", -1, " ") = "ab"
+ * StringUtils.center("ab", 4, " ") = " ab"
+ * StringUtils.center("abcd", 2, " ") = "abcd"
+ * StringUtils.center("a", 4, " ") = " a "
+ * StringUtils.center("a", 4, "yz") = "yayz"
+ * StringUtils.center("abc", 7, null) = " abc "
+ * StringUtils.center("abc", 7, "") = " abc "
+ *
+ *
+ * @param str the String to center, may be null
+ * @param size the int size of new String, negative treated as zero
+ * @param padStr the String to pad the new String with, must not be null or empty
+ * @return centered String, {@code null} if null String input
+ * @throws IllegalArgumentException if padStr is {@code null} or empty
+ */
+ public static String center(String str, int size, String padStr) {
+ if (str == null || size <= 0) {
+ return str;
+ }
+ if (isEmpty(padStr)) {
+ padStr = " ";
+ }
+ int strLen = str.length();
+ int pads = size - strLen;
+ if (pads <= 0) {
+ return str;
+ }
+ str = leftPad(str, strLen + pads / 2, padStr);
+ str = rightPad(str, size, padStr);
+ return str;
+ }
+
+ // Case conversion
+ //-----------------------------------------------------------------------
+ /**
+ * Converts a String to upper case as per {@link String#toUpperCase()}.
+ *
+ * A {@code null} input String returns {@code null}.
+ *
+ *
+ * StringUtils.upperCase(null) = null
+ * StringUtils.upperCase("") = ""
+ * StringUtils.upperCase("aBc") = "ABC"
+ *
+ *
+ * Note: As described in the documentation for {@link String#toUpperCase()},
+ * the result of this method is affected by the current locale.
+ * For platform-independent case transformations, the method {@link #lowerCase(String, Locale)}
+ * should be used with a specific locale (e.g. {@link Locale#ENGLISH}).
+ *
+ * @param str the String to upper case, may be null
+ * @return the upper cased String, {@code null} if null String input
+ */
+ public static String upperCase(String str) {
+ if (str == null) {
+ return null;
+ }
+ return str.toUpperCase();
+ }
+
+ /**
+ * Converts a String to upper case as per {@link String#toUpperCase(Locale)}.
+ *
+ * A {@code null} input String returns {@code null}.
+ *
+ *
+ * StringUtils.upperCase(null, Locale.ENGLISH) = null
+ * StringUtils.upperCase("", Locale.ENGLISH) = ""
+ * StringUtils.upperCase("aBc", Locale.ENGLISH) = "ABC"
+ *
+ *
+ * @param str the String to upper case, may be null
+ * @param locale the locale that defines the case transformation rules, must not be null
+ * @return the upper cased String, {@code null} if null String input
+ * @since 2.5
+ */
+ public static String upperCase(String str, Locale locale) {
+ if (str == null) {
+ return null;
+ }
+ return str.toUpperCase(locale);
+ }
+
+ /**
+ * Converts a String to lower case as per {@link String#toLowerCase()}.
+ *
+ * A {@code null} input String returns {@code null}.
+ *
+ *
+ * StringUtils.lowerCase(null) = null
+ * StringUtils.lowerCase("") = ""
+ * StringUtils.lowerCase("aBc") = "abc"
+ *
+ *
+ * Note: As described in the documentation for {@link String#toLowerCase()},
+ * the result of this method is affected by the current locale.
+ * For platform-independent case transformations, the method {@link #lowerCase(String, Locale)}
+ * should be used with a specific locale (e.g. {@link Locale#ENGLISH}).
+ *
+ * @param str the String to lower case, may be null
+ * @return the lower cased String, {@code null} if null String input
+ */
+ public static String lowerCase(String str) {
+ if (str == null) {
+ return null;
+ }
+ return str.toLowerCase();
+ }
+
+ /**
+ * Converts a String to lower case as per {@link String#toLowerCase(Locale)}.
+ *
+ * A {@code null} input String returns {@code null}.
+ *
+ *
+ * StringUtils.lowerCase(null, Locale.ENGLISH) = null
+ * StringUtils.lowerCase("", Locale.ENGLISH) = ""
+ * StringUtils.lowerCase("aBc", Locale.ENGLISH) = "abc"
+ *
+ *
+ * @param str the String to lower case, may be null
+ * @param locale the locale that defines the case transformation rules, must not be null
+ * @return the lower cased String, {@code null} if null String input
+ * @since 2.5
+ */
+ public static String lowerCase(String str, Locale locale) {
+ if (str == null) {
+ return null;
+ }
+ return str.toLowerCase(locale);
+ }
+
+ /**
+ * Capitalizes a String changing the first letter to title case as
+ * per {@link Character#toTitleCase(char)}. No other letters are changed.
+ *
+ * For a word based algorithm, see {@link external.org.apache.commons.lang3.text.WordUtils#capitalize(String)}.
+ * A {@code null} input String returns {@code null}.
+ *
+ *
+ * StringUtils.capitalize(null) = null
+ * StringUtils.capitalize("") = ""
+ * StringUtils.capitalize("cat") = "Cat"
+ * StringUtils.capitalize("cAt") = "CAt"
+ *
+ *
+ * @param str the String to capitalize, may be null
+ * @return the capitalized String, {@code null} if null String input
+ * @see external.org.apache.commons.lang3.text.WordUtils#capitalize(String)
+ * @see #uncapitalize(String)
+ * @since 2.0
+ */
+ public static String capitalize(String str) {
+ int strLen;
+ if (str == null || (strLen = str.length()) == 0) {
+ return str;
+ }
+ return new StringBuilder(strLen)
+ .append(Character.toTitleCase(str.charAt(0)))
+ .append(str.substring(1))
+ .toString();
+ }
+
+ /**
+ * Uncapitalizes a String changing the first letter to title case as
+ * per {@link Character#toLowerCase(char)}. No other letters are changed.
+ *
+ * For a word based algorithm, see {@link external.org.apache.commons.lang3.text.WordUtils#uncapitalize(String)}.
+ * A {@code null} input String returns {@code null}.
+ *
+ *
+ * StringUtils.uncapitalize(null) = null
+ * StringUtils.uncapitalize("") = ""
+ * StringUtils.uncapitalize("Cat") = "cat"
+ * StringUtils.uncapitalize("CAT") = "cAT"
+ *
+ *
+ * @param str the String to uncapitalize, may be null
+ * @return the uncapitalized String, {@code null} if null String input
+ * @see external.org.apache.commons.lang3.text.WordUtils#uncapitalize(String)
+ * @see #capitalize(String)
+ * @since 2.0
+ */
+ public static String uncapitalize(String str) {
+ int strLen;
+ if (str == null || (strLen = str.length()) == 0) {
+ return str;
+ }
+ return new StringBuilder(strLen)
+ .append(Character.toLowerCase(str.charAt(0)))
+ .append(str.substring(1))
+ .toString();
+ }
+
+ /**
+ * Swaps the case of a String changing upper and title case to
+ * lower case, and lower case to upper case.
+ *
+ *
+ * Upper case character converts to Lower case
+ * Title case character converts to Lower case
+ * Lower case character converts to Upper case
+ *
+ *
+ * For a word based algorithm, see {@link external.org.apache.commons.lang3.text.WordUtils#swapCase(String)}.
+ * A {@code null} input String returns {@code null}.
+ *
+ *
+ * StringUtils.swapCase(null) = null
+ * StringUtils.swapCase("") = ""
+ * StringUtils.swapCase("The dog has a BONE") = "tHE DOG HAS A bone"
+ *
+ *
+ * NOTE: This method changed in Lang version 2.0.
+ * It no longer performs a word based algorithm.
+ * If you only use ASCII, you will notice no change.
+ * That functionality is available in org.apache.commons.lang3.text.WordUtils.
+ *
+ * @param str the String to swap case, may be null
+ * @return the changed String, {@code null} if null String input
+ */
+ public static String swapCase(String str) {
+ if (StringUtils.isEmpty(str)) {
+ return str;
+ }
+
+ char[] buffer = str.toCharArray();
+
+ for (int i = 0; i < buffer.length; i++) {
+ char ch = buffer[i];
+ if (Character.isUpperCase(ch)) {
+ buffer[i] = Character.toLowerCase(ch);
+ } else if (Character.isTitleCase(ch)) {
+ buffer[i] = Character.toLowerCase(ch);
+ } else if (Character.isLowerCase(ch)) {
+ buffer[i] = Character.toUpperCase(ch);
+ }
+ }
+ return new String(buffer);
+ }
+
+ // Count matches
+ //-----------------------------------------------------------------------
+ /**
+ * Counts how many times the substring appears in the larger string.
+ *
+ * A {@code null} or empty ("") String input returns {@code 0}.
+ *
+ *
+ * StringUtils.countMatches(null, *) = 0
+ * StringUtils.countMatches("", *) = 0
+ * StringUtils.countMatches("abba", null) = 0
+ * StringUtils.countMatches("abba", "") = 0
+ * StringUtils.countMatches("abba", "a") = 2
+ * StringUtils.countMatches("abba", "ab") = 1
+ * StringUtils.countMatches("abba", "xxx") = 0
+ *
+ *
+ * @param str the CharSequence to check, may be null
+ * @param sub the substring to count, may be null
+ * @return the number of occurrences, 0 if either CharSequence is {@code null}
+ * @since 3.0 Changed signature from countMatches(String, String) to countMatches(CharSequence, CharSequence)
+ */
+ public static int countMatches(CharSequence str, CharSequence sub) {
+ if (isEmpty(str) || isEmpty(sub)) {
+ return 0;
+ }
+ int count = 0;
+ int idx = 0;
+ while ((idx = CharSequenceUtils.indexOf(str, sub, idx)) != INDEX_NOT_FOUND) {
+ count++;
+ idx += sub.length();
+ }
+ return count;
+ }
+
+ // Character Tests
+ //-----------------------------------------------------------------------
+ /**
+ * Checks if the CharSequence contains only Unicode letters.
+ *
+ * {@code null} will return {@code false}.
+ * An empty CharSequence (length()=0) will return {@code false}.
+ *
+ *
+ * StringUtils.isAlpha(null) = false
+ * StringUtils.isAlpha("") = false
+ * StringUtils.isAlpha(" ") = false
+ * StringUtils.isAlpha("abc") = true
+ * StringUtils.isAlpha("ab2c") = false
+ * StringUtils.isAlpha("ab-c") = false
+ *
+ *
+ * @param cs the CharSequence to check, may be null
+ * @return {@code true} if only contains letters, and is non-null
+ * @since 3.0 Changed signature from isAlpha(String) to isAlpha(CharSequence)
+ * @since 3.0 Changed "" to return false and not true
+ */
+ public static boolean isAlpha(CharSequence cs) {
+ if (cs == null || cs.length() == 0) {
+ return false;
+ }
+ int sz = cs.length();
+ for (int i = 0; i < sz; i++) {
+ if (Character.isLetter(cs.charAt(i)) == false) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ /**
+ * Checks if the CharSequence contains only Unicode letters and
+ * space (' ').
+ *
+ * {@code null} will return {@code false}
+ * An empty CharSequence (length()=0) will return {@code true}.
+ *
+ *
+ * StringUtils.isAlphaSpace(null) = false
+ * StringUtils.isAlphaSpace("") = true
+ * StringUtils.isAlphaSpace(" ") = true
+ * StringUtils.isAlphaSpace("abc") = true
+ * StringUtils.isAlphaSpace("ab c") = true
+ * StringUtils.isAlphaSpace("ab2c") = false
+ * StringUtils.isAlphaSpace("ab-c") = false
+ *
+ *
+ * @param cs the CharSequence to check, may be null
+ * @return {@code true} if only contains letters and space,
+ * and is non-null
+ * @since 3.0 Changed signature from isAlphaSpace(String) to isAlphaSpace(CharSequence)
+ */
+ public static boolean isAlphaSpace(CharSequence cs) {
+ if (cs == null) {
+ return false;
+ }
+ int sz = cs.length();
+ for (int i = 0; i < sz; i++) {
+ if (Character.isLetter(cs.charAt(i)) == false && cs.charAt(i) != ' ') {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ /**
+ * Checks if the CharSequence contains only Unicode letters or digits.
+ *
+ * {@code null} will return {@code false}.
+ * An empty CharSequence (length()=0) will return {@code false}.
+ *
+ *
+ * StringUtils.isAlphanumeric(null) = false
+ * StringUtils.isAlphanumeric("") = false
+ * StringUtils.isAlphanumeric(" ") = false
+ * StringUtils.isAlphanumeric("abc") = true
+ * StringUtils.isAlphanumeric("ab c") = false
+ * StringUtils.isAlphanumeric("ab2c") = true
+ * StringUtils.isAlphanumeric("ab-c") = false
+ *
+ *
+ * @param cs the CharSequence to check, may be null
+ * @return {@code true} if only contains letters or digits,
+ * and is non-null
+ * @since 3.0 Changed signature from isAlphanumeric(String) to isAlphanumeric(CharSequence)
+ * @since 3.0 Changed "" to return false and not true
+ */
+ public static boolean isAlphanumeric(CharSequence cs) {
+ if (cs == null || cs.length() == 0) {
+ return false;
+ }
+ int sz = cs.length();
+ for (int i = 0; i < sz; i++) {
+ if (Character.isLetterOrDigit(cs.charAt(i)) == false) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ /**
+ * Checks if the CharSequence contains only Unicode letters, digits
+ * or space ({@code ' '}).
+ *
+ * {@code null} will return {@code false}.
+ * An empty CharSequence (length()=0) will return {@code true}.
+ *
+ *
+ * StringUtils.isAlphanumericSpace(null) = false
+ * StringUtils.isAlphanumericSpace("") = true
+ * StringUtils.isAlphanumericSpace(" ") = true
+ * StringUtils.isAlphanumericSpace("abc") = true
+ * StringUtils.isAlphanumericSpace("ab c") = true
+ * StringUtils.isAlphanumericSpace("ab2c") = true
+ * StringUtils.isAlphanumericSpace("ab-c") = false
+ *
+ *
+ * @param cs the CharSequence to check, may be null
+ * @return {@code true} if only contains letters, digits or space,
+ * and is non-null
+ * @since 3.0 Changed signature from isAlphanumericSpace(String) to isAlphanumericSpace(CharSequence)
+ */
+ public static boolean isAlphanumericSpace(CharSequence cs) {
+ if (cs == null) {
+ return false;
+ }
+ int sz = cs.length();
+ for (int i = 0; i < sz; i++) {
+ if (Character.isLetterOrDigit(cs.charAt(i)) == false && cs.charAt(i) != ' ') {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ /**
+ * Checks if the CharSequence contains only ASCII printable characters.
+ *
+ * {@code null} will return {@code false}.
+ * An empty CharSequence (length()=0) will return {@code true}.
+ *
+ *
+ * StringUtils.isAsciiPrintable(null) = false
+ * StringUtils.isAsciiPrintable("") = true
+ * StringUtils.isAsciiPrintable(" ") = true
+ * StringUtils.isAsciiPrintable("Ceki") = true
+ * StringUtils.isAsciiPrintable("ab2c") = true
+ * StringUtils.isAsciiPrintable("!ab-c~") = true
+ * StringUtils.isAsciiPrintable("\u0020") = true
+ * StringUtils.isAsciiPrintable("\u0021") = true
+ * StringUtils.isAsciiPrintable("\u007e") = true
+ * StringUtils.isAsciiPrintable("\u007f") = false
+ * StringUtils.isAsciiPrintable("Ceki G\u00fclc\u00fc") = false
+ *
+ *
+ * @param cs the CharSequence to check, may be null
+ * @return {@code true} if every character is in the range
+ * 32 thru 126
+ * @since 2.1
+ * @since 3.0 Changed signature from isAsciiPrintable(String) to isAsciiPrintable(CharSequence)
+ */
+ public static boolean isAsciiPrintable(CharSequence cs) {
+ if (cs == null) {
+ return false;
+ }
+ int sz = cs.length();
+ for (int i = 0; i < sz; i++) {
+ if (CharUtils.isAsciiPrintable(cs.charAt(i)) == false) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ /**
+ * Checks if the CharSequence contains only Unicode digits.
+ * A decimal point is not a Unicode digit and returns false.
+ *
+ * {@code null} will return {@code false}.
+ * An empty CharSequence (length()=0) will return {@code false}.
+ *
+ *
+ * StringUtils.isNumeric(null) = false
+ * StringUtils.isNumeric("") = false
+ * StringUtils.isNumeric(" ") = false
+ * StringUtils.isNumeric("123") = true
+ * StringUtils.isNumeric("12 3") = false
+ * StringUtils.isNumeric("ab2c") = false
+ * StringUtils.isNumeric("12-3") = false
+ * StringUtils.isNumeric("12.3") = false
+ *
+ *
+ * @param cs the CharSequence to check, may be null
+ * @return {@code true} if only contains digits, and is non-null
+ * @since 3.0 Changed signature from isNumeric(String) to isNumeric(CharSequence)
+ * @since 3.0 Changed "" to return false and not true
+ */
+ public static boolean isNumeric(CharSequence cs) {
+ if (cs == null || cs.length() == 0) {
+ return false;
+ }
+ int sz = cs.length();
+ for (int i = 0; i < sz; i++) {
+ if (Character.isDigit(cs.charAt(i)) == false) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ /**
+ * Checks if the CharSequence contains only Unicode digits or space
+ * ({@code ' '}).
+ * A decimal point is not a Unicode digit and returns false.
+ *
+ * {@code null} will return {@code false}.
+ * An empty CharSequence (length()=0) will return {@code true}.
+ *
+ *
+ * StringUtils.isNumericSpace(null) = false
+ * StringUtils.isNumericSpace("") = true
+ * StringUtils.isNumericSpace(" ") = true
+ * StringUtils.isNumericSpace("123") = true
+ * StringUtils.isNumericSpace("12 3") = true
+ * StringUtils.isNumericSpace("ab2c") = false
+ * StringUtils.isNumericSpace("12-3") = false
+ * StringUtils.isNumericSpace("12.3") = false
+ *
+ *
+ * @param cs the CharSequence to check, may be null
+ * @return {@code true} if only contains digits or space,
+ * and is non-null
+ * @since 3.0 Changed signature from isNumericSpace(String) to isNumericSpace(CharSequence)
+ */
+ public static boolean isNumericSpace(CharSequence cs) {
+ if (cs == null) {
+ return false;
+ }
+ int sz = cs.length();
+ for (int i = 0; i < sz; i++) {
+ if (Character.isDigit(cs.charAt(i)) == false && cs.charAt(i) != ' ') {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ /**
+ * Checks if the CharSequence contains only whitespace.
+ *
+ * {@code null} will return {@code false}.
+ * An empty CharSequence (length()=0) will return {@code true}.
+ *
+ *
+ * StringUtils.isWhitespace(null) = false
+ * StringUtils.isWhitespace("") = true
+ * StringUtils.isWhitespace(" ") = true
+ * StringUtils.isWhitespace("abc") = false
+ * StringUtils.isWhitespace("ab2c") = false
+ * StringUtils.isWhitespace("ab-c") = false
+ *
+ *
+ * @param cs the CharSequence to check, may be null
+ * @return {@code true} if only contains whitespace, and is non-null
+ * @since 2.0
+ * @since 3.0 Changed signature from isWhitespace(String) to isWhitespace(CharSequence)
+ */
+ public static boolean isWhitespace(CharSequence cs) {
+ if (cs == null) {
+ return false;
+ }
+ int sz = cs.length();
+ for (int i = 0; i < sz; i++) {
+ if (Character.isWhitespace(cs.charAt(i)) == false) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ /**
+ * Checks if the CharSequence contains only lowercase characters.
+ *
+ * {@code null} will return {@code false}.
+ * An empty CharSequence (length()=0) will return {@code false}.
+ *
+ *
+ * StringUtils.isAllLowerCase(null) = false
+ * StringUtils.isAllLowerCase("") = false
+ * StringUtils.isAllLowerCase(" ") = false
+ * StringUtils.isAllLowerCase("abc") = true
+ * StringUtils.isAllLowerCase("abC") = false
+ *
+ *
+ * @param cs the CharSequence to check, may be null
+ * @return {@code true} if only contains lowercase characters, and is non-null
+ * @since 2.5
+ * @since 3.0 Changed signature from isAllLowerCase(String) to isAllLowerCase(CharSequence)
+ */
+ public static boolean isAllLowerCase(CharSequence cs) {
+ if (cs == null || isEmpty(cs)) {
+ return false;
+ }
+ int sz = cs.length();
+ for (int i = 0; i < sz; i++) {
+ if (Character.isLowerCase(cs.charAt(i)) == false) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ /**
+ * Checks if the CharSequence contains only uppercase characters.
+ *
+ * {@code null} will return {@code false}.
+ * An empty String (length()=0) will return {@code false}.
+ *
+ *
+ * StringUtils.isAllUpperCase(null) = false
+ * StringUtils.isAllUpperCase("") = false
+ * StringUtils.isAllUpperCase(" ") = false
+ * StringUtils.isAllUpperCase("ABC") = true
+ * StringUtils.isAllUpperCase("aBC") = false
+ *
+ *
+ * @param cs the CharSequence to check, may be null
+ * @return {@code true} if only contains uppercase characters, and is non-null
+ * @since 2.5
+ * @since 3.0 Changed signature from isAllUpperCase(String) to isAllUpperCase(CharSequence)
+ */
+ public static boolean isAllUpperCase(CharSequence cs) {
+ if (cs == null || isEmpty(cs)) {
+ return false;
+ }
+ int sz = cs.length();
+ for (int i = 0; i < sz; i++) {
+ if (Character.isUpperCase(cs.charAt(i)) == false) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ // Defaults
+ //-----------------------------------------------------------------------
+ /**
+ * Returns either the passed in String,
+ * or if the String is {@code null}, an empty String ("").
+ *
+ *
+ * StringUtils.defaultString(null) = ""
+ * StringUtils.defaultString("") = ""
+ * StringUtils.defaultString("bat") = "bat"
+ *
+ *
+ * @see ObjectUtils#toString(Object)
+ * @see String#valueOf(Object)
+ * @param str the String to check, may be null
+ * @return the passed in String, or the empty String if it
+ * was {@code null}
+ */
+ public static String defaultString(String str) {
+ return str == null ? EMPTY : str;
+ }
+
+ /**
+ * Returns either the passed in String, or if the String is
+ * {@code null}, the value of {@code defaultStr}.
+ *
+ *
+ * StringUtils.defaultString(null, "NULL") = "NULL"
+ * StringUtils.defaultString("", "NULL") = ""
+ * StringUtils.defaultString("bat", "NULL") = "bat"
+ *
+ *
+ * @see ObjectUtils#toString(Object,String)
+ * @see String#valueOf(Object)
+ * @param str the String to check, may be null
+ * @param defaultStr the default String to return
+ * if the input is {@code null}, may be null
+ * @return the passed in String, or the default if it was {@code null}
+ */
+ public static String defaultString(String str, String defaultStr) {
+ return str == null ? defaultStr : str;
+ }
+
+ /**
+ * Returns either the passed in CharSequence, or if the CharSequence is
+ * whitespace, empty ("") or {@code null}, the value of {@code defaultStr}.
+ *
+ *
+ * StringUtils.defaultIfBlank(null, "NULL") = "NULL"
+ * StringUtils.defaultIfBlank("", "NULL") = "NULL"
+ * StringUtils.defaultIfBlank(" ", "NULL") = "NULL"
+ * StringUtils.defaultIfBlank("bat", "NULL") = "bat"
+ * StringUtils.defaultIfBlank("", null) = null
+ *
+ * @param the specific kind of CharSequence
+ * @param str the CharSequence to check, may be null
+ * @param defaultStr the default CharSequence to return
+ * if the input is whitespace, empty ("") or {@code null}, may be null
+ * @return the passed in CharSequence, or the default
+ * @see StringUtils#defaultString(String, String)
+ */
+ public static T defaultIfBlank(T str, T defaultStr) {
+ return StringUtils.isBlank(str) ? defaultStr : str;
+ }
+
+ /**
+ * Returns either the passed in CharSequence, or if the CharSequence is
+ * empty or {@code null}, the value of {@code defaultStr}.
+ *
+ *
+ * StringUtils.defaultIfEmpty(null, "NULL") = "NULL"
+ * StringUtils.defaultIfEmpty("", "NULL") = "NULL"
+ * StringUtils.defaultIfEmpty(" ", "NULL") = " "
+ * StringUtils.defaultIfEmpty("bat", "NULL") = "bat"
+ * StringUtils.defaultIfEmpty("", null) = null
+ *
+ * @param the specific kind of CharSequence
+ * @param str the CharSequence to check, may be null
+ * @param defaultStr the default CharSequence to return
+ * if the input is empty ("") or {@code null}, may be null
+ * @return the passed in CharSequence, or the default
+ * @see StringUtils#defaultString(String, String)
+ */
+ public static T defaultIfEmpty(T str, T defaultStr) {
+ return StringUtils.isEmpty(str) ? defaultStr : str;
+ }
+
+ // Reversing
+ //-----------------------------------------------------------------------
+ /**
+ * Reverses a String as per {@link StringBuilder#reverse()}.
+ *
+ * A {@code null} String returns {@code null}.
+ *
+ *
+ * StringUtils.reverse(null) = null
+ * StringUtils.reverse("") = ""
+ * StringUtils.reverse("bat") = "tab"
+ *
+ *
+ * @param str the String to reverse, may be null
+ * @return the reversed String, {@code null} if null String input
+ */
+ public static String reverse(String str) {
+ if (str == null) {
+ return null;
+ }
+ return new StringBuilder(str).reverse().toString();
+ }
+
+ /**
+ * Reverses a String that is delimited by a specific character.
+ *
+ * The Strings between the delimiters are not reversed.
+ * Thus java.lang.String becomes String.lang.java (if the delimiter
+ * is {@code '.'}).
+ *
+ *
+ * StringUtils.reverseDelimited(null, *) = null
+ * StringUtils.reverseDelimited("", *) = ""
+ * StringUtils.reverseDelimited("a.b.c", 'x') = "a.b.c"
+ * StringUtils.reverseDelimited("a.b.c", ".") = "c.b.a"
+ *
+ *
+ * @param str the String to reverse, may be null
+ * @param separatorChar the separator character to use
+ * @return the reversed String, {@code null} if null String input
+ * @since 2.0
+ */
+ public static String reverseDelimited(String str, char separatorChar) {
+ if (str == null) {
+ return null;
+ }
+ // could implement manually, but simple way is to reuse other,
+ // probably slower, methods.
+ String[] strs = split(str, separatorChar);
+ ArrayUtils.reverse(strs);
+ return join(strs, separatorChar);
+ }
+
+ // Abbreviating
+ //-----------------------------------------------------------------------
+ /**
+ * Abbreviates a String using ellipses. This will turn
+ * "Now is the time for all good men" into "Now is the time for..."
+ *
+ * Specifically:
+ *
+ * If {@code str} is less than {@code maxWidth} characters
+ * long, return it.
+ * Else abbreviate it to {@code (substring(str, 0, max-3) + "...")}.
+ * If {@code maxWidth} is less than {@code 4}, throw an
+ * {@code IllegalArgumentException}.
+ * In no case will it return a String of length greater than
+ * {@code maxWidth}.
+ *
+ *
+ *
+ *
+ * StringUtils.abbreviate(null, *) = null
+ * StringUtils.abbreviate("", 4) = ""
+ * StringUtils.abbreviate("abcdefg", 6) = "abc..."
+ * StringUtils.abbreviate("abcdefg", 7) = "abcdefg"
+ * StringUtils.abbreviate("abcdefg", 8) = "abcdefg"
+ * StringUtils.abbreviate("abcdefg", 4) = "a..."
+ * StringUtils.abbreviate("abcdefg", 3) = IllegalArgumentException
+ *
+ *
+ * @param str the String to check, may be null
+ * @param maxWidth maximum length of result String, must be at least 4
+ * @return abbreviated String, {@code null} if null String input
+ * @throws IllegalArgumentException if the width is too small
+ * @since 2.0
+ */
+ public static String abbreviate(String str, int maxWidth) {
+ return abbreviate(str, 0, maxWidth);
+ }
+
+ /**
+ * Abbreviates a String using ellipses. This will turn
+ * "Now is the time for all good men" into "...is the time for..."
+ *
+ * Works like {@code abbreviate(String, int)}, but allows you to specify
+ * a "left edge" offset. Note that this left edge is not necessarily going to
+ * be the leftmost character in the result, or the first character following the
+ * ellipses, but it will appear somewhere in the result.
+ *
+ *
In no case will it return a String of length greater than
+ * {@code maxWidth}.
+ *
+ *
+ * StringUtils.abbreviate(null, *, *) = null
+ * StringUtils.abbreviate("", 0, 4) = ""
+ * StringUtils.abbreviate("abcdefghijklmno", -1, 10) = "abcdefg..."
+ * StringUtils.abbreviate("abcdefghijklmno", 0, 10) = "abcdefg..."
+ * StringUtils.abbreviate("abcdefghijklmno", 1, 10) = "abcdefg..."
+ * StringUtils.abbreviate("abcdefghijklmno", 4, 10) = "abcdefg..."
+ * StringUtils.abbreviate("abcdefghijklmno", 5, 10) = "...fghi..."
+ * StringUtils.abbreviate("abcdefghijklmno", 6, 10) = "...ghij..."
+ * StringUtils.abbreviate("abcdefghijklmno", 8, 10) = "...ijklmno"
+ * StringUtils.abbreviate("abcdefghijklmno", 10, 10) = "...ijklmno"
+ * StringUtils.abbreviate("abcdefghijklmno", 12, 10) = "...ijklmno"
+ * StringUtils.abbreviate("abcdefghij", 0, 3) = IllegalArgumentException
+ * StringUtils.abbreviate("abcdefghij", 5, 6) = IllegalArgumentException
+ *
+ *
+ * @param str the String to check, may be null
+ * @param offset left edge of source String
+ * @param maxWidth maximum length of result String, must be at least 4
+ * @return abbreviated String, {@code null} if null String input
+ * @throws IllegalArgumentException if the width is too small
+ * @since 2.0
+ */
+ public static String abbreviate(String str, int offset, int maxWidth) {
+ if (str == null) {
+ return null;
+ }
+ if (maxWidth < 4) {
+ throw new IllegalArgumentException("Minimum abbreviation width is 4");
+ }
+ if (str.length() <= maxWidth) {
+ return str;
+ }
+ if (offset > str.length()) {
+ offset = str.length();
+ }
+ if (str.length() - offset < maxWidth - 3) {
+ offset = str.length() - (maxWidth - 3);
+ }
+ final String abrevMarker = "...";
+ if (offset <= 4) {
+ return str.substring(0, maxWidth - 3) + abrevMarker;
+ }
+ if (maxWidth < 7) {
+ throw new IllegalArgumentException("Minimum abbreviation width with offset is 7");
+ }
+ if (offset + maxWidth - 3 < str.length()) {
+ return abrevMarker + abbreviate(str.substring(offset), maxWidth - 3);
+ }
+ return abrevMarker + str.substring(str.length() - (maxWidth - 3));
+ }
+
+ /**
+ * Abbreviates a String to the length passed, replacing the middle characters with the supplied
+ * replacement String.
+ *
+ * This abbreviation only occurs if the following criteria is met:
+ *
+ * Neither the String for abbreviation nor the replacement String are null or empty
+ * The length to truncate to is less than the length of the supplied String
+ * The length to truncate to is greater than 0
+ * The abbreviated String will have enough room for the length supplied replacement String
+ * and the first and last characters of the supplied String for abbreviation
+ *
+ * Otherwise, the returned String will be the same as the supplied String for abbreviation.
+ *
+ *
+ *
+ * StringUtils.abbreviateMiddle(null, null, 0) = null
+ * StringUtils.abbreviateMiddle("abc", null, 0) = "abc"
+ * StringUtils.abbreviateMiddle("abc", ".", 0) = "abc"
+ * StringUtils.abbreviateMiddle("abc", ".", 3) = "abc"
+ * StringUtils.abbreviateMiddle("abcdef", ".", 4) = "ab.f"
+ *
+ *
+ * @param str the String to abbreviate, may be null
+ * @param middle the String to replace the middle characters with, may be null
+ * @param length the length to abbreviate {@code str} to.
+ * @return the abbreviated String if the above criteria is met, or the original String supplied for abbreviation.
+ * @since 2.5
+ */
+ public static String abbreviateMiddle(String str, String middle, int length) {
+ if (isEmpty(str) || isEmpty(middle)) {
+ return str;
+ }
+
+ if (length >= str.length() || length < middle.length()+2) {
+ return str;
+ }
+
+ int targetSting = length-middle.length();
+ int startOffset = targetSting/2+targetSting%2;
+ int endOffset = str.length()-targetSting/2;
+
+ StringBuilder builder = new StringBuilder(length);
+ builder.append(str.substring(0,startOffset));
+ builder.append(middle);
+ builder.append(str.substring(endOffset));
+
+ return builder.toString();
+ }
+
+ // Difference
+ //-----------------------------------------------------------------------
+ /**
+ * Compares two Strings, and returns the portion where they differ.
+ * (More precisely, return the remainder of the second String,
+ * starting from where it's different from the first.)
+ *
+ * For example,
+ * {@code difference("i am a machine", "i am a robot") -> "robot"}.
+ *
+ *
+ * StringUtils.difference(null, null) = null
+ * StringUtils.difference("", "") = ""
+ * StringUtils.difference("", "abc") = "abc"
+ * StringUtils.difference("abc", "") = ""
+ * StringUtils.difference("abc", "abc") = ""
+ * StringUtils.difference("ab", "abxyz") = "xyz"
+ * StringUtils.difference("abcde", "abxyz") = "xyz"
+ * StringUtils.difference("abcde", "xyz") = "xyz"
+ *
+ *
+ * @param str1 the first String, may be null
+ * @param str2 the second String, may be null
+ * @return the portion of str2 where it differs from str1; returns the
+ * empty String if they are equal
+ * @since 2.0
+ */
+ public static String difference(String str1, String str2) {
+ if (str1 == null) {
+ return str2;
+ }
+ if (str2 == null) {
+ return str1;
+ }
+ int at = indexOfDifference(str1, str2);
+ if (at == INDEX_NOT_FOUND) {
+ return EMPTY;
+ }
+ return str2.substring(at);
+ }
+
+ /**
+ * Compares two CharSequences, and returns the index at which the
+ * CharSequences begin to differ.
+ *
+ * For example,
+ * {@code indexOfDifference("i am a machine", "i am a robot") -> 7}
+ *
+ *
+ * StringUtils.indexOfDifference(null, null) = -1
+ * StringUtils.indexOfDifference("", "") = -1
+ * StringUtils.indexOfDifference("", "abc") = 0
+ * StringUtils.indexOfDifference("abc", "") = 0
+ * StringUtils.indexOfDifference("abc", "abc") = -1
+ * StringUtils.indexOfDifference("ab", "abxyz") = 2
+ * StringUtils.indexOfDifference("abcde", "abxyz") = 2
+ * StringUtils.indexOfDifference("abcde", "xyz") = 0
+ *
+ *
+ * @param cs1 the first CharSequence, may be null
+ * @param cs2 the second CharSequence, may be null
+ * @return the index where cs1 and cs2 begin to differ; -1 if they are equal
+ * @since 2.0
+ * @since 3.0 Changed signature from indexOfDifference(String, String) to
+ * indexOfDifference(CharSequence, CharSequence)
+ */
+ public static int indexOfDifference(CharSequence cs1, CharSequence cs2) {
+ if (cs1 == cs2) {
+ return INDEX_NOT_FOUND;
+ }
+ if (cs1 == null || cs2 == null) {
+ return 0;
+ }
+ int i;
+ for (i = 0; i < cs1.length() && i < cs2.length(); ++i) {
+ if (cs1.charAt(i) != cs2.charAt(i)) {
+ break;
+ }
+ }
+ if (i < cs2.length() || i < cs1.length()) {
+ return i;
+ }
+ return INDEX_NOT_FOUND;
+ }
+
+ /**
+ * Compares all CharSequences in an array and returns the index at which the
+ * CharSequences begin to differ.
+ *
+ * For example,
+ * indexOfDifference(new String[] {"i am a machine", "i am a robot"}) -> 7
+ *
+ *
+ * StringUtils.indexOfDifference(null) = -1
+ * StringUtils.indexOfDifference(new String[] {}) = -1
+ * StringUtils.indexOfDifference(new String[] {"abc"}) = -1
+ * StringUtils.indexOfDifference(new String[] {null, null}) = -1
+ * StringUtils.indexOfDifference(new String[] {"", ""}) = -1
+ * StringUtils.indexOfDifference(new String[] {"", null}) = 0
+ * StringUtils.indexOfDifference(new String[] {"abc", null, null}) = 0
+ * StringUtils.indexOfDifference(new String[] {null, null, "abc"}) = 0
+ * StringUtils.indexOfDifference(new String[] {"", "abc"}) = 0
+ * StringUtils.indexOfDifference(new String[] {"abc", ""}) = 0
+ * StringUtils.indexOfDifference(new String[] {"abc", "abc"}) = -1
+ * StringUtils.indexOfDifference(new String[] {"abc", "a"}) = 1
+ * StringUtils.indexOfDifference(new String[] {"ab", "abxyz"}) = 2
+ * StringUtils.indexOfDifference(new String[] {"abcde", "abxyz"}) = 2
+ * StringUtils.indexOfDifference(new String[] {"abcde", "xyz"}) = 0
+ * StringUtils.indexOfDifference(new String[] {"xyz", "abcde"}) = 0
+ * StringUtils.indexOfDifference(new String[] {"i am a machine", "i am a robot"}) = 7
+ *
+ *
+ * @param css array of CharSequences, entries may be null
+ * @return the index where the strings begin to differ; -1 if they are all equal
+ * @since 2.4
+ * @since 3.0 Changed signature from indexOfDifference(String...) to indexOfDifference(CharSequence...)
+ */
+ public static int indexOfDifference(CharSequence... css) {
+ if (css == null || css.length <= 1) {
+ return INDEX_NOT_FOUND;
+ }
+ boolean anyStringNull = false;
+ boolean allStringsNull = true;
+ int arrayLen = css.length;
+ int shortestStrLen = Integer.MAX_VALUE;
+ int longestStrLen = 0;
+
+ // find the min and max string lengths; this avoids checking to make
+ // sure we are not exceeding the length of the string each time through
+ // the bottom loop.
+ for (int i = 0; i < arrayLen; i++) {
+ if (css[i] == null) {
+ anyStringNull = true;
+ shortestStrLen = 0;
+ } else {
+ allStringsNull = false;
+ shortestStrLen = Math.min(css[i].length(), shortestStrLen);
+ longestStrLen = Math.max(css[i].length(), longestStrLen);
+ }
+ }
+
+ // handle lists containing all nulls or all empty strings
+ if (allStringsNull || longestStrLen == 0 && !anyStringNull) {
+ return INDEX_NOT_FOUND;
+ }
+
+ // handle lists containing some nulls or some empty strings
+ if (shortestStrLen == 0) {
+ return 0;
+ }
+
+ // find the position with the first difference across all strings
+ int firstDiff = -1;
+ for (int stringPos = 0; stringPos < shortestStrLen; stringPos++) {
+ char comparisonChar = css[0].charAt(stringPos);
+ for (int arrayPos = 1; arrayPos < arrayLen; arrayPos++) {
+ if (css[arrayPos].charAt(stringPos) != comparisonChar) {
+ firstDiff = stringPos;
+ break;
+ }
+ }
+ if (firstDiff != -1) {
+ break;
+ }
+ }
+
+ if (firstDiff == -1 && shortestStrLen != longestStrLen) {
+ // we compared all of the characters up to the length of the
+ // shortest string and didn't find a match, but the string lengths
+ // vary, so return the length of the shortest string.
+ return shortestStrLen;
+ }
+ return firstDiff;
+ }
+
+ /**
+ * Compares all Strings in an array and returns the initial sequence of
+ * characters that is common to all of them.
+ *
+ * For example,
+ * getCommonPrefix(new String[] {"i am a machine", "i am a robot"}) -> "i am a "
+ *
+ *
+ * StringUtils.getCommonPrefix(null) = ""
+ * StringUtils.getCommonPrefix(new String[] {}) = ""
+ * StringUtils.getCommonPrefix(new String[] {"abc"}) = "abc"
+ * StringUtils.getCommonPrefix(new String[] {null, null}) = ""
+ * StringUtils.getCommonPrefix(new String[] {"", ""}) = ""
+ * StringUtils.getCommonPrefix(new String[] {"", null}) = ""
+ * StringUtils.getCommonPrefix(new String[] {"abc", null, null}) = ""
+ * StringUtils.getCommonPrefix(new String[] {null, null, "abc"}) = ""
+ * StringUtils.getCommonPrefix(new String[] {"", "abc"}) = ""
+ * StringUtils.getCommonPrefix(new String[] {"abc", ""}) = ""
+ * StringUtils.getCommonPrefix(new String[] {"abc", "abc"}) = "abc"
+ * StringUtils.getCommonPrefix(new String[] {"abc", "a"}) = "a"
+ * StringUtils.getCommonPrefix(new String[] {"ab", "abxyz"}) = "ab"
+ * StringUtils.getCommonPrefix(new String[] {"abcde", "abxyz"}) = "ab"
+ * StringUtils.getCommonPrefix(new String[] {"abcde", "xyz"}) = ""
+ * StringUtils.getCommonPrefix(new String[] {"xyz", "abcde"}) = ""
+ * StringUtils.getCommonPrefix(new String[] {"i am a machine", "i am a robot"}) = "i am a "
+ *
+ *
+ * @param strs array of String objects, entries may be null
+ * @return the initial sequence of characters that are common to all Strings
+ * in the array; empty String if the array is null, the elements are all null
+ * or if there is no common prefix.
+ * @since 2.4
+ */
+ public static String getCommonPrefix(String... strs) {
+ if (strs == null || strs.length == 0) {
+ return EMPTY;
+ }
+ int smallestIndexOfDiff = indexOfDifference(strs);
+ if (smallestIndexOfDiff == INDEX_NOT_FOUND) {
+ // all strings were identical
+ if (strs[0] == null) {
+ return EMPTY;
+ }
+ return strs[0];
+ } else if (smallestIndexOfDiff == 0) {
+ // there were no common initial characters
+ return EMPTY;
+ } else {
+ // we found a common initial character sequence
+ return strs[0].substring(0, smallestIndexOfDiff);
+ }
+ }
+
+ // Misc
+ //-----------------------------------------------------------------------
+ /**
+ * Find the Levenshtein distance between two Strings.
+ *
+ * This is the number of changes needed to change one String into
+ * another, where each change is a single character modification (deletion,
+ * insertion or substitution).
+ *
+ * The previous implementation of the Levenshtein distance algorithm
+ * was from http://www.merriampark.com/ld.htm
+ *
+ * Chas Emerick has written an implementation in Java, which avoids an OutOfMemoryError
+ * which can occur when my Java implementation is used with very large strings.
+ * This implementation of the Levenshtein distance algorithm
+ * is from http://www.merriampark.com/ldjava.htm
+ *
+ *
+ * StringUtils.getLevenshteinDistance(null, *) = IllegalArgumentException
+ * StringUtils.getLevenshteinDistance(*, null) = IllegalArgumentException
+ * StringUtils.getLevenshteinDistance("","") = 0
+ * StringUtils.getLevenshteinDistance("","a") = 1
+ * StringUtils.getLevenshteinDistance("aaapppp", "") = 7
+ * StringUtils.getLevenshteinDistance("frog", "fog") = 1
+ * StringUtils.getLevenshteinDistance("fly", "ant") = 3
+ * StringUtils.getLevenshteinDistance("elephant", "hippo") = 7
+ * StringUtils.getLevenshteinDistance("hippo", "elephant") = 7
+ * StringUtils.getLevenshteinDistance("hippo", "zzzzzzzz") = 8
+ * StringUtils.getLevenshteinDistance("hello", "hallo") = 1
+ *
+ *
+ * @param s the first String, must not be null
+ * @param t the second String, must not be null
+ * @return result distance
+ * @throws IllegalArgumentException if either String input {@code null}
+ * @since 3.0 Changed signature from getLevenshteinDistance(String, String) to
+ * getLevenshteinDistance(CharSequence, CharSequence)
+ */
+ public static int getLevenshteinDistance(CharSequence s, CharSequence t) {
+ if (s == null || t == null) {
+ throw new IllegalArgumentException("Strings must not be null");
+ }
+
+ /*
+ The difference between this impl. and the previous is that, rather
+ than creating and retaining a matrix of size s.length() + 1 by t.length() + 1,
+ we maintain two single-dimensional arrays of length s.length() + 1. The first, d,
+ is the 'current working' distance array that maintains the newest distance cost
+ counts as we iterate through the characters of String s. Each time we increment
+ the index of String t we are comparing, d is copied to p, the second int[]. Doing so
+ allows us to retain the previous cost counts as required by the algorithm (taking
+ the minimum of the cost count to the left, up one, and diagonally up and to the left
+ of the current cost count being calculated). (Note that the arrays aren't really
+ copied anymore, just switched...this is clearly much better than cloning an array
+ or doing a System.arraycopy() each time through the outer loop.)
+
+ Effectively, the difference between the two implementations is this one does not
+ cause an out of memory condition when calculating the LD over two very large strings.
+ */
+
+ int n = s.length(); // length of s
+ int m = t.length(); // length of t
+
+ if (n == 0) {
+ return m;
+ } else if (m == 0) {
+ return n;
+ }
+
+ if (n > m) {
+ // swap the input strings to consume less memory
+ CharSequence tmp = s;
+ s = t;
+ t = tmp;
+ n = m;
+ m = t.length();
+ }
+
+ int p[] = new int[n + 1]; //'previous' cost array, horizontally
+ int d[] = new int[n + 1]; // cost array, horizontally
+ int _d[]; //placeholder to assist in swapping p and d
+
+ // indexes into strings s and t
+ int i; // iterates through s
+ int j; // iterates through t
+
+ char t_j; // jth character of t
+
+ int cost; // cost
+
+ for (i = 0; i <= n; i++) {
+ p[i] = i;
+ }
+
+ for (j = 1; j <= m; j++) {
+ t_j = t.charAt(j - 1);
+ d[0] = j;
+
+ for (i = 1; i <= n; i++) {
+ cost = s.charAt(i - 1) == t_j ? 0 : 1;
+ // minimum of cell to the left+1, to the top+1, diagonally left and up +cost
+ d[i] = Math.min(Math.min(d[i - 1] + 1, p[i] + 1), p[i - 1] + cost);
+ }
+
+ // copy current distance counts to 'previous row' distance counts
+ _d = p;
+ p = d;
+ d = _d;
+ }
+
+ // our last action in the above loop was to switch d and p, so p now
+ // actually has the most recent cost counts
+ return p[n];
+ }
+
+ /**
+ * Find the Levenshtein distance between two Strings if it's less than or equal to a given
+ * threshold.
+ *
+ * This is the number of changes needed to change one String into
+ * another, where each change is a single character modification (deletion,
+ * insertion or substitution).
+ *
+ * This implementation follows from Algorithms on Strings, Trees and Sequences by Dan Gusfield
+ * and Chas Emerick's implementation of the Levenshtein distance algorithm from
+ * http://www.merriampark.com/ld.htm
+ *
+ *
+ * StringUtils.getLevenshteinDistance(null, *, *) = IllegalArgumentException
+ * StringUtils.getLevenshteinDistance(*, null, *) = IllegalArgumentException
+ * StringUtils.getLevenshteinDistance(*, *, -1) = IllegalArgumentException
+ * StringUtils.getLevenshteinDistance("","", 0) = 0
+ * StringUtils.getLevenshteinDistance("aaapppp", "", 8) = 7
+ * StringUtils.getLevenshteinDistance("aaapppp", "", 7) = 7
+ * StringUtils.getLevenshteinDistance("aaapppp", "", 6)) = -1
+ * StringUtils.getLevenshteinDistance("elephant", "hippo", 7) = 7
+ * StringUtils.getLevenshteinDistance("elephant", "hippo", 6) = -1
+ * StringUtils.getLevenshteinDistance("hippo", "elephant", 7) = 7
+ * StringUtils.getLevenshteinDistance("hippo", "elephant", 6) = -1
+ *
+ *
+ * @param s the first String, must not be null
+ * @param t the second String, must not be null
+ * @param threshold the target threshold, must not be negative
+ * @return result distance, or {@code -1} if the distance would be greater than the threshold
+ * @throws IllegalArgumentException if either String input {@code null} or negative threshold
+ */
+ public static int getLevenshteinDistance(CharSequence s, CharSequence t, int threshold) {
+ if (s == null || t == null) {
+ throw new IllegalArgumentException("Strings must not be null");
+ }
+ if (threshold < 0) {
+ throw new IllegalArgumentException("Threshold must not be negative");
+ }
+
+ /*
+ This implementation only computes the distance if it's less than or equal to the
+ threshold value, returning -1 if it's greater. The advantage is performance: unbounded
+ distance is O(nm), but a bound of k allows us to reduce it to O(km) time by only
+ computing a diagonal stripe of width 2k + 1 of the cost table.
+ It is also possible to use this to compute the unbounded Levenshtein distance by starting
+ the threshold at 1 and doubling each time until the distance is found; this is O(dm), where
+ d is the distance.
+
+ One subtlety comes from needing to ignore entries on the border of our stripe
+ eg.
+ p[] = |#|#|#|*
+ d[] = *|#|#|#|
+ We must ignore the entry to the left of the leftmost member
+ We must ignore the entry above the rightmost member
+
+ Another subtlety comes from our stripe running off the matrix if the strings aren't
+ of the same size. Since string s is always swapped to be the shorter of the two,
+ the stripe will always run off to the upper right instead of the lower left of the matrix.
+
+ As a concrete example, suppose s is of length 5, t is of length 7, and our threshold is 1.
+ In this case we're going to walk a stripe of length 3. The matrix would look like so:
+
+ 1 2 3 4 5
+ 1 |#|#| | | |
+ 2 |#|#|#| | |
+ 3 | |#|#|#| |
+ 4 | | |#|#|#|
+ 5 | | | |#|#|
+ 6 | | | | |#|
+ 7 | | | | | |
+
+ Note how the stripe leads off the table as there is no possible way to turn a string of length 5
+ into one of length 7 in edit distance of 1.
+
+ Additionally, this implementation decreases memory usage by using two
+ single-dimensional arrays and swapping them back and forth instead of allocating
+ an entire n by m matrix. This requires a few minor changes, such as immediately returning
+ when it's detected that the stripe has run off the matrix and initially filling the arrays with
+ large values so that entries we don't compute are ignored.
+
+ See Algorithms on Strings, Trees and Sequences by Dan Gusfield for some discussion.
+ */
+
+ int n = s.length(); // length of s
+ int m = t.length(); // length of t
+
+ // if one string is empty, the edit distance is necessarily the length of the other
+ if (n == 0) {
+ return m <= threshold ? m : -1;
+ } else if (m == 0) {
+ return n <= threshold ? n : -1;
+ }
+
+ if (n > m) {
+ // swap the two strings to consume less memory
+ CharSequence tmp = s;
+ s = t;
+ t = tmp;
+ n = m;
+ m = t.length();
+ }
+
+ int p[] = new int[n + 1]; // 'previous' cost array, horizontally
+ int d[] = new int[n + 1]; // cost array, horizontally
+ int _d[]; // placeholder to assist in swapping p and d
+
+ // fill in starting table values
+ int boundary = Math.min(n, threshold) + 1;
+ for (int i = 0; i < boundary; i++) {
+ p[i] = i;
+ }
+ // these fills ensure that the value above the rightmost entry of our
+ // stripe will be ignored in following loop iterations
+ Arrays.fill(p, boundary, p.length, Integer.MAX_VALUE);
+ Arrays.fill(d, Integer.MAX_VALUE);
+
+ // iterates through t
+ for (int j = 1; j <= m; j++) {
+ char t_j = t.charAt(j - 1); // jth character of t
+ d[0] = j;
+
+ // compute stripe indices, constrain to array size
+ int min = Math.max(1, j - threshold);
+ int max = Math.min(n, j + threshold);
+
+ // the stripe may lead off of the table if s and t are of different sizes
+ if (min > max) {
+ return -1;
+ }
+
+ // ignore entry left of leftmost
+ if (min > 1) {
+ d[min - 1] = Integer.MAX_VALUE;
+ }
+
+ // iterates through [min, max] in s
+ for (int i = min; i <= max; i++) {
+ if (s.charAt(i - 1) == t_j) {
+ // diagonally left and up
+ d[i] = p[i - 1];
+ } else {
+ // 1 + minimum of cell to the left, to the top, diagonally left and up
+ d[i] = 1 + Math.min(Math.min(d[i - 1], p[i]), p[i - 1]);
+ }
+ }
+
+ // copy current distance counts to 'previous row' distance counts
+ _d = p;
+ p = d;
+ d = _d;
+ }
+
+ // if p[n] is greater than the threshold, there's no guarantee on it being the correct
+ // distance
+ if (p[n] <= threshold) {
+ return p[n];
+ } else {
+ return -1;
+ }
+ }
+
+ // startsWith
+ //-----------------------------------------------------------------------
+
+ /**
+ * Check if a CharSequence starts with a specified prefix.
+ *
+ * {@code null}s are handled without exceptions. Two {@code null}
+ * references are considered to be equal. The comparison is case sensitive.
+ *
+ *
+ * StringUtils.startsWith(null, null) = true
+ * StringUtils.startsWith(null, "abc") = false
+ * StringUtils.startsWith("abcdef", null) = false
+ * StringUtils.startsWith("abcdef", "abc") = true
+ * StringUtils.startsWith("ABCDEF", "abc") = false
+ *
+ *
+ * @see java.lang.String#startsWith(String)
+ * @param str the CharSequence to check, may be null
+ * @param prefix the prefix to find, may be null
+ * @return {@code true} if the CharSequence starts with the prefix, case sensitive, or
+ * both {@code null}
+ * @since 2.4
+ * @since 3.0 Changed signature from startsWith(String, String) to startsWith(CharSequence, CharSequence)
+ */
+ public static boolean startsWith(CharSequence str, CharSequence prefix) {
+ return startsWith(str, prefix, false);
+ }
+
+ /**
+ * Case insensitive check if a CharSequence starts with a specified prefix.
+ *
+ * {@code null}s are handled without exceptions. Two {@code null}
+ * references are considered to be equal. The comparison is case insensitive.
+ *
+ *
+ * StringUtils.startsWithIgnoreCase(null, null) = true
+ * StringUtils.startsWithIgnoreCase(null, "abc") = false
+ * StringUtils.startsWithIgnoreCase("abcdef", null) = false
+ * StringUtils.startsWithIgnoreCase("abcdef", "abc") = true
+ * StringUtils.startsWithIgnoreCase("ABCDEF", "abc") = true
+ *
+ *
+ * @see java.lang.String#startsWith(String)
+ * @param str the CharSequence to check, may be null
+ * @param prefix the prefix to find, may be null
+ * @return {@code true} if the CharSequence starts with the prefix, case insensitive, or
+ * both {@code null}
+ * @since 2.4
+ * @since 3.0 Changed signature from startsWithIgnoreCase(String, String) to startsWithIgnoreCase(CharSequence, CharSequence)
+ */
+ public static boolean startsWithIgnoreCase(CharSequence str, CharSequence prefix) {
+ return startsWith(str, prefix, true);
+ }
+
+ /**
+ * Check if a CharSequence starts with a specified prefix (optionally case insensitive).
+ *
+ * @see java.lang.String#startsWith(String)
+ * @param str the CharSequence to check, may be null
+ * @param prefix the prefix to find, may be null
+ * @param ignoreCase indicates whether the compare should ignore case
+ * (case insensitive) or not.
+ * @return {@code true} if the CharSequence starts with the prefix or
+ * both {@code null}
+ */
+ private static boolean startsWith(CharSequence str, CharSequence prefix, boolean ignoreCase) {
+ if (str == null || prefix == null) {
+ return str == null && prefix == null;
+ }
+ if (prefix.length() > str.length()) {
+ return false;
+ }
+ return CharSequenceUtils.regionMatches(str, ignoreCase, 0, prefix, 0, prefix.length());
+ }
+
+ /**
+ * Check if a CharSequence starts with any of an array of specified strings.
+ *
+ *
+ * StringUtils.startsWithAny(null, null) = false
+ * StringUtils.startsWithAny(null, new String[] {"abc"}) = false
+ * StringUtils.startsWithAny("abcxyz", null) = false
+ * StringUtils.startsWithAny("abcxyz", new String[] {""}) = false
+ * StringUtils.startsWithAny("abcxyz", new String[] {"abc"}) = true
+ * StringUtils.startsWithAny("abcxyz", new String[] {null, "xyz", "abc"}) = true
+ *
+ *
+ * @param string the CharSequence to check, may be null
+ * @param searchStrings the CharSequences to find, may be null or empty
+ * @return {@code true} if the CharSequence starts with any of the the prefixes, case insensitive, or
+ * both {@code null}
+ * @since 2.5
+ * @since 3.0 Changed signature from startsWithAny(String, String[]) to startsWithAny(CharSequence, CharSequence...)
+ */
+ public static boolean startsWithAny(CharSequence string, CharSequence... searchStrings) {
+ if (isEmpty(string) || ArrayUtils.isEmpty(searchStrings)) {
+ return false;
+ }
+ for (CharSequence searchString : searchStrings) {
+ if (StringUtils.startsWith(string, searchString)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ // endsWith
+ //-----------------------------------------------------------------------
+
+ /**
+ * Check if a CharSequence ends with a specified suffix.
+ *
+ * {@code null}s are handled without exceptions. Two {@code null}
+ * references are considered to be equal. The comparison is case sensitive.
+ *
+ *
+ * StringUtils.endsWith(null, null) = true
+ * StringUtils.endsWith(null, "def") = false
+ * StringUtils.endsWith("abcdef", null) = false
+ * StringUtils.endsWith("abcdef", "def") = true
+ * StringUtils.endsWith("ABCDEF", "def") = false
+ * StringUtils.endsWith("ABCDEF", "cde") = false
+ *
+ *
+ * @see java.lang.String#endsWith(String)
+ * @param str the CharSequence to check, may be null
+ * @param suffix the suffix to find, may be null
+ * @return {@code true} if the CharSequence ends with the suffix, case sensitive, or
+ * both {@code null}
+ * @since 2.4
+ * @since 3.0 Changed signature from endsWith(String, String) to endsWith(CharSequence, CharSequence)
+ */
+ public static boolean endsWith(CharSequence str, CharSequence suffix) {
+ return endsWith(str, suffix, false);
+ }
+
+ /**
+ * Case insensitive check if a CharSequence ends with a specified suffix.
+ *
+ * {@code null}s are handled without exceptions. Two {@code null}
+ * references are considered to be equal. The comparison is case insensitive.
+ *
+ *
+ * StringUtils.endsWithIgnoreCase(null, null) = true
+ * StringUtils.endsWithIgnoreCase(null, "def") = false
+ * StringUtils.endsWithIgnoreCase("abcdef", null) = false
+ * StringUtils.endsWithIgnoreCase("abcdef", "def") = true
+ * StringUtils.endsWithIgnoreCase("ABCDEF", "def") = true
+ * StringUtils.endsWithIgnoreCase("ABCDEF", "cde") = false
+ *
+ *
+ * @see java.lang.String#endsWith(String)
+ * @param str the CharSequence to check, may be null
+ * @param suffix the suffix to find, may be null
+ * @return {@code true} if the CharSequence ends with the suffix, case insensitive, or
+ * both {@code null}
+ * @since 2.4
+ * @since 3.0 Changed signature from endsWithIgnoreCase(String, String) to endsWithIgnoreCase(CharSequence, CharSequence)
+ */
+ public static boolean endsWithIgnoreCase(CharSequence str, CharSequence suffix) {
+ return endsWith(str, suffix, true);
+ }
+
+ /**
+ * Check if a CharSequence ends with a specified suffix (optionally case insensitive).
+ *
+ * @see java.lang.String#endsWith(String)
+ * @param str the CharSequence to check, may be null
+ * @param suffix the suffix to find, may be null
+ * @param ignoreCase indicates whether the compare should ignore case
+ * (case insensitive) or not.
+ * @return {@code true} if the CharSequence starts with the prefix or
+ * both {@code null}
+ */
+ private static boolean endsWith(CharSequence str, CharSequence suffix, boolean ignoreCase) {
+ if (str == null || suffix == null) {
+ return str == null && suffix == null;
+ }
+ if (suffix.length() > str.length()) {
+ return false;
+ }
+ int strOffset = str.length() - suffix.length();
+ return CharSequenceUtils.regionMatches(str, ignoreCase, strOffset, suffix, 0, suffix.length());
+ }
+
+ /**
+ *
+ * Similar to http://www.w3.org/TR/xpath/#function-normalize
+ * -space
+ *
+ *
+ * The function returns the argument string with whitespace normalized by using
+ * {@link #trim(String)} to remove leading and trailing whitespace
+ * and then replacing sequences of whitespace characters by a single space.
+ *
+ * In XML Whitespace characters are the same as those allowed by the S production, which is S ::= (#x20 | #x9 | #xD | #xA)+
+ *
+ * Java's regexp pattern \s defines whitespace as [ \t\n\x0B\f\r]
+ *
+ * For reference:
+ *
+ * \x0B = vertical tab
+ * \f = #xC = form feed
+ * #x20 = space
+ * #x9 = \t
+ * #xA = \n
+ * #xD = \r
+ *
+ *
+ *
+ * The difference is that Java's whitespace includes vertical tab and form feed, which this functional will also
+ * normalize. Additionally {@link #trim(String)} removes control characters (char <= 32) from both
+ * ends of this String.
+ *
+ *
+ * @see Pattern
+ * @see #trim(String)
+ * @see http://www.w3.org/TR/xpath/#function-normalize-space
+ * @param str the source String to normalize whitespaces from, may be null
+ * @return the modified string with whitespace normalized, {@code null} if null String input
+ *
+ * @since 3.0
+ */
+ public static String normalizeSpace(String str) {
+ if (str == null) {
+ return null;
+ }
+ return WHITESPACE_BLOCK.matcher(trim(str)).replaceAll(" ");
+ }
+
+ /**
+ * Check if a CharSequence ends with any of an array of specified strings.
+ *
+ *
+ * StringUtils.endsWithAny(null, null) = false
+ * StringUtils.endsWithAny(null, new String[] {"abc"}) = false
+ * StringUtils.endsWithAny("abcxyz", null) = false
+ * StringUtils.endsWithAny("abcxyz", new String[] {""}) = true
+ * StringUtils.endsWithAny("abcxyz", new String[] {"xyz"}) = true
+ * StringUtils.endsWithAny("abcxyz", new String[] {null, "xyz", "abc"}) = true
+ *
+ *
+ * @param string the CharSequence to check, may be null
+ * @param searchStrings the CharSequences to find, may be null or empty
+ * @return {@code true} if the CharSequence ends with any of the the prefixes, case insensitive, or
+ * both {@code null}
+ * @since 3.0
+ */
+ public static boolean endsWithAny(CharSequence string, CharSequence... searchStrings) {
+ if (isEmpty(string) || ArrayUtils.isEmpty(searchStrings)) {
+ return false;
+ }
+ for (CharSequence searchString : searchStrings) {
+ if (StringUtils.endsWith(string, searchString)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Converts a byte[] to a String using the specified character encoding.
+ *
+ * @param bytes
+ * the byte array to read from
+ * @param charsetName
+ * the encoding to use, if null then use the platform default
+ * @return a new String
+ * @throws UnsupportedEncodingException
+ * If the named charset is not supported
+ * @throws NullPointerException
+ * if the input is null
+ * @since 3.1
+ */
+ public static String toString(byte[] bytes, String charsetName) throws UnsupportedEncodingException {
+ return charsetName == null ? new String(bytes) : new String(bytes, charsetName);
+ }
+
+}
diff --git a/Bridge/src/main/apacheCommonsLang/external/org/apache/commons/lang3/SystemUtils.java b/Bridge/src/main/apacheCommonsLang/external/org/apache/commons/lang3/SystemUtils.java
new file mode 100644
index 00000000..e6a1c10f
--- /dev/null
+++ b/Bridge/src/main/apacheCommonsLang/external/org/apache/commons/lang3/SystemUtils.java
@@ -0,0 +1,1443 @@
+/*
+ * 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 external.org.apache.commons.lang3;
+
+import java.io.File;
+
+/**
+ *
+ * Helpers for {@code java.lang.System}.
+ *
+ *
+ * If a system property cannot be read due to security restrictions, the corresponding field in this class will be set
+ * to {@code null} and a message will be written to {@code System.err}.
+ *
+ *
+ * #ThreadSafe#
+ *
+ *
+ * @since 1.0
+ * @version $Id: SystemUtils.java 1199816 2011-11-09 16:11:34Z bayard $
+ */
+public class SystemUtils {
+
+ /**
+ * The prefix String for all Windows OS.
+ */
+ private static final String OS_NAME_WINDOWS_PREFIX = "Windows";
+
+ // System property constants
+ // -----------------------------------------------------------------------
+ // These MUST be declared first. Other constants depend on this.
+
+ /**
+ * The System property key for the user home directory.
+ */
+ private static final String USER_HOME_KEY = "user.home";
+
+ /**
+ * The System property key for the user directory.
+ */
+ private static final String USER_DIR_KEY = "user.dir";
+
+ /**
+ * The System property key for the Java IO temporary directory.
+ */
+ private static final String JAVA_IO_TMPDIR_KEY = "java.io.tmpdir";
+
+ /**
+ * The System property key for the Java home directory.
+ */
+ private static final String JAVA_HOME_KEY = "java.home";
+
+ /**
+ *
+ * The {@code awt.toolkit} System Property.
+ *
+ *
+ * Holds a class name, on Windows XP this is {@code sun.awt.windows.WToolkit}.
+ *
+ *
+ * On platforms without a GUI, this value is {@code null}.
+ *
+ *
+ * Defaults to {@code null} if the runtime does not have security access to read this property or the property does
+ * not exist.
+ *
+ *
+ * This value is initialized when the class is loaded. If {@link System#setProperty(String,String)} or
+ * {@link System#setProperties(java.util.Properties)} is called after this class is loaded, the value will be out of
+ * sync with that System property.
+ *
+ *
+ * @since 2.1
+ */
+ public static final String AWT_TOOLKIT = getSystemProperty("awt.toolkit");
+
+ /**
+ *
+ * The {@code file.encoding} System Property.
+ *
+ *
+ * File encoding, such as {@code Cp1252}.
+ *
+ *
+ * Defaults to {@code null} if the runtime does not have security access to read this property or the property does
+ * not exist.
+ *
+ *
+ * This value is initialized when the class is loaded. If {@link System#setProperty(String,String)} or
+ * {@link System#setProperties(java.util.Properties)} is called after this class is loaded, the value will be out of
+ * sync with that System property.
+ *
+ *
+ * @since 2.0
+ * @since Java 1.2
+ */
+ public static final String FILE_ENCODING = getSystemProperty("file.encoding");
+
+ /**
+ *
+ * The {@code file.separator} System Property. File separator ("/" on UNIX).
+ *
+ *
+ * Defaults to {@code null} if the runtime does not have security access to read this property or the property does
+ * not exist.
+ *
+ *
+ * This value is initialized when the class is loaded. If {@link System#setProperty(String,String)} or
+ * {@link System#setProperties(java.util.Properties)} is called after this class is loaded, the value will be out of
+ * sync with that System property.
+ *
+ *
+ * @since Java 1.1
+ */
+ public static final String FILE_SEPARATOR = getSystemProperty("file.separator");
+
+ /**
+ *
+ * The {@code java.awt.fonts} System Property.
+ *
+ *
+ * Defaults to {@code null} if the runtime does not have security access to read this property or the property does
+ * not exist.
+ *
+ *
+ * This value is initialized when the class is loaded. If {@link System#setProperty(String,String)} or
+ * {@link System#setProperties(java.util.Properties)} is called after this class is loaded, the value will be out of
+ * sync with that System property.
+ *
+ *
+ * @since 2.1
+ */
+ public static final String JAVA_AWT_FONTS = getSystemProperty("java.awt.fonts");
+
+ /**
+ *
+ * The {@code java.awt.graphicsenv} System Property.
+ *
+ *
+ * Defaults to {@code null} if the runtime does not have security access to read this property or the property does
+ * not exist.
+ *
+ *
+ * This value is initialized when the class is loaded. If {@link System#setProperty(String,String)} or
+ * {@link System#setProperties(java.util.Properties)} is called after this class is loaded, the value will be out of
+ * sync with that System property.
+ *
+ *
+ * @since 2.1
+ */
+ public static final String JAVA_AWT_GRAPHICSENV = getSystemProperty("java.awt.graphicsenv");
+
+ /**
+ *
+ * The {@code java.awt.headless} System Property. The value of this property is the String {@code "true"} or
+ * {@code "false"}.
+ *
+ *
+ * Defaults to {@code null} if the runtime does not have security access to read this property or the property does
+ * not exist.
+ *
+ *
+ * This value is initialized when the class is loaded. If {@link System#setProperty(String,String)} or
+ * {@link System#setProperties(java.util.Properties)} is called after this class is loaded, the value will be out of
+ * sync with that System property.
+ *
+ *
+ * @see #isJavaAwtHeadless()
+ * @since 2.1
+ * @since Java 1.4
+ */
+ public static final String JAVA_AWT_HEADLESS = getSystemProperty("java.awt.headless");
+
+ /**
+ *
+ * The {@code java.awt.printerjob} System Property.
+ *
+ *
+ * Defaults to {@code null} if the runtime does not have security access to read this property or the property does
+ * not exist.
+ *
+ *
+ * This value is initialized when the class is loaded. If {@link System#setProperty(String,String)} or
+ * {@link System#setProperties(java.util.Properties)} is called after this class is loaded, the value will be out of
+ * sync with that System property.
+ *
+ *
+ * @since 2.1
+ */
+ public static final String JAVA_AWT_PRINTERJOB = getSystemProperty("java.awt.printerjob");
+
+ /**
+ *
+ * The {@code java.class.path} System Property. Java class path.
+ *
+ *
+ * Defaults to {@code null} if the runtime does not have security access to read this property or the property does
+ * not exist.
+ *
+ *
+ * This value is initialized when the class is loaded. If {@link System#setProperty(String,String)} or
+ * {@link System#setProperties(java.util.Properties)} is called after this class is loaded, the value will be out of
+ * sync with that System property.
+ *
+ *
+ * @since Java 1.1
+ */
+ public static final String JAVA_CLASS_PATH = getSystemProperty("java.class.path");
+
+ /**
+ *
+ * The {@code java.class.version} System Property. Java class format version number.
+ *
+ *
+ * Defaults to {@code null} if the runtime does not have security access to read this property or the property does
+ * not exist.
+ *
+ *
+ * This value is initialized when the class is loaded. If {@link System#setProperty(String,String)} or
+ * {@link System#setProperties(java.util.Properties)} is called after this class is loaded, the value will be out of
+ * sync with that System property.
+ *
+ *
+ * @since Java 1.1
+ */
+ public static final String JAVA_CLASS_VERSION = getSystemProperty("java.class.version");
+
+ /**
+ *
+ * The {@code java.compiler} System Property. Name of JIT compiler to use. First in JDK version 1.2. Not used in Sun
+ * JDKs after 1.2.
+ *
+ *
+ * Defaults to {@code null} if the runtime does not have security access to read this property or the property does
+ * not exist.
+ *
+ *
+ * This value is initialized when the class is loaded. If {@link System#setProperty(String,String)} or
+ * {@link System#setProperties(java.util.Properties)} is called after this class is loaded, the value will be out of
+ * sync with that System property.
+ *
+ *
+ * @since Java 1.2. Not used in Sun versions after 1.2.
+ */
+ public static final String JAVA_COMPILER = getSystemProperty("java.compiler");
+
+ /**
+ *
+ * The {@code java.endorsed.dirs} System Property. Path of endorsed directory or directories.
+ *
+ *
+ * Defaults to {@code null} if the runtime does not have security access to read this property or the property does
+ * not exist.
+ *
+ *
+ * This value is initialized when the class is loaded. If {@link System#setProperty(String,String)} or
+ * {@link System#setProperties(java.util.Properties)} is called after this class is loaded, the value will be out of
+ * sync with that System property.
+ *
+ *
+ * @since Java 1.4
+ */
+ public static final String JAVA_ENDORSED_DIRS = getSystemProperty("java.endorsed.dirs");
+
+ /**
+ *
+ * The {@code java.ext.dirs} System Property. Path of extension directory or directories.
+ *
+ *
+ * Defaults to {@code null} if the runtime does not have security access to read this property or the property does
+ * not exist.
+ *
+ *
+ * This value is initialized when the class is loaded. If {@link System#setProperty(String,String)} or
+ * {@link System#setProperties(java.util.Properties)} is called after this class is loaded, the value will be out of
+ * sync with that System property.
+ *
+ *
+ * @since Java 1.3
+ */
+ public static final String JAVA_EXT_DIRS = getSystemProperty("java.ext.dirs");
+
+ /**
+ *
+ * The {@code java.home} System Property. Java installation directory.
+ *
+ *
+ * Defaults to {@code null} if the runtime does not have security access to read this property or the property does
+ * not exist.
+ *
+ *
+ * This value is initialized when the class is loaded. If {@link System#setProperty(String,String)} or
+ * {@link System#setProperties(java.util.Properties)} is called after this class is loaded, the value will be out of
+ * sync with that System property.
+ *
+ *
+ * @since Java 1.1
+ */
+ public static final String JAVA_HOME = getSystemProperty(JAVA_HOME_KEY);
+
+ /**
+ *
+ * The {@code java.io.tmpdir} System Property. Default temp file path.
+ *
+ *
+ * Defaults to {@code null} if the runtime does not have security access to read this property or the property does
+ * not exist.
+ *
+ *
+ * This value is initialized when the class is loaded. If {@link System#setProperty(String,String)} or
+ * {@link System#setProperties(java.util.Properties)} is called after this class is loaded, the value will be out of
+ * sync with that System property.
+ *
+ *
+ * @since Java 1.2
+ */
+ public static final String JAVA_IO_TMPDIR = getSystemProperty(JAVA_IO_TMPDIR_KEY);
+
+ /**
+ *
+ * The {@code java.library.path} System Property. List of paths to search when loading libraries.
+ *
+ *
+ * Defaults to {@code null} if the runtime does not have security access to read this property or the property does
+ * not exist.
+ *
+ *
+ * This value is initialized when the class is loaded. If {@link System#setProperty(String,String)} or
+ * {@link System#setProperties(java.util.Properties)} is called after this class is loaded, the value will be out of
+ * sync with that System property.
+ *
+ *
+ * @since Java 1.2
+ */
+ public static final String JAVA_LIBRARY_PATH = getSystemProperty("java.library.path");
+
+ /**
+ *
+ * The {@code java.runtime.name} System Property. Java Runtime Environment name.
+ *
+ *
+ * Defaults to {@code null} if the runtime does not have security access to read this property or the property does
+ * not exist.
+ *
+ *
+ * This value is initialized when the class is loaded. If {@link System#setProperty(String,String)} or
+ * {@link System#setProperties(java.util.Properties)} is called after this class is loaded, the value will be out of
+ * sync with that System property.
+ *
+ *
+ * @since 2.0
+ * @since Java 1.3
+ */
+ public static final String JAVA_RUNTIME_NAME = getSystemProperty("java.runtime.name");
+
+ /**
+ *
+ * The {@code java.runtime.version} System Property. Java Runtime Environment version.
+ *
+ *
+ * Defaults to {@code null} if the runtime does not have security access to read this property or the property does
+ * not exist.
+ *
+ *
+ * This value is initialized when the class is loaded. If {@link System#setProperty(String,String)} or
+ * {@link System#setProperties(java.util.Properties)} is called after this class is loaded, the value will be out of
+ * sync with that System property.
+ *
+ *
+ * @since 2.0
+ * @since Java 1.3
+ */
+ public static final String JAVA_RUNTIME_VERSION = getSystemProperty("java.runtime.version");
+
+ /**
+ *
+ * The {@code java.specification.name} System Property. Java Runtime Environment specification name.
+ *
+ *
+ * Defaults to {@code null} if the runtime does not have security access to read this property or the property does
+ * not exist.
+ *
+ *
+ * This value is initialized when the class is loaded. If {@link System#setProperty(String,String)} or
+ * {@link System#setProperties(java.util.Properties)} is called after this class is loaded, the value will be out of
+ * sync with that System property.
+ *
+ *
+ * @since Java 1.2
+ */
+ public static final String JAVA_SPECIFICATION_NAME = getSystemProperty("java.specification.name");
+
+ /**
+ *
+ * The {@code java.specification.vendor} System Property. Java Runtime Environment specification vendor.
+ *
+ *
+ * Defaults to {@code null} if the runtime does not have security access to read this property or the property does
+ * not exist.
+ *
+ *
+ * This value is initialized when the class is loaded. If {@link System#setProperty(String,String)} or
+ * {@link System#setProperties(java.util.Properties)} is called after this class is loaded, the value will be out of
+ * sync with that System property.
+ *
+ *
+ * @since Java 1.2
+ */
+ public static final String JAVA_SPECIFICATION_VENDOR = getSystemProperty("java.specification.vendor");
+
+ /**
+ *
+ * The {@code java.specification.version} System Property. Java Runtime Environment specification version.
+ *
+ *
+ * Defaults to {@code null} if the runtime does not have security access to read this property or the property does
+ * not exist.
+ *
+ *
+ * This value is initialized when the class is loaded. If {@link System#setProperty(String,String)} or
+ * {@link System#setProperties(java.util.Properties)} is called after this class is loaded, the value will be out of
+ * sync with that System property.
+ *
+ *
+ * @since Java 1.3
+ */
+ public static final String JAVA_SPECIFICATION_VERSION = getSystemProperty("java.specification.version");
+ private static final JavaVersion JAVA_SPECIFICATION_VERSION_AS_ENUM = JavaVersion.get(JAVA_SPECIFICATION_VERSION);
+
+ /**
+ *
+ * The {@code java.util.prefs.PreferencesFactory} System Property. A class name.
+ *
+ *
+ * Defaults to {@code null} if the runtime does not have security access to read this property or the property does
+ * not exist.
+ *
+ *
+ * This value is initialized when the class is loaded. If {@link System#setProperty(String,String)} or
+ * {@link System#setProperties(java.util.Properties)} is called after this class is loaded, the value will be out of
+ * sync with that System property.
+ *
+ *
+ * @since 2.1
+ * @since Java 1.4
+ */
+ public static final String JAVA_UTIL_PREFS_PREFERENCES_FACTORY =
+ getSystemProperty("java.util.prefs.PreferencesFactory");
+
+ /**
+ *
+ * The {@code java.vendor} System Property. Java vendor-specific string.
+ *
+ *
+ * Defaults to {@code null} if the runtime does not have security access to read this property or the property does
+ * not exist.
+ *
+ *
+ * This value is initialized when the class is loaded. If {@link System#setProperty(String,String)} or
+ * {@link System#setProperties(java.util.Properties)} is called after this class is loaded, the value will be out of
+ * sync with that System property.
+ *
+ *
+ * @since Java 1.1
+ */
+ public static final String JAVA_VENDOR = getSystemProperty("java.vendor");
+
+ /**
+ *
+ * The {@code java.vendor.url} System Property. Java vendor URL.
+ *
+ *
+ * Defaults to {@code null} if the runtime does not have security access to read this property or the property does
+ * not exist.
+ *
+ *
+ * This value is initialized when the class is loaded. If {@link System#setProperty(String,String)} or
+ * {@link System#setProperties(java.util.Properties)} is called after this class is loaded, the value will be out of
+ * sync with that System property.
+ *
+ *
+ * @since Java 1.1
+ */
+ public static final String JAVA_VENDOR_URL = getSystemProperty("java.vendor.url");
+
+ /**
+ *
+ * The {@code java.version} System Property. Java version number.
+ *
+ *
+ * Defaults to {@code null} if the runtime does not have security access to read this property or the property does
+ * not exist.
+ *
+ *
+ * This value is initialized when the class is loaded. If {@link System#setProperty(String,String)} or
+ * {@link System#setProperties(java.util.Properties)} is called after this class is loaded, the value will be out of
+ * sync with that System property.
+ *
+ *
+ * @since Java 1.1
+ */
+ public static final String JAVA_VERSION = getSystemProperty("java.version");
+
+ /**
+ *
+ * The {@code java.vm.info} System Property. Java Virtual Machine implementation info.
+ *
+ *
+ * Defaults to {@code null} if the runtime does not have security access to read this property or the property does
+ * not exist.
+ *
+ *
+ * This value is initialized when the class is loaded. If {@link System#setProperty(String,String)} or
+ * {@link System#setProperties(java.util.Properties)} is called after this class is loaded, the value will be out of
+ * sync with that System property.
+ *
+ *
+ * @since 2.0
+ * @since Java 1.2
+ */
+ public static final String JAVA_VM_INFO = getSystemProperty("java.vm.info");
+
+ /**
+ *
+ * The {@code java.vm.name} System Property. Java Virtual Machine implementation name.
+ *
+ *
+ * Defaults to {@code null} if the runtime does not have security access to read this property or the property does
+ * not exist.
+ *
+ *
+ * This value is initialized when the class is loaded. If {@link System#setProperty(String,String)} or
+ * {@link System#setProperties(java.util.Properties)} is called after this class is loaded, the value will be out of
+ * sync with that System property.
+ *
+ *
+ * @since Java 1.2
+ */
+ public static final String JAVA_VM_NAME = getSystemProperty("java.vm.name");
+
+ /**
+ *
+ * The {@code java.vm.specification.name} System Property. Java Virtual Machine specification name.
+ *
+ *
+ * Defaults to {@code null} if the runtime does not have security access to read this property or the property does
+ * not exist.
+ *
+ *
+ * This value is initialized when the class is loaded. If {@link System#setProperty(String,String)} or
+ * {@link System#setProperties(java.util.Properties)} is called after this class is loaded, the value will be out of
+ * sync with that System property.
+ *
+ *
+ * @since Java 1.2
+ */
+ public static final String JAVA_VM_SPECIFICATION_NAME = getSystemProperty("java.vm.specification.name");
+
+ /**
+ *
+ * The {@code java.vm.specification.vendor} System Property. Java Virtual Machine specification vendor.
+ *
+ *
+ * Defaults to {@code null} if the runtime does not have security access to read this property or the property does
+ * not exist.
+ *
+ *
+ * This value is initialized when the class is loaded. If {@link System#setProperty(String,String)} or
+ * {@link System#setProperties(java.util.Properties)} is called after this class is loaded, the value will be out of
+ * sync with that System property.
+ *
+ *
+ * @since Java 1.2
+ */
+ public static final String JAVA_VM_SPECIFICATION_VENDOR = getSystemProperty("java.vm.specification.vendor");
+
+ /**
+ *
+ * The {@code java.vm.specification.version} System Property. Java Virtual Machine specification version.
+ *
+ *
+ * Defaults to {@code null} if the runtime does not have security access to read this property or the property does
+ * not exist.
+ *
+ *
+ * This value is initialized when the class is loaded. If {@link System#setProperty(String,String)} or
+ * {@link System#setProperties(java.util.Properties)} is called after this class is loaded, the value will be out of
+ * sync with that System property.
+ *
+ *
+ * @since Java 1.2
+ */
+ public static final String JAVA_VM_SPECIFICATION_VERSION = getSystemProperty("java.vm.specification.version");
+
+ /**
+ *
+ * The {@code java.vm.vendor} System Property. Java Virtual Machine implementation vendor.
+ *
+ *
+ * Defaults to {@code null} if the runtime does not have security access to read this property or the property does
+ * not exist.
+ *
+ *
+ * This value is initialized when the class is loaded. If {@link System#setProperty(String,String)} or
+ * {@link System#setProperties(java.util.Properties)} is called after this class is loaded, the value will be out of
+ * sync with that System property.
+ *
+ *
+ * @since Java 1.2
+ */
+ public static final String JAVA_VM_VENDOR = getSystemProperty("java.vm.vendor");
+
+ /**
+ *
+ * The {@code java.vm.version} System Property. Java Virtual Machine implementation version.
+ *
+ *
+ * Defaults to {@code null} if the runtime does not have security access to read this property or the property does
+ * not exist.
+ *
+ *
+ * This value is initialized when the class is loaded. If {@link System#setProperty(String,String)} or
+ * {@link System#setProperties(java.util.Properties)} is called after this class is loaded, the value will be out of
+ * sync with that System property.
+ *
+ *
+ * @since Java 1.2
+ */
+ public static final String JAVA_VM_VERSION = getSystemProperty("java.vm.version");
+
+ /**
+ *
+ * The {@code line.separator} System Property. Line separator ("\n" on UNIX).
+ *
+ *
+ * Defaults to {@code null} if the runtime does not have security access to read this property or the property does
+ * not exist.
+ *
+ *
+ * This value is initialized when the class is loaded. If {@link System#setProperty(String,String)} or
+ * {@link System#setProperties(java.util.Properties)} is called after this class is loaded, the value will be out of
+ * sync with that System property.
+ *
+ *
+ * @since Java 1.1
+ */
+ public static final String LINE_SEPARATOR = getSystemProperty("line.separator");
+
+ /**
+ *
+ * The {@code os.arch} System Property. Operating system architecture.
+ *
+ *
+ * Defaults to {@code null} if the runtime does not have security access to read this property or the property does
+ * not exist.
+ *
+ *
+ * This value is initialized when the class is loaded. If {@link System#setProperty(String,String)} or
+ * {@link System#setProperties(java.util.Properties)} is called after this class is loaded, the value will be out of
+ * sync with that System property.
+ *
+ *
+ * @since Java 1.1
+ */
+ public static final String OS_ARCH = getSystemProperty("os.arch");
+
+ /**
+ *
+ * The {@code os.name} System Property. Operating system name.
+ *
+ *
+ * Defaults to {@code null} if the runtime does not have security access to read this property or the property does
+ * not exist.
+ *
+ *
+ * This value is initialized when the class is loaded. If {@link System#setProperty(String,String)} or
+ * {@link System#setProperties(java.util.Properties)} is called after this class is loaded, the value will be out of
+ * sync with that System property.
+ *
+ *
+ * @since Java 1.1
+ */
+ public static final String OS_NAME = getSystemProperty("os.name");
+
+ /**
+ *
+ * The {@code os.version} System Property. Operating system version.
+ *
+ *
+ * Defaults to {@code null} if the runtime does not have security access to read this property or the property does
+ * not exist.
+ *
+ *
+ * This value is initialized when the class is loaded. If {@link System#setProperty(String,String)} or
+ * {@link System#setProperties(java.util.Properties)} is called after this class is loaded, the value will be out of
+ * sync with that System property.
+ *
+ *
+ * @since Java 1.1
+ */
+ public static final String OS_VERSION = getSystemProperty("os.version");
+
+ /**
+ *
+ * The {@code path.separator} System Property. Path separator (":" on UNIX).
+ *
+ *
+ * Defaults to {@code null} if the runtime does not have security access to read this property or the property does
+ * not exist.
+ *
+ *
+ * This value is initialized when the class is loaded. If {@link System#setProperty(String,String)} or
+ * {@link System#setProperties(java.util.Properties)} is called after this class is loaded, the value will be out of
+ * sync with that System property.
+ *
+ *
+ * @since Java 1.1
+ */
+ public static final String PATH_SEPARATOR = getSystemProperty("path.separator");
+
+ /**
+ *
+ * The {@code user.country} or {@code user.region} System Property. User's country code, such as {@code GB}. First
+ * in Java version 1.2 as {@code user.region}. Renamed to {@code user.country} in 1.4
+ *
+ *
+ * Defaults to {@code null} if the runtime does not have security access to read this property or the property does
+ * not exist.
+ *
+ *
+ * This value is initialized when the class is loaded. If {@link System#setProperty(String,String)} or
+ * {@link System#setProperties(java.util.Properties)} is called after this class is loaded, the value will be out of
+ * sync with that System property.
+ *
+ *
+ * @since 2.0
+ * @since Java 1.2
+ */
+ public static final String USER_COUNTRY = getSystemProperty("user.country") == null ?
+ getSystemProperty("user.region") : getSystemProperty("user.country");
+
+ /**
+ *
+ * The {@code user.dir} System Property. User's current working directory.
+ *
+ *
+ * Defaults to {@code null} if the runtime does not have security access to read this property or the property does
+ * not exist.
+ *
+ *
+ * This value is initialized when the class is loaded. If {@link System#setProperty(String,String)} or
+ * {@link System#setProperties(java.util.Properties)} is called after this class is loaded, the value will be out of
+ * sync with that System property.
+ *
+ *
+ * @since Java 1.1
+ */
+ public static final String USER_DIR = getSystemProperty(USER_DIR_KEY);
+
+ /**
+ *
+ * The {@code user.home} System Property. User's home directory.
+ *
+ *
+ * Defaults to {@code null} if the runtime does not have security access to read this property or the property does
+ * not exist.
+ *
+ *
+ * This value is initialized when the class is loaded. If {@link System#setProperty(String,String)} or
+ * {@link System#setProperties(java.util.Properties)} is called after this class is loaded, the value will be out of
+ * sync with that System property.
+ *
+ *
+ * @since Java 1.1
+ */
+ public static final String USER_HOME = getSystemProperty(USER_HOME_KEY);
+
+ /**
+ *
+ * The {@code user.language} System Property. User's language code, such as {@code "en"}.
+ *
+ *
+ * Defaults to {@code null} if the runtime does not have security access to read this property or the property does
+ * not exist.
+ *
+ *
+ * This value is initialized when the class is loaded. If {@link System#setProperty(String,String)} or
+ * {@link System#setProperties(java.util.Properties)} is called after this class is loaded, the value will be out of
+ * sync with that System property.
+ *
+ *
+ * @since 2.0
+ * @since Java 1.2
+ */
+ public static final String USER_LANGUAGE = getSystemProperty("user.language");
+
+ /**
+ *
+ * The {@code user.name} System Property. User's account name.
+ *
+ *
+ * Defaults to {@code null} if the runtime does not have security access to read this property or the property does
+ * not exist.
+ *
+ *
+ * This value is initialized when the class is loaded. If {@link System#setProperty(String,String)} or
+ * {@link System#setProperties(java.util.Properties)} is called after this class is loaded, the value will be out of
+ * sync with that System property.
+ *
+ *
+ * @since Java 1.1
+ */
+ public static final String USER_NAME = getSystemProperty("user.name");
+
+ /**
+ *
+ * The {@code user.timezone} System Property. For example: {@code "America/Los_Angeles"}.
+ *
+ *
+ * Defaults to {@code null} if the runtime does not have security access to read this property or the property does
+ * not exist.
+ *
+ *
+ * This value is initialized when the class is loaded. If {@link System#setProperty(String,String)} or
+ * {@link System#setProperties(java.util.Properties)} is called after this class is loaded, the value will be out of
+ * sync with that System property.
+ *
+ *
+ * @since 2.1
+ */
+ public static final String USER_TIMEZONE = getSystemProperty("user.timezone");
+
+ // Java version checks
+ // -----------------------------------------------------------------------
+ // These MUST be declared after those above as they depend on the
+ // values being set up
+
+ /**
+ *
+ * Is {@code true} if this is Java version 1.1 (also 1.1.x versions).
+ *
+ *
+ * The field will return {@code false} if {@link #JAVA_VERSION} is {@code null}.
+ *
+ */
+ public static final boolean IS_JAVA_1_1 = getJavaVersionMatches("1.1");
+
+ /**
+ *
+ * Is {@code true} if this is Java version 1.2 (also 1.2.x versions).
+ *
+ *
+ * The field will return {@code false} if {@link #JAVA_VERSION} is {@code null}.
+ *
+ */
+ public static final boolean IS_JAVA_1_2 = getJavaVersionMatches("1.2");
+
+ /**
+ *
+ * Is {@code true} if this is Java version 1.3 (also 1.3.x versions).
+ *
+ *
+ * The field will return {@code false} if {@link #JAVA_VERSION} is {@code null}.
+ *
+ */
+ public static final boolean IS_JAVA_1_3 = getJavaVersionMatches("1.3");
+
+ /**
+ *
+ * Is {@code true} if this is Java version 1.4 (also 1.4.x versions).
+ *
+ *
+ * The field will return {@code false} if {@link #JAVA_VERSION} is {@code null}.
+ *
+ */
+ public static final boolean IS_JAVA_1_4 = getJavaVersionMatches("1.4");
+
+ /**
+ *
+ * Is {@code true} if this is Java version 1.5 (also 1.5.x versions).
+ *
+ *
+ * The field will return {@code false} if {@link #JAVA_VERSION} is {@code null}.
+ *
+ */
+ public static final boolean IS_JAVA_1_5 = getJavaVersionMatches("1.5");
+
+ /**
+ *
+ * Is {@code true} if this is Java version 1.6 (also 1.6.x versions).
+ *
+ *
+ * The field will return {@code false} if {@link #JAVA_VERSION} is {@code null}.
+ *
+ */
+ public static final boolean IS_JAVA_1_6 = getJavaVersionMatches("1.6");
+
+ /**
+ *
+ * Is {@code true} if this is Java version 1.7 (also 1.7.x versions).
+ *
+ *
+ * The field will return {@code false} if {@link #JAVA_VERSION} is {@code null}.
+ *
+ *
+ * @since 3.0
+ */
+ public static final boolean IS_JAVA_1_7 = getJavaVersionMatches("1.7");
+
+ // Operating system checks
+ // -----------------------------------------------------------------------
+ // These MUST be declared after those above as they depend on the
+ // values being set up
+ // OS names from http://www.vamphq.com/os.html
+ // Selected ones included - please advise dev@commons.apache.org
+ // if you want another added or a mistake corrected
+
+ /**
+ *
+ * Is {@code true} if this is AIX.
+ *
+ *
+ * The field will return {@code false} if {@code OS_NAME} is {@code null}.
+ *
+ *
+ * @since 2.0
+ */
+ public static final boolean IS_OS_AIX = getOSMatchesName("AIX");
+
+ /**
+ *
+ * Is {@code true} if this is HP-UX.
+ *
+ *
+ * The field will return {@code false} if {@code OS_NAME} is {@code null}.
+ *
+ *
+ * @since 2.0
+ */
+ public static final boolean IS_OS_HP_UX = getOSMatchesName("HP-UX");
+
+ /**
+ *
+ * Is {@code true} if this is Irix.
+ *
+ *
+ * The field will return {@code false} if {@code OS_NAME} is {@code null}.
+ *
+ *
+ * @since 2.0
+ */
+ public static final boolean IS_OS_IRIX = getOSMatchesName("Irix");
+
+ /**
+ *
+ * Is {@code true} if this is Linux.
+ *
+ *
+ * The field will return {@code false} if {@code OS_NAME} is {@code null}.
+ *
+ *
+ * @since 2.0
+ */
+ public static final boolean IS_OS_LINUX = getOSMatchesName("Linux") || getOSMatchesName("LINUX");
+
+ /**
+ *
+ * Is {@code true} if this is Mac.
+ *
+ *
+ * The field will return {@code false} if {@code OS_NAME} is {@code null}.
+ *
+ *
+ * @since 2.0
+ */
+ public static final boolean IS_OS_MAC = getOSMatchesName("Mac");
+
+ /**
+ *
+ * Is {@code true} if this is Mac.
+ *
+ *
+ * The field will return {@code false} if {@code OS_NAME} is {@code null}.
+ *
+ *
+ * @since 2.0
+ */
+ public static final boolean IS_OS_MAC_OSX = getOSMatchesName("Mac OS X");
+
+ /**
+ *
+ * Is {@code true} if this is FreeBSD.
+ *
+ *
+ * The field will return {@code false} if {@code OS_NAME} is {@code null}.
+ *
+ *
+ * @since 3.1
+ */
+ public static final boolean IS_OS_FREE_BSD = getOSMatchesName("FreeBSD");
+
+ /**
+ *
+ * Is {@code true} if this is OpenBSD.
+ *
+ *
+ * The field will return {@code false} if {@code OS_NAME} is {@code null}.
+ *
+ *
+ * @since 3.1
+ */
+ public static final boolean IS_OS_OPEN_BSD = getOSMatchesName("OpenBSD");
+
+ /**
+ *
+ * Is {@code true} if this is NetBSD.
+ *
+ *
+ * The field will return {@code false} if {@code OS_NAME} is {@code null}.
+ *
+ *
+ * @since 3.1
+ */
+ public static final boolean IS_OS_NET_BSD = getOSMatchesName("NetBSD");
+
+ /**
+ *
+ * Is {@code true} if this is OS/2.
+ *
+ *
+ * The field will return {@code false} if {@code OS_NAME} is {@code null}.
+ *
+ *
+ * @since 2.0
+ */
+ public static final boolean IS_OS_OS2 = getOSMatchesName("OS/2");
+
+ /**
+ *
+ * Is {@code true} if this is Solaris.
+ *
+ *
+ * The field will return {@code false} if {@code OS_NAME} is {@code null}.
+ *
+ *
+ * @since 2.0
+ */
+ public static final boolean IS_OS_SOLARIS = getOSMatchesName("Solaris");
+
+ /**
+ *
+ * Is {@code true} if this is SunOS.
+ *
+ *
+ * The field will return {@code false} if {@code OS_NAME} is {@code null}.
+ *
+ *
+ * @since 2.0
+ */
+ public static final boolean IS_OS_SUN_OS = getOSMatchesName("SunOS");
+
+ /**
+ *
+ * Is {@code true} if this is a UNIX like system, as in any of AIX, HP-UX, Irix, Linux, MacOSX, Solaris or SUN OS.
+ *
+ *
+ * The field will return {@code false} if {@code OS_NAME} is {@code null}.
+ *
+ *
+ * @since 2.1
+ */
+ public static final boolean IS_OS_UNIX = IS_OS_AIX || IS_OS_HP_UX || IS_OS_IRIX || IS_OS_LINUX || IS_OS_MAC_OSX
+ || IS_OS_SOLARIS || IS_OS_SUN_OS || IS_OS_FREE_BSD || IS_OS_OPEN_BSD || IS_OS_NET_BSD;
+
+ /**
+ *
+ * Is {@code true} if this is Windows.
+ *
+ *
+ * The field will return {@code false} if {@code OS_NAME} is {@code null}.
+ *
+ *
+ * @since 2.0
+ */
+ public static final boolean IS_OS_WINDOWS = getOSMatchesName(OS_NAME_WINDOWS_PREFIX);
+
+ /**
+ *
+ * Is {@code true} if this is Windows 2000.
+ *
+ *
+ * The field will return {@code false} if {@code OS_NAME} is {@code null}.
+ *
+ *
+ * @since 2.0
+ */
+ public static final boolean IS_OS_WINDOWS_2000 = getOSMatches(OS_NAME_WINDOWS_PREFIX, "5.0");
+
+ /**
+ *
+ * Is {@code true} if this is Windows 2003.
+ *
+ *
+ * The field will return {@code false} if {@code OS_NAME} is {@code null}.
+ *
+ *
+ * @since 3.1
+ */
+ public static final boolean IS_OS_WINDOWS_2003 = getOSMatches(OS_NAME_WINDOWS_PREFIX, "5.2");
+
+ /**
+ *
+ * Is {@code true} if this is Windows 2008.
+ *
+ *
+ * The field will return {@code false} if {@code OS_NAME} is {@code null}.
+ *
+ *
+ * @since 3.1
+ */
+ public static final boolean IS_OS_WINDOWS_2008 = getOSMatches(OS_NAME_WINDOWS_PREFIX + " Server 2008", "6.1");
+
+ /**
+ *
+ * Is {@code true} if this is Windows 95.
+ *
+ *
+ * The field will return {@code false} if {@code OS_NAME} is {@code null}.
+ *
+ *
+ * @since 2.0
+ */
+ public static final boolean IS_OS_WINDOWS_95 = getOSMatches(OS_NAME_WINDOWS_PREFIX + " 9", "4.0");
+ // Java 1.2 running on Windows98 returns 'Windows 95', hence the above
+
+ /**
+ *
+ * Is {@code true} if this is Windows 98.
+ *
+ *
+ * The field will return {@code false} if {@code OS_NAME} is {@code null}.
+ *
+ *
+ * @since 2.0
+ */
+ public static final boolean IS_OS_WINDOWS_98 = getOSMatches(OS_NAME_WINDOWS_PREFIX + " 9", "4.1");
+ // Java 1.2 running on Windows98 returns 'Windows 95', hence the above
+
+ /**
+ *
+ * Is {@code true} if this is Windows ME.
+ *
+ *
+ * The field will return {@code false} if {@code OS_NAME} is {@code null}.
+ *
+ *
+ * @since 2.0
+ */
+ public static final boolean IS_OS_WINDOWS_ME = getOSMatches(OS_NAME_WINDOWS_PREFIX, "4.9");
+ // Java 1.2 running on WindowsME may return 'Windows 95', hence the above
+
+ /**
+ *
+ * Is {@code true} if this is Windows NT.
+ *
+ *
+ * The field will return {@code false} if {@code OS_NAME} is {@code null}.
+ *
+ *
+ * @since 2.0
+ */
+ public static final boolean IS_OS_WINDOWS_NT = getOSMatchesName(OS_NAME_WINDOWS_PREFIX + " NT");
+ // Windows 2000 returns 'Windows 2000' but may suffer from same Java1.2 problem
+
+ /**
+ *
+ * Is {@code true} if this is Windows XP.
+ *
+ *
+ * The field will return {@code false} if {@code OS_NAME} is {@code null}.
+ *
+ *
+ * @since 2.0
+ */
+ public static final boolean IS_OS_WINDOWS_XP = getOSMatches(OS_NAME_WINDOWS_PREFIX, "5.1");
+
+ // -----------------------------------------------------------------------
+ /**
+ *
+ * Is {@code true} if this is Windows Vista.
+ *
+ *
+ * The field will return {@code false} if {@code OS_NAME} is {@code null}.
+ *
+ *
+ * @since 2.4
+ */
+ public static final boolean IS_OS_WINDOWS_VISTA = getOSMatches(OS_NAME_WINDOWS_PREFIX, "6.0");
+
+ /**
+ *
+ * Is {@code true} if this is Windows 7.
+ *
+ *
+ * The field will return {@code false} if {@code OS_NAME} is {@code null}.
+ *
+ *
+ * @since 3.0
+ */
+ public static final boolean IS_OS_WINDOWS_7 = getOSMatches(OS_NAME_WINDOWS_PREFIX, "6.1");
+
+ /**
+ *
+ * Gets the Java home directory as a {@code File}.
+ *
+ *
+ * @return a directory
+ * @throws SecurityException if a security manager exists and its {@code checkPropertyAccess} method doesn't allow
+ * access to the specified system property.
+ * @see System#getProperty(String)
+ * @since 2.1
+ */
+ public static File getJavaHome() {
+ return new File(System.getProperty(JAVA_HOME_KEY));
+ }
+
+ /**
+ *
+ * Gets the Java IO temporary directory as a {@code File}.
+ *
+ *
+ * @return a directory
+ * @throws SecurityException if a security manager exists and its {@code checkPropertyAccess} method doesn't allow
+ * access to the specified system property.
+ * @see System#getProperty(String)
+ * @since 2.1
+ */
+ public static File getJavaIoTmpDir() {
+ return new File(System.getProperty(JAVA_IO_TMPDIR_KEY));
+ }
+
+ /**
+ *
+ * Decides if the Java version matches.
+ *
+ *
+ * @param versionPrefix the prefix for the java version
+ * @return true if matches, or false if not or can't determine
+ */
+ private static boolean getJavaVersionMatches(String versionPrefix) {
+ return isJavaVersionMatch(JAVA_SPECIFICATION_VERSION, versionPrefix);
+ }
+
+ /**
+ * Decides if the operating system matches.
+ *
+ * @param osNamePrefix the prefix for the os name
+ * @param osVersionPrefix the prefix for the version
+ * @return true if matches, or false if not or can't determine
+ */
+ private static boolean getOSMatches(String osNamePrefix, String osVersionPrefix) {
+ return isOSMatch(OS_NAME, OS_VERSION, osNamePrefix, osVersionPrefix);
+ }
+
+ /**
+ * Decides if the operating system matches.
+ *
+ * @param osNamePrefix the prefix for the os name
+ * @return true if matches, or false if not or can't determine
+ */
+ private static boolean getOSMatchesName(String osNamePrefix) {
+ return isOSNameMatch(OS_NAME, osNamePrefix);
+ }
+
+ // -----------------------------------------------------------------------
+ /**
+ *
+ * Gets a System property, defaulting to {@code null} if the property cannot be read.
+ *
+ *
+ * If a {@code SecurityException} is caught, the return value is {@code null} and a message is written to
+ * {@code System.err}.
+ *
+ *
+ * @param property the system property name
+ * @return the system property value or {@code null} if a security problem occurs
+ */
+ private static String getSystemProperty(String property) {
+ try {
+ return System.getProperty(property);
+ } catch (SecurityException ex) {
+ // we are not allowed to look at this property
+ System.err.println("Caught a SecurityException reading the system property '" + property
+ + "'; the SystemUtils property value will default to null.");
+ return null;
+ }
+ }
+
+ /**
+ *
+ * Gets the user directory as a {@code File}.
+ *
+ *
+ * @return a directory
+ * @throws SecurityException if a security manager exists and its {@code checkPropertyAccess} method doesn't allow
+ * access to the specified system property.
+ * @see System#getProperty(String)
+ * @since 2.1
+ */
+ public static File getUserDir() {
+ return new File(System.getProperty(USER_DIR_KEY));
+ }
+
+ /**
+ *
+ * Gets the user home directory as a {@code File}.
+ *
+ *
+ * @return a directory
+ * @throws SecurityException if a security manager exists and its {@code checkPropertyAccess} method doesn't allow
+ * access to the specified system property.
+ * @see System#getProperty(String)
+ * @since 2.1
+ */
+ public static File getUserHome() {
+ return new File(System.getProperty(USER_HOME_KEY));
+ }
+
+ /**
+ * Returns whether the {@link #JAVA_AWT_HEADLESS} value is {@code true}.
+ *
+ * @return {@code true} if {@code JAVA_AWT_HEADLESS} is {@code "true"}, {@code false} otherwise.
+ * @see #JAVA_AWT_HEADLESS
+ * @since 2.1
+ * @since Java 1.4
+ */
+ public static boolean isJavaAwtHeadless() {
+ return JAVA_AWT_HEADLESS != null ? JAVA_AWT_HEADLESS.equals(Boolean.TRUE.toString()) : false;
+ }
+
+ /**
+ *
+ * Is the Java version at least the requested version.
+ *
+ *
+ * Example input:
+ *
+ *
+ * {@code 1.2f} to test for Java 1.2
+ * {@code 1.31f} to test for Java 1.3.1
+ *
+ *
+ * @param requiredVersion the required version, for example 1.31f
+ * @return {@code true} if the actual version is equal or greater than the required version
+ */
+ public static boolean isJavaVersionAtLeast(JavaVersion requiredVersion) {
+ return JAVA_SPECIFICATION_VERSION_AS_ENUM.atLeast(requiredVersion);
+ }
+
+ /**
+ *
+ * Decides if the Java version matches.
+ *
+ *
+ * This method is package private instead of private to support unit test invocation.
+ *
+ *
+ * @param version the actual Java version
+ * @param versionPrefix the prefix for the expected Java version
+ * @return true if matches, or false if not or can't determine
+ */
+ static boolean isJavaVersionMatch(String version, String versionPrefix) {
+ if (version == null) {
+ return false;
+ }
+ return version.startsWith(versionPrefix);
+ }
+
+ /**
+ * Decides if the operating system matches.
+ *
+ * This method is package private instead of private to support unit test invocation.
+ *
+ *
+ * @param osName the actual OS name
+ * @param osVersion the actual OS version
+ * @param osNamePrefix the prefix for the expected OS name
+ * @param osVersionPrefix the prefix for the expected OS version
+ * @return true if matches, or false if not or can't determine
+ */
+ static boolean isOSMatch(String osName, String osVersion, String osNamePrefix, String osVersionPrefix) {
+ if (osName == null || osVersion == null) {
+ return false;
+ }
+ return osName.startsWith(osNamePrefix) && osVersion.startsWith(osVersionPrefix);
+ }
+
+ /**
+ * Decides if the operating system matches.
+ *
+ * This method is package private instead of private to support unit test invocation.
+ *
+ *
+ * @param osName the actual OS name
+ * @param osNamePrefix the prefix for the expected OS name
+ * @return true if matches, or false if not or can't determine
+ */
+ static boolean isOSNameMatch(String osName, String osNamePrefix) {
+ if (osName == null) {
+ return false;
+ }
+ return osName.startsWith(osNamePrefix);
+ }
+
+ // -----------------------------------------------------------------------
+ /**
+ *
+ * SystemUtils instances should NOT be constructed in standard programming. Instead, the class should be used as
+ * {@code SystemUtils.FILE_SEPARATOR}.
+ *
+ *
+ * This constructor is public to permit tools that require a JavaBean instance to operate.
+ *
+ */
+ public SystemUtils() {
+ super();
+ }
+
+}
diff --git a/Bridge/src/main/apacheCommonsLang/external/org/apache/commons/lang3/Validate.java b/Bridge/src/main/apacheCommonsLang/external/org/apache/commons/lang3/Validate.java
new file mode 100644
index 00000000..72213854
--- /dev/null
+++ b/Bridge/src/main/apacheCommonsLang/external/org/apache/commons/lang3/Validate.java
@@ -0,0 +1,1070 @@
+/*
+ * 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 external.org.apache.commons.lang3;
+
+import java.util.Collection;
+import java.util.Iterator;
+import java.util.Map;
+import java.util.regex.Pattern;
+
+/**
+ * This class assists in validating arguments. The validation methods are
+ * based along the following principles:
+ *
+ * An invalid {@code null} argument causes a {@link NullPointerException}.
+ * A non-{@code null} argument causes an {@link IllegalArgumentException}.
+ * An invalid index into an array/collection/map/string causes an {@link IndexOutOfBoundsException}.
+ *
+ *
+ * All exceptions messages are
+ * format strings
+ * as defined by the Java platform. For example:
+ *
+ *
+ * Validate.isTrue(i > 0, "The value must be greater than zero: %d", i);
+ * Validate.notNull(surname, "The surname must not be %s", null);
+ *
+ *
+ * #ThreadSafe#
+ * @version $Id: Validate.java 1199983 2011-11-09 21:41:24Z ggregory $
+ * @see java.lang.String#format(String, Object...)
+ * @since 2.0
+ */
+public class Validate {
+
+ private static final String DEFAULT_EXCLUSIVE_BETWEEN_EX_MESSAGE =
+ "The value %s is not in the specified exclusive range of %s to %s";
+ private static final String DEFAULT_INCLUSIVE_BETWEEN_EX_MESSAGE =
+ "The value %s is not in the specified inclusive range of %s to %s";
+ private static final String DEFAULT_MATCHES_PATTERN_EX = "The string %s does not match the pattern %s";
+ private static final String DEFAULT_IS_NULL_EX_MESSAGE = "The validated object is null";
+ private static final String DEFAULT_IS_TRUE_EX_MESSAGE = "The validated expression is false";
+ private static final String DEFAULT_NO_NULL_ELEMENTS_ARRAY_EX_MESSAGE =
+ "The validated array contains null element at index: %d";
+ private static final String DEFAULT_NO_NULL_ELEMENTS_COLLECTION_EX_MESSAGE =
+ "The validated collection contains null element at index: %d";
+ private static final String DEFAULT_NOT_BLANK_EX_MESSAGE = "The validated character sequence is blank";
+ private static final String DEFAULT_NOT_EMPTY_ARRAY_EX_MESSAGE = "The validated array is empty";
+ private static final String DEFAULT_NOT_EMPTY_CHAR_SEQUENCE_EX_MESSAGE =
+ "The validated character sequence is empty";
+ private static final String DEFAULT_NOT_EMPTY_COLLECTION_EX_MESSAGE = "The validated collection is empty";
+ private static final String DEFAULT_NOT_EMPTY_MAP_EX_MESSAGE = "The validated map is empty";
+ private static final String DEFAULT_VALID_INDEX_ARRAY_EX_MESSAGE = "The validated array index is invalid: %d";
+ private static final String DEFAULT_VALID_INDEX_CHAR_SEQUENCE_EX_MESSAGE =
+ "The validated character sequence index is invalid: %d";
+ private static final String DEFAULT_VALID_INDEX_COLLECTION_EX_MESSAGE =
+ "The validated collection index is invalid: %d";
+ private static final String DEFAULT_VALID_STATE_EX_MESSAGE = "The validated state is false";
+ private static final String DEFAULT_IS_ASSIGNABLE_EX_MESSAGE = "Cannot assign a %s to a %s";
+ private static final String DEFAULT_IS_INSTANCE_OF_EX_MESSAGE = "Expected type: %s, actual: %s";
+
+ /**
+ * Constructor. This class should not normally be instantiated.
+ */
+ public Validate() {
+ super();
+ }
+
+ // isTrue
+ //---------------------------------------------------------------------------------
+
+ /**
+ * Validate that the argument condition is {@code true}; otherwise
+ * throwing an exception with the specified message. This method is useful when
+ * validating according to an arbitrary boolean expression, such as validating a
+ * primitive number or using your own custom validation expression.
+ *
+ * Validate.isTrue(i > 0.0, "The value must be greater than zero: %d", i);
+ *
+ * For performance reasons, the long value is passed as a separate parameter and
+ * appended to the exception message only in the case of an error.
+ *
+ * @param expression the boolean expression to check
+ * @param message the {@link String#format(String, Object...)} exception message if invalid, not null
+ * @param value the value to append to the message when invalid
+ * @throws IllegalArgumentException if expression is {@code false}
+ * @see #isTrue(boolean)
+ * @see #isTrue(boolean, String, double)
+ * @see #isTrue(boolean, String, Object...)
+ */
+ public static void isTrue(boolean expression, String message, long value) {
+ if (expression == false) {
+ throw new IllegalArgumentException(String.format(message, Long.valueOf(value)));
+ }
+ }
+
+ /**
+ * Validate that the argument condition is {@code true}; otherwise
+ * throwing an exception with the specified message. This method is useful when
+ * validating according to an arbitrary boolean expression, such as validating a
+ * primitive number or using your own custom validation expression.
+ *
+ * Validate.isTrue(d > 0.0, "The value must be greater than zero: %s", d);
+ *
+ * For performance reasons, the double value is passed as a separate parameter and
+ * appended to the exception message only in the case of an error.
+ *
+ * @param expression the boolean expression to check
+ * @param message the {@link String#format(String, Object...)} exception message if invalid, not null
+ * @param value the value to append to the message when invalid
+ * @throws IllegalArgumentException if expression is {@code false}
+ * @see #isTrue(boolean)
+ * @see #isTrue(boolean, String, long)
+ * @see #isTrue(boolean, String, Object...)
+ */
+ public static void isTrue(boolean expression, String message, double value) {
+ if (expression == false) {
+ throw new IllegalArgumentException(String.format(message, Double.valueOf(value)));
+ }
+ }
+
+ /**
+ * Validate that the argument condition is {@code true}; otherwise
+ * throwing an exception with the specified message. This method is useful when
+ * validating according to an arbitrary boolean expression, such as validating a
+ * primitive number or using your own custom validation expression.
+ *
+ *
+ * Validate.isTrue(i >= min && i <= max, "The value must be between %d and %d", min, max);
+ * Validate.isTrue(myObject.isOk(), "The object is not okay");
+ *
+ * @param expression the boolean expression to check
+ * @param message the {@link String#format(String, Object...)} exception message if invalid, not null
+ * @param values the optional values for the formatted exception message, null array not recommended
+ * @throws IllegalArgumentException if expression is {@code false}
+ * @see #isTrue(boolean)
+ * @see #isTrue(boolean, String, long)
+ * @see #isTrue(boolean, String, double)
+ */
+ public static void isTrue(boolean expression, String message, Object... values) {
+ if (expression == false) {
+ throw new IllegalArgumentException(String.format(message, values));
+ }
+ }
+
+ /**
+ * Validate that the argument condition is {@code true}; otherwise
+ * throwing an exception. This method is useful when validating according
+ * to an arbitrary boolean expression, such as validating a
+ * primitive number or using your own custom validation expression.
+ *
+ *
+ * Validate.isTrue(i > 0);
+ * Validate.isTrue(myObject.isOk());
+ *
+ * The message of the exception is "The validated expression is
+ * false".
+ *
+ * @param expression the boolean expression to check
+ * @throws IllegalArgumentException if expression is {@code false}
+ * @see #isTrue(boolean, String, long)
+ * @see #isTrue(boolean, String, double)
+ * @see #isTrue(boolean, String, Object...)
+ */
+ public static void isTrue(boolean expression) {
+ if (expression == false) {
+ throw new IllegalArgumentException(DEFAULT_IS_TRUE_EX_MESSAGE);
+ }
+ }
+
+ // notNull
+ //---------------------------------------------------------------------------------
+
+ /**
+ * Validate that the specified argument is not {@code null};
+ * otherwise throwing an exception.
+ *
+ *
Validate.notNull(myObject, "The object must not be null");
+ *
+ * The message of the exception is "The validated object is
+ * null".
+ *
+ * @param the object type
+ * @param object the object to check
+ * @return the validated object (never {@code null} for method chaining)
+ * @throws NullPointerException if the object is {@code null}
+ * @see #notNull(Object, String, Object...)
+ */
+ public static T notNull(T object) {
+ return notNull(object, DEFAULT_IS_NULL_EX_MESSAGE);
+ }
+
+ /**
+ * Validate that the specified argument is not {@code null};
+ * otherwise throwing an exception with the specified message.
+ *
+ *
Validate.notNull(myObject, "The object must not be null");
+ *
+ * @param the object type
+ * @param object the object to check
+ * @param message the {@link String#format(String, Object...)} exception message if invalid, not null
+ * @param values the optional values for the formatted exception message
+ * @return the validated object (never {@code null} for method chaining)
+ * @throws NullPointerException if the object is {@code null}
+ * @see #notNull(Object)
+ */
+ public static T notNull(T object, String message, Object... values) {
+ if (object == null) {
+ throw new NullPointerException(String.format(message, values));
+ }
+ return object;
+ }
+
+ // notEmpty array
+ //---------------------------------------------------------------------------------
+
+ /**
+ * Validate that the specified argument array is neither {@code null}
+ * nor a length of zero (no elements); otherwise throwing an exception
+ * with the specified message.
+ *
+ *
Validate.notEmpty(myArray, "The array must not be empty");
+ *
+ * @param the array type
+ * @param array the array to check, validated not null by this method
+ * @param message the {@link String#format(String, Object...)} exception message if invalid, not null
+ * @param values the optional values for the formatted exception message, null array not recommended
+ * @return the validated array (never {@code null} method for chaining)
+ * @throws NullPointerException if the array is {@code null}
+ * @throws IllegalArgumentException if the array is empty
+ * @see #notEmpty(Object[])
+ */
+ public static T[] notEmpty(T[] array, String message, Object... values) {
+ if (array == null) {
+ throw new NullPointerException(String.format(message, values));
+ }
+ if (array.length == 0) {
+ throw new IllegalArgumentException(String.format(message, values));
+ }
+ return array;
+ }
+
+ /**
+ * Validate that the specified argument array is neither {@code null}
+ * nor a length of zero (no elements); otherwise throwing an exception.
+ *
+ *
Validate.notEmpty(myArray);
+ *
+ * The message in the exception is "The validated array is
+ * empty".
+ *
+ * @param the array type
+ * @param array the array to check, validated not null by this method
+ * @return the validated array (never {@code null} method for chaining)
+ * @throws NullPointerException if the array is {@code null}
+ * @throws IllegalArgumentException if the array is empty
+ * @see #notEmpty(Object[], String, Object...)
+ */
+ public static T[] notEmpty(T[] array) {
+ return notEmpty(array, DEFAULT_NOT_EMPTY_ARRAY_EX_MESSAGE);
+ }
+
+ // notEmpty collection
+ //---------------------------------------------------------------------------------
+
+ /**
+ * Validate that the specified argument collection is neither {@code null}
+ * nor a size of zero (no elements); otherwise throwing an exception
+ * with the specified message.
+ *
+ *
Validate.notEmpty(myCollection, "The collection must not be empty");
+ *
+ * @param the collection type
+ * @param collection the collection to check, validated not null by this method
+ * @param message the {@link String#format(String, Object...)} exception message if invalid, not null
+ * @param values the optional values for the formatted exception message, null array not recommended
+ * @return the validated collection (never {@code null} method for chaining)
+ * @throws NullPointerException if the collection is {@code null}
+ * @throws IllegalArgumentException if the collection is empty
+ * @see #notEmpty(Object[])
+ */
+ public static > T notEmpty(T collection, String message, Object... values) {
+ if (collection == null) {
+ throw new NullPointerException(String.format(message, values));
+ }
+ if (collection.isEmpty()) {
+ throw new IllegalArgumentException(String.format(message, values));
+ }
+ return collection;
+ }
+
+ /**
+ * Validate that the specified argument collection is neither {@code null}
+ * nor a size of zero (no elements); otherwise throwing an exception.
+ *
+ *
Validate.notEmpty(myCollection);
+ *
+ * The message in the exception is "The validated collection is
+ * empty".
+ *
+ * @param the collection type
+ * @param collection the collection to check, validated not null by this method
+ * @return the validated collection (never {@code null} method for chaining)
+ * @throws NullPointerException if the collection is {@code null}
+ * @throws IllegalArgumentException if the collection is empty
+ * @see #notEmpty(Collection, String, Object...)
+ */
+ public static > T notEmpty(T collection) {
+ return notEmpty(collection, DEFAULT_NOT_EMPTY_COLLECTION_EX_MESSAGE);
+ }
+
+ // notEmpty map
+ //---------------------------------------------------------------------------------
+
+ /**
+ * Validate that the specified argument map is neither {@code null}
+ * nor a size of zero (no elements); otherwise throwing an exception
+ * with the specified message.
+ *
+ *
Validate.notEmpty(myMap, "The map must not be empty");
+ *
+ * @param the map type
+ * @param map the map to check, validated not null by this method
+ * @param message the {@link String#format(String, Object...)} exception message if invalid, not null
+ * @param values the optional values for the formatted exception message, null array not recommended
+ * @return the validated map (never {@code null} method for chaining)
+ * @throws NullPointerException if the map is {@code null}
+ * @throws IllegalArgumentException if the map is empty
+ * @see #notEmpty(Object[])
+ */
+ public static > T notEmpty(T map, String message, Object... values) {
+ if (map == null) {
+ throw new NullPointerException(String.format(message, values));
+ }
+ if (map.isEmpty()) {
+ throw new IllegalArgumentException(String.format(message, values));
+ }
+ return map;
+ }
+
+ /**
+ * Validate that the specified argument map is neither {@code null}
+ * nor a size of zero (no elements); otherwise throwing an exception.
+ *
+ *
Validate.notEmpty(myMap);
+ *
+ * The message in the exception is "The validated map is
+ * empty".
+ *
+ * @param the map type
+ * @param map the map to check, validated not null by this method
+ * @return the validated map (never {@code null} method for chaining)
+ * @throws NullPointerException if the map is {@code null}
+ * @throws IllegalArgumentException if the map is empty
+ * @see #notEmpty(Map, String, Object...)
+ */
+ public static > T notEmpty(T map) {
+ return notEmpty(map, DEFAULT_NOT_EMPTY_MAP_EX_MESSAGE);
+ }
+
+ // notEmpty string
+ //---------------------------------------------------------------------------------
+
+ /**
+ * Validate that the specified argument character sequence is
+ * neither {@code null} nor a length of zero (no characters);
+ * otherwise throwing an exception with the specified message.
+ *
+ *
Validate.notEmpty(myString, "The string must not be empty");
+ *
+ * @param the character sequence type
+ * @param chars the character sequence to check, validated not null by this method
+ * @param message the {@link String#format(String, Object...)} exception message if invalid, not null
+ * @param values the optional values for the formatted exception message, null array not recommended
+ * @return the validated character sequence (never {@code null} method for chaining)
+ * @throws NullPointerException if the character sequence is {@code null}
+ * @throws IllegalArgumentException if the character sequence is empty
+ * @see #notEmpty(CharSequence)
+ */
+ public static T notEmpty(T chars, String message, Object... values) {
+ if (chars == null) {
+ throw new NullPointerException(String.format(message, values));
+ }
+ if (chars.length() == 0) {
+ throw new IllegalArgumentException(String.format(message, values));
+ }
+ return chars;
+ }
+
+ /**
+ * Validate that the specified argument character sequence is
+ * neither {@code null} nor a length of zero (no characters);
+ * otherwise throwing an exception with the specified message.
+ *
+ *
Validate.notEmpty(myString);
+ *
+ * The message in the exception is "The validated
+ * character sequence is empty".
+ *
+ * @param the character sequence type
+ * @param chars the character sequence to check, validated not null by this method
+ * @return the validated character sequence (never {@code null} method for chaining)
+ * @throws NullPointerException if the character sequence is {@code null}
+ * @throws IllegalArgumentException if the character sequence is empty
+ * @see #notEmpty(CharSequence, String, Object...)
+ */
+ public static T notEmpty(T chars) {
+ return notEmpty(chars, DEFAULT_NOT_EMPTY_CHAR_SEQUENCE_EX_MESSAGE);
+ }
+
+ // notBlank string
+ //---------------------------------------------------------------------------------
+
+ /**
+ * Validate that the specified argument character sequence is
+ * neither {@code null}, a length of zero (no characters), empty
+ * nor whitespace; otherwise throwing an exception with the specified
+ * message.
+ *
+ *
Validate.notBlank(myString, "The string must not be blank");
+ *
+ * @param the character sequence type
+ * @param chars the character sequence to check, validated not null by this method
+ * @param message the {@link String#format(String, Object...)} exception message if invalid, not null
+ * @param values the optional values for the formatted exception message, null array not recommended
+ * @return the validated character sequence (never {@code null} method for chaining)
+ * @throws NullPointerException if the character sequence is {@code null}
+ * @throws IllegalArgumentException if the character sequence is blank
+ * @see #notBlank(CharSequence)
+ *
+ * @since 3.0
+ */
+ public static T notBlank(T chars, String message, Object... values) {
+ if (chars == null) {
+ throw new NullPointerException(String.format(message, values));
+ }
+ if (StringUtils.isBlank(chars)) {
+ throw new IllegalArgumentException(String.format(message, values));
+ }
+ return chars;
+ }
+
+ /**
+ * Validate that the specified argument character sequence is
+ * neither {@code null}, a length of zero (no characters), empty
+ * nor whitespace; otherwise throwing an exception.
+ *
+ *
Validate.notBlank(myString);
+ *
+ * The message in the exception is "The validated character
+ * sequence is blank".
+ *
+ * @param the character sequence type
+ * @param chars the character sequence to check, validated not null by this method
+ * @return the validated character sequence (never {@code null} method for chaining)
+ * @throws NullPointerException if the character sequence is {@code null}
+ * @throws IllegalArgumentException if the character sequence is blank
+ * @see #notBlank(CharSequence, String, Object...)
+ *
+ * @since 3.0
+ */
+ public static T notBlank(T chars) {
+ return notBlank(chars, DEFAULT_NOT_BLANK_EX_MESSAGE);
+ }
+
+ // noNullElements array
+ //---------------------------------------------------------------------------------
+
+ /**
+ * Validate that the specified argument array is neither
+ * {@code null} nor contains any elements that are {@code null};
+ * otherwise throwing an exception with the specified message.
+ *
+ *
Validate.noNullElements(myArray, "The array contain null at position %d");
+ *
+ * If the array is {@code null}, then the message in the exception
+ * is "The validated object is null".
+ *
+ * If the array has a {@code null} element, then the iteration
+ * index of the invalid element is appended to the {@code values}
+ * argument.
+ *
+ * @param the array type
+ * @param array the array to check, validated not null by this method
+ * @param message the {@link String#format(String, Object...)} exception message if invalid, not null
+ * @param values the optional values for the formatted exception message, null array not recommended
+ * @return the validated array (never {@code null} method for chaining)
+ * @throws NullPointerException if the array is {@code null}
+ * @throws IllegalArgumentException if an element is {@code null}
+ * @see #noNullElements(Object[])
+ */
+ public static T[] noNullElements(T[] array, String message, Object... values) {
+ Validate.notNull(array);
+ for (int i = 0; i < array.length; i++) {
+ if (array[i] == null) {
+ Object[] values2 = ArrayUtils.add(values, Integer.valueOf(i));
+ throw new IllegalArgumentException(String.format(message, values2));
+ }
+ }
+ return array;
+ }
+
+ /**
+ * Validate that the specified argument array is neither
+ * {@code null} nor contains any elements that are {@code null};
+ * otherwise throwing an exception.
+ *
+ *
Validate.noNullElements(myArray);
+ *
+ * If the array is {@code null}, then the message in the exception
+ * is "The validated object is null".
+ *
+ * If the array has a {@code null} element, then the message in the
+ * exception is "The validated array contains null element at index:
+ * " followed by the index.
+ *
+ * @param the array type
+ * @param array the array to check, validated not null by this method
+ * @return the validated array (never {@code null} method for chaining)
+ * @throws NullPointerException if the array is {@code null}
+ * @throws IllegalArgumentException if an element is {@code null}
+ * @see #noNullElements(Object[], String, Object...)
+ */
+ public static T[] noNullElements(T[] array) {
+ return noNullElements(array, DEFAULT_NO_NULL_ELEMENTS_ARRAY_EX_MESSAGE);
+ }
+
+ // noNullElements iterable
+ //---------------------------------------------------------------------------------
+
+ /**
+ * Validate that the specified argument iterable is neither
+ * {@code null} nor contains any elements that are {@code null};
+ * otherwise throwing an exception with the specified message.
+ *
+ *
Validate.noNullElements(myCollection, "The collection contains null at position %d");
+ *
+ * If the iterable is {@code null}, then the message in the exception
+ * is "The validated object is null".
+ *
+ * If the iterable has a {@code null} element, then the iteration
+ * index of the invalid element is appended to the {@code values}
+ * argument.
+ *
+ * @param the iterable type
+ * @param iterable the iterable to check, validated not null by this method
+ * @param message the {@link String#format(String, Object...)} exception message if invalid, not null
+ * @param values the optional values for the formatted exception message, null array not recommended
+ * @return the validated iterable (never {@code null} method for chaining)
+ * @throws NullPointerException if the array is {@code null}
+ * @throws IllegalArgumentException if an element is {@code null}
+ * @see #noNullElements(Iterable)
+ */
+ public static > T noNullElements(T iterable, String message, Object... values) {
+ Validate.notNull(iterable);
+ int i = 0;
+ for (Iterator> it = iterable.iterator(); it.hasNext(); i++) {
+ if (it.next() == null) {
+ Object[] values2 = ArrayUtils.addAll(values, Integer.valueOf(i));
+ throw new IllegalArgumentException(String.format(message, values2));
+ }
+ }
+ return iterable;
+ }
+
+ /**
+ * Validate that the specified argument iterable is neither
+ * {@code null} nor contains any elements that are {@code null};
+ * otherwise throwing an exception.
+ *
+ *
Validate.noNullElements(myCollection);
+ *
+ * If the iterable is {@code null}, then the message in the exception
+ * is "The validated object is null".
+ *
+ * If the array has a {@code null} element, then the message in the
+ * exception is "The validated iterable contains null element at index:
+ * " followed by the index.
+ *
+ * @param the iterable type
+ * @param iterable the iterable to check, validated not null by this method
+ * @return the validated iterable (never {@code null} method for chaining)
+ * @throws NullPointerException if the array is {@code null}
+ * @throws IllegalArgumentException if an element is {@code null}
+ * @see #noNullElements(Iterable, String, Object...)
+ */
+ public static > T noNullElements(T iterable) {
+ return noNullElements(iterable, DEFAULT_NO_NULL_ELEMENTS_COLLECTION_EX_MESSAGE);
+ }
+
+ // validIndex array
+ //---------------------------------------------------------------------------------
+
+ /**
+ * Validates that the index is within the bounds of the argument
+ * array; otherwise throwing an exception with the specified message.
+ *
+ * Validate.validIndex(myArray, 2, "The array index is invalid: ");
+ *
+ * If the array is {@code null}, then the message of the exception
+ * is "The validated object is null".
+ *
+ * @param the array type
+ * @param array the array to check, validated not null by this method
+ * @param index the index to check
+ * @param message the {@link String#format(String, Object...)} exception message if invalid, not null
+ * @param values the optional values for the formatted exception message, null array not recommended
+ * @return the validated array (never {@code null} for method chaining)
+ * @throws NullPointerException if the array is {@code null}
+ * @throws IndexOutOfBoundsException if the index is invalid
+ * @see #validIndex(Object[], int)
+ *
+ * @since 3.0
+ */
+ public static T[] validIndex(T[] array, int index, String message, Object... values) {
+ Validate.notNull(array);
+ if (index < 0 || index >= array.length) {
+ throw new IndexOutOfBoundsException(String.format(message, values));
+ }
+ return array;
+ }
+
+ /**
+ * Validates that the index is within the bounds of the argument
+ * array; otherwise throwing an exception.
+ *
+ * Validate.validIndex(myArray, 2);
+ *
+ * If the array is {@code null}, then the message of the exception
+ * is "The validated object is null".
+ *
+ * If the index is invalid, then the message of the exception is
+ * "The validated array index is invalid: " followed by the
+ * index.
+ *
+ * @param the array type
+ * @param array the array to check, validated not null by this method
+ * @param index the index to check
+ * @return the validated array (never {@code null} for method chaining)
+ * @throws NullPointerException if the array is {@code null}
+ * @throws IndexOutOfBoundsException if the index is invalid
+ * @see #validIndex(Object[], int, String, Object...)
+ *
+ * @since 3.0
+ */
+ public static T[] validIndex(T[] array, int index) {
+ return validIndex(array, index, DEFAULT_VALID_INDEX_ARRAY_EX_MESSAGE, Integer.valueOf(index));
+ }
+
+ // validIndex collection
+ //---------------------------------------------------------------------------------
+
+ /**
+ * Validates that the index is within the bounds of the argument
+ * collection; otherwise throwing an exception with the specified message.
+ *
+ * Validate.validIndex(myCollection, 2, "The collection index is invalid: ");
+ *
+ * If the collection is {@code null}, then the message of the
+ * exception is "The validated object is null".
+ *
+ * @param the collection type
+ * @param collection the collection to check, validated not null by this method
+ * @param index the index to check
+ * @param message the {@link String#format(String, Object...)} exception message if invalid, not null
+ * @param values the optional values for the formatted exception message, null array not recommended
+ * @return the validated collection (never {@code null} for chaining)
+ * @throws NullPointerException if the collection is {@code null}
+ * @throws IndexOutOfBoundsException if the index is invalid
+ * @see #validIndex(Collection, int)
+ *
+ * @since 3.0
+ */
+ public static > T validIndex(T collection, int index, String message, Object... values) {
+ Validate.notNull(collection);
+ if (index < 0 || index >= collection.size()) {
+ throw new IndexOutOfBoundsException(String.format(message, values));
+ }
+ return collection;
+ }
+
+ /**
+ * Validates that the index is within the bounds of the argument
+ * collection; otherwise throwing an exception.
+ *
+ * Validate.validIndex(myCollection, 2);
+ *
+ * If the index is invalid, then the message of the exception
+ * is "The validated collection index is invalid: "
+ * followed by the index.
+ *
+ * @param the collection type
+ * @param collection the collection to check, validated not null by this method
+ * @param index the index to check
+ * @return the validated collection (never {@code null} for method chaining)
+ * @throws NullPointerException if the collection is {@code null}
+ * @throws IndexOutOfBoundsException if the index is invalid
+ * @see #validIndex(Collection, int, String, Object...)
+ *
+ * @since 3.0
+ */
+ public static > T validIndex(T collection, int index) {
+ return validIndex(collection, index, DEFAULT_VALID_INDEX_COLLECTION_EX_MESSAGE, Integer.valueOf(index));
+ }
+
+ // validIndex string
+ //---------------------------------------------------------------------------------
+
+ /**
+ * Validates that the index is within the bounds of the argument
+ * character sequence; otherwise throwing an exception with the
+ * specified message.
+ *
+ * Validate.validIndex(myStr, 2, "The string index is invalid: ");
+ *
+ * If the character sequence is {@code null}, then the message
+ * of the exception is "The validated object is null".
+ *
+ * @param the character sequence type
+ * @param chars the character sequence to check, validated not null by this method
+ * @param index the index to check
+ * @param message the {@link String#format(String, Object...)} exception message if invalid, not null
+ * @param values the optional values for the formatted exception message, null array not recommended
+ * @return the validated character sequence (never {@code null} for method chaining)
+ * @throws NullPointerException if the character sequence is {@code null}
+ * @throws IndexOutOfBoundsException if the index is invalid
+ * @see #validIndex(CharSequence, int)
+ *
+ * @since 3.0
+ */
+ public static T validIndex(T chars, int index, String message, Object... values) {
+ Validate.notNull(chars);
+ if (index < 0 || index >= chars.length()) {
+ throw new IndexOutOfBoundsException(String.format(message, values));
+ }
+ return chars;
+ }
+
+ /**
+ * Validates that the index is within the bounds of the argument
+ * character sequence; otherwise throwing an exception.
+ *
+ * Validate.validIndex(myStr, 2);
+ *
+ * If the character sequence is {@code null}, then the message
+ * of the exception is "The validated object is
+ * null".
+ *
+ * If the index is invalid, then the message of the exception
+ * is "The validated character sequence index is invalid: "
+ * followed by the index.
+ *
+ * @param the character sequence type
+ * @param chars the character sequence to check, validated not null by this method
+ * @param index the index to check
+ * @return the validated character sequence (never {@code null} for method chaining)
+ * @throws NullPointerException if the character sequence is {@code null}
+ * @throws IndexOutOfBoundsException if the index is invalid
+ * @see #validIndex(CharSequence, int, String, Object...)
+ *
+ * @since 3.0
+ */
+ public static T validIndex(T chars, int index) {
+ return validIndex(chars, index, DEFAULT_VALID_INDEX_CHAR_SEQUENCE_EX_MESSAGE, Integer.valueOf(index));
+ }
+
+ // validState
+ //---------------------------------------------------------------------------------
+
+ /**
+ * Validate that the stateful condition is {@code true}; otherwise
+ * throwing an exception. This method is useful when validating according
+ * to an arbitrary boolean expression, such as validating a
+ * primitive number or using your own custom validation expression.
+ *
+ *
+ * Validate.validState(field > 0);
+ * Validate.validState(this.isOk());
+ *
+ * The message of the exception is "The validated state is
+ * false".
+ *
+ * @param expression the boolean expression to check
+ * @throws IllegalStateException if expression is {@code false}
+ * @see #validState(boolean, String, Object...)
+ *
+ * @since 3.0
+ */
+ public static void validState(boolean expression) {
+ if (expression == false) {
+ throw new IllegalStateException(DEFAULT_VALID_STATE_EX_MESSAGE);
+ }
+ }
+
+ /**
+ * Validate that the stateful condition is {@code true}; otherwise
+ * throwing an exception with the specified message. This method is useful when
+ * validating according to an arbitrary boolean expression, such as validating a
+ * primitive number or using your own custom validation expression.
+ *
+ * Validate.validState(this.isOk(), "The state is not OK: %s", myObject);
+ *
+ * @param expression the boolean expression to check
+ * @param message the {@link String#format(String, Object...)} exception message if invalid, not null
+ * @param values the optional values for the formatted exception message, null array not recommended
+ * @throws IllegalStateException if expression is {@code false}
+ * @see #validState(boolean)
+ *
+ * @since 3.0
+ */
+ public static void validState(boolean expression, String message, Object... values) {
+ if (expression == false) {
+ throw new IllegalStateException(String.format(message, values));
+ }
+ }
+
+ // matchesPattern
+ //---------------------------------------------------------------------------------
+
+ /**
+ * Validate that the specified argument character sequence matches the specified regular
+ * expression pattern; otherwise throwing an exception.
+ *
+ * Validate.matchesPattern("hi", "[a-z]*");
+ *
+ * The syntax of the pattern is the one used in the {@link Pattern} class.
+ *
+ * @param input the character sequence to validate, not null
+ * @param pattern the regular expression pattern, not null
+ * @throws IllegalArgumentException if the character sequence does not match the pattern
+ * @see #matchesPattern(CharSequence, String, String, Object...)
+ *
+ * @since 3.0
+ */
+ public static void matchesPattern(CharSequence input, String pattern) {
+ if (Pattern.matches(pattern, input) == false) {
+ throw new IllegalArgumentException(String.format(DEFAULT_MATCHES_PATTERN_EX, input, pattern));
+ }
+ }
+
+ /**
+ * Validate that the specified argument character sequence matches the specified regular
+ * expression pattern; otherwise throwing an exception with the specified message.
+ *
+ * Validate.matchesPattern("hi", "[a-z]*", "%s does not match %s", "hi" "[a-z]*");
+ *
+ * The syntax of the pattern is the one used in the {@link Pattern} class.
+ *
+ * @param input the character sequence to validate, not null
+ * @param pattern the regular expression pattern, not null
+ * @param message the {@link String#format(String, Object...)} exception message if invalid, not null
+ * @param values the optional values for the formatted exception message, null array not recommended
+ * @throws IllegalArgumentException if the character sequence does not match the pattern
+ * @see #matchesPattern(CharSequence, String)
+ *
+ * @since 3.0
+ */
+ public static void matchesPattern(CharSequence input, String pattern, String message, Object... values) {
+ if (Pattern.matches(pattern, input) == false) {
+ throw new IllegalArgumentException(String.format(message, values));
+ }
+ }
+
+ // inclusiveBetween
+ //---------------------------------------------------------------------------------
+
+ /**
+ * Validate that the specified argument object fall between the two
+ * inclusive values specified; otherwise, throws an exception.
+ *
+ * Validate.inclusiveBetween(0, 2, 1);
+ *
+ * @param the type of the argument object
+ * @param start the inclusive start value, not null
+ * @param end the inclusive end value, not null
+ * @param value the object to validate, not null
+ * @throws IllegalArgumentException if the value falls out of the boundaries
+ * @see #inclusiveBetween(Object, Object, Comparable, String, Object...)
+ *
+ * @since 3.0
+ */
+ public static void inclusiveBetween(T start, T end, Comparable value) {
+ if (value.compareTo(start) < 0 || value.compareTo(end) > 0) {
+ throw new IllegalArgumentException(String.format(DEFAULT_INCLUSIVE_BETWEEN_EX_MESSAGE, value, start, end));
+ }
+ }
+
+ /**
+ * Validate that the specified argument object fall between the two
+ * inclusive values specified; otherwise, throws an exception with the
+ * specified message.
+ *
+ * Validate.inclusiveBetween(0, 2, 1, "Not in boundaries");
+ *
+ * @param the type of the argument object
+ * @param start the inclusive start value, not null
+ * @param end the inclusive end value, not null
+ * @param value the object to validate, not null
+ * @param message the {@link String#format(String, Object...)} exception message if invalid, not null
+ * @param values the optional values for the formatted exception message, null array not recommended
+ * @throws IllegalArgumentException if the value falls out of the boundaries
+ * @see #inclusiveBetween(Object, Object, Comparable)
+ *
+ * @since 3.0
+ */
+ public static void inclusiveBetween(T start, T end, Comparable value, String message, Object... values) {
+ if (value.compareTo(start) < 0 || value.compareTo(end) > 0) {
+ throw new IllegalArgumentException(String.format(message, values));
+ }
+ }
+
+ // exclusiveBetween
+ //---------------------------------------------------------------------------------
+
+ /**
+ * Validate that the specified argument object fall between the two
+ * exclusive values specified; otherwise, throws an exception.
+ *
+ * Validate.inclusiveBetween(0, 2, 1);
+ *
+ * @param the type of the argument object
+ * @param start the exclusive start value, not null
+ * @param end the exclusive end value, not null
+ * @param value the object to validate, not null
+ * @throws IllegalArgumentException if the value falls out of the boundaries
+ * @see #exclusiveBetween(Object, Object, Comparable, String, Object...)
+ *
+ * @since 3.0
+ */
+ public static void exclusiveBetween(T start, T end, Comparable value) {
+ if (value.compareTo(start) <= 0 || value.compareTo(end) >= 0) {
+ throw new IllegalArgumentException(String.format(DEFAULT_EXCLUSIVE_BETWEEN_EX_MESSAGE, value, start, end));
+ }
+ }
+
+ /**
+ * Validate that the specified argument object fall between the two
+ * exclusive values specified; otherwise, throws an exception with the
+ * specified message.
+ *
+ * Validate.inclusiveBetween(0, 2, 1, "Not in boundaries");
+ *
+ * @param the type of the argument object
+ * @param start the exclusive start value, not null
+ * @param end the exclusive end value, not null
+ * @param value the object to validate, not null
+ * @param message the {@link String#format(String, Object...)} exception message if invalid, not null
+ * @param values the optional values for the formatted exception message, null array not recommended
+ * @throws IllegalArgumentException if the value falls out of the boundaries
+ * @see #exclusiveBetween(Object, Object, Comparable)
+ *
+ * @since 3.0
+ */
+ public static void exclusiveBetween(T start, T end, Comparable value, String message, Object... values) {
+ if (value.compareTo(start) <= 0 || value.compareTo(end) >= 0) {
+ throw new IllegalArgumentException(String.format(message, values));
+ }
+ }
+
+ // isInstanceOf
+ //---------------------------------------------------------------------------------
+
+ /**
+ * Validates that the argument is an instance of the specified class, if not throws an exception.
+ *
+ * This method is useful when validating according to an arbitrary class
+ *
+ * Validate.isInstanceOf(OkClass.class, object);
+ *
+ * The message of the exception is "Expected type: {type}, actual: {obj_type}"
+ *
+ * @param type the class the object must be validated against, not null
+ * @param obj the object to check, null throws an exception
+ * @throws IllegalArgumentException if argument is not of specified class
+ * @see #isInstanceOf(Class, Object, String, Object...)
+ *
+ * @since 3.0
+ */
+ public static void isInstanceOf(Class> type, Object obj) {
+ if (type.isInstance(obj) == false) {
+ throw new IllegalArgumentException(String.format(DEFAULT_IS_INSTANCE_OF_EX_MESSAGE, type.getName(),
+ obj == null ? "null" : obj.getClass().getName()));
+ }
+ }
+
+ /**
+ * Validate that the argument is an instance of the specified class; otherwise
+ * throwing an exception with the specified message. This method is useful when
+ * validating according to an arbitrary class
+ *
+ * Validate.isInstanceOf(OkClass.classs, object, "Wrong class, object is of class %s",
+ * object.getClass().getName());
+ *
+ * @param type the class the object must be validated against, not null
+ * @param obj the object to check, null throws an exception
+ * @param message the {@link String#format(String, Object...)} exception message if invalid, not null
+ * @param values the optional values for the formatted exception message, null array not recommended
+ * @throws IllegalArgumentException if argument is not of specified class
+ * @see #isInstanceOf(Class, Object)
+ *
+ * @since 3.0
+ */
+ public static void isInstanceOf(Class> type, Object obj, String message, Object... values) {
+ if (type.isInstance(obj) == false) {
+ throw new IllegalArgumentException(String.format(message, values));
+ }
+ }
+
+ // isAssignableFrom
+ //---------------------------------------------------------------------------------
+
+ /**
+ * Validates that the argument can be converted to the specified class, if not, throws an exception.
+ *
+ * This method is useful when validating that there will be no casting errors.
+ *
+ * Validate.isAssignableFrom(SuperClass.class, object.getClass());
+ *
+ * The message format of the exception is "Cannot assign {type} to {superType}"
+ *
+ * @param superType the class the class must be validated against, not null
+ * @param type the class to check, not null
+ * @throws IllegalArgumentException if type argument is not assignable to the specified superType
+ * @see #isAssignableFrom(Class, Class, String, Object...)
+ *
+ * @since 3.0
+ */
+ public static void isAssignableFrom(Class> superType, Class> type) {
+ if (superType.isAssignableFrom(type) == false) {
+ throw new IllegalArgumentException(String.format(DEFAULT_IS_ASSIGNABLE_EX_MESSAGE, type == null ? "null" : type.getName(),
+ superType.getName()));
+ }
+ }
+
+ /**
+ * Validates that the argument can be converted to the specified class, if not throws an exception.
+ *
+ * This method is useful when validating if there will be no casting errors.
+ *
+ * Validate.isAssignableFrom(SuperClass.class, object.getClass());
+ *
+ * The message of the exception is "The validated object can not be converted to the"
+ * followed by the name of the class and "class"
+ *
+ * @param superType the class the class must be validated against, not null
+ * @param type the class to check, not null
+ * @param message the {@link String#format(String, Object...)} exception message if invalid, not null
+ * @param values the optional values for the formatted exception message, null array not recommended
+ * @throws IllegalArgumentException if argument can not be converted to the specified class
+ * @see #isAssignableFrom(Class, Class)
+ */
+ public static void isAssignableFrom(Class> superType, Class> type, String message, Object... values) {
+ if (superType.isAssignableFrom(type) == false) {
+ throw new IllegalArgumentException(String.format(message, values));
+ }
+ }
+}
diff --git a/Bridge/src/main/apacheCommonsLang/external/org/apache/commons/lang3/builder/Builder.java b/Bridge/src/main/apacheCommonsLang/external/org/apache/commons/lang3/builder/Builder.java
new file mode 100644
index 00000000..a3b840cf
--- /dev/null
+++ b/Bridge/src/main/apacheCommonsLang/external/org/apache/commons/lang3/builder/Builder.java
@@ -0,0 +1,89 @@
+/*
+ * 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 external.org.apache.commons.lang3.builder;
+
+/**
+ *
+ * The Builder interface is designed to designate a class as a builder
+ * object in the Builder design pattern. Builders are capable of creating and
+ * configuring objects or results that normally take multiple steps to construct
+ * or are very complex to derive.
+ *
+ *
+ *
+ * The builder interface defines a single method, {@link #build()}, that
+ * classes must implement. The result of this method should be the final
+ * configured object or result after all building operations are performed.
+ *
+ *
+ *
+ * It is a recommended practice that the methods supplied to configure the
+ * object or result being built return a reference to {@code this} so that
+ * method calls can be chained together.
+ *
+ *
+ *
+ * Example Builder:
+ *
+ * class FontBuilder implements Builder<Font> {
+ * private Font font;
+ *
+ * public FontBuilder(String fontName) {
+ * this.font = new Font(fontName, Font.PLAIN, 12);
+ * }
+ *
+ * public FontBuilder bold() {
+ * this.font = this.font.deriveFont(Font.BOLD);
+ * return this; // Reference returned so calls can be chained
+ * }
+ *
+ * public FontBuilder size(float pointSize) {
+ * this.font = this.font.deriveFont(pointSize);
+ * return this; // Reference returned so calls can be chained
+ * }
+ *
+ * // Other Font construction methods
+ *
+ * public Font build() {
+ * return this.font;
+ * }
+ * }
+ *
+ *
+ * Example Builder Usage:
+ *
+ * Font bold14ptSansSerifFont = new FontBuilder(Font.SANS_SERIF).bold()
+ * .size(14.0f)
+ * .build();
+ *
+ *
+ *
+ * @param the type of object that the builder will construct or compute.
+ *
+ * @since 3.0
+ * @version $Id: Builder.java 1088899 2011-04-05 05:31:27Z bayard $
+ */
+public interface Builder {
+
+ /**
+ * Returns a reference to the object being constructed or result being
+ * calculated by the builder.
+ *
+ * @return the object constructed or result calculated by the builder.
+ */
+ public T build();
+}
diff --git a/Bridge/src/main/apacheCommonsLang/external/org/apache/commons/lang3/builder/CompareToBuilder.java b/Bridge/src/main/apacheCommonsLang/external/org/apache/commons/lang3/builder/CompareToBuilder.java
new file mode 100644
index 00000000..9d06f589
--- /dev/null
+++ b/Bridge/src/main/apacheCommonsLang/external/org/apache/commons/lang3/builder/CompareToBuilder.java
@@ -0,0 +1,1020 @@
+/*
+ * 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 external.org.apache.commons.lang3.builder;
+
+import java.lang.reflect.AccessibleObject;
+import java.lang.reflect.Field;
+import java.lang.reflect.Modifier;
+import java.util.Collection;
+import java.util.Comparator;
+
+import external.org.apache.commons.lang3.ArrayUtils;
+
+/**
+ * Assists in implementing {@link java.lang.Comparable#compareTo(Object)} methods.
+ *
+ * It is consistent with equals(Object) and
+ * hashcode() built with {@link EqualsBuilder} and
+ * {@link HashCodeBuilder}.
+ *
+ * Two Objects that compare equal using equals(Object) should normally
+ * also compare equal using compareTo(Object).
+ *
+ * All relevant fields should be included in the calculation of the
+ * comparison. Derived fields may be ignored. The same fields, in the same
+ * order, should be used in both compareTo(Object) and
+ * equals(Object).
+ *
+ * To use this class write code as follows:
+ *
+ *
+ * public class MyClass {
+ * String field1;
+ * int field2;
+ * boolean field3;
+ *
+ * ...
+ *
+ * public int compareTo(Object o) {
+ * MyClass myClass = (MyClass) o;
+ * return new CompareToBuilder()
+ * .appendSuper(super.compareTo(o)
+ * .append(this.field1, myClass.field1)
+ * .append(this.field2, myClass.field2)
+ * .append(this.field3, myClass.field3)
+ * .toComparison();
+ * }
+ * }
+ *
+ *
+ * Alternatively, there are {@link #reflectionCompare(Object, Object) reflectionCompare} methods that use
+ * reflection to determine the fields to append. Because fields can be private,
+ * reflectionCompare uses {@link java.lang.reflect.AccessibleObject#setAccessible(boolean)} to
+ * bypass normal access control checks. This will fail under a security manager,
+ * unless the appropriate permissions are set up correctly. It is also
+ * slower than appending explicitly.
+ *
+ * A typical implementation of compareTo(Object) using
+ * reflectionCompare looks like:
+
+ *
+ * public int compareTo(Object o) {
+ * return CompareToBuilder.reflectionCompare(this, o);
+ * }
+ *
+ *
+ * @see java.lang.Comparable
+ * @see java.lang.Object#equals(Object)
+ * @see java.lang.Object#hashCode()
+ * @see EqualsBuilder
+ * @see HashCodeBuilder
+ * @since 1.0
+ * @version $Id: CompareToBuilder.java 1199735 2011-11-09 13:11:07Z sebb $
+ */
+public class CompareToBuilder implements Builder {
+
+ /**
+ * Current state of the comparison as appended fields are checked.
+ */
+ private int comparison;
+
+ /**
+ * Constructor for CompareToBuilder.
+ *
+ * Starts off assuming that the objects are equal. Multiple calls are
+ * then made to the various append methods, followed by a call to
+ * {@link #toComparison} to get the result.
+ */
+ public CompareToBuilder() {
+ super();
+ comparison = 0;
+ }
+
+ //-----------------------------------------------------------------------
+ /**
+ * Compares two Objects via reflection.
+ *
+ * Fields can be private, thus AccessibleObject.setAccessible
+ * is used to bypass normal access control checks. This will fail under a
+ * security manager unless the appropriate permissions are set.
+ *
+ *
+ * Static fields will not be compared
+ * Transient members will be not be compared, as they are likely derived
+ * fields
+ * Superclass fields will be compared
+ *
+ *
+ * If both lhs and rhs are null,
+ * they are considered equal.
+ *
+ * @param lhs left-hand object
+ * @param rhs right-hand object
+ * @return a negative integer, zero, or a positive integer as lhs
+ * is less than, equal to, or greater than rhs
+ * @throws NullPointerException if either (but not both) parameters are
+ * null
+ * @throws ClassCastException if rhs is not assignment-compatible
+ * with lhs
+ */
+ public static int reflectionCompare(Object lhs, Object rhs) {
+ return reflectionCompare(lhs, rhs, false, null);
+ }
+
+ /**
+ * Compares two Objects via reflection.
+ *
+ * Fields can be private, thus AccessibleObject.setAccessible
+ * is used to bypass normal access control checks. This will fail under a
+ * security manager unless the appropriate permissions are set.
+ *
+ *
+ * Static fields will not be compared
+ * If compareTransients is true,
+ * compares transient members. Otherwise ignores them, as they
+ * are likely derived fields.
+ * Superclass fields will be compared
+ *
+ *
+ * If both lhs and rhs are null,
+ * they are considered equal.
+ *
+ * @param lhs left-hand object
+ * @param rhs right-hand object
+ * @param compareTransients whether to compare transient fields
+ * @return a negative integer, zero, or a positive integer as lhs
+ * is less than, equal to, or greater than rhs
+ * @throws NullPointerException if either lhs or rhs
+ * (but not both) is null
+ * @throws ClassCastException if rhs is not assignment-compatible
+ * with lhs
+ */
+ public static int reflectionCompare(Object lhs, Object rhs, boolean compareTransients) {
+ return reflectionCompare(lhs, rhs, compareTransients, null);
+ }
+
+ /**
+ * Compares two Objects via reflection.
+ *
+ * Fields can be private, thus AccessibleObject.setAccessible
+ * is used to bypass normal access control checks. This will fail under a
+ * security manager unless the appropriate permissions are set.
+ *
+ *
+ * Static fields will not be compared
+ * If compareTransients is true,
+ * compares transient members. Otherwise ignores them, as they
+ * are likely derived fields.
+ * Superclass fields will be compared
+ *
+ *
+ * If both lhs and rhs are null,
+ * they are considered equal.
+ *
+ * @param lhs left-hand object
+ * @param rhs right-hand object
+ * @param excludeFields Collection of String fields to exclude
+ * @return a negative integer, zero, or a positive integer as lhs
+ * is less than, equal to, or greater than rhs
+ * @throws NullPointerException if either lhs or rhs
+ * (but not both) is null
+ * @throws ClassCastException if rhs is not assignment-compatible
+ * with lhs
+ * @since 2.2
+ */
+ public static int reflectionCompare(Object lhs, Object rhs, Collection excludeFields) {
+ return reflectionCompare(lhs, rhs, ReflectionToStringBuilder.toNoNullStringArray(excludeFields));
+ }
+
+ /**
+ * Compares two Objects via reflection.
+ *
+ * Fields can be private, thus AccessibleObject.setAccessible
+ * is used to bypass normal access control checks. This will fail under a
+ * security manager unless the appropriate permissions are set.
+ *
+ *
+ * Static fields will not be compared
+ * If compareTransients is true,
+ * compares transient members. Otherwise ignores them, as they
+ * are likely derived fields.
+ * Superclass fields will be compared
+ *
+ *
+ * If both lhs and rhs are null,
+ * they are considered equal.
+ *
+ * @param lhs left-hand object
+ * @param rhs right-hand object
+ * @param excludeFields array of fields to exclude
+ * @return a negative integer, zero, or a positive integer as lhs
+ * is less than, equal to, or greater than rhs
+ * @throws NullPointerException if either lhs or rhs
+ * (but not both) is null
+ * @throws ClassCastException if rhs is not assignment-compatible
+ * with lhs
+ * @since 2.2
+ */
+ public static int reflectionCompare(Object lhs, Object rhs, String... excludeFields) {
+ return reflectionCompare(lhs, rhs, false, null, excludeFields);
+ }
+
+ /**
+ * Compares two Objects via reflection.
+ *
+ * Fields can be private, thus AccessibleObject.setAccessible
+ * is used to bypass normal access control checks. This will fail under a
+ * security manager unless the appropriate permissions are set.
+ *
+ *
+ * Static fields will not be compared
+ * If the compareTransients is true,
+ * compares transient members. Otherwise ignores them, as they
+ * are likely derived fields.
+ * Compares superclass fields up to and including reflectUpToClass.
+ * If reflectUpToClass is null, compares all superclass fields.
+ *
+ *
+ * If both lhs and rhs are null,
+ * they are considered equal.
+ *
+ * @param lhs left-hand object
+ * @param rhs right-hand object
+ * @param compareTransients whether to compare transient fields
+ * @param reflectUpToClass last superclass for which fields are compared
+ * @param excludeFields fields to exclude
+ * @return a negative integer, zero, or a positive integer as lhs
+ * is less than, equal to, or greater than rhs
+ * @throws NullPointerException if either lhs or rhs
+ * (but not both) is null
+ * @throws ClassCastException if rhs is not assignment-compatible
+ * with lhs
+ * @since 2.2 (2.0 as reflectionCompare(Object, Object, boolean, Class))
+ */
+ public static int reflectionCompare(
+ Object lhs,
+ Object rhs,
+ boolean compareTransients,
+ Class> reflectUpToClass,
+ String... excludeFields) {
+
+ if (lhs == rhs) {
+ return 0;
+ }
+ if (lhs == null || rhs == null) {
+ throw new NullPointerException();
+ }
+ Class> lhsClazz = lhs.getClass();
+ if (!lhsClazz.isInstance(rhs)) {
+ throw new ClassCastException();
+ }
+ CompareToBuilder compareToBuilder = new CompareToBuilder();
+ reflectionAppend(lhs, rhs, lhsClazz, compareToBuilder, compareTransients, excludeFields);
+ while (lhsClazz.getSuperclass() != null && lhsClazz != reflectUpToClass) {
+ lhsClazz = lhsClazz.getSuperclass();
+ reflectionAppend(lhs, rhs, lhsClazz, compareToBuilder, compareTransients, excludeFields);
+ }
+ return compareToBuilder.toComparison();
+ }
+
+ /**
+ * Appends to builder the comparison of lhs
+ * to rhs using the fields defined in clazz.
+ *
+ * @param lhs left-hand object
+ * @param rhs right-hand object
+ * @param clazz Class that defines fields to be compared
+ * @param builder CompareToBuilder to append to
+ * @param useTransients whether to compare transient fields
+ * @param excludeFields fields to exclude
+ */
+ private static void reflectionAppend(
+ Object lhs,
+ Object rhs,
+ Class> clazz,
+ CompareToBuilder builder,
+ boolean useTransients,
+ String[] excludeFields) {
+
+ Field[] fields = clazz.getDeclaredFields();
+ AccessibleObject.setAccessible(fields, true);
+ for (int i = 0; i < fields.length && builder.comparison == 0; i++) {
+ Field f = fields[i];
+ if (!ArrayUtils.contains(excludeFields, f.getName())
+ && (f.getName().indexOf('$') == -1)
+ && (useTransients || !Modifier.isTransient(f.getModifiers()))
+ && (!Modifier.isStatic(f.getModifiers()))) {
+ try {
+ builder.append(f.get(lhs), f.get(rhs));
+ } catch (IllegalAccessException e) {
+ // This can't happen. Would get a Security exception instead.
+ // Throw a runtime exception in case the impossible happens.
+ throw new InternalError("Unexpected IllegalAccessException");
+ }
+ }
+ }
+ }
+
+ //-----------------------------------------------------------------------
+ /**
+ * Appends to the builder the compareTo(Object)
+ * result of the superclass.
+ *
+ * @param superCompareTo result of calling super.compareTo(Object)
+ * @return this - used to chain append calls
+ * @since 2.0
+ */
+ public CompareToBuilder appendSuper(int superCompareTo) {
+ if (comparison != 0) {
+ return this;
+ }
+ comparison = superCompareTo;
+ return this;
+ }
+
+ //-----------------------------------------------------------------------
+ /**
+ * Appends to the builder the comparison of
+ * two Objects.
+ *
+ *
+ * Check if lhs == rhs
+ * Check if either lhs or rhs is null,
+ * a null object is less than a non-null object
+ * Check the object contents
+ *
+ *
+ * lhs must either be an array or implement {@link Comparable}.
+ *
+ * @param lhs left-hand object
+ * @param rhs right-hand object
+ * @return this - used to chain append calls
+ * @throws ClassCastException if rhs is not assignment-compatible
+ * with lhs
+ */
+ public CompareToBuilder append(Object lhs, Object rhs) {
+ return append(lhs, rhs, null);
+ }
+
+ /**
+ * Appends to the builder the comparison of
+ * two Objects.
+ *
+ *
+ * Check if lhs == rhs
+ * Check if either lhs or rhs is null,
+ * a null object is less than a non-null object
+ * Check the object contents
+ *
+ *
+ * If lhs is an array, array comparison methods will be used.
+ * Otherwise comparator will be used to compare the objects.
+ * If comparator is null, lhs must
+ * implement {@link Comparable} instead.
+ *
+ * @param lhs left-hand object
+ * @param rhs right-hand object
+ * @param comparator Comparator used to compare the objects,
+ * null means treat lhs as Comparable
+ * @return this - used to chain append calls
+ * @throws ClassCastException if rhs is not assignment-compatible
+ * with lhs
+ * @since 2.0
+ */
+ public CompareToBuilder append(Object lhs, Object rhs, Comparator> comparator) {
+ if (comparison != 0) {
+ return this;
+ }
+ if (lhs == rhs) {
+ return this;
+ }
+ if (lhs == null) {
+ comparison = -1;
+ return this;
+ }
+ if (rhs == null) {
+ comparison = +1;
+ return this;
+ }
+ if (lhs.getClass().isArray()) {
+ // switch on type of array, to dispatch to the correct handler
+ // handles multi dimensional arrays
+ // throws a ClassCastException if rhs is not the correct array type
+ if (lhs instanceof long[]) {
+ append((long[]) lhs, (long[]) rhs);
+ } else if (lhs instanceof int[]) {
+ append((int[]) lhs, (int[]) rhs);
+ } else if (lhs instanceof short[]) {
+ append((short[]) lhs, (short[]) rhs);
+ } else if (lhs instanceof char[]) {
+ append((char[]) lhs, (char[]) rhs);
+ } else if (lhs instanceof byte[]) {
+ append((byte[]) lhs, (byte[]) rhs);
+ } else if (lhs instanceof double[]) {
+ append((double[]) lhs, (double[]) rhs);
+ } else if (lhs instanceof float[]) {
+ append((float[]) lhs, (float[]) rhs);
+ } else if (lhs instanceof boolean[]) {
+ append((boolean[]) lhs, (boolean[]) rhs);
+ } else {
+ // not an array of primitives
+ // throws a ClassCastException if rhs is not an array
+ append((Object[]) lhs, (Object[]) rhs, comparator);
+ }
+ } else {
+ // the simple case, not an array, just test the element
+ if (comparator == null) {
+ @SuppressWarnings("unchecked") // assume this can be done; if not throw CCE as per Javadoc
+ final Comparable comparable = (Comparable) lhs;
+ comparison = comparable.compareTo(rhs);
+ } else {
+ @SuppressWarnings("unchecked") // assume this can be done; if not throw CCE as per Javadoc
+ final Comparator comparator2 = (Comparator) comparator;
+ comparison = comparator2.compare(lhs, rhs);
+ }
+ }
+ return this;
+ }
+
+ //-------------------------------------------------------------------------
+ /**
+ * Appends to the builder the comparison of
+ * two longs.
+ *
+ * @param lhs left-hand value
+ * @param rhs right-hand value
+ * @return this - used to chain append calls
+ */
+ public CompareToBuilder append(long lhs, long rhs) {
+ if (comparison != 0) {
+ return this;
+ }
+ comparison = ((lhs < rhs) ? -1 : ((lhs > rhs) ? 1 : 0));
+ return this;
+ }
+
+ /**
+ * Appends to the builder the comparison of
+ * two ints.
+ *
+ * @param lhs left-hand value
+ * @param rhs right-hand value
+ * @return this - used to chain append calls
+ */
+ public CompareToBuilder append(int lhs, int rhs) {
+ if (comparison != 0) {
+ return this;
+ }
+ comparison = ((lhs < rhs) ? -1 : ((lhs > rhs) ? 1 : 0));
+ return this;
+ }
+
+ /**
+ * Appends to the builder the comparison of
+ * two shorts.
+ *
+ * @param lhs left-hand value
+ * @param rhs right-hand value
+ * @return this - used to chain append calls
+ */
+ public CompareToBuilder append(short lhs, short rhs) {
+ if (comparison != 0) {
+ return this;
+ }
+ comparison = ((lhs < rhs) ? -1 : ((lhs > rhs) ? 1 : 0));
+ return this;
+ }
+
+ /**
+ * Appends to the builder the comparison of
+ * two chars.
+ *
+ * @param lhs left-hand value
+ * @param rhs right-hand value
+ * @return this - used to chain append calls
+ */
+ public CompareToBuilder append(char lhs, char rhs) {
+ if (comparison != 0) {
+ return this;
+ }
+ comparison = ((lhs < rhs) ? -1 : ((lhs > rhs) ? 1 : 0));
+ return this;
+ }
+
+ /**
+ * Appends to the builder the comparison of
+ * two bytes.
+ *
+ * @param lhs left-hand value
+ * @param rhs right-hand value
+ * @return this - used to chain append calls
+ */
+ public CompareToBuilder append(byte lhs, byte rhs) {
+ if (comparison != 0) {
+ return this;
+ }
+ comparison = ((lhs < rhs) ? -1 : ((lhs > rhs) ? 1 : 0));
+ return this;
+ }
+
+ /**
+ * Appends to the builder the comparison of
+ * two doubles.
+ *
+ * This handles NaNs, Infinities, and -0.0.
+ *
+ * It is compatible with the hash code generated by
+ * HashCodeBuilder.
+ *
+ * @param lhs left-hand value
+ * @param rhs right-hand value
+ * @return this - used to chain append calls
+ */
+ public CompareToBuilder append(double lhs, double rhs) {
+ if (comparison != 0) {
+ return this;
+ }
+ comparison = Double.compare(lhs, rhs);
+ return this;
+ }
+
+ /**
+ * Appends to the builder the comparison of
+ * two floats.
+ *
+ * This handles NaNs, Infinities, and -0.0.
+ *
+ * It is compatible with the hash code generated by
+ * HashCodeBuilder.
+ *
+ * @param lhs left-hand value
+ * @param rhs right-hand value
+ * @return this - used to chain append calls
+ */
+ public CompareToBuilder append(float lhs, float rhs) {
+ if (comparison != 0) {
+ return this;
+ }
+ comparison = Float.compare(lhs, rhs);
+ return this;
+ }
+
+ /**
+ * Appends to the builder the comparison of
+ * two booleanss.
+ *
+ * @param lhs left-hand value
+ * @param rhs right-hand value
+ * @return this - used to chain append calls
+ */
+ public CompareToBuilder append(boolean lhs, boolean rhs) {
+ if (comparison != 0) {
+ return this;
+ }
+ if (lhs == rhs) {
+ return this;
+ }
+ if (lhs == false) {
+ comparison = -1;
+ } else {
+ comparison = +1;
+ }
+ return this;
+ }
+
+ //-----------------------------------------------------------------------
+ /**
+ * Appends to the builder the deep comparison of
+ * two Object arrays.
+ *
+ *
+ * Check if arrays are the same using ==
+ * Check if for null, null is less than non-null
+ * Check array length, a short length array is less than a long length array
+ * Check array contents element by element using {@link #append(Object, Object, Comparator)}
+ *
+ *
+ * This method will also will be called for the top level of multi-dimensional,
+ * ragged, and multi-typed arrays.
+ *
+ * @param lhs left-hand array
+ * @param rhs right-hand array
+ * @return this - used to chain append calls
+ * @throws ClassCastException if rhs is not assignment-compatible
+ * with lhs
+ */
+ public CompareToBuilder append(Object[] lhs, Object[] rhs) {
+ return append(lhs, rhs, null);
+ }
+
+ /**
+ * Appends to the builder the deep comparison of
+ * two Object arrays.
+ *
+ *
+ * Check if arrays are the same using ==
+ * Check if for null, null is less than non-null
+ * Check array length, a short length array is less than a long length array
+ * Check array contents element by element using {@link #append(Object, Object, Comparator)}
+ *
+ *
+ * This method will also will be called for the top level of multi-dimensional,
+ * ragged, and multi-typed arrays.
+ *
+ * @param lhs left-hand array
+ * @param rhs right-hand array
+ * @param comparator Comparator to use to compare the array elements,
+ * null means to treat lhs elements as Comparable.
+ * @return this - used to chain append calls
+ * @throws ClassCastException if rhs is not assignment-compatible
+ * with lhs
+ * @since 2.0
+ */
+ public CompareToBuilder append(Object[] lhs, Object[] rhs, Comparator> comparator) {
+ if (comparison != 0) {
+ return this;
+ }
+ if (lhs == rhs) {
+ return this;
+ }
+ if (lhs == null) {
+ comparison = -1;
+ return this;
+ }
+ if (rhs == null) {
+ comparison = +1;
+ return this;
+ }
+ if (lhs.length != rhs.length) {
+ comparison = (lhs.length < rhs.length) ? -1 : +1;
+ return this;
+ }
+ for (int i = 0; i < lhs.length && comparison == 0; i++) {
+ append(lhs[i], rhs[i], comparator);
+ }
+ return this;
+ }
+
+ /**
+ * Appends to the builder the deep comparison of
+ * two long arrays.
+ *
+ *
+ * Check if arrays are the same using ==
+ * Check if for null, null is less than non-null
+ * Check array length, a shorter length array is less than a longer length array
+ * Check array contents element by element using {@link #append(long, long)}
+ *
+ *
+ * @param lhs left-hand array
+ * @param rhs right-hand array
+ * @return this - used to chain append calls
+ */
+ public CompareToBuilder append(long[] lhs, long[] rhs) {
+ if (comparison != 0) {
+ return this;
+ }
+ if (lhs == rhs) {
+ return this;
+ }
+ if (lhs == null) {
+ comparison = -1;
+ return this;
+ }
+ if (rhs == null) {
+ comparison = +1;
+ return this;
+ }
+ if (lhs.length != rhs.length) {
+ comparison = (lhs.length < rhs.length) ? -1 : +1;
+ return this;
+ }
+ for (int i = 0; i < lhs.length && comparison == 0; i++) {
+ append(lhs[i], rhs[i]);
+ }
+ return this;
+ }
+
+ /**
+ * Appends to the builder the deep comparison of
+ * two int arrays.
+ *
+ *
+ * Check if arrays are the same using ==
+ * Check if for null, null is less than non-null
+ * Check array length, a shorter length array is less than a longer length array
+ * Check array contents element by element using {@link #append(int, int)}
+ *
+ *
+ * @param lhs left-hand array
+ * @param rhs right-hand array
+ * @return this - used to chain append calls
+ */
+ public CompareToBuilder append(int[] lhs, int[] rhs) {
+ if (comparison != 0) {
+ return this;
+ }
+ if (lhs == rhs) {
+ return this;
+ }
+ if (lhs == null) {
+ comparison = -1;
+ return this;
+ }
+ if (rhs == null) {
+ comparison = +1;
+ return this;
+ }
+ if (lhs.length != rhs.length) {
+ comparison = (lhs.length < rhs.length) ? -1 : +1;
+ return this;
+ }
+ for (int i = 0; i < lhs.length && comparison == 0; i++) {
+ append(lhs[i], rhs[i]);
+ }
+ return this;
+ }
+
+ /**
+ * Appends to the builder the deep comparison of
+ * two short arrays.
+ *
+ *
+ * Check if arrays are the same using ==
+ * Check if for null, null is less than non-null
+ * Check array length, a shorter length array is less than a longer length array
+ * Check array contents element by element using {@link #append(short, short)}
+ *
+ *
+ * @param lhs left-hand array
+ * @param rhs right-hand array
+ * @return this - used to chain append calls
+ */
+ public CompareToBuilder append(short[] lhs, short[] rhs) {
+ if (comparison != 0) {
+ return this;
+ }
+ if (lhs == rhs) {
+ return this;
+ }
+ if (lhs == null) {
+ comparison = -1;
+ return this;
+ }
+ if (rhs == null) {
+ comparison = +1;
+ return this;
+ }
+ if (lhs.length != rhs.length) {
+ comparison = (lhs.length < rhs.length) ? -1 : +1;
+ return this;
+ }
+ for (int i = 0; i < lhs.length && comparison == 0; i++) {
+ append(lhs[i], rhs[i]);
+ }
+ return this;
+ }
+
+ /**
+ * Appends to the builder the deep comparison of
+ * two char arrays.
+ *
+ *
+ * Check if arrays are the same using ==
+ * Check if for null, null is less than non-null
+ * Check array length, a shorter length array is less than a longer length array
+ * Check array contents element by element using {@link #append(char, char)}
+ *
+ *
+ * @param lhs left-hand array
+ * @param rhs right-hand array
+ * @return this - used to chain append calls
+ */
+ public CompareToBuilder append(char[] lhs, char[] rhs) {
+ if (comparison != 0) {
+ return this;
+ }
+ if (lhs == rhs) {
+ return this;
+ }
+ if (lhs == null) {
+ comparison = -1;
+ return this;
+ }
+ if (rhs == null) {
+ comparison = +1;
+ return this;
+ }
+ if (lhs.length != rhs.length) {
+ comparison = (lhs.length < rhs.length) ? -1 : +1;
+ return this;
+ }
+ for (int i = 0; i < lhs.length && comparison == 0; i++) {
+ append(lhs[i], rhs[i]);
+ }
+ return this;
+ }
+
+ /**
+ * Appends to the builder the deep comparison of
+ * two byte arrays.
+ *
+ *
+ * Check if arrays are the same using ==
+ * Check if for null, null is less than non-null
+ * Check array length, a shorter length array is less than a longer length array
+ * Check array contents element by element using {@link #append(byte, byte)}
+ *
+ *
+ * @param lhs left-hand array
+ * @param rhs right-hand array
+ * @return this - used to chain append calls
+ */
+ public CompareToBuilder append(byte[] lhs, byte[] rhs) {
+ if (comparison != 0) {
+ return this;
+ }
+ if (lhs == rhs) {
+ return this;
+ }
+ if (lhs == null) {
+ comparison = -1;
+ return this;
+ }
+ if (rhs == null) {
+ comparison = +1;
+ return this;
+ }
+ if (lhs.length != rhs.length) {
+ comparison = (lhs.length < rhs.length) ? -1 : +1;
+ return this;
+ }
+ for (int i = 0; i < lhs.length && comparison == 0; i++) {
+ append(lhs[i], rhs[i]);
+ }
+ return this;
+ }
+
+ /**
+ * Appends to the builder the deep comparison of
+ * two double arrays.
+ *
+ *
+ * Check if arrays are the same using ==
+ * Check if for null, null is less than non-null
+ * Check array length, a shorter length array is less than a longer length array
+ * Check array contents element by element using {@link #append(double, double)}
+ *
+ *
+ * @param lhs left-hand array
+ * @param rhs right-hand array
+ * @return this - used to chain append calls
+ */
+ public CompareToBuilder append(double[] lhs, double[] rhs) {
+ if (comparison != 0) {
+ return this;
+ }
+ if (lhs == rhs) {
+ return this;
+ }
+ if (lhs == null) {
+ comparison = -1;
+ return this;
+ }
+ if (rhs == null) {
+ comparison = +1;
+ return this;
+ }
+ if (lhs.length != rhs.length) {
+ comparison = (lhs.length < rhs.length) ? -1 : +1;
+ return this;
+ }
+ for (int i = 0; i < lhs.length && comparison == 0; i++) {
+ append(lhs[i], rhs[i]);
+ }
+ return this;
+ }
+
+ /**
+ * Appends to the builder the deep comparison of
+ * two float arrays.
+ *
+ *
+ * Check if arrays are the same using ==
+ * Check if for null, null is less than non-null
+ * Check array length, a shorter length array is less than a longer length array
+ * Check array contents element by element using {@link #append(float, float)}
+ *
+ *
+ * @param lhs left-hand array
+ * @param rhs right-hand array
+ * @return this - used to chain append calls
+ */
+ public CompareToBuilder append(float[] lhs, float[] rhs) {
+ if (comparison != 0) {
+ return this;
+ }
+ if (lhs == rhs) {
+ return this;
+ }
+ if (lhs == null) {
+ comparison = -1;
+ return this;
+ }
+ if (rhs == null) {
+ comparison = +1;
+ return this;
+ }
+ if (lhs.length != rhs.length) {
+ comparison = (lhs.length < rhs.length) ? -1 : +1;
+ return this;
+ }
+ for (int i = 0; i < lhs.length && comparison == 0; i++) {
+ append(lhs[i], rhs[i]);
+ }
+ return this;
+ }
+
+ /**
+ * Appends to the builder the deep comparison of
+ * two boolean arrays.
+ *
+ *
+ * Check if arrays are the same using ==
+ * Check if for null, null is less than non-null
+ * Check array length, a shorter length array is less than a longer length array
+ * Check array contents element by element using {@link #append(boolean, boolean)}
+ *
+ *
+ * @param lhs left-hand array
+ * @param rhs right-hand array
+ * @return this - used to chain append calls
+ */
+ public CompareToBuilder append(boolean[] lhs, boolean[] rhs) {
+ if (comparison != 0) {
+ return this;
+ }
+ if (lhs == rhs) {
+ return this;
+ }
+ if (lhs == null) {
+ comparison = -1;
+ return this;
+ }
+ if (rhs == null) {
+ comparison = +1;
+ return this;
+ }
+ if (lhs.length != rhs.length) {
+ comparison = (lhs.length < rhs.length) ? -1 : +1;
+ return this;
+ }
+ for (int i = 0; i < lhs.length && comparison == 0; i++) {
+ append(lhs[i], rhs[i]);
+ }
+ return this;
+ }
+
+ //-----------------------------------------------------------------------
+ /**
+ * Returns a negative integer, a positive integer, or zero as
+ * the builder has judged the "left-hand" side
+ * as less than, greater than, or equal to the "right-hand"
+ * side.
+ *
+ * @return final comparison result
+ * @see #build()
+ */
+ public int toComparison() {
+ return comparison;
+ }
+
+ /**
+ * Returns a negative Integer, a positive Integer, or zero as
+ * the builder has judged the "left-hand" side
+ * as less than, greater than, or equal to the "right-hand"
+ * side.
+ *
+ * @return final comparison result as an Integer
+ * @see #toComparison()
+ * @since 3.0
+ */
+ public Integer build() {
+ return Integer.valueOf(toComparison());
+ }
+}
+
diff --git a/Bridge/src/main/apacheCommonsLang/external/org/apache/commons/lang3/builder/EqualsBuilder.java b/Bridge/src/main/apacheCommonsLang/external/org/apache/commons/lang3/builder/EqualsBuilder.java
new file mode 100644
index 00000000..c8a459c5
--- /dev/null
+++ b/Bridge/src/main/apacheCommonsLang/external/org/apache/commons/lang3/builder/EqualsBuilder.java
@@ -0,0 +1,945 @@
+/*
+ * 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 external.org.apache.commons.lang3.builder;
+
+import java.lang.reflect.AccessibleObject;
+import java.lang.reflect.Field;
+import java.lang.reflect.Modifier;
+import java.util.Collection;
+import java.util.HashSet;
+import java.util.Set;
+
+
+import external.org.apache.commons.lang3.ArrayUtils;
+import external.org.apache.commons.lang3.tuple.Pair;
+
+/**
+ * Assists in implementing {@link Object#equals(Object)} methods.
+ *
+ * This class provides methods to build a good equals method for any
+ * class. It follows rules laid out in
+ * Effective Java
+ * , by Joshua Bloch. In particular the rule for comparing doubles,
+ * floats, and arrays can be tricky. Also, making sure that
+ * equals() and hashCode() are consistent can be
+ * difficult.
+ *
+ * Two Objects that compare as equals must generate the same hash code,
+ * but two Objects with the same hash code do not have to be equal.
+ *
+ * All relevant fields should be included in the calculation of equals.
+ * Derived fields may be ignored. In particular, any field used in
+ * generating a hash code must be used in the equals method, and vice
+ * versa.
+ *
+ * Typical use for the code is as follows:
+ *
+ * public boolean equals(Object obj) {
+ * if (obj == null) { return false; }
+ * if (obj == this) { return true; }
+ * if (obj.getClass() != getClass()) {
+ * return false;
+ * }
+ * MyClass rhs = (MyClass) obj;
+ * return new EqualsBuilder()
+ * .appendSuper(super.equals(obj))
+ * .append(field1, rhs.field1)
+ * .append(field2, rhs.field2)
+ * .append(field3, rhs.field3)
+ * .isEquals();
+ * }
+ *
+ *
+ * Alternatively, there is a method that uses reflection to determine
+ * the fields to test. Because these fields are usually private, the method,
+ * reflectionEquals, uses AccessibleObject.setAccessible to
+ * change the visibility of the fields. This will fail under a security
+ * manager, unless the appropriate permissions are set up correctly. It is
+ * also slower than testing explicitly.
+ *
+ * A typical invocation for this method would look like:
+ *
+ * public boolean equals(Object obj) {
+ * return EqualsBuilder.reflectionEquals(this, obj);
+ * }
+ *
+ *
+ * @since 1.0
+ * @version $Id: EqualsBuilder.java 1091531 2011-04-12 18:29:49Z ggregory $
+ */
+public class EqualsBuilder implements Builder {
+
+ /**
+ *
+ * A registry of objects used by reflection methods to detect cyclical object references and avoid infinite loops.
+ *
+ *
+ * @since 3.0
+ */
+ private static final ThreadLocal>> REGISTRY = new ThreadLocal>>();
+
+ /*
+ * NOTE: we cannot store the actual objects in a HashSet, as that would use the very hashCode()
+ * we are in the process of calculating.
+ *
+ * So we generate a one-to-one mapping from the original object to a new object.
+ *
+ * Now HashSet uses equals() to determine if two elements with the same hashcode really
+ * are equal, so we also need to ensure that the replacement objects are only equal
+ * if the original objects are identical.
+ *
+ * The original implementation (2.4 and before) used the System.indentityHashCode()
+ * method - however this is not guaranteed to generate unique ids (e.g. LANG-459)
+ *
+ * We now use the IDKey helper class (adapted from org.apache.axis.utils.IDKey)
+ * to disambiguate the duplicate ids.
+ */
+
+ /**
+ *
+ * Returns the registry of object pairs being traversed by the reflection
+ * methods in the current thread.
+ *
+ *
+ * @return Set the registry of objects being traversed
+ * @since 3.0
+ */
+ static Set> getRegistry() {
+ return REGISTRY.get();
+ }
+
+ /**
+ *
+ * Converters value pair into a register pair.
+ *
+ *
+ * @param lhs this object
+ * @param rhs the other object
+ *
+ * @return the pair
+ */
+ static Pair getRegisterPair(Object lhs, Object rhs) {
+ IDKey left = new IDKey(lhs);
+ IDKey right = new IDKey(rhs);
+ return Pair.of(left, right);
+ }
+
+ /**
+ *
+ * Returns true if the registry contains the given object pair.
+ * Used by the reflection methods to avoid infinite loops.
+ * Objects might be swapped therefore a check is needed if the object pair
+ * is registered in given or swapped order.
+ *
+ *
+ * @param lhs this object to lookup in registry
+ * @param rhs the other object to lookup on registry
+ * @return boolean true if the registry contains the given object.
+ * @since 3.0
+ */
+ static boolean isRegistered(Object lhs, Object rhs) {
+ Set> registry = getRegistry();
+ Pair pair = getRegisterPair(lhs, rhs);
+ Pair swappedPair = Pair.of(pair.getLeft(), pair.getRight());
+
+ return registry != null
+ && (registry.contains(pair) || registry.contains(swappedPair));
+ }
+
+ /**
+ *
+ * Registers the given object pair.
+ * Used by the reflection methods to avoid infinite loops.
+ *
+ *
+ * @param lhs this object to register
+ * @param rhs the other object to register
+ */
+ static void register(Object lhs, Object rhs) {
+ synchronized (EqualsBuilder.class) {
+ if (getRegistry() == null) {
+ REGISTRY.set(new HashSet>());
+ }
+ }
+
+ Set> registry = getRegistry();
+ Pair pair = getRegisterPair(lhs, rhs);
+ registry.add(pair);
+ }
+
+ /**
+ *
+ * Unregisters the given object pair.
+ *
+ *
+ *
+ * Used by the reflection methods to avoid infinite loops.
+ *
+ * @param lhs this object to unregister
+ * @param rhs the other object to unregister
+ * @since 3.0
+ */
+ static void unregister(Object lhs, Object rhs) {
+ Set> registry = getRegistry();
+ if (registry != null) {
+ Pair pair = getRegisterPair(lhs, rhs);
+ registry.remove(pair);
+ synchronized (EqualsBuilder.class) {
+ //read again
+ registry = getRegistry();
+ if (registry != null && registry.isEmpty()) {
+ REGISTRY.remove();
+ }
+ }
+ }
+ }
+
+ /**
+ * If the fields tested are equals.
+ * The default value is true.
+ */
+ private boolean isEquals = true;
+
+ /**
+ * Constructor for EqualsBuilder.
+ *
+ * Starts off assuming that equals is true.
+ * @see Object#equals(Object)
+ */
+ public EqualsBuilder() {
+ // do nothing for now.
+ }
+
+ //-------------------------------------------------------------------------
+
+ /**
+ * This method uses reflection to determine if the two Objects
+ * are equal.
+ *
+ * It uses AccessibleObject.setAccessible to gain access to private
+ * fields. This means that it will throw a security exception if run under
+ * a security manager, if the permissions are not set up correctly. It is also
+ * not as efficient as testing explicitly.
+ *
+ * Transient members will be not be tested, as they are likely derived
+ * fields, and not part of the value of the Object.
+ *
+ * Static fields will not be tested. Superclass fields will be included.
+ *
+ * @param lhs this object
+ * @param rhs the other object
+ * @param excludeFields Collection of String field names to exclude from testing
+ * @return true if the two Objects have tested equals.
+ */
+ public static boolean reflectionEquals(Object lhs, Object rhs, Collection excludeFields) {
+ return reflectionEquals(lhs, rhs, ReflectionToStringBuilder.toNoNullStringArray(excludeFields));
+ }
+
+ /**
+ * This method uses reflection to determine if the two Objects
+ * are equal.
+ *
+ * It uses AccessibleObject.setAccessible to gain access to private
+ * fields. This means that it will throw a security exception if run under
+ * a security manager, if the permissions are not set up correctly. It is also
+ * not as efficient as testing explicitly.
+ *
+ * Transient members will be not be tested, as they are likely derived
+ * fields, and not part of the value of the Object.
+ *
+ * Static fields will not be tested. Superclass fields will be included.
+ *
+ * @param lhs this object
+ * @param rhs the other object
+ * @param excludeFields array of field names to exclude from testing
+ * @return true if the two Objects have tested equals.
+ */
+ public static boolean reflectionEquals(Object lhs, Object rhs, String... excludeFields) {
+ return reflectionEquals(lhs, rhs, false, null, excludeFields);
+ }
+
+ /**
+ * This method uses reflection to determine if the two Objects
+ * are equal.
+ *
+ * It uses AccessibleObject.setAccessible to gain access to private
+ * fields. This means that it will throw a security exception if run under
+ * a security manager, if the permissions are not set up correctly. It is also
+ * not as efficient as testing explicitly.
+ *
+ * If the TestTransients parameter is set to true, transient
+ * members will be tested, otherwise they are ignored, as they are likely
+ * derived fields, and not part of the value of the Object.
+ *
+ * Static fields will not be tested. Superclass fields will be included.
+ *
+ * @param lhs this object
+ * @param rhs the other object
+ * @param testTransients whether to include transient fields
+ * @return true if the two Objects have tested equals.
+ */
+ public static boolean reflectionEquals(Object lhs, Object rhs, boolean testTransients) {
+ return reflectionEquals(lhs, rhs, testTransients, null);
+ }
+
+ /**
+ * This method uses reflection to determine if the two Objects
+ * are equal.
+ *
+ * It uses AccessibleObject.setAccessible to gain access to private
+ * fields. This means that it will throw a security exception if run under
+ * a security manager, if the permissions are not set up correctly. It is also
+ * not as efficient as testing explicitly.
+ *
+ * If the testTransients parameter is set to true, transient
+ * members will be tested, otherwise they are ignored, as they are likely
+ * derived fields, and not part of the value of the Object.
+ *
+ * Static fields will not be included. Superclass fields will be appended
+ * up to and including the specified superclass. A null superclass is treated
+ * as java.lang.Object.
+ *
+ * @param lhs this object
+ * @param rhs the other object
+ * @param testTransients whether to include transient fields
+ * @param reflectUpToClass the superclass to reflect up to (inclusive),
+ * may be null
+ * @param excludeFields array of field names to exclude from testing
+ * @return true if the two Objects have tested equals.
+ * @since 2.0
+ */
+ public static boolean reflectionEquals(Object lhs, Object rhs, boolean testTransients, Class> reflectUpToClass,
+ String... excludeFields) {
+ if (lhs == rhs) {
+ return true;
+ }
+ if (lhs == null || rhs == null) {
+ return false;
+ }
+ // Find the leaf class since there may be transients in the leaf
+ // class or in classes between the leaf and root.
+ // If we are not testing transients or a subclass has no ivars,
+ // then a subclass can test equals to a superclass.
+ Class> lhsClass = lhs.getClass();
+ Class> rhsClass = rhs.getClass();
+ Class> testClass;
+ if (lhsClass.isInstance(rhs)) {
+ testClass = lhsClass;
+ if (!rhsClass.isInstance(lhs)) {
+ // rhsClass is a subclass of lhsClass
+ testClass = rhsClass;
+ }
+ } else if (rhsClass.isInstance(lhs)) {
+ testClass = rhsClass;
+ if (!lhsClass.isInstance(rhs)) {
+ // lhsClass is a subclass of rhsClass
+ testClass = lhsClass;
+ }
+ } else {
+ // The two classes are not related.
+ return false;
+ }
+ EqualsBuilder equalsBuilder = new EqualsBuilder();
+ try {
+ reflectionAppend(lhs, rhs, testClass, equalsBuilder, testTransients, excludeFields);
+ while (testClass.getSuperclass() != null && testClass != reflectUpToClass) {
+ testClass = testClass.getSuperclass();
+ reflectionAppend(lhs, rhs, testClass, equalsBuilder, testTransients, excludeFields);
+ }
+ } catch (IllegalArgumentException e) {
+ // In this case, we tried to test a subclass vs. a superclass and
+ // the subclass has ivars or the ivars are transient and
+ // we are testing transients.
+ // If a subclass has ivars that we are trying to test them, we get an
+ // exception and we know that the objects are not equal.
+ return false;
+ }
+ return equalsBuilder.isEquals();
+ }
+
+ /**
+ * Appends the fields and values defined by the given object of the
+ * given Class.
+ *
+ * @param lhs the left hand object
+ * @param rhs the right hand object
+ * @param clazz the class to append details of
+ * @param builder the builder to append to
+ * @param useTransients whether to test transient fields
+ * @param excludeFields array of field names to exclude from testing
+ */
+ private static void reflectionAppend(
+ Object lhs,
+ Object rhs,
+ Class> clazz,
+ EqualsBuilder builder,
+ boolean useTransients,
+ String[] excludeFields) {
+
+ if (isRegistered(lhs, rhs)) {
+ return;
+ }
+
+ try {
+ register(lhs, rhs);
+ Field[] fields = clazz.getDeclaredFields();
+ AccessibleObject.setAccessible(fields, true);
+ for (int i = 0; i < fields.length && builder.isEquals; i++) {
+ Field f = fields[i];
+ if (!ArrayUtils.contains(excludeFields, f.getName())
+ && (f.getName().indexOf('$') == -1)
+ && (useTransients || !Modifier.isTransient(f.getModifiers()))
+ && (!Modifier.isStatic(f.getModifiers()))) {
+ try {
+ builder.append(f.get(lhs), f.get(rhs));
+ } catch (IllegalAccessException e) {
+ //this can't happen. Would get a Security exception instead
+ //throw a runtime exception in case the impossible happens.
+ throw new InternalError("Unexpected IllegalAccessException");
+ }
+ }
+ }
+ } finally {
+ unregister(lhs, rhs);
+ }
+ }
+
+ //-------------------------------------------------------------------------
+
+ /**
+ * Adds the result of super.equals() to this builder.
+ *
+ * @param superEquals the result of calling super.equals()
+ * @return EqualsBuilder - used to chain calls.
+ * @since 2.0
+ */
+ public EqualsBuilder appendSuper(boolean superEquals) {
+ if (isEquals == false) {
+ return this;
+ }
+ isEquals = superEquals;
+ return this;
+ }
+
+ //-------------------------------------------------------------------------
+
+ /**
+ * Test if two Objects are equal using their
+ * equals method.
+ *
+ * @param lhs the left hand object
+ * @param rhs the right hand object
+ * @return EqualsBuilder - used to chain calls.
+ */
+ public EqualsBuilder append(Object lhs, Object rhs) {
+ if (isEquals == false) {
+ return this;
+ }
+ if (lhs == rhs) {
+ return this;
+ }
+ if (lhs == null || rhs == null) {
+ this.setEquals(false);
+ return this;
+ }
+ Class> lhsClass = lhs.getClass();
+ if (!lhsClass.isArray()) {
+ // The simple case, not an array, just test the element
+ isEquals = lhs.equals(rhs);
+ } else if (lhs.getClass() != rhs.getClass()) {
+ // Here when we compare different dimensions, for example: a boolean[][] to a boolean[]
+ this.setEquals(false);
+ }
+ // 'Switch' on type of array, to dispatch to the correct handler
+ // This handles multi dimensional arrays of the same depth
+ else if (lhs instanceof long[]) {
+ append((long[]) lhs, (long[]) rhs);
+ } else if (lhs instanceof int[]) {
+ append((int[]) lhs, (int[]) rhs);
+ } else if (lhs instanceof short[]) {
+ append((short[]) lhs, (short[]) rhs);
+ } else if (lhs instanceof char[]) {
+ append((char[]) lhs, (char[]) rhs);
+ } else if (lhs instanceof byte[]) {
+ append((byte[]) lhs, (byte[]) rhs);
+ } else if (lhs instanceof double[]) {
+ append((double[]) lhs, (double[]) rhs);
+ } else if (lhs instanceof float[]) {
+ append((float[]) lhs, (float[]) rhs);
+ } else if (lhs instanceof boolean[]) {
+ append((boolean[]) lhs, (boolean[]) rhs);
+ } else {
+ // Not an array of primitives
+ append((Object[]) lhs, (Object[]) rhs);
+ }
+ return this;
+ }
+
+ /**
+ *
+ * Test if two long s are equal.
+ *
+ *
+ * @param lhs
+ * the left hand long
+ * @param rhs
+ * the right hand long
+ * @return EqualsBuilder - used to chain calls.
+ */
+ public EqualsBuilder append(long lhs, long rhs) {
+ if (isEquals == false) {
+ return this;
+ }
+ isEquals = (lhs == rhs);
+ return this;
+ }
+
+ /**
+ * Test if two ints are equal.
+ *
+ * @param lhs the left hand int
+ * @param rhs the right hand int
+ * @return EqualsBuilder - used to chain calls.
+ */
+ public EqualsBuilder append(int lhs, int rhs) {
+ if (isEquals == false) {
+ return this;
+ }
+ isEquals = (lhs == rhs);
+ return this;
+ }
+
+ /**
+ * Test if two shorts are equal.
+ *
+ * @param lhs the left hand short
+ * @param rhs the right hand short
+ * @return EqualsBuilder - used to chain calls.
+ */
+ public EqualsBuilder append(short lhs, short rhs) {
+ if (isEquals == false) {
+ return this;
+ }
+ isEquals = (lhs == rhs);
+ return this;
+ }
+
+ /**
+ * Test if two chars are equal.
+ *
+ * @param lhs the left hand char
+ * @param rhs the right hand char
+ * @return EqualsBuilder - used to chain calls.
+ */
+ public EqualsBuilder append(char lhs, char rhs) {
+ if (isEquals == false) {
+ return this;
+ }
+ isEquals = (lhs == rhs);
+ return this;
+ }
+
+ /**
+ * Test if two bytes are equal.
+ *
+ * @param lhs the left hand byte
+ * @param rhs the right hand byte
+ * @return EqualsBuilder - used to chain calls.
+ */
+ public EqualsBuilder append(byte lhs, byte rhs) {
+ if (isEquals == false) {
+ return this;
+ }
+ isEquals = (lhs == rhs);
+ return this;
+ }
+
+ /**
+ * Test if two doubles are equal by testing that the
+ * pattern of bits returned by doubleToLong are equal.
+ *
+ * This handles NaNs, Infinities, and -0.0.
+ *
+ * It is compatible with the hash code generated by
+ * HashCodeBuilder.
+ *
+ * @param lhs the left hand double
+ * @param rhs the right hand double
+ * @return EqualsBuilder - used to chain calls.
+ */
+ public EqualsBuilder append(double lhs, double rhs) {
+ if (isEquals == false) {
+ return this;
+ }
+ return append(Double.doubleToLongBits(lhs), Double.doubleToLongBits(rhs));
+ }
+
+ /**
+ * Test if two floats are equal byt testing that the
+ * pattern of bits returned by doubleToLong are equal.
+ *
+ * This handles NaNs, Infinities, and -0.0.
+ *
+ * It is compatible with the hash code generated by
+ * HashCodeBuilder.
+ *
+ * @param lhs the left hand float
+ * @param rhs the right hand float
+ * @return EqualsBuilder - used to chain calls.
+ */
+ public EqualsBuilder append(float lhs, float rhs) {
+ if (isEquals == false) {
+ return this;
+ }
+ return append(Float.floatToIntBits(lhs), Float.floatToIntBits(rhs));
+ }
+
+ /**
+ * Test if two booleanss are equal.
+ *
+ * @param lhs the left hand boolean
+ * @param rhs the right hand boolean
+ * @return EqualsBuilder - used to chain calls.
+ */
+ public EqualsBuilder append(boolean lhs, boolean rhs) {
+ if (isEquals == false) {
+ return this;
+ }
+ isEquals = (lhs == rhs);
+ return this;
+ }
+
+ /**
+ * Performs a deep comparison of two Object arrays.
+ *
+ * This also will be called for the top level of
+ * multi-dimensional, ragged, and multi-typed arrays.
+ *
+ * @param lhs the left hand Object[]
+ * @param rhs the right hand Object[]
+ * @return EqualsBuilder - used to chain calls.
+ */
+ public EqualsBuilder append(Object[] lhs, Object[] rhs) {
+ if (isEquals == false) {
+ return this;
+ }
+ if (lhs == rhs) {
+ return this;
+ }
+ if (lhs == null || rhs == null) {
+ this.setEquals(false);
+ return this;
+ }
+ if (lhs.length != rhs.length) {
+ this.setEquals(false);
+ return this;
+ }
+ for (int i = 0; i < lhs.length && isEquals; ++i) {
+ append(lhs[i], rhs[i]);
+ }
+ return this;
+ }
+
+ /**
+ * Deep comparison of array of long. Length and all
+ * values are compared.
+ *
+ * The method {@link #append(long, long)} is used.
+ *
+ * @param lhs the left hand long[]
+ * @param rhs the right hand long[]
+ * @return EqualsBuilder - used to chain calls.
+ */
+ public EqualsBuilder append(long[] lhs, long[] rhs) {
+ if (isEquals == false) {
+ return this;
+ }
+ if (lhs == rhs) {
+ return this;
+ }
+ if (lhs == null || rhs == null) {
+ this.setEquals(false);
+ return this;
+ }
+ if (lhs.length != rhs.length) {
+ this.setEquals(false);
+ return this;
+ }
+ for (int i = 0; i < lhs.length && isEquals; ++i) {
+ append(lhs[i], rhs[i]);
+ }
+ return this;
+ }
+
+ /**
+ * Deep comparison of array of int. Length and all
+ * values are compared.
+ *
+ * The method {@link #append(int, int)} is used.
+ *
+ * @param lhs the left hand int[]
+ * @param rhs the right hand int[]
+ * @return EqualsBuilder - used to chain calls.
+ */
+ public EqualsBuilder append(int[] lhs, int[] rhs) {
+ if (isEquals == false) {
+ return this;
+ }
+ if (lhs == rhs) {
+ return this;
+ }
+ if (lhs == null || rhs == null) {
+ this.setEquals(false);
+ return this;
+ }
+ if (lhs.length != rhs.length) {
+ this.setEquals(false);
+ return this;
+ }
+ for (int i = 0; i < lhs.length && isEquals; ++i) {
+ append(lhs[i], rhs[i]);
+ }
+ return this;
+ }
+
+ /**
+ * Deep comparison of array of short. Length and all
+ * values are compared.
+ *
+ * The method {@link #append(short, short)} is used.
+ *
+ * @param lhs the left hand short[]
+ * @param rhs the right hand short[]
+ * @return EqualsBuilder - used to chain calls.
+ */
+ public EqualsBuilder append(short[] lhs, short[] rhs) {
+ if (isEquals == false) {
+ return this;
+ }
+ if (lhs == rhs) {
+ return this;
+ }
+ if (lhs == null || rhs == null) {
+ this.setEquals(false);
+ return this;
+ }
+ if (lhs.length != rhs.length) {
+ this.setEquals(false);
+ return this;
+ }
+ for (int i = 0; i < lhs.length && isEquals; ++i) {
+ append(lhs[i], rhs[i]);
+ }
+ return this;
+ }
+
+ /**
+ * Deep comparison of array of char. Length and all
+ * values are compared.
+ *
+ * The method {@link #append(char, char)} is used.
+ *
+ * @param lhs the left hand char[]
+ * @param rhs the right hand char[]
+ * @return EqualsBuilder - used to chain calls.
+ */
+ public EqualsBuilder append(char[] lhs, char[] rhs) {
+ if (isEquals == false) {
+ return this;
+ }
+ if (lhs == rhs) {
+ return this;
+ }
+ if (lhs == null || rhs == null) {
+ this.setEquals(false);
+ return this;
+ }
+ if (lhs.length != rhs.length) {
+ this.setEquals(false);
+ return this;
+ }
+ for (int i = 0; i < lhs.length && isEquals; ++i) {
+ append(lhs[i], rhs[i]);
+ }
+ return this;
+ }
+
+ /**
+ * Deep comparison of array of byte. Length and all
+ * values are compared.
+ *
+ * The method {@link #append(byte, byte)} is used.
+ *
+ * @param lhs the left hand byte[]
+ * @param rhs the right hand byte[]
+ * @return EqualsBuilder - used to chain calls.
+ */
+ public EqualsBuilder append(byte[] lhs, byte[] rhs) {
+ if (isEquals == false) {
+ return this;
+ }
+ if (lhs == rhs) {
+ return this;
+ }
+ if (lhs == null || rhs == null) {
+ this.setEquals(false);
+ return this;
+ }
+ if (lhs.length != rhs.length) {
+ this.setEquals(false);
+ return this;
+ }
+ for (int i = 0; i < lhs.length && isEquals; ++i) {
+ append(lhs[i], rhs[i]);
+ }
+ return this;
+ }
+
+ /**
+ * Deep comparison of array of double. Length and all
+ * values are compared.
+ *
+ * The method {@link #append(double, double)} is used.
+ *
+ * @param lhs the left hand double[]
+ * @param rhs the right hand double[]
+ * @return EqualsBuilder - used to chain calls.
+ */
+ public EqualsBuilder append(double[] lhs, double[] rhs) {
+ if (isEquals == false) {
+ return this;
+ }
+ if (lhs == rhs) {
+ return this;
+ }
+ if (lhs == null || rhs == null) {
+ this.setEquals(false);
+ return this;
+ }
+ if (lhs.length != rhs.length) {
+ this.setEquals(false);
+ return this;
+ }
+ for (int i = 0; i < lhs.length && isEquals; ++i) {
+ append(lhs[i], rhs[i]);
+ }
+ return this;
+ }
+
+ /**
+ * Deep comparison of array of float. Length and all
+ * values are compared.
+ *
+ * The method {@link #append(float, float)} is used.
+ *
+ * @param lhs the left hand float[]
+ * @param rhs the right hand float[]
+ * @return EqualsBuilder - used to chain calls.
+ */
+ public EqualsBuilder append(float[] lhs, float[] rhs) {
+ if (isEquals == false) {
+ return this;
+ }
+ if (lhs == rhs) {
+ return this;
+ }
+ if (lhs == null || rhs == null) {
+ this.setEquals(false);
+ return this;
+ }
+ if (lhs.length != rhs.length) {
+ this.setEquals(false);
+ return this;
+ }
+ for (int i = 0; i < lhs.length && isEquals; ++i) {
+ append(lhs[i], rhs[i]);
+ }
+ return this;
+ }
+
+ /**
+ * Deep comparison of array of boolean. Length and all
+ * values are compared.
+ *
+ * The method {@link #append(boolean, boolean)} is used.
+ *
+ * @param lhs the left hand boolean[]
+ * @param rhs the right hand boolean[]
+ * @return EqualsBuilder - used to chain calls.
+ */
+ public EqualsBuilder append(boolean[] lhs, boolean[] rhs) {
+ if (isEquals == false) {
+ return this;
+ }
+ if (lhs == rhs) {
+ return this;
+ }
+ if (lhs == null || rhs == null) {
+ this.setEquals(false);
+ return this;
+ }
+ if (lhs.length != rhs.length) {
+ this.setEquals(false);
+ return this;
+ }
+ for (int i = 0; i < lhs.length && isEquals; ++i) {
+ append(lhs[i], rhs[i]);
+ }
+ return this;
+ }
+
+ /**
+ * Returns true if the fields that have been checked
+ * are all equal.
+ *
+ * @return boolean
+ */
+ public boolean isEquals() {
+ return this.isEquals;
+ }
+
+ /**
+ * Returns true if the fields that have been checked
+ * are all equal.
+ *
+ * @return true if all of the fields that have been checked
+ * are equal, false otherwise.
+ *
+ * @since 3.0
+ */
+ public Boolean build() {
+ return Boolean.valueOf(isEquals());
+ }
+
+ /**
+ * Sets the isEquals value.
+ *
+ * @param isEquals The value to set.
+ * @since 2.1
+ */
+ protected void setEquals(boolean isEquals) {
+ this.isEquals = isEquals;
+ }
+
+ /**
+ * Reset the EqualsBuilder so you can use the same object again
+ * @since 2.5
+ */
+ public void reset() {
+ this.isEquals = true;
+ }
+}
diff --git a/Bridge/src/main/apacheCommonsLang/external/org/apache/commons/lang3/builder/HashCodeBuilder.java b/Bridge/src/main/apacheCommonsLang/external/org/apache/commons/lang3/builder/HashCodeBuilder.java
new file mode 100644
index 00000000..093a9661
--- /dev/null
+++ b/Bridge/src/main/apacheCommonsLang/external/org/apache/commons/lang3/builder/HashCodeBuilder.java
@@ -0,0 +1,961 @@
+/*
+ * 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 external.org.apache.commons.lang3.builder;
+
+import java.lang.reflect.AccessibleObject;
+import java.lang.reflect.Field;
+import java.lang.reflect.Modifier;
+import java.util.Collection;
+import java.util.HashSet;
+import java.util.Set;
+
+import external.org.apache.commons.lang3.ArrayUtils;
+
+/**
+ *
+ * Assists in implementing {@link Object#hashCode()} methods.
+ *
+ *
+ *
+ * This class enables a good hashCode method to be built for any class. It follows the rules laid out in
+ * the book Effective Java by Joshua Bloch. Writing a
+ * good hashCode method is actually quite difficult. This class aims to simplify the process.
+ *
+ *
+ *
+ * The following is the approach taken. When appending a data field, the current total is multiplied by the
+ * multiplier then a relevant value
+ * for that data type is added. For example, if the current hashCode is 17, and the multiplier is 37, then
+ * appending the integer 45 will create a hashcode of 674, namely 17 * 37 + 45.
+ *
+ *
+ *
+ * All relevant fields from the object should be included in the hashCode method. Derived fields may be
+ * excluded. In general, any field used in the equals method must be used in the hashCode
+ * method.
+ *
+ *
+ *
+ * To use this class write code as follows:
+ *
+ *
+ *
+ * public class Person {
+ * String name;
+ * int age;
+ * boolean smoker;
+ * ...
+ *
+ * public int hashCode() {
+ * // you pick a hard-coded, randomly chosen, non-zero, odd number
+ * // ideally different for each class
+ * return new HashCodeBuilder(17, 37).
+ * append(name).
+ * append(age).
+ * append(smoker).
+ * toHashCode();
+ * }
+ * }
+ *
+ *
+ *
+ * If required, the superclass hashCode() can be added using {@link #appendSuper}.
+ *
+ *
+ *
+ * Alternatively, there is a method that uses reflection to determine the fields to test. Because these fields are
+ * usually private, the method, reflectionHashCode, uses AccessibleObject.setAccessible
+ * to change the visibility of the fields. This will fail under a security manager, unless the appropriate permissions
+ * are set up correctly. It is also slower than testing explicitly.
+ *
+ *
+ *
+ * A typical invocation for this method would look like:
+ *
+ *
+ *
+ * public int hashCode() {
+ * return HashCodeBuilder.reflectionHashCode(this);
+ * }
+ *
+ *
+ * @since 1.0
+ * @version $Id: HashCodeBuilder.java 1144929 2011-07-10 18:26:16Z ggregory $
+ */
+public class HashCodeBuilder implements Builder {
+ /**
+ *
+ * A registry of objects used by reflection methods to detect cyclical object references and avoid infinite loops.
+ *
+ *
+ * @since 2.3
+ */
+ private static final ThreadLocal> REGISTRY = new ThreadLocal>();
+
+ /*
+ * NOTE: we cannot store the actual objects in a HashSet, as that would use the very hashCode()
+ * we are in the process of calculating.
+ *
+ * So we generate a one-to-one mapping from the original object to a new object.
+ *
+ * Now HashSet uses equals() to determine if two elements with the same hashcode really
+ * are equal, so we also need to ensure that the replacement objects are only equal
+ * if the original objects are identical.
+ *
+ * The original implementation (2.4 and before) used the System.indentityHashCode()
+ * method - however this is not guaranteed to generate unique ids (e.g. LANG-459)
+ *
+ * We now use the IDKey helper class (adapted from org.apache.axis.utils.IDKey)
+ * to disambiguate the duplicate ids.
+ */
+
+ /**
+ *
+ * Returns the registry of objects being traversed by the reflection methods in the current thread.
+ *
+ *
+ * @return Set the registry of objects being traversed
+ * @since 2.3
+ */
+ static Set getRegistry() {
+ return REGISTRY.get();
+ }
+
+ /**
+ *
+ * Returns true if the registry contains the given object. Used by the reflection methods to avoid
+ * infinite loops.
+ *
+ *
+ * @param value
+ * The object to lookup in the registry.
+ * @return boolean true if the registry contains the given object.
+ * @since 2.3
+ */
+ static boolean isRegistered(Object value) {
+ Set registry = getRegistry();
+ return registry != null && registry.contains(new IDKey(value));
+ }
+
+ /**
+ *
+ * Appends the fields and values defined by the given object of the given Class.
+ *
+ *
+ * @param object
+ * the object to append details of
+ * @param clazz
+ * the class to append details of
+ * @param builder
+ * the builder to append to
+ * @param useTransients
+ * whether to use transient fields
+ * @param excludeFields
+ * Collection of String field names to exclude from use in calculation of hash code
+ */
+ private static void reflectionAppend(Object object, Class> clazz, HashCodeBuilder builder, boolean useTransients,
+ String[] excludeFields) {
+ if (isRegistered(object)) {
+ return;
+ }
+ try {
+ register(object);
+ Field[] fields = clazz.getDeclaredFields();
+ AccessibleObject.setAccessible(fields, true);
+ for (Field field : fields) {
+ if (!ArrayUtils.contains(excludeFields, field.getName())
+ && (field.getName().indexOf('$') == -1)
+ && (useTransients || !Modifier.isTransient(field.getModifiers()))
+ && (!Modifier.isStatic(field.getModifiers()))) {
+ try {
+ Object fieldValue = field.get(object);
+ builder.append(fieldValue);
+ } catch (IllegalAccessException e) {
+ // this can't happen. Would get a Security exception instead
+ // throw a runtime exception in case the impossible happens.
+ throw new InternalError("Unexpected IllegalAccessException");
+ }
+ }
+ }
+ } finally {
+ unregister(object);
+ }
+ }
+
+ /**
+ *
+ * This method uses reflection to build a valid hash code.
+ *
+ *
+ *
+ * It uses AccessibleObject.setAccessible to gain access to private fields. This means that it will
+ * throw a security exception if run under a security manager, if the permissions are not set up correctly. It is
+ * also not as efficient as testing explicitly.
+ *
+ *
+ *
+ * Transient members will be not be used, as they are likely derived fields, and not part of the value of the
+ * Object.
+ *
+ *
+ *
+ * Static fields will not be tested. Superclass fields will be included.
+ *
+ *
+ *
+ * Two randomly chosen, non-zero, odd numbers must be passed in. Ideally these should be different for each class,
+ * however this is not vital. Prime numbers are preferred, especially for the multiplier.
+ *
+ *
+ * @param initialNonZeroOddNumber
+ * a non-zero, odd number used as the initial value
+ * @param multiplierNonZeroOddNumber
+ * a non-zero, odd number used as the multiplier
+ * @param object
+ * the Object to create a hashCode for
+ * @return int hash code
+ * @throws IllegalArgumentException
+ * if the Object is null
+ * @throws IllegalArgumentException
+ * if the number is zero or even
+ */
+ public static int reflectionHashCode(int initialNonZeroOddNumber, int multiplierNonZeroOddNumber, Object object) {
+ return reflectionHashCode(initialNonZeroOddNumber, multiplierNonZeroOddNumber, object, false, null);
+ }
+
+ /**
+ *
+ * This method uses reflection to build a valid hash code.
+ *
+ *
+ *
+ * It uses AccessibleObject.setAccessible to gain access to private fields. This means that it will
+ * throw a security exception if run under a security manager, if the permissions are not set up correctly. It is
+ * also not as efficient as testing explicitly.
+ *
+ *
+ *
+ * If the TestTransients parameter is set to true, transient members will be tested, otherwise they
+ * are ignored, as they are likely derived fields, and not part of the value of the Object.
+ *
+ *
+ *
+ * Static fields will not be tested. Superclass fields will be included.
+ *
+ *
+ *
+ * Two randomly chosen, non-zero, odd numbers must be passed in. Ideally these should be different for each class,
+ * however this is not vital. Prime numbers are preferred, especially for the multiplier.
+ *
+ *
+ * @param initialNonZeroOddNumber
+ * a non-zero, odd number used as the initial value
+ * @param multiplierNonZeroOddNumber
+ * a non-zero, odd number used as the multiplier
+ * @param object
+ * the Object to create a hashCode for
+ * @param testTransients
+ * whether to include transient fields
+ * @return int hash code
+ * @throws IllegalArgumentException
+ * if the Object is null
+ * @throws IllegalArgumentException
+ * if the number is zero or even
+ */
+ public static int reflectionHashCode(int initialNonZeroOddNumber, int multiplierNonZeroOddNumber, Object object,
+ boolean testTransients) {
+ return reflectionHashCode(initialNonZeroOddNumber, multiplierNonZeroOddNumber, object, testTransients, null);
+ }
+
+ /**
+ *
+ * This method uses reflection to build a valid hash code.
+ *
+ *
+ *
+ * It uses AccessibleObject.setAccessible to gain access to private fields. This means that it will
+ * throw a security exception if run under a security manager, if the permissions are not set up correctly. It is
+ * also not as efficient as testing explicitly.
+ *
+ *
+ *
+ * If the TestTransients parameter is set to true, transient members will be tested, otherwise they
+ * are ignored, as they are likely derived fields, and not part of the value of the Object.
+ *
+ *
+ *
+ * Static fields will not be included. Superclass fields will be included up to and including the specified
+ * superclass. A null superclass is treated as java.lang.Object.
+ *
+ *
+ *
+ * Two randomly chosen, non-zero, odd numbers must be passed in. Ideally these should be different for each class,
+ * however this is not vital. Prime numbers are preferred, especially for the multiplier.
+ *
+ *
+ * @param
+ * the type of the object involved
+ * @param initialNonZeroOddNumber
+ * a non-zero, odd number used as the initial value
+ * @param multiplierNonZeroOddNumber
+ * a non-zero, odd number used as the multiplier
+ * @param object
+ * the Object to create a hashCode for
+ * @param testTransients
+ * whether to include transient fields
+ * @param reflectUpToClass
+ * the superclass to reflect up to (inclusive), may be null
+ * @param excludeFields
+ * array of field names to exclude from use in calculation of hash code
+ * @return int hash code
+ * @throws IllegalArgumentException
+ * if the Object is null
+ * @throws IllegalArgumentException
+ * if the number is zero or even
+ * @since 2.0
+ */
+ public static int reflectionHashCode(int initialNonZeroOddNumber, int multiplierNonZeroOddNumber, T object,
+ boolean testTransients, Class super T> reflectUpToClass, String... excludeFields) {
+
+ if (object == null) {
+ throw new IllegalArgumentException("The object to build a hash code for must not be null");
+ }
+ HashCodeBuilder builder = new HashCodeBuilder(initialNonZeroOddNumber, multiplierNonZeroOddNumber);
+ Class> clazz = object.getClass();
+ reflectionAppend(object, clazz, builder, testTransients, excludeFields);
+ while (clazz.getSuperclass() != null && clazz != reflectUpToClass) {
+ clazz = clazz.getSuperclass();
+ reflectionAppend(object, clazz, builder, testTransients, excludeFields);
+ }
+ return builder.toHashCode();
+ }
+
+ /**
+ *
+ * This method uses reflection to build a valid hash code.
+ *
+ *
+ *
+ * This constructor uses two hard coded choices for the constants needed to build a hash code.
+ *
+ *
+ *
+ * It uses AccessibleObject.setAccessible to gain access to private fields. This means that it will
+ * throw a security exception if run under a security manager, if the permissions are not set up correctly. It is
+ * also not as efficient as testing explicitly.
+ *
+ *
+ *
+ * If the TestTransients parameter is set to true, transient members will be tested, otherwise they
+ * are ignored, as they are likely derived fields, and not part of the value of the Object.
+ *
+ *
+ *
+ * Static fields will not be tested. Superclass fields will be included.
+ *
+ *
+ * @param object
+ * the Object to create a hashCode for
+ * @param testTransients
+ * whether to include transient fields
+ * @return int hash code
+ * @throws IllegalArgumentException
+ * if the object is null
+ */
+ public static int reflectionHashCode(Object object, boolean testTransients) {
+ return reflectionHashCode(17, 37, object, testTransients, null);
+ }
+
+ /**
+ *
+ * This method uses reflection to build a valid hash code.
+ *
+ *
+ *
+ * This constructor uses two hard coded choices for the constants needed to build a hash code.
+ *
+ *
+ *
+ * It uses AccessibleObject.setAccessible to gain access to private fields. This means that it will
+ * throw a security exception if run under a security manager, if the permissions are not set up correctly. It is
+ * also not as efficient as testing explicitly.
+ *
+ *
+ *
+ * Transient members will be not be used, as they are likely derived fields, and not part of the value of the
+ * Object.
+ *
+ *
+ *
+ * Static fields will not be tested. Superclass fields will be included.
+ *
+ *
+ * @param object
+ * the Object to create a hashCode for
+ * @param excludeFields
+ * Collection of String field names to exclude from use in calculation of hash code
+ * @return int hash code
+ * @throws IllegalArgumentException
+ * if the object is null
+ */
+ public static int reflectionHashCode(Object object, Collection excludeFields) {
+ return reflectionHashCode(object, ReflectionToStringBuilder.toNoNullStringArray(excludeFields));
+ }
+
+ // -------------------------------------------------------------------------
+
+ /**
+ *
+ * This method uses reflection to build a valid hash code.
+ *
+ *
+ *
+ * This constructor uses two hard coded choices for the constants needed to build a hash code.
+ *
+ *
+ *
+ * It uses AccessibleObject.setAccessible to gain access to private fields. This means that it will
+ * throw a security exception if run under a security manager, if the permissions are not set up correctly. It is
+ * also not as efficient as testing explicitly.
+ *
+ *
+ *
+ * Transient members will be not be used, as they are likely derived fields, and not part of the value of the
+ * Object.
+ *
+ *
+ *
+ * Static fields will not be tested. Superclass fields will be included.
+ *
+ *
+ * @param object
+ * the Object to create a hashCode for
+ * @param excludeFields
+ * array of field names to exclude from use in calculation of hash code
+ * @return int hash code
+ * @throws IllegalArgumentException
+ * if the object is null
+ */
+ public static int reflectionHashCode(Object object, String... excludeFields) {
+ return reflectionHashCode(17, 37, object, false, null, excludeFields);
+ }
+
+ /**
+ *
+ * Registers the given object. Used by the reflection methods to avoid infinite loops.
+ *
+ *
+ * @param value
+ * The object to register.
+ */
+ static void register(Object value) {
+ synchronized (HashCodeBuilder.class) {
+ if (getRegistry() == null) {
+ REGISTRY.set(new HashSet());
+ }
+ }
+ getRegistry().add(new IDKey(value));
+ }
+
+ /**
+ *
+ * Unregisters the given object.
+ *
+ *
+ *
+ * Used by the reflection methods to avoid infinite loops.
+ *
+ * @param value
+ * The object to unregister.
+ * @since 2.3
+ */
+ static void unregister(Object value) {
+ Set registry = getRegistry();
+ if (registry != null) {
+ registry.remove(new IDKey(value));
+ synchronized (HashCodeBuilder.class) {
+ //read again
+ registry = getRegistry();
+ if (registry != null && registry.isEmpty()) {
+ REGISTRY.remove();
+ }
+ }
+ }
+ }
+
+ /**
+ * Constant to use in building the hashCode.
+ */
+ private final int iConstant;
+
+ /**
+ * Running total of the hashCode.
+ */
+ private int iTotal = 0;
+
+ /**
+ *
+ * Uses two hard coded choices for the constants needed to build a hashCode.
+ *
+ */
+ public HashCodeBuilder() {
+ iConstant = 37;
+ iTotal = 17;
+ }
+
+ /**
+ *
+ * Two randomly chosen, non-zero, odd numbers must be passed in. Ideally these should be different for each class,
+ * however this is not vital.
+ *
+ *
+ *
+ * Prime numbers are preferred, especially for the multiplier.
+ *
+ *
+ * @param initialNonZeroOddNumber
+ * a non-zero, odd number used as the initial value
+ * @param multiplierNonZeroOddNumber
+ * a non-zero, odd number used as the multiplier
+ * @throws IllegalArgumentException
+ * if the number is zero or even
+ */
+ public HashCodeBuilder(int initialNonZeroOddNumber, int multiplierNonZeroOddNumber) {
+ if (initialNonZeroOddNumber == 0) {
+ throw new IllegalArgumentException("HashCodeBuilder requires a non zero initial value");
+ }
+ if (initialNonZeroOddNumber % 2 == 0) {
+ throw new IllegalArgumentException("HashCodeBuilder requires an odd initial value");
+ }
+ if (multiplierNonZeroOddNumber == 0) {
+ throw new IllegalArgumentException("HashCodeBuilder requires a non zero multiplier");
+ }
+ if (multiplierNonZeroOddNumber % 2 == 0) {
+ throw new IllegalArgumentException("HashCodeBuilder requires an odd multiplier");
+ }
+ iConstant = multiplierNonZeroOddNumber;
+ iTotal = initialNonZeroOddNumber;
+ }
+
+ /**
+ *
+ * Append a hashCode for a boolean.
+ *
+ *
+ * This adds 1 when true, and 0 when false to the hashCode.
+ *
+ *
+ * This is in contrast to the standard java.lang.Boolean.hashCode handling, which computes
+ * a hashCode value of 1231 for java.lang.Boolean instances
+ * that represent true or 1237 for java.lang.Boolean instances
+ * that represent false.
+ *
+ *
+ * This is in accordance with the Effective Java
design.
+ *
+ *
+ * @param value
+ * the boolean to add to the hashCode
+ * @return this
+ */
+ public HashCodeBuilder append(boolean value) {
+ iTotal = iTotal * iConstant + (value ? 0 : 1);
+ return this;
+ }
+
+ /**
+ *
+ * Append a hashCode for a boolean array.
+ *
+ *
+ * @param array
+ * the array to add to the hashCode
+ * @return this
+ */
+ public HashCodeBuilder append(boolean[] array) {
+ if (array == null) {
+ iTotal = iTotal * iConstant;
+ } else {
+ for (boolean element : array) {
+ append(element);
+ }
+ }
+ return this;
+ }
+
+ // -------------------------------------------------------------------------
+
+ /**
+ *
+ * Append a hashCode for a byte.
+ *
+ *
+ * @param value
+ * the byte to add to the hashCode
+ * @return this
+ */
+ public HashCodeBuilder append(byte value) {
+ iTotal = iTotal * iConstant + value;
+ return this;
+ }
+
+ // -------------------------------------------------------------------------
+
+ /**
+ *
+ * Append a hashCode for a byte array.
+ *
+ *
+ * @param array
+ * the array to add to the hashCode
+ * @return this
+ */
+ public HashCodeBuilder append(byte[] array) {
+ if (array == null) {
+ iTotal = iTotal * iConstant;
+ } else {
+ for (byte element : array) {
+ append(element);
+ }
+ }
+ return this;
+ }
+
+ /**
+ *
+ * Append a hashCode for a char.
+ *
+ *
+ * @param value
+ * the char to add to the hashCode
+ * @return this
+ */
+ public HashCodeBuilder append(char value) {
+ iTotal = iTotal * iConstant + value;
+ return this;
+ }
+
+ /**
+ *
+ * Append a hashCode for a char array.
+ *
+ *
+ * @param array
+ * the array to add to the hashCode
+ * @return this
+ */
+ public HashCodeBuilder append(char[] array) {
+ if (array == null) {
+ iTotal = iTotal * iConstant;
+ } else {
+ for (char element : array) {
+ append(element);
+ }
+ }
+ return this;
+ }
+
+ /**
+ *
+ * Append a hashCode for a double.
+ *
+ *
+ * @param value
+ * the double to add to the hashCode
+ * @return this
+ */
+ public HashCodeBuilder append(double value) {
+ return append(Double.doubleToLongBits(value));
+ }
+
+ /**
+ *
+ * Append a hashCode for a double array.
+ *
+ *
+ * @param array
+ * the array to add to the hashCode
+ * @return this
+ */
+ public HashCodeBuilder append(double[] array) {
+ if (array == null) {
+ iTotal = iTotal * iConstant;
+ } else {
+ for (double element : array) {
+ append(element);
+ }
+ }
+ return this;
+ }
+
+ /**
+ *
+ * Append a hashCode for a float.
+ *
+ *
+ * @param value
+ * the float to add to the hashCode
+ * @return this
+ */
+ public HashCodeBuilder append(float value) {
+ iTotal = iTotal * iConstant + Float.floatToIntBits(value);
+ return this;
+ }
+
+ /**
+ *
+ * Append a hashCode for a float array.
+ *
+ *
+ * @param array
+ * the array to add to the hashCode
+ * @return this
+ */
+ public HashCodeBuilder append(float[] array) {
+ if (array == null) {
+ iTotal = iTotal * iConstant;
+ } else {
+ for (float element : array) {
+ append(element);
+ }
+ }
+ return this;
+ }
+
+ /**
+ *
+ * Append a hashCode for an int.
+ *
+ *
+ * @param value
+ * the int to add to the hashCode
+ * @return this
+ */
+ public HashCodeBuilder append(int value) {
+ iTotal = iTotal * iConstant + value;
+ return this;
+ }
+
+ /**
+ *
+ * Append a hashCode for an int array.
+ *
+ *
+ * @param array
+ * the array to add to the hashCode
+ * @return this
+ */
+ public HashCodeBuilder append(int[] array) {
+ if (array == null) {
+ iTotal = iTotal * iConstant;
+ } else {
+ for (int element : array) {
+ append(element);
+ }
+ }
+ return this;
+ }
+
+ /**
+ *
+ * Append a hashCode for a long.
+ *
+ *
+ * @param value
+ * the long to add to the hashCode
+ * @return this
+ */
+ // NOTE: This method uses >> and not >>> as Effective Java and
+ // Long.hashCode do. Ideally we should switch to >>> at
+ // some stage. There are backwards compat issues, so
+ // that will have to wait for the time being. cf LANG-342.
+ public HashCodeBuilder append(long value) {
+ iTotal = iTotal * iConstant + ((int) (value ^ (value >> 32)));
+ return this;
+ }
+
+ /**
+ *
+ * Append a hashCode for a long array.
+ *
+ *
+ * @param array
+ * the array to add to the hashCode
+ * @return this
+ */
+ public HashCodeBuilder append(long[] array) {
+ if (array == null) {
+ iTotal = iTotal * iConstant;
+ } else {
+ for (long element : array) {
+ append(element);
+ }
+ }
+ return this;
+ }
+
+ /**
+ *
+ * Append a hashCode for an Object.
+ *
+ *
+ * @param object
+ * the Object to add to the hashCode
+ * @return this
+ */
+ public HashCodeBuilder append(Object object) {
+ if (object == null) {
+ iTotal = iTotal * iConstant;
+
+ } else {
+ if(object.getClass().isArray()) {
+ // 'Switch' on type of array, to dispatch to the correct handler
+ // This handles multi dimensional arrays
+ if (object instanceof long[]) {
+ append((long[]) object);
+ } else if (object instanceof int[]) {
+ append((int[]) object);
+ } else if (object instanceof short[]) {
+ append((short[]) object);
+ } else if (object instanceof char[]) {
+ append((char[]) object);
+ } else if (object instanceof byte[]) {
+ append((byte[]) object);
+ } else if (object instanceof double[]) {
+ append((double[]) object);
+ } else if (object instanceof float[]) {
+ append((float[]) object);
+ } else if (object instanceof boolean[]) {
+ append((boolean[]) object);
+ } else {
+ // Not an array of primitives
+ append((Object[]) object);
+ }
+ } else {
+ iTotal = iTotal * iConstant + object.hashCode();
+ }
+ }
+ return this;
+ }
+
+ /**
+ *
+ * Append a hashCode for an Object array.
+ *
+ *
+ * @param array
+ * the array to add to the hashCode
+ * @return this
+ */
+ public HashCodeBuilder append(Object[] array) {
+ if (array == null) {
+ iTotal = iTotal * iConstant;
+ } else {
+ for (Object element : array) {
+ append(element);
+ }
+ }
+ return this;
+ }
+
+ /**
+ *
+ * Append a hashCode for a short.
+ *
+ *
+ * @param value
+ * the short to add to the hashCode
+ * @return this
+ */
+ public HashCodeBuilder append(short value) {
+ iTotal = iTotal * iConstant + value;
+ return this;
+ }
+
+ /**
+ *
+ * Append a hashCode for a short array.
+ *
+ *
+ * @param array
+ * the array to add to the hashCode
+ * @return this
+ */
+ public HashCodeBuilder append(short[] array) {
+ if (array == null) {
+ iTotal = iTotal * iConstant;
+ } else {
+ for (short element : array) {
+ append(element);
+ }
+ }
+ return this;
+ }
+
+ /**
+ *
+ * Adds the result of super.hashCode() to this builder.
+ *
+ *
+ * @param superHashCode
+ * the result of calling super.hashCode()
+ * @return this HashCodeBuilder, used to chain calls.
+ * @since 2.0
+ */
+ public HashCodeBuilder appendSuper(int superHashCode) {
+ iTotal = iTotal * iConstant + superHashCode;
+ return this;
+ }
+
+ /**
+ *
+ * Return the computed hashCode.
+ *
+ *
+ * @return hashCode based on the fields appended
+ */
+ public int toHashCode() {
+ return iTotal;
+ }
+
+ /**
+ * Returns the computed hashCode.
+ *
+ * @return hashCode based on the fields appended
+ *
+ * @since 3.0
+ */
+ public Integer build() {
+ return Integer.valueOf(toHashCode());
+ }
+
+ /**
+ *
+ * The computed hashCode from toHashCode() is returned due to the likelihood
+ * of bugs in mis-calling toHashCode() and the unlikeliness of it mattering what the hashCode for
+ * HashCodeBuilder itself is.
+ *
+ * @return hashCode based on the fields appended
+ * @since 2.5
+ */
+ @Override
+ public int hashCode() {
+ return toHashCode();
+ }
+
+}
diff --git a/Bridge/src/main/apacheCommonsLang/external/org/apache/commons/lang3/builder/IDKey.java b/Bridge/src/main/apacheCommonsLang/external/org/apache/commons/lang3/builder/IDKey.java
new file mode 100644
index 00000000..68d93885
--- /dev/null
+++ b/Bridge/src/main/apacheCommonsLang/external/org/apache/commons/lang3/builder/IDKey.java
@@ -0,0 +1,74 @@
+/*
+ * 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 external.org.apache.commons.lang3.builder;
+
+// adapted from org.apache.axis.utils.IDKey
+
+/**
+ * Wrap an identity key (System.identityHashCode())
+ * so that an object can only be equal() to itself.
+ *
+ * This is necessary to disambiguate the occasional duplicate
+ * identityHashCodes that can occur.
+ *
+ */
+final class IDKey {
+ private final Object value;
+ private final int id;
+
+ /**
+ * Constructor for IDKey
+ * @param _value The value
+ */
+ public IDKey(Object _value) {
+ // This is the Object hashcode
+ id = System.identityHashCode(_value);
+ // There have been some cases (LANG-459) that return the
+ // same identity hash code for different objects. So
+ // the value is also added to disambiguate these cases.
+ value = _value;
+ }
+
+ /**
+ * returns hashcode - i.e. the system identity hashcode.
+ * @return the hashcode
+ */
+ @Override
+ public int hashCode() {
+ return id;
+ }
+
+ /**
+ * checks if instances are equal
+ * @param other The other object to compare to
+ * @return if the instances are for the same object
+ */
+ @Override
+ public boolean equals(Object other) {
+ if (!(other instanceof IDKey)) {
+ return false;
+ }
+ IDKey idKey = (IDKey) other;
+ if (id != idKey.id) {
+ return false;
+ }
+ // Note that identity equals is used.
+ return value == idKey.value;
+ }
+}
diff --git a/Bridge/src/main/apacheCommonsLang/external/org/apache/commons/lang3/builder/ReflectionToStringBuilder.java b/Bridge/src/main/apacheCommonsLang/external/org/apache/commons/lang3/builder/ReflectionToStringBuilder.java
new file mode 100644
index 00000000..a6f41ec6
--- /dev/null
+++ b/Bridge/src/main/apacheCommonsLang/external/org/apache/commons/lang3/builder/ReflectionToStringBuilder.java
@@ -0,0 +1,691 @@
+/*
+ * 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 external.org.apache.commons.lang3.builder;
+
+import java.lang.reflect.AccessibleObject;
+import java.lang.reflect.Field;
+import java.lang.reflect.Modifier;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.List;
+
+import external.org.apache.commons.lang3.ArrayUtils;
+import external.org.apache.commons.lang3.ClassUtils;
+
+/**
+ *
+ * Assists in implementing {@link Object#toString()} methods using reflection.
+ *
+ *
+ * This class uses reflection to determine the fields to append. Because these fields are usually private, the class
+ * uses {@link java.lang.reflect.AccessibleObject#setAccessible(java.lang.reflect.AccessibleObject[], boolean)} to
+ * change the visibility of the fields. This will fail under a security manager, unless the appropriate permissions are
+ * set up correctly.
+ *
+ *
+ * Using reflection to access (private) fields circumvents any synchronization protection guarding access to these
+ * fields. If a toString method cannot safely read a field, you should exclude it from the toString method, or use
+ * synchronization consistent with the class' lock management around the invocation of the method. Take special care to
+ * exclude non-thread-safe collection classes, because these classes may throw ConcurrentModificationException if
+ * modified while the toString method is executing.
+ *
+ *
+ * A typical invocation for this method would look like:
+ *
+ *
+ * public String toString() {
+ * return ReflectionToStringBuilder.toString(this);
+ * }
+ *
+ *
+ * You can also use the builder to debug 3rd party objects:
+ *
+ *
+ * System.out.println("An object: " + ReflectionToStringBuilder.toString(anObject));
+ *
+ *
+ * A subclass can control field output by overriding the methods:
+ *
+ * {@link #accept(java.lang.reflect.Field)}
+ * {@link #getValue(java.lang.reflect.Field)}
+ *
+ *
+ *
+ * For example, this method does not include the password field in the returned String:
+ *
+ *
+ * public String toString() {
+ * return (new ReflectionToStringBuilder(this) {
+ * protected boolean accept(Field f) {
+ * return super.accept(f) && !f.getName().equals("password");
+ * }
+ * }).toString();
+ * }
+ *
+ *
+ * The exact format of the toString is determined by the {@link ToStringStyle} passed into the constructor.
+ *
+ *
+ * @since 2.0
+ * @version $Id: ReflectionToStringBuilder.java 1200177 2011-11-10 06:14:33Z ggregory $
+ */
+public class ReflectionToStringBuilder extends ToStringBuilder {
+
+ /**
+ *
+ * Builds a toString value using the default ToStringStyle through reflection.
+ *
+ *
+ *
+ * It uses AccessibleObject.setAccessible to gain access to private fields. This means that it will
+ * throw a security exception if run under a security manager, if the permissions are not set up correctly. It is
+ * also not as efficient as testing explicitly.
+ *
+ *
+ *
+ * Transient members will be not be included, as they are likely derived. Static fields will not be included.
+ * Superclass fields will be appended.
+ *
+ *
+ * @param object
+ * the Object to be output
+ * @return the String result
+ * @throws IllegalArgumentException
+ * if the Object is null
+ */
+ public static String toString(Object object) {
+ return toString(object, null, false, false, null);
+ }
+
+ /**
+ *
+ * Builds a toString value through reflection.
+ *
+ *
+ *
+ * It uses AccessibleObject.setAccessible to gain access to private fields. This means that it will
+ * throw a security exception if run under a security manager, if the permissions are not set up correctly. It is
+ * also not as efficient as testing explicitly.
+ *
+ *
+ *
+ * Transient members will be not be included, as they are likely derived. Static fields will not be included.
+ * Superclass fields will be appended.
+ *
+ *
+ *
+ * If the style is null, the default ToStringStyle is used.
+ *
+ *
+ * @param object
+ * the Object to be output
+ * @param style
+ * the style of the toString to create, may be null
+ * @return the String result
+ * @throws IllegalArgumentException
+ * if the Object or ToStringStyle is null
+ */
+ public static String toString(Object object, ToStringStyle style) {
+ return toString(object, style, false, false, null);
+ }
+
+ /**
+ *
+ * Builds a toString value through reflection.
+ *
+ *
+ *
+ * It uses AccessibleObject.setAccessible to gain access to private fields. This means that it will
+ * throw a security exception if run under a security manager, if the permissions are not set up correctly. It is
+ * also not as efficient as testing explicitly.
+ *
+ *
+ *
+ * If the outputTransients is true, transient members will be output, otherwise they
+ * are ignored, as they are likely derived fields, and not part of the value of the Object.
+ *
+ *
+ *
+ * Static fields will not be included. Superclass fields will be appended.
+ *
+ *
+ *
+ * If the style is null, the default ToStringStyle is used.
+ *
+ *
+ * @param object
+ * the Object to be output
+ * @param style
+ * the style of the toString to create, may be null
+ * @param outputTransients
+ * whether to include transient fields
+ * @return the String result
+ * @throws IllegalArgumentException
+ * if the Object is null
+ */
+ public static String toString(Object object, ToStringStyle style, boolean outputTransients) {
+ return toString(object, style, outputTransients, false, null);
+ }
+
+ /**
+ *
+ * Builds a toString value through reflection.
+ *
+ *
+ *
+ * It uses AccessibleObject.setAccessible to gain access to private fields. This means that it will
+ * throw a security exception if run under a security manager, if the permissions are not set up correctly. It is
+ * also not as efficient as testing explicitly.
+ *
+ *
+ *
+ * If the outputTransients is true, transient fields will be output, otherwise they
+ * are ignored, as they are likely derived fields, and not part of the value of the Object.
+ *
+ *
+ *
+ * If the outputStatics is true, static fields will be output, otherwise they are
+ * ignored.
+ *
+ *
+ *
+ * Static fields will not be included. Superclass fields will be appended.
+ *
+ *
+ *
+ * If the style is null, the default ToStringStyle is used.
+ *
+ *
+ * @param object
+ * the Object to be output
+ * @param style
+ * the style of the toString to create, may be null
+ * @param outputTransients
+ * whether to include transient fields
+ * @param outputStatics
+ * whether to include transient fields
+ * @return the String result
+ * @throws IllegalArgumentException
+ * if the Object is null
+ * @since 2.1
+ */
+ public static String toString(Object object, ToStringStyle style, boolean outputTransients, boolean outputStatics) {
+ return toString(object, style, outputTransients, outputStatics, null);
+ }
+
+ /**
+ *
+ * Builds a toString value through reflection.
+ *
+ *
+ *
+ * It uses AccessibleObject.setAccessible to gain access to private fields. This means that it will
+ * throw a security exception if run under a security manager, if the permissions are not set up correctly. It is
+ * also not as efficient as testing explicitly.
+ *
+ *
+ *
+ * If the outputTransients is true, transient fields will be output, otherwise they
+ * are ignored, as they are likely derived fields, and not part of the value of the Object.
+ *
+ *
+ *
+ * If the outputStatics is true, static fields will be output, otherwise they are
+ * ignored.
+ *
+ *
+ *
+ * Superclass fields will be appended up to and including the specified superclass. A null superclass is treated as
+ * java.lang.Object.
+ *
+ *
+ *
+ * If the style is null, the default ToStringStyle is used.
+ *
+ *
+ * @param
+ * the type of the object
+ * @param object
+ * the Object to be output
+ * @param style
+ * the style of the toString to create, may be null
+ * @param outputTransients
+ * whether to include transient fields
+ * @param outputStatics
+ * whether to include static fields
+ * @param reflectUpToClass
+ * the superclass to reflect up to (inclusive), may be null
+ * @return the String result
+ * @throws IllegalArgumentException
+ * if the Object is null
+ * @since 2.1
+ */
+ public static String toString(
+ T object, ToStringStyle style, boolean outputTransients,
+ boolean outputStatics, Class super T> reflectUpToClass) {
+ return new ReflectionToStringBuilder(object, style, null, reflectUpToClass, outputTransients, outputStatics)
+ .toString();
+ }
+
+ /**
+ * Builds a String for a toString method excluding the given field names.
+ *
+ * @param object
+ * The object to "toString".
+ * @param excludeFieldNames
+ * The field names to exclude. Null excludes nothing.
+ * @return The toString value.
+ */
+ public static String toStringExclude(Object object, Collection excludeFieldNames) {
+ return toStringExclude(object, toNoNullStringArray(excludeFieldNames));
+ }
+
+ /**
+ * Converts the given Collection into an array of Strings. The returned array does not contain null
+ * entries. Note that {@link Arrays#sort(Object[])} will throw an {@link NullPointerException} if an array element
+ * is null.
+ *
+ * @param collection
+ * The collection to convert
+ * @return A new array of Strings.
+ */
+ static String[] toNoNullStringArray(Collection collection) {
+ if (collection == null) {
+ return ArrayUtils.EMPTY_STRING_ARRAY;
+ }
+ return toNoNullStringArray(collection.toArray());
+ }
+
+ /**
+ * Returns a new array of Strings without null elements. Internal method used to normalize exclude lists
+ * (arrays and collections). Note that {@link Arrays#sort(Object[])} will throw an {@link NullPointerException}
+ * if an array element is null.
+ *
+ * @param array
+ * The array to check
+ * @return The given array or a new array without null.
+ */
+ static String[] toNoNullStringArray(Object[] array) {
+ List list = new ArrayList(array.length);
+ for (Object e : array) {
+ if (e != null) {
+ list.add(e.toString());
+ }
+ }
+ return list.toArray(ArrayUtils.EMPTY_STRING_ARRAY);
+ }
+
+
+ /**
+ * Builds a String for a toString method excluding the given field names.
+ *
+ * @param object
+ * The object to "toString".
+ * @param excludeFieldNames
+ * The field names to exclude
+ * @return The toString value.
+ */
+ public static String toStringExclude(Object object, String... excludeFieldNames) {
+ return new ReflectionToStringBuilder(object).setExcludeFieldNames(excludeFieldNames).toString();
+ }
+
+ /**
+ * Whether or not to append static fields.
+ */
+ private boolean appendStatics = false;
+
+ /**
+ * Whether or not to append transient fields.
+ */
+ private boolean appendTransients = false;
+
+ /**
+ * Which field names to exclude from output. Intended for fields like "password".
+ *
+ * @since 3.0 this is protected instead of private
+ */
+ protected String[] excludeFieldNames;
+
+ /**
+ * The last super class to stop appending fields for.
+ */
+ private Class> upToClass = null;
+
+ /**
+ *
+ * Constructor.
+ *
+ *
+ *
+ * This constructor outputs using the default style set with setDefaultStyle.
+ *
+ *
+ * @param object
+ * the Object to build a toString for, must not be null
+ * @throws IllegalArgumentException
+ * if the Object passed in is null
+ */
+ public ReflectionToStringBuilder(Object object) {
+ super(object);
+ }
+
+ /**
+ *
+ * Constructor.
+ *
+ *
+ *
+ * If the style is null, the default style is used.
+ *
+ *
+ * @param object
+ * the Object to build a toString for, must not be null
+ * @param style
+ * the style of the toString to create, may be null
+ * @throws IllegalArgumentException
+ * if the Object passed in is null
+ */
+ public ReflectionToStringBuilder(Object object, ToStringStyle style) {
+ super(object, style);
+ }
+
+ /**
+ *
+ * Constructor.
+ *
+ *
+ *
+ * If the style is null, the default style is used.
+ *
+ *
+ *
+ * If the buffer is null, a new one is created.
+ *
+ *
+ * @param object
+ * the Object to build a toString for
+ * @param style
+ * the style of the toString to create, may be null
+ * @param buffer
+ * the StringBuffer to populate, may be null
+ * @throws IllegalArgumentException
+ * if the Object passed in is null
+ */
+ public ReflectionToStringBuilder(Object object, ToStringStyle style, StringBuffer buffer) {
+ super(object, style, buffer);
+ }
+
+ /**
+ * Constructor.
+ *
+ * @param
+ * the type of the object
+ * @param object
+ * the Object to build a toString for
+ * @param style
+ * the style of the toString to create, may be null
+ * @param buffer
+ * the StringBuffer to populate, may be null
+ * @param reflectUpToClass
+ * the superclass to reflect up to (inclusive), may be null
+ * @param outputTransients
+ * whether to include transient fields
+ * @param outputStatics
+ * whether to include static fields
+ * @since 2.1
+ */
+ public ReflectionToStringBuilder(
+ T object, ToStringStyle style, StringBuffer buffer,
+ Class super T> reflectUpToClass, boolean outputTransients, boolean outputStatics) {
+ super(object, style, buffer);
+ this.setUpToClass(reflectUpToClass);
+ this.setAppendTransients(outputTransients);
+ this.setAppendStatics(outputStatics);
+ }
+
+ /**
+ * Returns whether or not to append the given Field.
+ *
+ * Transient fields are appended only if {@link #isAppendTransients()} returns true.
+ * Static fields are appended only if {@link #isAppendStatics()} returns true.
+ * Inner class fields are not appened.
+ *
+ *
+ * @param field
+ * The Field to test.
+ * @return Whether or not to append the given Field.
+ */
+ protected boolean accept(Field field) {
+ if (field.getName().indexOf(ClassUtils.INNER_CLASS_SEPARATOR_CHAR) != -1) {
+ // Reject field from inner class.
+ return false;
+ }
+ if (Modifier.isTransient(field.getModifiers()) && !this.isAppendTransients()) {
+ // Reject transient fields.
+ return false;
+ }
+ if (Modifier.isStatic(field.getModifiers()) && !this.isAppendStatics()) {
+ // Reject static fields.
+ return false;
+ }
+ if (this.excludeFieldNames != null
+ && Arrays.binarySearch(this.excludeFieldNames, field.getName()) >= 0) {
+ // Reject fields from the getExcludeFieldNames list.
+ return false;
+ }
+ return true;
+ }
+
+ /**
+ *
+ * Appends the fields and values defined by the given object of the given Class.
+ *
+ *
+ *
+ * If a cycle is detected as an object is "toString()'ed", such an object is rendered as if
+ * Object.toString() had been called and not implemented by the object.
+ *
+ *
+ * @param clazz
+ * The class of object parameter
+ */
+ protected void appendFieldsIn(Class> clazz) {
+ if (clazz.isArray()) {
+ this.reflectionAppendArray(this.getObject());
+ return;
+ }
+ Field[] fields = clazz.getDeclaredFields();
+ AccessibleObject.setAccessible(fields, true);
+ for (Field field : fields) {
+ String fieldName = field.getName();
+ if (this.accept(field)) {
+ try {
+ // Warning: Field.get(Object) creates wrappers objects
+ // for primitive types.
+ Object fieldValue = this.getValue(field);
+ this.append(fieldName, fieldValue);
+ } catch (IllegalAccessException ex) {
+ //this can't happen. Would get a Security exception
+ // instead
+ //throw a runtime exception in case the impossible
+ // happens.
+ throw new InternalError("Unexpected IllegalAccessException: " + ex.getMessage());
+ }
+ }
+ }
+ }
+
+ /**
+ * @return Returns the excludeFieldNames.
+ */
+ public String[] getExcludeFieldNames() {
+ return this.excludeFieldNames.clone();
+ }
+
+ /**
+ *
+ * Gets the last super class to stop appending fields for.
+ *
+ *
+ * @return The last super class to stop appending fields for.
+ */
+ public Class> getUpToClass() {
+ return this.upToClass;
+ }
+
+ /**
+ *
+ * Calls java.lang.reflect.Field.get(Object).
+ *
+ *
+ * @param field
+ * The Field to query.
+ * @return The Object from the given Field.
+ *
+ * @throws IllegalArgumentException
+ * see {@link java.lang.reflect.Field#get(Object)}
+ * @throws IllegalAccessException
+ * see {@link java.lang.reflect.Field#get(Object)}
+ *
+ * @see java.lang.reflect.Field#get(Object)
+ */
+ protected Object getValue(Field field) throws IllegalArgumentException, IllegalAccessException {
+ return field.get(this.getObject());
+ }
+
+ /**
+ *
+ * Gets whether or not to append static fields.
+ *
+ *
+ * @return Whether or not to append static fields.
+ * @since 2.1
+ */
+ public boolean isAppendStatics() {
+ return this.appendStatics;
+ }
+
+ /**
+ *
+ * Gets whether or not to append transient fields.
+ *
+ *
+ * @return Whether or not to append transient fields.
+ */
+ public boolean isAppendTransients() {
+ return this.appendTransients;
+ }
+
+ /**
+ *
+ * Append to the toString an Object array.
+ *
+ *
+ * @param array
+ * the array to add to the toString
+ * @return this
+ */
+ public ReflectionToStringBuilder reflectionAppendArray(Object array) {
+ this.getStyle().reflectionAppendArrayDetail(this.getStringBuffer(), null, array);
+ return this;
+ }
+
+ /**
+ *
+ * Sets whether or not to append static fields.
+ *
+ *
+ * @param appendStatics
+ * Whether or not to append static fields.
+ * @since 2.1
+ */
+ public void setAppendStatics(boolean appendStatics) {
+ this.appendStatics = appendStatics;
+ }
+
+ /**
+ *
+ * Sets whether or not to append transient fields.
+ *
+ *
+ * @param appendTransients
+ * Whether or not to append transient fields.
+ */
+ public void setAppendTransients(boolean appendTransients) {
+ this.appendTransients = appendTransients;
+ }
+
+ /**
+ * Sets the field names to exclude.
+ *
+ * @param excludeFieldNamesParam
+ * The excludeFieldNames to excluding from toString or null.
+ * @return this
+ */
+ public ReflectionToStringBuilder setExcludeFieldNames(String... excludeFieldNamesParam) {
+ if (excludeFieldNamesParam == null) {
+ this.excludeFieldNames = null;
+ } else {
+ //clone and remove nulls
+ this.excludeFieldNames = toNoNullStringArray(excludeFieldNamesParam);
+ Arrays.sort(this.excludeFieldNames);
+ }
+ return this;
+ }
+
+ /**
+ *
+ * Sets the last super class to stop appending fields for.
+ *
+ *
+ * @param clazz
+ * The last super class to stop appending fields for.
+ */
+ public void setUpToClass(Class> clazz) {
+ if (clazz != null) {
+ Object object = getObject();
+ if (object != null && clazz.isInstance(object) == false) {
+ throw new IllegalArgumentException("Specified class is not a superclass of the object");
+ }
+ }
+ this.upToClass = clazz;
+ }
+
+ /**
+ *
+ * Gets the String built by this builder.
+ *
+ *
+ * @return the built string
+ */
+ @Override
+ public String toString() {
+ if (this.getObject() == null) {
+ return this.getStyle().getNullText();
+ }
+ Class> clazz = this.getObject().getClass();
+ this.appendFieldsIn(clazz);
+ while (clazz.getSuperclass() != null && clazz != this.getUpToClass()) {
+ clazz = clazz.getSuperclass();
+ this.appendFieldsIn(clazz);
+ }
+ return super.toString();
+ }
+
+}
diff --git a/Bridge/src/main/apacheCommonsLang/external/org/apache/commons/lang3/builder/ToStringBuilder.java b/Bridge/src/main/apacheCommonsLang/external/org/apache/commons/lang3/builder/ToStringBuilder.java
new file mode 100644
index 00000000..1cb42b5d
--- /dev/null
+++ b/Bridge/src/main/apacheCommonsLang/external/org/apache/commons/lang3/builder/ToStringBuilder.java
@@ -0,0 +1,1079 @@
+/*
+ * 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 external.org.apache.commons.lang3.builder;
+
+import external.org.apache.commons.lang3.ObjectUtils;
+
+/**
+ * Assists in implementing {@link Object#toString()} methods.
+ *
+ * This class enables a good and consistent toString() to be built for any
+ * class or object. This class aims to simplify the process by:
+ *
+ * allowing field names
+ * handling all types consistently
+ * handling nulls consistently
+ * outputting arrays and multi-dimensional arrays
+ * enabling the detail level to be controlled for Objects and Collections
+ * handling class hierarchies
+ *
+ *
+ * To use this class write code as follows:
+ *
+ *
+ * public class Person {
+ * String name;
+ * int age;
+ * boolean smoker;
+ *
+ * ...
+ *
+ * public String toString() {
+ * return new ToStringBuilder(this).
+ * append("name", name).
+ * append("age", age).
+ * append("smoker", smoker).
+ * toString();
+ * }
+ * }
+ *
+ *
+ * This will produce a toString of the format:
+ * Person@7f54[name=Stephen,age=29,smoker=false]
+ *
+ * To add the superclass toString, use {@link #appendSuper}.
+ * To append the toString from an object that is delegated
+ * to (or any other object), use {@link #appendToString}.
+ *
+ * Alternatively, there is a method that uses reflection to determine
+ * the fields to test. Because these fields are usually private, the method,
+ * reflectionToString, uses AccessibleObject.setAccessible to
+ * change the visibility of the fields. This will fail under a security manager,
+ * unless the appropriate permissions are set up correctly. It is also
+ * slower than testing explicitly.
+ *
+ * A typical invocation for this method would look like:
+ *
+ *
+ * public String toString() {
+ * return ToStringBuilder.reflectionToString(this);
+ * }
+ *
+ *
+ * You can also use the builder to debug 3rd party objects:
+ *
+ *
+ * System.out.println("An object: " + ToStringBuilder.reflectionToString(anObject));
+ *
+ *
+ * The exact format of the toString is determined by
+ * the {@link ToStringStyle} passed into the constructor.
+ *
+ * @since 1.0
+ * @version $Id: ToStringBuilder.java 1088899 2011-04-05 05:31:27Z bayard $
+ */
+public class ToStringBuilder implements Builder {
+
+ /**
+ * The default style of output to use, not null.
+ */
+ private static volatile ToStringStyle defaultStyle = ToStringStyle.DEFAULT_STYLE;
+
+ //----------------------------------------------------------------------------
+
+ /**
+ * Gets the default ToStringStyle to use.
+ *
+ * This method gets a singleton default value, typically for the whole JVM.
+ * Changing this default should generally only be done during application startup.
+ * It is recommended to pass a ToStringStyle to the constructor instead
+ * of using this global default.
+ *
+ * This method can be used from multiple threads.
+ * Internally, a volatile variable is used to provide the guarantee
+ * that the latest value set using {@link #setDefaultStyle} is the value returned.
+ * It is strongly recommended that the default style is only changed during application startup.
+ *
+ * One reason for changing the default could be to have a verbose style during
+ * development and a compact style in production.
+ *
+ * @return the default ToStringStyle, never null
+ */
+ public static ToStringStyle getDefaultStyle() {
+ return defaultStyle;
+ }
+
+ /**
+ * Sets the default ToStringStyle to use.
+ *
+ * This method sets a singleton default value, typically for the whole JVM.
+ * Changing this default should generally only be done during application startup.
+ * It is recommended to pass a ToStringStyle to the constructor instead
+ * of changing this global default.
+ *
+ * This method is not intended for use from multiple threads.
+ * Internally, a volatile variable is used to provide the guarantee
+ * that the latest value set is the value returned from {@link #getDefaultStyle}.
+ *
+ * @param style the default ToStringStyle
+ * @throws IllegalArgumentException if the style is null
+ */
+ public static void setDefaultStyle(ToStringStyle style) {
+ if (style == null) {
+ throw new IllegalArgumentException("The style must not be null");
+ }
+ defaultStyle = style;
+ }
+
+ //----------------------------------------------------------------------------
+ /**
+ * Uses ReflectionToStringBuilder to generate a
+ * toString for the specified object.
+ *
+ * @param object the Object to be output
+ * @return the String result
+ * @see ReflectionToStringBuilder#toString(Object)
+ */
+ public static String reflectionToString(Object object) {
+ return ReflectionToStringBuilder.toString(object);
+ }
+
+ /**
+ * Uses ReflectionToStringBuilder to generate a
+ * toString for the specified object.
+ *
+ * @param object the Object to be output
+ * @param style the style of the toString to create, may be null
+ * @return the String result
+ * @see ReflectionToStringBuilder#toString(Object,ToStringStyle)
+ */
+ public static String reflectionToString(Object object, ToStringStyle style) {
+ return ReflectionToStringBuilder.toString(object, style);
+ }
+
+ /**
+ * Uses ReflectionToStringBuilder to generate a
+ * toString for the specified object.
+ *
+ * @param object the Object to be output
+ * @param style the style of the toString to create, may be null
+ * @param outputTransients whether to include transient fields
+ * @return the String result
+ * @see ReflectionToStringBuilder#toString(Object,ToStringStyle,boolean)
+ */
+ public static String reflectionToString(Object object, ToStringStyle style, boolean outputTransients) {
+ return ReflectionToStringBuilder.toString(object, style, outputTransients, false, null);
+ }
+
+ /**
+ * Uses ReflectionToStringBuilder to generate a
+ * toString for the specified object.
+ *
+ * @param the type of the object
+ * @param object the Object to be output
+ * @param style the style of the toString to create, may be null
+ * @param outputTransients whether to include transient fields
+ * @param reflectUpToClass the superclass to reflect up to (inclusive), may be null
+ * @return the String result
+ * @see ReflectionToStringBuilder#toString(Object,ToStringStyle,boolean,boolean,Class)
+ * @since 2.0
+ */
+ public static String reflectionToString(
+ T object,
+ ToStringStyle style,
+ boolean outputTransients,
+ Class super T> reflectUpToClass) {
+ return ReflectionToStringBuilder.toString(object, style, outputTransients, false, reflectUpToClass);
+ }
+
+ //----------------------------------------------------------------------------
+
+ /**
+ * Current toString buffer, not null.
+ */
+ private final StringBuffer buffer;
+ /**
+ * The object being output, may be null.
+ */
+ private final Object object;
+ /**
+ * The style of output to use, not null.
+ */
+ private final ToStringStyle style;
+
+ /**
+ * Constructs a builder for the specified object using the default output style.
+ *
+ * This default style is obtained from {@link #getDefaultStyle()}.
+ *
+ * @param object the Object to build a toString for, not recommended to be null
+ */
+ public ToStringBuilder(Object object) {
+ this(object, null, null);
+ }
+
+ /**
+ * Constructs a builder for the specified object using the a defined output style.
+ *
+ * If the style is null, the default style is used.
+ *
+ * @param object the Object to build a toString for, not recommended to be null
+ * @param style the style of the toString to create, null uses the default style
+ */
+ public ToStringBuilder(Object object, ToStringStyle style) {
+ this(object, style, null);
+ }
+
+ /**
+ * Constructs a builder for the specified object.
+ *
+ * If the style is null, the default style is used.
+ *
+ * If the buffer is null, a new one is created.
+ *
+ * @param object the Object to build a toString for, not recommended to be null
+ * @param style the style of the toString to create, null uses the default style
+ * @param buffer the StringBuffer to populate, may be null
+ */
+ public ToStringBuilder(Object object, ToStringStyle style, StringBuffer buffer) {
+ if (style == null) {
+ style = getDefaultStyle();
+ }
+ if (buffer == null) {
+ buffer = new StringBuffer(512);
+ }
+ this.buffer = buffer;
+ this.style = style;
+ this.object = object;
+
+ style.appendStart(buffer, object);
+ }
+
+ //----------------------------------------------------------------------------
+
+ /**
+ * Append to the toString a boolean
+ * value.
+ *
+ * @param value the value to add to the toString
+ * @return this
+ */
+ public ToStringBuilder append(boolean value) {
+ style.append(buffer, null, value);
+ return this;
+ }
+
+ //----------------------------------------------------------------------------
+
+ /**
+ * Append to the toString a boolean
+ * array.
+ *
+ * @param array the array to add to the toString
+ * @return this
+ */
+ public ToStringBuilder append(boolean[] array) {
+ style.append(buffer, null, array, null);
+ return this;
+ }
+
+ //----------------------------------------------------------------------------
+
+ /**
+ * Append to the toString a byte
+ * value.
+ *
+ * @param value the value to add to the toString
+ * @return this
+ */
+ public ToStringBuilder append(byte value) {
+ style.append(buffer, null, value);
+ return this;
+ }
+
+ //----------------------------------------------------------------------------
+
+ /**
+ * Append to the toString a byte
+ * array.
+ *
+ * @param array the array to add to the toString
+ * @return this
+ */
+ public ToStringBuilder append(byte[] array) {
+ style.append(buffer, null, array, null);
+ return this;
+ }
+
+ //----------------------------------------------------------------------------
+
+ /**
+ * Append to the toString a char
+ * value.
+ *
+ * @param value the value to add to the toString
+ * @return this
+ */
+ public ToStringBuilder append(char value) {
+ style.append(buffer, null, value);
+ return this;
+ }
+
+ //----------------------------------------------------------------------------
+
+ /**
+ * Append to the toString a char
+ * array.
+ *
+ * @param array the array to add to the toString
+ * @return this
+ */
+ public ToStringBuilder append(char[] array) {
+ style.append(buffer, null, array, null);
+ return this;
+ }
+
+ //----------------------------------------------------------------------------
+
+ /**
+ * Append to the toString a double
+ * value.
+ *
+ * @param value the value to add to the toString
+ * @return this
+ */
+ public ToStringBuilder append(double value) {
+ style.append(buffer, null, value);
+ return this;
+ }
+
+ //----------------------------------------------------------------------------
+
+ /**
+ * Append to the toString a double
+ * array.
+ *
+ * @param array the array to add to the toString
+ * @return this
+ */
+ public ToStringBuilder append(double[] array) {
+ style.append(buffer, null, array, null);
+ return this;
+ }
+
+ //----------------------------------------------------------------------------
+
+ /**
+ * Append to the toString a float
+ * value.
+ *
+ * @param value the value to add to the toString
+ * @return this
+ */
+ public ToStringBuilder append(float value) {
+ style.append(buffer, null, value);
+ return this;
+ }
+
+ //----------------------------------------------------------------------------
+
+ /**
+ * Append to the toString a float
+ * array.
+ *
+ * @param array the array to add to the toString
+ * @return this
+ */
+ public ToStringBuilder append(float[] array) {
+ style.append(buffer, null, array, null);
+ return this;
+ }
+
+ //----------------------------------------------------------------------------
+
+ /**
+ * Append to the toString an int
+ * value.
+ *
+ * @param value the value to add to the toString
+ * @return this
+ */
+ public ToStringBuilder append(int value) {
+ style.append(buffer, null, value);
+ return this;
+ }
+
+ //----------------------------------------------------------------------------
+
+ /**
+ * Append to the toString an int
+ * array.
+ *
+ * @param array the array to add to the toString
+ * @return this
+ */
+ public ToStringBuilder append(int[] array) {
+ style.append(buffer, null, array, null);
+ return this;
+ }
+
+ //----------------------------------------------------------------------------
+
+ /**
+ * Append to the toString a long
+ * value.
+ *
+ * @param value the value to add to the toString
+ * @return this
+ */
+ public ToStringBuilder append(long value) {
+ style.append(buffer, null, value);
+ return this;
+ }
+
+ //----------------------------------------------------------------------------
+
+ /**
+ * Append to the toString a long
+ * array.
+ *
+ * @param array the array to add to the toString
+ * @return this
+ */
+ public ToStringBuilder append(long[] array) {
+ style.append(buffer, null, array, null);
+ return this;
+ }
+
+ //----------------------------------------------------------------------------
+
+ /**
+ * Append to the toString an Object
+ * value.
+ *
+ * @param obj the value to add to the toString
+ * @return this
+ */
+ public ToStringBuilder append(Object obj) {
+ style.append(buffer, null, obj, null);
+ return this;
+ }
+
+ //----------------------------------------------------------------------------
+
+ /**
+ * Append to the toString an Object
+ * array.
+ *
+ * @param array the array to add to the toString
+ * @return this
+ */
+ public ToStringBuilder append(Object[] array) {
+ style.append(buffer, null, array, null);
+ return this;
+ }
+
+ //----------------------------------------------------------------------------
+
+ /**
+ * Append to the toString a short
+ * value.
+ *
+ * @param value the value to add to the toString
+ * @return this
+ */
+ public ToStringBuilder append(short value) {
+ style.append(buffer, null, value);
+ return this;
+ }
+
+ //----------------------------------------------------------------------------
+
+ /**
+ * Append to the toString a short
+ * array.
+ *
+ * @param array the array to add to the toString
+ * @return this
+ */
+ public ToStringBuilder append(short[] array) {
+ style.append(buffer, null, array, null);
+ return this;
+ }
+
+ /**
+ * Append to the toString a boolean
+ * value.
+ *
+ * @param fieldName the field name
+ * @param value the value to add to the toString
+ * @return this
+ */
+ public ToStringBuilder append(String fieldName, boolean value) {
+ style.append(buffer, fieldName, value);
+ return this;
+ }
+
+ /**
+ * Append to the toString a boolean
+ * array.
+ *
+ * @param fieldName the field name
+ * @param array the array to add to the hashCode
+ * @return this
+ */
+ public ToStringBuilder append(String fieldName, boolean[] array) {
+ style.append(buffer, fieldName, array, null);
+ return this;
+ }
+
+ /**
+ * Append to the toString a boolean
+ * array.
+ *
+ * A boolean parameter controls the level of detail to show.
+ * Setting true will output the array in full. Setting
+ * false will output a summary, typically the size of
+ * the array.
+ *
+ * @param fieldName the field name
+ * @param array the array to add to the toString
+ * @param fullDetail true for detail, false
+ * for summary info
+ * @return this
+ */
+ public ToStringBuilder append(String fieldName, boolean[] array, boolean fullDetail) {
+ style.append(buffer, fieldName, array, Boolean.valueOf(fullDetail));
+ return this;
+ }
+
+ /**
+ * Append to the toString an byte
+ * value.
+ *
+ * @param fieldName the field name
+ * @param value the value to add to the toString
+ * @return this
+ */
+ public ToStringBuilder append(String fieldName, byte value) {
+ style.append(buffer, fieldName, value);
+ return this;
+ }
+
+ /**
+ * Append to the toString a byte array.
+ *
+ * @param fieldName the field name
+ * @param array the array to add to the toString
+ * @return this
+ */
+ public ToStringBuilder append(String fieldName, byte[] array) {
+ style.append(buffer, fieldName, array, null);
+ return this;
+ }
+
+ /**
+ * Append to the toString a byte
+ * array.
+ *
+ * A boolean parameter controls the level of detail to show.
+ * Setting true will output the array in full. Setting
+ * false will output a summary, typically the size of
+ * the array.
+ *
+ * @param fieldName the field name
+ * @param array the array to add to the toString
+ * @param fullDetail true for detail, false
+ * for summary info
+ * @return this
+ */
+ public ToStringBuilder append(String fieldName, byte[] array, boolean fullDetail) {
+ style.append(buffer, fieldName, array, Boolean.valueOf(fullDetail));
+ return this;
+ }
+
+ /**
+ *
Append to the toString a char
+ * value.
+ *
+ * @param fieldName the field name
+ * @param value the value to add to the toString
+ * @return this
+ */
+ public ToStringBuilder append(String fieldName, char value) {
+ style.append(buffer, fieldName, value);
+ return this;
+ }
+
+ /**
+ * Append to the toString a char
+ * array.
+ *
+ * @param fieldName the field name
+ * @param array the array to add to the toString
+ * @return this
+ */
+ public ToStringBuilder append(String fieldName, char[] array) {
+ style.append(buffer, fieldName, array, null);
+ return this;
+ }
+
+ /**
+ * Append to the toString a char
+ * array.
+ *
+ * A boolean parameter controls the level of detail to show.
+ * Setting true will output the array in full. Setting
+ * false will output a summary, typically the size of
+ * the array.
+ *
+ * @param fieldName the field name
+ * @param array the array to add to the toString
+ * @param fullDetail true for detail, false
+ * for summary info
+ * @return this
+ */
+ public ToStringBuilder append(String fieldName, char[] array, boolean fullDetail) {
+ style.append(buffer, fieldName, array, Boolean.valueOf(fullDetail));
+ return this;
+ }
+
+ /**
+ * Append to the toString a double
+ * value.
+ *
+ * @param fieldName the field name
+ * @param value the value to add to the toString
+ * @return this
+ */
+ public ToStringBuilder append(String fieldName, double value) {
+ style.append(buffer, fieldName, value);
+ return this;
+ }
+
+ /**
+ * Append to the toString a double
+ * array.
+ *
+ * @param fieldName the field name
+ * @param array the array to add to the toString
+ * @return this
+ */
+ public ToStringBuilder append(String fieldName, double[] array) {
+ style.append(buffer, fieldName, array, null);
+ return this;
+ }
+
+ /**
+ * Append to the toString a double
+ * array.
+ *
+ * A boolean parameter controls the level of detail to show.
+ * Setting true will output the array in full. Setting
+ * false will output a summary, typically the size of
+ * the array.
+ *
+ * @param fieldName the field name
+ * @param array the array to add to the toString
+ * @param fullDetail true for detail, false
+ * for summary info
+ * @return this
+ */
+ public ToStringBuilder append(String fieldName, double[] array, boolean fullDetail) {
+ style.append(buffer, fieldName, array, Boolean.valueOf(fullDetail));
+ return this;
+ }
+
+ /**
+ * Append to the toString an float
+ * value.
+ *
+ * @param fieldName the field name
+ * @param value the value to add to the toString
+ * @return this
+ */
+ public ToStringBuilder append(String fieldName, float value) {
+ style.append(buffer, fieldName, value);
+ return this;
+ }
+
+ /**
+ * Append to the toString a float
+ * array.
+ *
+ * @param fieldName the field name
+ * @param array the array to add to the toString
+ * @return this
+ */
+ public ToStringBuilder append(String fieldName, float[] array) {
+ style.append(buffer, fieldName, array, null);
+ return this;
+ }
+
+ /**
+ * Append to the toString a float
+ * array.
+ *
+ * A boolean parameter controls the level of detail to show.
+ * Setting true will output the array in full. Setting
+ * false will output a summary, typically the size of
+ * the array.
+ *
+ * @param fieldName the field name
+ * @param array the array to add to the toString
+ * @param fullDetail true for detail, false
+ * for summary info
+ * @return this
+ */
+ public ToStringBuilder append(String fieldName, float[] array, boolean fullDetail) {
+ style.append(buffer, fieldName, array, Boolean.valueOf(fullDetail));
+ return this;
+ }
+
+ /**
+ * Append to the toString an int
+ * value.
+ *
+ * @param fieldName the field name
+ * @param value the value to add to the toString
+ * @return this
+ */
+ public ToStringBuilder append(String fieldName, int value) {
+ style.append(buffer, fieldName, value);
+ return this;
+ }
+
+ /**
+ * Append to the toString an int
+ * array.
+ *
+ * @param fieldName the field name
+ * @param array the array to add to the toString
+ * @return this
+ */
+ public ToStringBuilder append(String fieldName, int[] array) {
+ style.append(buffer, fieldName, array, null);
+ return this;
+ }
+
+ /**
+ * Append to the toString an int
+ * array.
+ *
+ * A boolean parameter controls the level of detail to show.
+ * Setting true will output the array in full. Setting
+ * false will output a summary, typically the size of
+ * the array.
+ *
+ * @param fieldName the field name
+ * @param array the array to add to the toString
+ * @param fullDetail true for detail, false
+ * for summary info
+ * @return this
+ */
+ public ToStringBuilder append(String fieldName, int[] array, boolean fullDetail) {
+ style.append(buffer, fieldName, array, Boolean.valueOf(fullDetail));
+ return this;
+ }
+
+ /**
+ * Append to the toString a long
+ * value.
+ *
+ * @param fieldName the field name
+ * @param value the value to add to the toString
+ * @return this
+ */
+ public ToStringBuilder append(String fieldName, long value) {
+ style.append(buffer, fieldName, value);
+ return this;
+ }
+
+ /**
+ * Append to the toString a long
+ * array.
+ *
+ * @param fieldName the field name
+ * @param array the array to add to the toString
+ * @return this
+ */
+ public ToStringBuilder append(String fieldName, long[] array) {
+ style.append(buffer, fieldName, array, null);
+ return this;
+ }
+
+ /**
+ * Append to the toString a long
+ * array.
+ *
+ * A boolean parameter controls the level of detail to show.
+ * Setting true will output the array in full. Setting
+ * false will output a summary, typically the size of
+ * the array.
+ *
+ * @param fieldName the field name
+ * @param array the array to add to the toString
+ * @param fullDetail true for detail, false
+ * for summary info
+ * @return this
+ */
+ public ToStringBuilder append(String fieldName, long[] array, boolean fullDetail) {
+ style.append(buffer, fieldName, array, Boolean.valueOf(fullDetail));
+ return this;
+ }
+
+ /**
+ * Append to the toString an Object
+ * value.
+ *
+ * @param fieldName the field name
+ * @param obj the value to add to the toString
+ * @return this
+ */
+ public ToStringBuilder append(String fieldName, Object obj) {
+ style.append(buffer, fieldName, obj, null);
+ return this;
+ }
+
+ /**
+ * Append to the toString an Object
+ * value.
+ *
+ * @param fieldName the field name
+ * @param obj the value to add to the toString
+ * @param fullDetail true for detail,
+ * false for summary info
+ * @return this
+ */
+ public ToStringBuilder append(String fieldName, Object obj, boolean fullDetail) {
+ style.append(buffer, fieldName, obj, Boolean.valueOf(fullDetail));
+ return this;
+ }
+
+ /**
+ * Append to the toString an Object
+ * array.
+ *
+ * @param fieldName the field name
+ * @param array the array to add to the toString
+ * @return this
+ */
+ public ToStringBuilder append(String fieldName, Object[] array) {
+ style.append(buffer, fieldName, array, null);
+ return this;
+ }
+
+ /**
+ * Append to the toString an Object
+ * array.
+ *
+ * A boolean parameter controls the level of detail to show.
+ * Setting true will output the array in full. Setting
+ * false will output a summary, typically the size of
+ * the array.
+ *
+ * @param fieldName the field name
+ * @param array the array to add to the toString
+ * @param fullDetail true for detail, false
+ * for summary info
+ * @return this
+ */
+ public ToStringBuilder append(String fieldName, Object[] array, boolean fullDetail) {
+ style.append(buffer, fieldName, array, Boolean.valueOf(fullDetail));
+ return this;
+ }
+
+ /**
+ * Append to the toString an short
+ * value.
+ *
+ * @param fieldName the field name
+ * @param value the value to add to the toString
+ * @return this
+ */
+ public ToStringBuilder append(String fieldName, short value) {
+ style.append(buffer, fieldName, value);
+ return this;
+ }
+
+ /**
+ * Append to the toString a short
+ * array.
+ *
+ * @param fieldName the field name
+ * @param array the array to add to the toString
+ * @return this
+ */
+ public ToStringBuilder append(String fieldName, short[] array) {
+ style.append(buffer, fieldName, array, null);
+ return this;
+ }
+
+ /**
+ * Append to the toString a short
+ * array.
+ *
+ * A boolean parameter controls the level of detail to show.
+ * Setting true will output the array in full. Setting
+ * false will output a summary, typically the size of
+ * the array.
+ *
+ * @param fieldName the field name
+ * @param array the array to add to the toString
+ * @param fullDetail true for detail, false
+ * for summary info
+ * @return this
+ */
+ public ToStringBuilder append(String fieldName, short[] array, boolean fullDetail) {
+ style.append(buffer, fieldName, array, Boolean.valueOf(fullDetail));
+ return this;
+ }
+
+ /**
+ *
Appends with the same format as the default Object toString()
+ * method. Appends the class name followed by
+ * {@link System#identityHashCode(java.lang.Object)}.
+ *
+ * @param object the Object whose class name and id to output
+ * @return this
+ * @since 2.0
+ */
+ public ToStringBuilder appendAsObjectToString(Object object) {
+ ObjectUtils.identityToString(this.getStringBuffer(), object);
+ return this;
+ }
+
+ //----------------------------------------------------------------------------
+
+ /**
+ * Append the toString from the superclass.
+ *
+ * This method assumes that the superclass uses the same ToStringStyle
+ * as this one.
+ *
+ * If superToString is null, no change is made.
+ *
+ * @param superToString the result of super.toString()
+ * @return this
+ * @since 2.0
+ */
+ public ToStringBuilder appendSuper(String superToString) {
+ if (superToString != null) {
+ style.appendSuper(buffer, superToString);
+ }
+ return this;
+ }
+
+ /**
+ * Append the toString from another object.
+ *
+ * This method is useful where a class delegates most of the implementation of
+ * its properties to another class. You can then call toString() on
+ * the other class and pass the result into this method.
+ *
+ *
+ * private AnotherObject delegate;
+ * private String fieldInThisClass;
+ *
+ * public String toString() {
+ * return new ToStringBuilder(this).
+ * appendToString(delegate.toString()).
+ * append(fieldInThisClass).
+ * toString();
+ * }
+ *
+ * This method assumes that the other object uses the same ToStringStyle
+ * as this one.
+ *
+ * If the toString is null, no change is made.
+ *
+ * @param toString the result of toString() on another object
+ * @return this
+ * @since 2.0
+ */
+ public ToStringBuilder appendToString(String toString) {
+ if (toString != null) {
+ style.appendToString(buffer, toString);
+ }
+ return this;
+ }
+
+ /**
+ * Returns the Object being output.
+ *
+ * @return The object being output.
+ * @since 2.0
+ */
+ public Object getObject() {
+ return object;
+ }
+
+ /**
+ * Gets the StringBuffer being populated.
+ *
+ * @return the StringBuffer being populated
+ */
+ public StringBuffer getStringBuffer() {
+ return buffer;
+ }
+
+ //----------------------------------------------------------------------------
+
+ /**
+ * Gets the ToStringStyle being used.
+ *
+ * @return the ToStringStyle being used
+ * @since 2.0
+ */
+ public ToStringStyle getStyle() {
+ return style;
+ }
+
+ /**
+ * Returns the built toString.
+ *
+ * This method appends the end of data indicator, and can only be called once.
+ * Use {@link #getStringBuffer} to get the current string state.
+ *
+ * If the object is null, return the style's nullText
+ *
+ * @return the String toString
+ */
+ @Override
+ public String toString() {
+ if (this.getObject() == null) {
+ this.getStringBuffer().append(this.getStyle().getNullText());
+ } else {
+ style.appendEnd(this.getStringBuffer(), this.getObject());
+ }
+ return this.getStringBuffer().toString();
+ }
+
+ /**
+ * Returns the String that was build as an object representation. The
+ * default implementation utilizes the {@link #toString()} implementation.
+ *
+ * @return the String toString
+ *
+ * @see #toString()
+ *
+ * @since 3.0
+ */
+ public String build() {
+ return toString();
+ }
+}
diff --git a/Bridge/src/main/apacheCommonsLang/external/org/apache/commons/lang3/builder/ToStringStyle.java b/Bridge/src/main/apacheCommonsLang/external/org/apache/commons/lang3/builder/ToStringStyle.java
new file mode 100644
index 00000000..783ae6f6
--- /dev/null
+++ b/Bridge/src/main/apacheCommonsLang/external/org/apache/commons/lang3/builder/ToStringStyle.java
@@ -0,0 +1,2271 @@
+/*
+ * 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 external.org.apache.commons.lang3.builder;
+
+import java.io.Serializable;
+import java.lang.reflect.Array;
+import java.util.Collection;
+import java.util.Map;
+import java.util.WeakHashMap;
+
+import external.org.apache.commons.lang3.ClassUtils;
+import external.org.apache.commons.lang3.ObjectUtils;
+import external.org.apache.commons.lang3.SystemUtils;
+
+/**
+ * Controls String formatting for {@link ToStringBuilder}.
+ * The main public interface is always via ToStringBuilder.
+ *
+ * These classes are intended to be used as Singletons.
+ * There is no need to instantiate a new style each time. A program
+ * will generally use one of the predefined constants on this class.
+ * Alternatively, the {@link StandardToStringStyle} class can be used
+ * to set the individual settings. Thus most styles can be achieved
+ * without subclassing.
+ *
+ * If required, a subclass can override as many or as few of the
+ * methods as it requires. Each object type (from boolean
+ * to long to Object to int[]) has
+ * its own methods to output it. Most have two versions, detail and summary.
+ *
+ *
For example, the detail version of the array based methods will
+ * output the whole array, whereas the summary method will just output
+ * the array length.
+ *
+ * If you want to format the output of certain objects, such as dates, you
+ * must create a subclass and override a method.
+ *
+ * public class MyStyle extends ToStringStyle {
+ * protected void appendDetail(StringBuffer buffer, String fieldName, Object value) {
+ * if (value instanceof Date) {
+ * value = new SimpleDateFormat("yyyy-MM-dd").format(value);
+ * }
+ * buffer.append(value);
+ * }
+ * }
+ *
+ *
+ *
+ * @since 1.0
+ * @version $Id: ToStringStyle.java 1091066 2011-04-11 13:30:11Z mbenson $
+ */
+public abstract class ToStringStyle implements Serializable {
+
+ /**
+ * Serialization version ID.
+ */
+ private static final long serialVersionUID = -2587890625525655916L;
+
+ /**
+ * The default toString style. Using the Using the Person
+ * example from {@link ToStringBuilder}, the output would look like this:
+ *
+ *
+ * Person@182f0db[name=John Doe,age=33,smoker=false]
+ *
+ */
+ public static final ToStringStyle DEFAULT_STYLE = new DefaultToStringStyle();
+
+ /**
+ * The multi line toString style. Using the Using the Person
+ * example from {@link ToStringBuilder}, the output would look like this:
+ *
+ *
+ * Person@182f0db[
+ * name=John Doe
+ * age=33
+ * smoker=false
+ * ]
+ *
+ */
+ public static final ToStringStyle MULTI_LINE_STYLE = new MultiLineToStringStyle();
+
+ /**
+ * The no field names toString style. Using the Using the
+ * Person example from {@link ToStringBuilder}, the output
+ * would look like this:
+ *
+ *
+ * Person@182f0db[John Doe,33,false]
+ *
+ */
+ public static final ToStringStyle NO_FIELD_NAMES_STYLE = new NoFieldNameToStringStyle();
+
+ /**
+ * The short prefix toString style. Using the Person example
+ * from {@link ToStringBuilder}, the output would look like this:
+ *
+ *
+ * Person[name=John Doe,age=33,smoker=false]
+ *
+ *
+ * @since 2.1
+ */
+ public static final ToStringStyle SHORT_PREFIX_STYLE = new ShortPrefixToStringStyle();
+
+ /**
+ * The simple toString style. Using the Using the Person
+ * example from {@link ToStringBuilder}, the output would look like this:
+ *
+ *
+ * John Doe,33,false
+ *
+ */
+ public static final ToStringStyle SIMPLE_STYLE = new SimpleToStringStyle();
+
+ /**
+ *
+ * A registry of objects used by reflectionToString methods
+ * to detect cyclical object references and avoid infinite loops.
+ *
+ */
+ private static final ThreadLocal> REGISTRY =
+ new ThreadLocal>();
+
+ /**
+ *
+ * Returns the registry of objects being traversed by the reflectionToString
+ * methods in the current thread.
+ *
+ *
+ * @return Set the registry of objects being traversed
+ */
+ static Map getRegistry() {
+ return REGISTRY.get();
+ }
+
+ /**
+ *
+ * Returns true if the registry contains the given object.
+ * Used by the reflection methods to avoid infinite loops.
+ *
+ *
+ * @param value
+ * The object to lookup in the registry.
+ * @return boolean true if the registry contains the given
+ * object.
+ */
+ static boolean isRegistered(Object value) {
+ Map m = getRegistry();
+ return m != null && m.containsKey(value);
+ }
+
+ /**
+ *
+ * Registers the given object. Used by the reflection methods to avoid
+ * infinite loops.
+ *
+ *
+ * @param value
+ * The object to register.
+ */
+ static void register(Object value) {
+ if (value != null) {
+ Map m = getRegistry();
+ if (m == null) {
+ REGISTRY.set(new WeakHashMap());
+ }
+ getRegistry().put(value, null);
+ }
+ }
+
+ /**
+ *
+ * Unregisters the given object.
+ *
+ *
+ *
+ * Used by the reflection methods to avoid infinite loops.
+ *
+ *
+ * @param value
+ * The object to unregister.
+ */
+ static void unregister(Object value) {
+ if (value != null) {
+ Map m = getRegistry();
+ if (m != null) {
+ m.remove(value);
+ if (m.isEmpty()) {
+ REGISTRY.remove();
+ }
+ }
+ }
+ }
+
+ /**
+ * Whether to use the field names, the default is true.
+ */
+ private boolean useFieldNames = true;
+
+ /**
+ * Whether to use the class name, the default is true.
+ */
+ private boolean useClassName = true;
+
+ /**
+ * Whether to use short class names, the default is false.
+ */
+ private boolean useShortClassName = false;
+
+ /**
+ * Whether to use the identity hash code, the default is true.
+ */
+ private boolean useIdentityHashCode = true;
+
+ /**
+ * The content start '['.
+ */
+ private String contentStart = "[";
+
+ /**
+ * The content end ']'.
+ */
+ private String contentEnd = "]";
+
+ /**
+ * The field name value separator '='.
+ */
+ private String fieldNameValueSeparator = "=";
+
+ /**
+ * Whether the field separator should be added before any other fields.
+ */
+ private boolean fieldSeparatorAtStart = false;
+
+ /**
+ * Whether the field separator should be added after any other fields.
+ */
+ private boolean fieldSeparatorAtEnd = false;
+
+ /**
+ * The field separator ','.
+ */
+ private String fieldSeparator = ",";
+
+ /**
+ * The array start '{'.
+ */
+ private String arrayStart = "{";
+
+ /**
+ * The array separator ','.
+ */
+ private String arraySeparator = ",";
+
+ /**
+ * The detail for array content.
+ */
+ private boolean arrayContentDetail = true;
+
+ /**
+ * The array end '}'.
+ */
+ private String arrayEnd = "}";
+
+ /**
+ * The value to use when fullDetail is null,
+ * the default value is true.
+ */
+ private boolean defaultFullDetail = true;
+
+ /**
+ * The null text '<null>'.
+ */
+ private String nullText = "";
+
+ /**
+ * The summary size text start '.
+ */
+ private String sizeStartText = "'>' .
+ */
+ private String sizeEndText = ">";
+
+ /**
+ * The summary object text start '<'.
+ */
+ private String summaryObjectStartText = "<";
+
+ /**
+ * The summary object text start '>'.
+ */
+ private String summaryObjectEndText = ">";
+
+ //----------------------------------------------------------------------------
+
+ /**
+ * Constructor.
+ */
+ protected ToStringStyle() {
+ super();
+ }
+
+ //----------------------------------------------------------------------------
+
+ /**
+ * Append to the toString the superclass toString.
+ * NOTE: It assumes that the toString has been created from the same ToStringStyle.
+ *
+ * A null superToString is ignored.
+ *
+ * @param buffer the StringBuffer to populate
+ * @param superToString the super.toString()
+ * @since 2.0
+ */
+ public void appendSuper(StringBuffer buffer, String superToString) {
+ appendToString(buffer, superToString);
+ }
+
+ /**
+ * Append to the toString another toString.
+ * NOTE: It assumes that the toString has been created from the same ToStringStyle.
+ *
+ * A null toString is ignored.
+ *
+ * @param buffer the StringBuffer to populate
+ * @param toString the additional toString
+ * @since 2.0
+ */
+ public void appendToString(StringBuffer buffer, String toString) {
+ if (toString != null) {
+ int pos1 = toString.indexOf(contentStart) + contentStart.length();
+ int pos2 = toString.lastIndexOf(contentEnd);
+ if (pos1 != pos2 && pos1 >= 0 && pos2 >= 0) {
+ String data = toString.substring(pos1, pos2);
+ if (fieldSeparatorAtStart) {
+ removeLastFieldSeparator(buffer);
+ }
+ buffer.append(data);
+ appendFieldSeparator(buffer);
+ }
+ }
+ }
+
+ /**
+ * Append to the toString the start of data indicator.
+ *
+ * @param buffer the StringBuffer to populate
+ * @param object the Object to build a toString for
+ */
+ public void appendStart(StringBuffer buffer, Object object) {
+ if (object != null) {
+ appendClassName(buffer, object);
+ appendIdentityHashCode(buffer, object);
+ appendContentStart(buffer);
+ if (fieldSeparatorAtStart) {
+ appendFieldSeparator(buffer);
+ }
+ }
+ }
+
+ /**
+ * Append to the toString the end of data indicator.
+ *
+ * @param buffer the StringBuffer to populate
+ * @param object the Object to build a
+ * toString for.
+ */
+ public void appendEnd(StringBuffer buffer, Object object) {
+ if (this.fieldSeparatorAtEnd == false) {
+ removeLastFieldSeparator(buffer);
+ }
+ appendContentEnd(buffer);
+ unregister(object);
+ }
+
+ /**
+ * Remove the last field separator from the buffer.
+ *
+ * @param buffer the StringBuffer to populate
+ * @since 2.0
+ */
+ protected void removeLastFieldSeparator(StringBuffer buffer) {
+ int len = buffer.length();
+ int sepLen = fieldSeparator.length();
+ if (len > 0 && sepLen > 0 && len >= sepLen) {
+ boolean match = true;
+ for (int i = 0; i < sepLen; i++) {
+ if (buffer.charAt(len - 1 - i) != fieldSeparator.charAt(sepLen - 1 - i)) {
+ match = false;
+ break;
+ }
+ }
+ if (match) {
+ buffer.setLength(len - sepLen);
+ }
+ }
+ }
+
+ //----------------------------------------------------------------------------
+
+ /**
+ * Append to the toString an Object
+ * value, printing the full toString of the
+ * Object passed in.
+ *
+ * @param buffer the StringBuffer to populate
+ * @param fieldName the field name
+ * @param value the value to add to the toString
+ * @param fullDetail true for detail, false
+ * for summary info, null for style decides
+ */
+ public void append(StringBuffer buffer, String fieldName, Object value, Boolean fullDetail) {
+ appendFieldStart(buffer, fieldName);
+
+ if (value == null) {
+ appendNullText(buffer, fieldName);
+
+ } else {
+ appendInternal(buffer, fieldName, value, isFullDetail(fullDetail));
+ }
+
+ appendFieldEnd(buffer, fieldName);
+ }
+
+ /**
+ * Append to the toString an Object,
+ * correctly interpreting its type.
+ *
+ * This method performs the main lookup by Class type to correctly
+ * route arrays, Collections, Maps and
+ * Objects to the appropriate method.
+ *
+ * Either detail or summary views can be specified.
+ *
+ * If a cycle is detected, an object will be appended with the
+ * Object.toString() format.
+ *
+ * @param buffer the StringBuffer to populate
+ * @param fieldName the field name, typically not used as already appended
+ * @param value the value to add to the toString,
+ * not null
+ * @param detail output detail or not
+ */
+ protected void appendInternal(StringBuffer buffer, String fieldName, Object value, boolean detail) {
+ if (isRegistered(value)
+ && !(value instanceof Number || value instanceof Boolean || value instanceof Character)) {
+ appendCyclicObject(buffer, fieldName, value);
+ return;
+ }
+
+ register(value);
+
+ try {
+ if (value instanceof Collection>) {
+ if (detail) {
+ appendDetail(buffer, fieldName, (Collection>) value);
+ } else {
+ appendSummarySize(buffer, fieldName, ((Collection>) value).size());
+ }
+
+ } else if (value instanceof Map, ?>) {
+ if (detail) {
+ appendDetail(buffer, fieldName, (Map, ?>) value);
+ } else {
+ appendSummarySize(buffer, fieldName, ((Map, ?>) value).size());
+ }
+
+ } else if (value instanceof long[]) {
+ if (detail) {
+ appendDetail(buffer, fieldName, (long[]) value);
+ } else {
+ appendSummary(buffer, fieldName, (long[]) value);
+ }
+
+ } else if (value instanceof int[]) {
+ if (detail) {
+ appendDetail(buffer, fieldName, (int[]) value);
+ } else {
+ appendSummary(buffer, fieldName, (int[]) value);
+ }
+
+ } else if (value instanceof short[]) {
+ if (detail) {
+ appendDetail(buffer, fieldName, (short[]) value);
+ } else {
+ appendSummary(buffer, fieldName, (short[]) value);
+ }
+
+ } else if (value instanceof byte[]) {
+ if (detail) {
+ appendDetail(buffer, fieldName, (byte[]) value);
+ } else {
+ appendSummary(buffer, fieldName, (byte[]) value);
+ }
+
+ } else if (value instanceof char[]) {
+ if (detail) {
+ appendDetail(buffer, fieldName, (char[]) value);
+ } else {
+ appendSummary(buffer, fieldName, (char[]) value);
+ }
+
+ } else if (value instanceof double[]) {
+ if (detail) {
+ appendDetail(buffer, fieldName, (double[]) value);
+ } else {
+ appendSummary(buffer, fieldName, (double[]) value);
+ }
+
+ } else if (value instanceof float[]) {
+ if (detail) {
+ appendDetail(buffer, fieldName, (float[]) value);
+ } else {
+ appendSummary(buffer, fieldName, (float[]) value);
+ }
+
+ } else if (value instanceof boolean[]) {
+ if (detail) {
+ appendDetail(buffer, fieldName, (boolean[]) value);
+ } else {
+ appendSummary(buffer, fieldName, (boolean[]) value);
+ }
+
+ } else if (value.getClass().isArray()) {
+ if (detail) {
+ appendDetail(buffer, fieldName, (Object[]) value);
+ } else {
+ appendSummary(buffer, fieldName, (Object[]) value);
+ }
+
+ } else {
+ if (detail) {
+ appendDetail(buffer, fieldName, value);
+ } else {
+ appendSummary(buffer, fieldName, value);
+ }
+ }
+ } finally {
+ unregister(value);
+ }
+ }
+
+ /**
+ * Append to the toString an Object
+ * value that has been detected to participate in a cycle. This
+ * implementation will print the standard string value of the value.
+ *
+ * @param buffer the StringBuffer to populate
+ * @param fieldName the field name, typically not used as already appended
+ * @param value the value to add to the toString,
+ * not null
+ *
+ * @since 2.2
+ */
+ protected void appendCyclicObject(StringBuffer buffer, String fieldName, Object value) {
+ ObjectUtils.identityToString(buffer, value);
+ }
+
+ /**
+ * Append to the toString an Object
+ * value, printing the full detail of the Object.
+ *
+ * @param buffer the StringBuffer to populate
+ * @param fieldName the field name, typically not used as already appended
+ * @param value the value to add to the toString,
+ * not null
+ */
+ protected void appendDetail(StringBuffer buffer, String fieldName, Object value) {
+ buffer.append(value);
+ }
+
+ /**
+ * Append to the toString a Collection.
+ *
+ * @param buffer the StringBuffer to populate
+ * @param fieldName the field name, typically not used as already appended
+ * @param coll the Collection to add to the
+ * toString, not null
+ */
+ protected void appendDetail(StringBuffer buffer, String fieldName, Collection> coll) {
+ buffer.append(coll);
+ }
+
+ /**
+ * Append to the toString a Map.
+ *
+ * @param buffer the StringBuffer to populate
+ * @param fieldName the field name, typically not used as already appended
+ * @param map the Map to add to the toString,
+ * not null
+ */
+ protected void appendDetail(StringBuffer buffer, String fieldName, Map, ?> map) {
+ buffer.append(map);
+ }
+
+ /**
+ * Append to the toString an Object
+ * value, printing a summary of the Object.
+ *
+ * @param buffer the StringBuffer to populate
+ * @param fieldName the field name, typically not used as already appended
+ * @param value the value to add to the toString,
+ * not null
+ */
+ protected void appendSummary(StringBuffer buffer, String fieldName, Object value) {
+ buffer.append(summaryObjectStartText);
+ buffer.append(getShortClassName(value.getClass()));
+ buffer.append(summaryObjectEndText);
+ }
+
+ //----------------------------------------------------------------------------
+
+ /**
+ * Append to the toString a long
+ * value.
+ *
+ * @param buffer the StringBuffer to populate
+ * @param fieldName the field name
+ * @param value the value to add to the toString
+ */
+ public void append(StringBuffer buffer, String fieldName, long value) {
+ appendFieldStart(buffer, fieldName);
+ appendDetail(buffer, fieldName, value);
+ appendFieldEnd(buffer, fieldName);
+ }
+
+ /**
+ * Append to the toString a long
+ * value.
+ *
+ * @param buffer the StringBuffer to populate
+ * @param fieldName the field name, typically not used as already appended
+ * @param value the value to add to the toString
+ */
+ protected void appendDetail(StringBuffer buffer, String fieldName, long value) {
+ buffer.append(value);
+ }
+
+ //----------------------------------------------------------------------------
+
+ /**
+ * Append to the toString an int
+ * value.
+ *
+ * @param buffer the StringBuffer to populate
+ * @param fieldName the field name
+ * @param value the value to add to the toString
+ */
+ public void append(StringBuffer buffer, String fieldName, int value) {
+ appendFieldStart(buffer, fieldName);
+ appendDetail(buffer, fieldName, value);
+ appendFieldEnd(buffer, fieldName);
+ }
+
+ /**
+ * Append to the toString an int
+ * value.
+ *
+ * @param buffer the StringBuffer to populate
+ * @param fieldName the field name, typically not used as already appended
+ * @param value the value to add to the toString
+ */
+ protected void appendDetail(StringBuffer buffer, String fieldName, int value) {
+ buffer.append(value);
+ }
+
+ //----------------------------------------------------------------------------
+
+ /**
+ * Append to the toString a short
+ * value.
+ *
+ * @param buffer the StringBuffer to populate
+ * @param fieldName the field name
+ * @param value the value to add to the toString
+ */
+ public void append(StringBuffer buffer, String fieldName, short value) {
+ appendFieldStart(buffer, fieldName);
+ appendDetail(buffer, fieldName, value);
+ appendFieldEnd(buffer, fieldName);
+ }
+
+ /**
+ * Append to the toString a short
+ * value.
+ *
+ * @param buffer the StringBuffer to populate
+ * @param fieldName the field name, typically not used as already appended
+ * @param value the value to add to the toString
+ */
+ protected void appendDetail(StringBuffer buffer, String fieldName, short value) {
+ buffer.append(value);
+ }
+
+ //----------------------------------------------------------------------------
+
+ /**
+ * Append to the toString a byte
+ * value.
+ *
+ * @param buffer the StringBuffer to populate
+ * @param fieldName the field name
+ * @param value the value to add to the toString
+ */
+ public void append(StringBuffer buffer, String fieldName, byte value) {
+ appendFieldStart(buffer, fieldName);
+ appendDetail(buffer, fieldName, value);
+ appendFieldEnd(buffer, fieldName);
+ }
+
+ /**
+ * Append to the toString a byte
+ * value.
+ *
+ * @param buffer the StringBuffer to populate
+ * @param fieldName the field name, typically not used as already appended
+ * @param value the value to add to the toString
+ */
+ protected void appendDetail(StringBuffer buffer, String fieldName, byte value) {
+ buffer.append(value);
+ }
+
+ //----------------------------------------------------------------------------
+
+ /**
+ * Append to the toString a char
+ * value.
+ *
+ * @param buffer the StringBuffer to populate
+ * @param fieldName the field name
+ * @param value the value to add to the toString
+ */
+ public void append(StringBuffer buffer, String fieldName, char value) {
+ appendFieldStart(buffer, fieldName);
+ appendDetail(buffer, fieldName, value);
+ appendFieldEnd(buffer, fieldName);
+ }
+
+ /**
+ * Append to the toString a char
+ * value.
+ *
+ * @param buffer the StringBuffer to populate
+ * @param fieldName the field name, typically not used as already appended
+ * @param value the value to add to the toString
+ */
+ protected void appendDetail(StringBuffer buffer, String fieldName, char value) {
+ buffer.append(value);
+ }
+
+ //----------------------------------------------------------------------------
+
+ /**
+ * Append to the toString a double
+ * value.
+ *
+ * @param buffer the StringBuffer to populate
+ * @param fieldName the field name
+ * @param value the value to add to the toString
+ */
+ public void append(StringBuffer buffer, String fieldName, double value) {
+ appendFieldStart(buffer, fieldName);
+ appendDetail(buffer, fieldName, value);
+ appendFieldEnd(buffer, fieldName);
+ }
+
+ /**
+ * Append to the toString a double
+ * value.
+ *
+ * @param buffer the StringBuffer to populate
+ * @param fieldName the field name, typically not used as already appended
+ * @param value the value to add to the toString
+ */
+ protected void appendDetail(StringBuffer buffer, String fieldName, double value) {
+ buffer.append(value);
+ }
+
+ //----------------------------------------------------------------------------
+
+ /**
+ * Append to the toString a float
+ * value.
+ *
+ * @param buffer the StringBuffer to populate
+ * @param fieldName the field name
+ * @param value the value to add to the toString
+ */
+ public void append(StringBuffer buffer, String fieldName, float value) {
+ appendFieldStart(buffer, fieldName);
+ appendDetail(buffer, fieldName, value);
+ appendFieldEnd(buffer, fieldName);
+ }
+
+ /**
+ * Append to the toString a float
+ * value.
+ *
+ * @param buffer the StringBuffer to populate
+ * @param fieldName the field name, typically not used as already appended
+ * @param value the value to add to the toString
+ */
+ protected void appendDetail(StringBuffer buffer, String fieldName, float value) {
+ buffer.append(value);
+ }
+
+ //----------------------------------------------------------------------------
+
+ /**
+ * Append to the toString a boolean
+ * value.
+ *
+ * @param buffer the StringBuffer to populate
+ * @param fieldName the field name
+ * @param value the value to add to the toString
+ */
+ public void append(StringBuffer buffer, String fieldName, boolean value) {
+ appendFieldStart(buffer, fieldName);
+ appendDetail(buffer, fieldName, value);
+ appendFieldEnd(buffer, fieldName);
+ }
+
+ /**
+ * Append to the toString a boolean
+ * value.
+ *
+ * @param buffer the StringBuffer to populate
+ * @param fieldName the field name, typically not used as already appended
+ * @param value the value to add to the toString
+ */
+ protected void appendDetail(StringBuffer buffer, String fieldName, boolean value) {
+ buffer.append(value);
+ }
+
+ /**
+ * Append to the toString an Object
+ * array.
+ *
+ * @param buffer the StringBuffer to populate
+ * @param fieldName the field name
+ * @param array the array to add to the toString
+ * @param fullDetail true for detail, false
+ * for summary info, null for style decides
+ */
+ public void append(StringBuffer buffer, String fieldName, Object[] array, Boolean fullDetail) {
+ appendFieldStart(buffer, fieldName);
+
+ if (array == null) {
+ appendNullText(buffer, fieldName);
+
+ } else if (isFullDetail(fullDetail)) {
+ appendDetail(buffer, fieldName, array);
+
+ } else {
+ appendSummary(buffer, fieldName, array);
+ }
+
+ appendFieldEnd(buffer, fieldName);
+ }
+
+ //----------------------------------------------------------------------------
+
+ /**
+ * Append to the toString the detail of an
+ * Object array.
+ *
+ * @param buffer the StringBuffer to populate
+ * @param fieldName the field name, typically not used as already appended
+ * @param array the array to add to the toString,
+ * not null
+ */
+ protected void appendDetail(StringBuffer buffer, String fieldName, Object[] array) {
+ buffer.append(arrayStart);
+ for (int i = 0; i < array.length; i++) {
+ Object item = array[i];
+ if (i > 0) {
+ buffer.append(arraySeparator);
+ }
+ if (item == null) {
+ appendNullText(buffer, fieldName);
+
+ } else {
+ appendInternal(buffer, fieldName, item, arrayContentDetail);
+ }
+ }
+ buffer.append(arrayEnd);
+ }
+
+ /**
+ * Append to the toString the detail of an array type.
+ *
+ * @param buffer the StringBuffer to populate
+ * @param fieldName the field name, typically not used as already appended
+ * @param array the array to add to the toString,
+ * not null
+ * @since 2.0
+ */
+ protected void reflectionAppendArrayDetail(StringBuffer buffer, String fieldName, Object array) {
+ buffer.append(arrayStart);
+ int length = Array.getLength(array);
+ for (int i = 0; i < length; i++) {
+ Object item = Array.get(array, i);
+ if (i > 0) {
+ buffer.append(arraySeparator);
+ }
+ if (item == null) {
+ appendNullText(buffer, fieldName);
+
+ } else {
+ appendInternal(buffer, fieldName, item, arrayContentDetail);
+ }
+ }
+ buffer.append(arrayEnd);
+ }
+
+ /**
+ * Append to the toString a summary of an
+ * Object array.
+ *
+ * @param buffer the StringBuffer to populate
+ * @param fieldName the field name, typically not used as already appended
+ * @param array the array to add to the toString,
+ * not null
+ */
+ protected void appendSummary(StringBuffer buffer, String fieldName, Object[] array) {
+ appendSummarySize(buffer, fieldName, array.length);
+ }
+
+ //----------------------------------------------------------------------------
+
+ /**
+ * Append to the toString a long
+ * array.
+ *
+ * @param buffer the StringBuffer to populate
+ * @param fieldName the field name
+ * @param array the array to add to the toString
+ * @param fullDetail true for detail, false
+ * for summary info, null for style decides
+ */
+ public void append(StringBuffer buffer, String fieldName, long[] array, Boolean fullDetail) {
+ appendFieldStart(buffer, fieldName);
+
+ if (array == null) {
+ appendNullText(buffer, fieldName);
+
+ } else if (isFullDetail(fullDetail)) {
+ appendDetail(buffer, fieldName, array);
+
+ } else {
+ appendSummary(buffer, fieldName, array);
+ }
+
+ appendFieldEnd(buffer, fieldName);
+ }
+
+ /**
+ * Append to the toString the detail of a
+ * long array.
+ *
+ * @param buffer the StringBuffer to populate
+ * @param fieldName the field name, typically not used as already appended
+ * @param array the array to add to the toString,
+ * not null
+ */
+ protected void appendDetail(StringBuffer buffer, String fieldName, long[] array) {
+ buffer.append(arrayStart);
+ for (int i = 0; i < array.length; i++) {
+ if (i > 0) {
+ buffer.append(arraySeparator);
+ }
+ appendDetail(buffer, fieldName, array[i]);
+ }
+ buffer.append(arrayEnd);
+ }
+
+ /**
+ * Append to the toString a summary of a
+ * long array.
+ *
+ * @param buffer the StringBuffer to populate
+ * @param fieldName the field name, typically not used as already appended
+ * @param array the array to add to the toString,
+ * not null
+ */
+ protected void appendSummary(StringBuffer buffer, String fieldName, long[] array) {
+ appendSummarySize(buffer, fieldName, array.length);
+ }
+
+ //----------------------------------------------------------------------------
+
+ /**
+ * Append to the toString an int
+ * array.
+ *
+ * @param buffer the StringBuffer to populate
+ * @param fieldName the field name
+ * @param array the array to add to the toString
+ * @param fullDetail true for detail, false
+ * for summary info, null for style decides
+ */
+ public void append(StringBuffer buffer, String fieldName, int[] array, Boolean fullDetail) {
+ appendFieldStart(buffer, fieldName);
+
+ if (array == null) {
+ appendNullText(buffer, fieldName);
+
+ } else if (isFullDetail(fullDetail)) {
+ appendDetail(buffer, fieldName, array);
+
+ } else {
+ appendSummary(buffer, fieldName, array);
+ }
+
+ appendFieldEnd(buffer, fieldName);
+ }
+
+ /**
+ * Append to the toString the detail of an
+ * int array.
+ *
+ * @param buffer the StringBuffer to populate
+ * @param fieldName the field name, typically not used as already appended
+ * @param array the array to add to the toString,
+ * not null
+ */
+ protected void appendDetail(StringBuffer buffer, String fieldName, int[] array) {
+ buffer.append(arrayStart);
+ for (int i = 0; i < array.length; i++) {
+ if (i > 0) {
+ buffer.append(arraySeparator);
+ }
+ appendDetail(buffer, fieldName, array[i]);
+ }
+ buffer.append(arrayEnd);
+ }
+
+ /**
+ * Append to the toString a summary of an
+ * int array.
+ *
+ * @param buffer the StringBuffer to populate
+ * @param fieldName the field name, typically not used as already appended
+ * @param array the array to add to the toString,
+ * not null
+ */
+ protected void appendSummary(StringBuffer buffer, String fieldName, int[] array) {
+ appendSummarySize(buffer, fieldName, array.length);
+ }
+
+ //----------------------------------------------------------------------------
+
+ /**
+ * Append to the toString a short
+ * array.
+ *
+ * @param buffer the StringBuffer to populate
+ * @param fieldName the field name
+ * @param array the array to add to the toString
+ * @param fullDetail true for detail, false
+ * for summary info, null for style decides
+ */
+ public void append(StringBuffer buffer, String fieldName, short[] array, Boolean fullDetail) {
+ appendFieldStart(buffer, fieldName);
+
+ if (array == null) {
+ appendNullText(buffer, fieldName);
+
+ } else if (isFullDetail(fullDetail)) {
+ appendDetail(buffer, fieldName, array);
+
+ } else {
+ appendSummary(buffer, fieldName, array);
+ }
+
+ appendFieldEnd(buffer, fieldName);
+ }
+
+ /**
+ * Append to the toString the detail of a
+ * short array.
+ *
+ * @param buffer the StringBuffer to populate
+ * @param fieldName the field name, typically not used as already appended
+ * @param array the array to add to the toString,
+ * not null
+ */
+ protected void appendDetail(StringBuffer buffer, String fieldName, short[] array) {
+ buffer.append(arrayStart);
+ for (int i = 0; i < array.length; i++) {
+ if (i > 0) {
+ buffer.append(arraySeparator);
+ }
+ appendDetail(buffer, fieldName, array[i]);
+ }
+ buffer.append(arrayEnd);
+ }
+
+ /**
+ * Append to the toString a summary of a
+ * short array.
+ *
+ * @param buffer the StringBuffer to populate
+ * @param fieldName the field name, typically not used as already appended
+ * @param array the array to add to the toString,
+ * not null
+ */
+ protected void appendSummary(StringBuffer buffer, String fieldName, short[] array) {
+ appendSummarySize(buffer, fieldName, array.length);
+ }
+
+ //----------------------------------------------------------------------------
+
+ /**
+ * Append to the toString a byte
+ * array.
+ *
+ * @param buffer the StringBuffer to populate
+ * @param fieldName the field name
+ * @param array the array to add to the toString
+ * @param fullDetail true for detail, false
+ * for summary info, null for style decides
+ */
+ public void append(StringBuffer buffer, String fieldName, byte[] array, Boolean fullDetail) {
+ appendFieldStart(buffer, fieldName);
+
+ if (array == null) {
+ appendNullText(buffer, fieldName);
+
+ } else if (isFullDetail(fullDetail)) {
+ appendDetail(buffer, fieldName, array);
+
+ } else {
+ appendSummary(buffer, fieldName, array);
+ }
+
+ appendFieldEnd(buffer, fieldName);
+ }
+
+ /**
+ * Append to the toString the detail of a
+ * byte array.
+ *
+ * @param buffer the StringBuffer to populate
+ * @param fieldName the field name, typically not used as already appended
+ * @param array the array to add to the toString,
+ * not null
+ */
+ protected void appendDetail(StringBuffer buffer, String fieldName, byte[] array) {
+ buffer.append(arrayStart);
+ for (int i = 0; i < array.length; i++) {
+ if (i > 0) {
+ buffer.append(arraySeparator);
+ }
+ appendDetail(buffer, fieldName, array[i]);
+ }
+ buffer.append(arrayEnd);
+ }
+
+ /**
+ * Append to the toString a summary of a
+ * byte array.
+ *
+ * @param buffer the StringBuffer to populate
+ * @param fieldName the field name, typically not used as already appended
+ * @param array the array to add to the toString,
+ * not null
+ */
+ protected void appendSummary(StringBuffer buffer, String fieldName, byte[] array) {
+ appendSummarySize(buffer, fieldName, array.length);
+ }
+
+ //----------------------------------------------------------------------------
+
+ /**
+ * Append to the toString a char
+ * array.
+ *
+ * @param buffer the StringBuffer to populate
+ * @param fieldName the field name
+ * @param array the array to add to the toString
+ * @param fullDetail true for detail, false
+ * for summary info, null for style decides
+ */
+ public void append(StringBuffer buffer, String fieldName, char[] array, Boolean fullDetail) {
+ appendFieldStart(buffer, fieldName);
+
+ if (array == null) {
+ appendNullText(buffer, fieldName);
+
+ } else if (isFullDetail(fullDetail)) {
+ appendDetail(buffer, fieldName, array);
+
+ } else {
+ appendSummary(buffer, fieldName, array);
+ }
+
+ appendFieldEnd(buffer, fieldName);
+ }
+
+ /**
+ * Append to the toString the detail of a
+ * char array.
+ *
+ * @param buffer the StringBuffer to populate
+ * @param fieldName the field name, typically not used as already appended
+ * @param array the array to add to the toString,
+ * not null
+ */
+ protected void appendDetail(StringBuffer buffer, String fieldName, char[] array) {
+ buffer.append(arrayStart);
+ for (int i = 0; i < array.length; i++) {
+ if (i > 0) {
+ buffer.append(arraySeparator);
+ }
+ appendDetail(buffer, fieldName, array[i]);
+ }
+ buffer.append(arrayEnd);
+ }
+
+ /**
+ * Append to the toString a summary of a
+ * char array.
+ *
+ * @param buffer the StringBuffer to populate
+ * @param fieldName the field name, typically not used as already appended
+ * @param array the array to add to the toString,
+ * not null
+ */
+ protected void appendSummary(StringBuffer buffer, String fieldName, char[] array) {
+ appendSummarySize(buffer, fieldName, array.length);
+ }
+
+ //----------------------------------------------------------------------------
+
+ /**
+ * Append to the toString a double
+ * array.
+ *
+ * @param buffer the StringBuffer to populate
+ * @param fieldName the field name
+ * @param array the array to add to the toString
+ * @param fullDetail true for detail, false
+ * for summary info, null for style decides
+ */
+ public void append(StringBuffer buffer, String fieldName, double[] array, Boolean fullDetail) {
+ appendFieldStart(buffer, fieldName);
+
+ if (array == null) {
+ appendNullText(buffer, fieldName);
+
+ } else if (isFullDetail(fullDetail)) {
+ appendDetail(buffer, fieldName, array);
+
+ } else {
+ appendSummary(buffer, fieldName, array);
+ }
+
+ appendFieldEnd(buffer, fieldName);
+ }
+
+ /**
+ * Append to the toString the detail of a
+ * double array.
+ *
+ * @param buffer the StringBuffer to populate
+ * @param fieldName the field name, typically not used as already appended
+ * @param array the array to add to the toString,
+ * not null
+ */
+ protected void appendDetail(StringBuffer buffer, String fieldName, double[] array) {
+ buffer.append(arrayStart);
+ for (int i = 0; i < array.length; i++) {
+ if (i > 0) {
+ buffer.append(arraySeparator);
+ }
+ appendDetail(buffer, fieldName, array[i]);
+ }
+ buffer.append(arrayEnd);
+ }
+
+ /**
+ * Append to the toString a summary of a
+ * double array.
+ *
+ * @param buffer the StringBuffer to populate
+ * @param fieldName the field name, typically not used as already appended
+ * @param array the array to add to the toString,
+ * not null
+ */
+ protected void appendSummary(StringBuffer buffer, String fieldName, double[] array) {
+ appendSummarySize(buffer, fieldName, array.length);
+ }
+
+ //----------------------------------------------------------------------------
+
+ /**
+ * Append to the toString a float
+ * array.
+ *
+ * @param buffer the StringBuffer to populate
+ * @param fieldName the field name
+ * @param array the array to add to the toString
+ * @param fullDetail true for detail, false
+ * for summary info, null for style decides
+ */
+ public void append(StringBuffer buffer, String fieldName, float[] array, Boolean fullDetail) {
+ appendFieldStart(buffer, fieldName);
+
+ if (array == null) {
+ appendNullText(buffer, fieldName);
+
+ } else if (isFullDetail(fullDetail)) {
+ appendDetail(buffer, fieldName, array);
+
+ } else {
+ appendSummary(buffer, fieldName, array);
+ }
+
+ appendFieldEnd(buffer, fieldName);
+ }
+
+ /**
+ * Append to the toString the detail of a
+ * float array.
+ *
+ * @param buffer the StringBuffer to populate
+ * @param fieldName the field name, typically not used as already appended
+ * @param array the array to add to the toString,
+ * not null
+ */
+ protected void appendDetail(StringBuffer buffer, String fieldName, float[] array) {
+ buffer.append(arrayStart);
+ for (int i = 0; i < array.length; i++) {
+ if (i > 0) {
+ buffer.append(arraySeparator);
+ }
+ appendDetail(buffer, fieldName, array[i]);
+ }
+ buffer.append(arrayEnd);
+ }
+
+ /**
+ * Append to the toString a summary of a
+ * float array.
+ *
+ * @param buffer the StringBuffer to populate
+ * @param fieldName the field name, typically not used as already appended
+ * @param array the array to add to the toString,
+ * not null
+ */
+ protected void appendSummary(StringBuffer buffer, String fieldName, float[] array) {
+ appendSummarySize(buffer, fieldName, array.length);
+ }
+
+ //----------------------------------------------------------------------------
+
+ /**
+ * Append to the toString a boolean
+ * array.
+ *
+ * @param buffer the StringBuffer to populate
+ * @param fieldName the field name
+ * @param array the array to add to the toString
+ * @param fullDetail true for detail, false
+ * for summary info, null for style decides
+ */
+ public void append(StringBuffer buffer, String fieldName, boolean[] array, Boolean fullDetail) {
+ appendFieldStart(buffer, fieldName);
+
+ if (array == null) {
+ appendNullText(buffer, fieldName);
+
+ } else if (isFullDetail(fullDetail)) {
+ appendDetail(buffer, fieldName, array);
+
+ } else {
+ appendSummary(buffer, fieldName, array);
+ }
+
+ appendFieldEnd(buffer, fieldName);
+ }
+
+ /**
+ * Append to the toString the detail of a
+ * boolean array.
+ *
+ * @param buffer the StringBuffer to populate
+ * @param fieldName the field name, typically not used as already appended
+ * @param array the array to add to the toString,
+ * not null
+ */
+ protected void appendDetail(StringBuffer buffer, String fieldName, boolean[] array) {
+ buffer.append(arrayStart);
+ for (int i = 0; i < array.length; i++) {
+ if (i > 0) {
+ buffer.append(arraySeparator);
+ }
+ appendDetail(buffer, fieldName, array[i]);
+ }
+ buffer.append(arrayEnd);
+ }
+
+ /**
+ * Append to the toString a summary of a
+ * boolean array.
+ *
+ * @param buffer the StringBuffer to populate
+ * @param fieldName the field name, typically not used as already appended
+ * @param array the array to add to the toString,
+ * not null
+ */
+ protected void appendSummary(StringBuffer buffer, String fieldName, boolean[] array) {
+ appendSummarySize(buffer, fieldName, array.length);
+ }
+
+ //----------------------------------------------------------------------------
+
+ /**
+ * Append to the toString the class name.
+ *
+ * @param buffer the StringBuffer to populate
+ * @param object the Object whose name to output
+ */
+ protected void appendClassName(StringBuffer buffer, Object object) {
+ if (useClassName && object != null) {
+ register(object);
+ if (useShortClassName) {
+ buffer.append(getShortClassName(object.getClass()));
+ } else {
+ buffer.append(object.getClass().getName());
+ }
+ }
+ }
+
+ /**
+ * Append the {@link System#identityHashCode(java.lang.Object)}.
+ *
+ * @param buffer the StringBuffer to populate
+ * @param object the Object whose id to output
+ */
+ protected void appendIdentityHashCode(StringBuffer buffer, Object object) {
+ if (this.isUseIdentityHashCode() && object!=null) {
+ register(object);
+ buffer.append('@');
+ buffer.append(Integer.toHexString(System.identityHashCode(object)));
+ }
+ }
+
+ /**
+ * Append to the toString the content start.
+ *
+ * @param buffer the StringBuffer to populate
+ */
+ protected void appendContentStart(StringBuffer buffer) {
+ buffer.append(contentStart);
+ }
+
+ /**
+ * Append to the toString the content end.
+ *
+ * @param buffer the StringBuffer to populate
+ */
+ protected void appendContentEnd(StringBuffer buffer) {
+ buffer.append(contentEnd);
+ }
+
+ /**
+ * Append to the toString an indicator for null.
+ *
+ * The default indicator is '<null>'.
+ *
+ * @param buffer the StringBuffer to populate
+ * @param fieldName the field name, typically not used as already appended
+ */
+ protected void appendNullText(StringBuffer buffer, String fieldName) {
+ buffer.append(nullText);
+ }
+
+ /**
+ * Append to the toString the field separator.
+ *
+ * @param buffer the StringBuffer to populate
+ */
+ protected void appendFieldSeparator(StringBuffer buffer) {
+ buffer.append(fieldSeparator);
+ }
+
+ /**
+ * Append to the toString the field start.
+ *
+ * @param buffer the StringBuffer to populate
+ * @param fieldName the field name
+ */
+ protected void appendFieldStart(StringBuffer buffer, String fieldName) {
+ if (useFieldNames && fieldName != null) {
+ buffer.append(fieldName);
+ buffer.append(fieldNameValueSeparator);
+ }
+ }
+
+ /**
+ * Append to the toString the field end.
+ *
+ * @param buffer the StringBuffer to populate
+ * @param fieldName the field name, typically not used as already appended
+ */
+ protected void appendFieldEnd(StringBuffer buffer, String fieldName) {
+ appendFieldSeparator(buffer);
+ }
+
+ /**
+ * Append to the toString a size summary.
+ *
+ * The size summary is used to summarize the contents of
+ * Collections, Maps and arrays.
+ *
+ * The output consists of a prefix, the passed in size
+ * and a suffix.
+ *
+ * The default format is '<size=n>'.
+ *
+ * @param buffer the StringBuffer to populate
+ * @param fieldName the field name, typically not used as already appended
+ * @param size the size to append
+ */
+ protected void appendSummarySize(StringBuffer buffer, String fieldName, int size) {
+ buffer.append(sizeStartText);
+ buffer.append(size);
+ buffer.append(sizeEndText);
+ }
+
+ /**
+ * Is this field to be output in full detail.
+ *
+ * This method converts a detail request into a detail level.
+ * The calling code may request full detail (true),
+ * but a subclass might ignore that and always return
+ * false. The calling code may pass in
+ * null indicating that it doesn't care about
+ * the detail level. In this case the default detail level is
+ * used.
+ *
+ * @param fullDetailRequest the detail level requested
+ * @return whether full detail is to be shown
+ */
+ protected boolean isFullDetail(Boolean fullDetailRequest) {
+ if (fullDetailRequest == null) {
+ return defaultFullDetail;
+ }
+ return fullDetailRequest.booleanValue();
+ }
+
+ /**
+ * Gets the short class name for a class.
+ *
+ * The short class name is the classname excluding
+ * the package name.
+ *
+ * @param cls the Class to get the short name of
+ * @return the short name
+ */
+ protected String getShortClassName(Class> cls) {
+ return ClassUtils.getShortClassName(cls);
+ }
+
+ // Setters and getters for the customizable parts of the style
+ // These methods are not expected to be overridden, except to make public
+ // (They are not public so that immutable subclasses can be written)
+ //---------------------------------------------------------------------
+
+ /**
+ * Gets whether to use the class name.
+ *
+ * @return the current useClassName flag
+ */
+ protected boolean isUseClassName() {
+ return useClassName;
+ }
+
+ /**
+ * Sets whether to use the class name.
+ *
+ * @param useClassName the new useClassName flag
+ */
+ protected void setUseClassName(boolean useClassName) {
+ this.useClassName = useClassName;
+ }
+
+ //---------------------------------------------------------------------
+
+ /**
+ * Gets whether to output short or long class names.
+ *
+ * @return the current useShortClassName flag
+ * @since 2.0
+ */
+ protected boolean isUseShortClassName() {
+ return useShortClassName;
+ }
+
+ /**
+ * Sets whether to output short or long class names.
+ *
+ * @param useShortClassName the new useShortClassName flag
+ * @since 2.0
+ */
+ protected void setUseShortClassName(boolean useShortClassName) {
+ this.useShortClassName = useShortClassName;
+ }
+
+ //---------------------------------------------------------------------
+
+ /**
+ * Gets whether to use the identity hash code.
+ *
+ * @return the current useIdentityHashCode flag
+ */
+ protected boolean isUseIdentityHashCode() {
+ return useIdentityHashCode;
+ }
+
+ /**
+ * Sets whether to use the identity hash code.
+ *
+ * @param useIdentityHashCode the new useIdentityHashCode flag
+ */
+ protected void setUseIdentityHashCode(boolean useIdentityHashCode) {
+ this.useIdentityHashCode = useIdentityHashCode;
+ }
+
+ //---------------------------------------------------------------------
+
+ /**
+ * Gets whether to use the field names passed in.
+ *
+ * @return the current useFieldNames flag
+ */
+ protected boolean isUseFieldNames() {
+ return useFieldNames;
+ }
+
+ /**
+ * Sets whether to use the field names passed in.
+ *
+ * @param useFieldNames the new useFieldNames flag
+ */
+ protected void setUseFieldNames(boolean useFieldNames) {
+ this.useFieldNames = useFieldNames;
+ }
+
+ //---------------------------------------------------------------------
+
+ /**
+ * Gets whether to use full detail when the caller doesn't
+ * specify.
+ *
+ * @return the current defaultFullDetail flag
+ */
+ protected boolean isDefaultFullDetail() {
+ return defaultFullDetail;
+ }
+
+ /**
+ * Sets whether to use full detail when the caller doesn't
+ * specify.
+ *
+ * @param defaultFullDetail the new defaultFullDetail flag
+ */
+ protected void setDefaultFullDetail(boolean defaultFullDetail) {
+ this.defaultFullDetail = defaultFullDetail;
+ }
+
+ //---------------------------------------------------------------------
+
+ /**
+ * Gets whether to output array content detail.
+ *
+ * @return the current array content detail setting
+ */
+ protected boolean isArrayContentDetail() {
+ return arrayContentDetail;
+ }
+
+ /**
+ * Sets whether to output array content detail.
+ *
+ * @param arrayContentDetail the new arrayContentDetail flag
+ */
+ protected void setArrayContentDetail(boolean arrayContentDetail) {
+ this.arrayContentDetail = arrayContentDetail;
+ }
+
+ //---------------------------------------------------------------------
+
+ /**
+ * Gets the array start text.
+ *
+ * @return the current array start text
+ */
+ protected String getArrayStart() {
+ return arrayStart;
+ }
+
+ /**
+ * Sets the array start text.
+ *
+ * null is accepted, but will be converted to
+ * an empty String.
+ *
+ * @param arrayStart the new array start text
+ */
+ protected void setArrayStart(String arrayStart) {
+ if (arrayStart == null) {
+ arrayStart = "";
+ }
+ this.arrayStart = arrayStart;
+ }
+
+ //---------------------------------------------------------------------
+
+ /**
+ * Gets the array end text.
+ *
+ * @return the current array end text
+ */
+ protected String getArrayEnd() {
+ return arrayEnd;
+ }
+
+ /**
+ * Sets the array end text.
+ *
+ * null is accepted, but will be converted to
+ * an empty String.
+ *
+ * @param arrayEnd the new array end text
+ */
+ protected void setArrayEnd(String arrayEnd) {
+ if (arrayEnd == null) {
+ arrayEnd = "";
+ }
+ this.arrayEnd = arrayEnd;
+ }
+
+ //---------------------------------------------------------------------
+
+ /**
+ * Gets the array separator text.
+ *
+ * @return the current array separator text
+ */
+ protected String getArraySeparator() {
+ return arraySeparator;
+ }
+
+ /**
+ * Sets the array separator text.
+ *
+ * null is accepted, but will be converted to
+ * an empty String.
+ *
+ * @param arraySeparator the new array separator text
+ */
+ protected void setArraySeparator(String arraySeparator) {
+ if (arraySeparator == null) {
+ arraySeparator = "";
+ }
+ this.arraySeparator = arraySeparator;
+ }
+
+ //---------------------------------------------------------------------
+
+ /**
+ * Gets the content start text.
+ *
+ * @return the current content start text
+ */
+ protected String getContentStart() {
+ return contentStart;
+ }
+
+ /**
+ * Sets the content start text.
+ *
+ * null is accepted, but will be converted to
+ * an empty String.
+ *
+ * @param contentStart the new content start text
+ */
+ protected void setContentStart(String contentStart) {
+ if (contentStart == null) {
+ contentStart = "";
+ }
+ this.contentStart = contentStart;
+ }
+
+ //---------------------------------------------------------------------
+
+ /**
+ * Gets the content end text.
+ *
+ * @return the current content end text
+ */
+ protected String getContentEnd() {
+ return contentEnd;
+ }
+
+ /**
+ * Sets the content end text.
+ *
+ * null is accepted, but will be converted to
+ * an empty String.
+ *
+ * @param contentEnd the new content end text
+ */
+ protected void setContentEnd(String contentEnd) {
+ if (contentEnd == null) {
+ contentEnd = "";
+ }
+ this.contentEnd = contentEnd;
+ }
+
+ //---------------------------------------------------------------------
+
+ /**
+ * Gets the field name value separator text.
+ *
+ * @return the current field name value separator text
+ */
+ protected String getFieldNameValueSeparator() {
+ return fieldNameValueSeparator;
+ }
+
+ /**
+ * Sets the field name value separator text.
+ *
+ * null is accepted, but will be converted to
+ * an empty String.
+ *
+ * @param fieldNameValueSeparator the new field name value separator text
+ */
+ protected void setFieldNameValueSeparator(String fieldNameValueSeparator) {
+ if (fieldNameValueSeparator == null) {
+ fieldNameValueSeparator = "";
+ }
+ this.fieldNameValueSeparator = fieldNameValueSeparator;
+ }
+
+ //---------------------------------------------------------------------
+
+ /**
+ * Gets the field separator text.
+ *
+ * @return the current field separator text
+ */
+ protected String getFieldSeparator() {
+ return fieldSeparator;
+ }
+
+ /**
+ * Sets the field separator text.
+ *
+ * null is accepted, but will be converted to
+ * an empty String.
+ *
+ * @param fieldSeparator the new field separator text
+ */
+ protected void setFieldSeparator(String fieldSeparator) {
+ if (fieldSeparator == null) {
+ fieldSeparator = "";
+ }
+ this.fieldSeparator = fieldSeparator;
+ }
+
+ //---------------------------------------------------------------------
+
+ /**
+ * Gets whether the field separator should be added at the start
+ * of each buffer.
+ *
+ * @return the fieldSeparatorAtStart flag
+ * @since 2.0
+ */
+ protected boolean isFieldSeparatorAtStart() {
+ return fieldSeparatorAtStart;
+ }
+
+ /**
+ * Sets whether the field separator should be added at the start
+ * of each buffer.
+ *
+ * @param fieldSeparatorAtStart the fieldSeparatorAtStart flag
+ * @since 2.0
+ */
+ protected void setFieldSeparatorAtStart(boolean fieldSeparatorAtStart) {
+ this.fieldSeparatorAtStart = fieldSeparatorAtStart;
+ }
+
+ //---------------------------------------------------------------------
+
+ /**
+ * Gets whether the field separator should be added at the end
+ * of each buffer.
+ *
+ * @return fieldSeparatorAtEnd flag
+ * @since 2.0
+ */
+ protected boolean isFieldSeparatorAtEnd() {
+ return fieldSeparatorAtEnd;
+ }
+
+ /**
+ * Sets whether the field separator should be added at the end
+ * of each buffer.
+ *
+ * @param fieldSeparatorAtEnd the fieldSeparatorAtEnd flag
+ * @since 2.0
+ */
+ protected void setFieldSeparatorAtEnd(boolean fieldSeparatorAtEnd) {
+ this.fieldSeparatorAtEnd = fieldSeparatorAtEnd;
+ }
+
+ //---------------------------------------------------------------------
+
+ /**
+ * Gets the text to output when null found.
+ *
+ * @return the current text to output when null found
+ */
+ protected String getNullText() {
+ return nullText;
+ }
+
+ /**
+ * Sets the text to output when null found.
+ *
+ * null is accepted, but will be converted to
+ * an empty String.
+ *
+ * @param nullText the new text to output when null found
+ */
+ protected void setNullText(String nullText) {
+ if (nullText == null) {
+ nullText = "";
+ }
+ this.nullText = nullText;
+ }
+
+ //---------------------------------------------------------------------
+
+ /**
+ * Gets the start text to output when a Collection,
+ * Map or array size is output.
+ *
+ * This is output before the size value.
+ *
+ * @return the current start of size text
+ */
+ protected String getSizeStartText() {
+ return sizeStartText;
+ }
+
+ /**
+ * Sets the start text to output when a Collection,
+ * Map or array size is output.
+ *
+ * This is output before the size value.
+ *
+ * null is accepted, but will be converted to
+ * an empty String.
+ *
+ * @param sizeStartText the new start of size text
+ */
+ protected void setSizeStartText(String sizeStartText) {
+ if (sizeStartText == null) {
+ sizeStartText = "";
+ }
+ this.sizeStartText = sizeStartText;
+ }
+
+ //---------------------------------------------------------------------
+
+ /**
+ * Gets the end text to output when a Collection,
+ * Map or array size is output.
+ *
+ * This is output after the size value.
+ *
+ * @return the current end of size text
+ */
+ protected String getSizeEndText() {
+ return sizeEndText;
+ }
+
+ /**
+ * Sets the end text to output when a Collection,
+ * Map or array size is output.
+ *
+ * This is output after the size value.
+ *
+ * null is accepted, but will be converted to
+ * an empty String.
+ *
+ * @param sizeEndText the new end of size text
+ */
+ protected void setSizeEndText(String sizeEndText) {
+ if (sizeEndText == null) {
+ sizeEndText = "";
+ }
+ this.sizeEndText = sizeEndText;
+ }
+
+ //---------------------------------------------------------------------
+
+ /**
+ * Gets the start text to output when an Object is
+ * output in summary mode.
+ *
+ * This is output before the size value.
+ *
+ * @return the current start of summary text
+ */
+ protected String getSummaryObjectStartText() {
+ return summaryObjectStartText;
+ }
+
+ /**
+ * Sets the start text to output when an Object is
+ * output in summary mode.
+ *
+ * This is output before the size value.
+ *
+ * null is accepted, but will be converted to
+ * an empty String.
+ *
+ * @param summaryObjectStartText the new start of summary text
+ */
+ protected void setSummaryObjectStartText(String summaryObjectStartText) {
+ if (summaryObjectStartText == null) {
+ summaryObjectStartText = "";
+ }
+ this.summaryObjectStartText = summaryObjectStartText;
+ }
+
+ //---------------------------------------------------------------------
+
+ /**
+ * Gets the end text to output when an Object is
+ * output in summary mode.
+ *
+ * This is output after the size value.
+ *
+ * @return the current end of summary text
+ */
+ protected String getSummaryObjectEndText() {
+ return summaryObjectEndText;
+ }
+
+ /**
+ * Sets the end text to output when an Object is
+ * output in summary mode.
+ *
+ * This is output after the size value.
+ *
+ * null is accepted, but will be converted to
+ * an empty String.
+ *
+ * @param summaryObjectEndText the new end of summary text
+ */
+ protected void setSummaryObjectEndText(String summaryObjectEndText) {
+ if (summaryObjectEndText == null) {
+ summaryObjectEndText = "";
+ }
+ this.summaryObjectEndText = summaryObjectEndText;
+ }
+
+ //----------------------------------------------------------------------------
+
+ /**
+ * Default ToStringStyle.
+ *
+ * This is an inner class rather than using
+ * StandardToStringStyle to ensure its immutability.
+ */
+ private static final class DefaultToStringStyle extends ToStringStyle {
+
+ /**
+ * Required for serialization support.
+ *
+ * @see java.io.Serializable
+ */
+ private static final long serialVersionUID = 1L;
+
+ /**
+ * Constructor.
+ *
+ * Use the static constant rather than instantiating.
+ */
+ DefaultToStringStyle() {
+ super();
+ }
+
+ /**
+ * Ensure Singleton after serialization.
+ *
+ * @return the singleton
+ */
+ private Object readResolve() {
+ return ToStringStyle.DEFAULT_STYLE;
+ }
+
+ }
+
+ //----------------------------------------------------------------------------
+
+ /**
+ * ToStringStyle that does not print out
+ * the field names.
+ *
+ * This is an inner class rather than using
+ * StandardToStringStyle to ensure its immutability.
+ */
+ private static final class NoFieldNameToStringStyle extends ToStringStyle {
+
+ private static final long serialVersionUID = 1L;
+
+ /**
+ *
Constructor.
+ *
+ * Use the static constant rather than instantiating.
+ */
+ NoFieldNameToStringStyle() {
+ super();
+ this.setUseFieldNames(false);
+ }
+
+ /**
+ * Ensure Singleton after serialization.
+ *
+ * @return the singleton
+ */
+ private Object readResolve() {
+ return ToStringStyle.NO_FIELD_NAMES_STYLE;
+ }
+
+ }
+
+ //----------------------------------------------------------------------------
+
+ /**
+ * ToStringStyle that prints out the short
+ * class name and no identity hashcode.
+ *
+ * This is an inner class rather than using
+ * StandardToStringStyle to ensure its immutability.
+ */
+ private static final class ShortPrefixToStringStyle extends ToStringStyle {
+
+ private static final long serialVersionUID = 1L;
+
+ /**
+ * Constructor.
+ *
+ * Use the static constant rather than instantiating.
+ */
+ ShortPrefixToStringStyle() {
+ super();
+ this.setUseShortClassName(true);
+ this.setUseIdentityHashCode(false);
+ }
+
+ /**
+ * Ensure Singleton after serialization.
+ * @return the singleton
+ */
+ private Object readResolve() {
+ return ToStringStyle.SHORT_PREFIX_STYLE;
+ }
+
+ }
+
+ /**
+ * ToStringStyle that does not print out the
+ * classname, identity hashcode, content start or field name.
+ *
+ * This is an inner class rather than using
+ * StandardToStringStyle to ensure its immutability.
+ */
+ private static final class SimpleToStringStyle extends ToStringStyle {
+
+ private static final long serialVersionUID = 1L;
+
+ /**
+ * Constructor.
+ *
+ * Use the static constant rather than instantiating.
+ */
+ SimpleToStringStyle() {
+ super();
+ this.setUseClassName(false);
+ this.setUseIdentityHashCode(false);
+ this.setUseFieldNames(false);
+ this.setContentStart("");
+ this.setContentEnd("");
+ }
+
+ /**
+ * Ensure Singleton after serialization.
+ * @return the singleton
+ */
+ private Object readResolve() {
+ return ToStringStyle.SIMPLE_STYLE;
+ }
+
+ }
+
+ //----------------------------------------------------------------------------
+
+ /**
+ * ToStringStyle that outputs on multiple lines.
+ *
+ * This is an inner class rather than using
+ * StandardToStringStyle to ensure its immutability.
+ */
+ private static final class MultiLineToStringStyle extends ToStringStyle {
+
+ private static final long serialVersionUID = 1L;
+
+ /**
+ * Constructor.
+ *
+ * Use the static constant rather than instantiating.
+ */
+ MultiLineToStringStyle() {
+ super();
+ this.setContentStart("[");
+ this.setFieldSeparator(SystemUtils.LINE_SEPARATOR + " ");
+ this.setFieldSeparatorAtStart(true);
+ this.setContentEnd(SystemUtils.LINE_SEPARATOR + "]");
+ }
+
+ /**
+ * Ensure Singleton after serialization.
+ *
+ * @return the singleton
+ */
+ private Object readResolve() {
+ return ToStringStyle.MULTI_LINE_STYLE;
+ }
+
+ }
+
+}
diff --git a/Bridge/src/main/apacheCommonsLang/external/org/apache/commons/lang3/builder/package.html b/Bridge/src/main/apacheCommonsLang/external/org/apache/commons/lang3/builder/package.html
new file mode 100644
index 00000000..dd40682d
--- /dev/null
+++ b/Bridge/src/main/apacheCommonsLang/external/org/apache/commons/lang3/builder/package.html
@@ -0,0 +1,28 @@
+
+
+
+Assists in creating consistent equals(Object), toString(),
+hashCode(), and compareTo(Object) methods.
+@see java.lang.Object#equals(Object)
+@see java.lang.Object#toString()
+@see java.lang.Object#hashCode()
+@see java.lang.Comparable#compareTo(Object)
+@since 1.0
+These classes are not thread-safe.
+
+
diff --git a/Bridge/src/main/apacheCommonsLang/external/org/apache/commons/lang3/exception/CloneFailedException.java b/Bridge/src/main/apacheCommonsLang/external/org/apache/commons/lang3/exception/CloneFailedException.java
new file mode 100644
index 00000000..edd2d775
--- /dev/null
+++ b/Bridge/src/main/apacheCommonsLang/external/org/apache/commons/lang3/exception/CloneFailedException.java
@@ -0,0 +1,62 @@
+/*
+ * 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 external.org.apache.commons.lang3.exception;
+
+/**
+ * Exception thrown when a clone cannot be created. In contrast to
+ * {@link CloneNotSupportedException} this is a {@link RuntimeException}.
+ *
+ * @since 3.0
+ */
+public class CloneFailedException extends RuntimeException {
+ // ~ Static fields/initializers ---------------------------------------------
+
+ private static final long serialVersionUID = 20091223L;
+
+ // ~ Constructors -----------------------------------------------------------
+
+ /**
+ * Constructs a CloneFailedException.
+ *
+ * @param message description of the exception
+ * @since upcoming
+ */
+ public CloneFailedException(final String message) {
+ super(message);
+ }
+
+ /**
+ * Constructs a CloneFailedException.
+ *
+ * @param cause cause of the exception
+ * @since upcoming
+ */
+ public CloneFailedException(final Throwable cause) {
+ super(cause);
+ }
+
+ /**
+ * Constructs a CloneFailedException.
+ *
+ * @param message description of the exception
+ * @param cause cause of the exception
+ * @since upcoming
+ */
+ public CloneFailedException(final String message, final Throwable cause) {
+ super(message, cause);
+ }
+}
diff --git a/Bridge/src/main/apacheCommonsLang/external/org/apache/commons/lang3/exception/package.html b/Bridge/src/main/apacheCommonsLang/external/org/apache/commons/lang3/exception/package.html
new file mode 100644
index 00000000..b9d94036
--- /dev/null
+++ b/Bridge/src/main/apacheCommonsLang/external/org/apache/commons/lang3/exception/package.html
@@ -0,0 +1,27 @@
+
+
+
+Provides functionality for Exceptions.
+Contains the concept of an exception with context i.e. such an exception
+will contain a map with keys and values. This provides an easy way to pass valuable
+state information at exception time in useful form to a calling process.
+Lastly, {@link org.apache.commons.lang3.exception.ExceptionUtils}
+also contains Throwable manipulation and examination routines.
+@since 1.0
+
+
diff --git a/Bridge/src/main/apacheCommonsLang/external/org/apache/commons/lang3/mutable/Mutable.java b/Bridge/src/main/apacheCommonsLang/external/org/apache/commons/lang3/mutable/Mutable.java
new file mode 100644
index 00000000..aa733e50
--- /dev/null
+++ b/Bridge/src/main/apacheCommonsLang/external/org/apache/commons/lang3/mutable/Mutable.java
@@ -0,0 +1,54 @@
+/*
+ * 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 external.org.apache.commons.lang3.mutable;
+
+/**
+ * Provides mutable access to a value.
+ *
+ * Mutable is used as a generic interface to the implementations in this package.
+ *
+ * A typical use case would be to enable a primitive or string to be passed to a method and allow that method to
+ * effectively change the value of the primitive/string. Another use case is to store a frequently changing primitive in
+ * a collection (for example a total in a map) without needing to create new Integer/Long wrapper objects.
+ *
+ * @since 2.1
+ * @param the type to set and get
+ * @version $Id: Mutable.java 1153213 2011-08-02 17:35:39Z ggregory $
+ */
+public interface Mutable {
+
+ /**
+ * Gets the value of this mutable.
+ *
+ * @return the stored value
+ */
+ T getValue();
+
+ /**
+ * Sets the value of this mutable.
+ *
+ * @param value
+ * the value to store
+ * @throws NullPointerException
+ * if the object is null and null is invalid
+ * @throws ClassCastException
+ * if the type is invalid
+ */
+ void setValue(T value);
+
+}
diff --git a/Bridge/src/main/apacheCommonsLang/external/org/apache/commons/lang3/mutable/MutableInt.java b/Bridge/src/main/apacheCommonsLang/external/org/apache/commons/lang3/mutable/MutableInt.java
new file mode 100644
index 00000000..d7e83d9b
--- /dev/null
+++ b/Bridge/src/main/apacheCommonsLang/external/org/apache/commons/lang3/mutable/MutableInt.java
@@ -0,0 +1,273 @@
+/*
+ * 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 external.org.apache.commons.lang3.mutable;
+
+/**
+ * A mutable int wrapper.
+ *
+ * Note that as MutableInt does not extend Integer, it is not treated by String.format as an Integer parameter.
+ *
+ * @see Integer
+ * @since 2.1
+ * @version $Id: MutableInt.java 1160571 2011-08-23 07:36:08Z bayard $
+ */
+public class MutableInt extends Number implements Comparable, Mutable {
+
+ /**
+ * Required for serialization support.
+ *
+ * @see java.io.Serializable
+ */
+ private static final long serialVersionUID = 512176391864L;
+
+ /** The mutable value. */
+ private int value;
+
+ /**
+ * Constructs a new MutableInt with the default value of zero.
+ */
+ public MutableInt() {
+ super();
+ }
+
+ /**
+ * Constructs a new MutableInt with the specified value.
+ *
+ * @param value the initial value to store
+ */
+ public MutableInt(int value) {
+ super();
+ this.value = value;
+ }
+
+ /**
+ * Constructs a new MutableInt with the specified value.
+ *
+ * @param value the initial value to store, not null
+ * @throws NullPointerException if the object is null
+ */
+ public MutableInt(Number value) {
+ super();
+ this.value = value.intValue();
+ }
+
+ /**
+ * Constructs a new MutableInt parsing the given string.
+ *
+ * @param value the string to parse, not null
+ * @throws NumberFormatException if the string cannot be parsed into an int
+ * @since 2.5
+ */
+ public MutableInt(String value) throws NumberFormatException {
+ super();
+ this.value = Integer.parseInt(value);
+ }
+
+ //-----------------------------------------------------------------------
+ /**
+ * Gets the value as a Integer instance.
+ *
+ * @return the value as a Integer, never null
+ */
+ public Integer getValue() {
+ return Integer.valueOf(this.value);
+ }
+
+ /**
+ * Sets the value.
+ *
+ * @param value the value to set
+ */
+ public void setValue(int value) {
+ this.value = value;
+ }
+
+ /**
+ * Sets the value from any Number instance.
+ *
+ * @param value the value to set, not null
+ * @throws NullPointerException if the object is null
+ */
+ public void setValue(Number value) {
+ this.value = value.intValue();
+ }
+
+ //-----------------------------------------------------------------------
+ /**
+ * Increments the value.
+ *
+ * @since Commons Lang 2.2
+ */
+ public void increment() {
+ value++;
+ }
+
+ /**
+ * Decrements the value.
+ *
+ * @since Commons Lang 2.2
+ */
+ public void decrement() {
+ value--;
+ }
+
+ //-----------------------------------------------------------------------
+ /**
+ * Adds a value to the value of this instance.
+ *
+ * @param operand the value to add, not null
+ * @since Commons Lang 2.2
+ */
+ public void add(int operand) {
+ this.value += operand;
+ }
+
+ /**
+ * Adds a value to the value of this instance.
+ *
+ * @param operand the value to add, not null
+ * @throws NullPointerException if the object is null
+ * @since Commons Lang 2.2
+ */
+ public void add(Number operand) {
+ this.value += operand.intValue();
+ }
+
+ /**
+ * Subtracts a value from the value of this instance.
+ *
+ * @param operand the value to subtract, not null
+ * @since Commons Lang 2.2
+ */
+ public void subtract(int operand) {
+ this.value -= operand;
+ }
+
+ /**
+ * Subtracts a value from the value of this instance.
+ *
+ * @param operand the value to subtract, not null
+ * @throws NullPointerException if the object is null
+ * @since Commons Lang 2.2
+ */
+ public void subtract(Number operand) {
+ this.value -= operand.intValue();
+ }
+
+ //-----------------------------------------------------------------------
+ // shortValue and byteValue rely on Number implementation
+ /**
+ * Returns the value of this MutableInt as an int.
+ *
+ * @return the numeric value represented by this object after conversion to type int.
+ */
+ @Override
+ public int intValue() {
+ return value;
+ }
+
+ /**
+ * Returns the value of this MutableInt as a long.
+ *
+ * @return the numeric value represented by this object after conversion to type long.
+ */
+ @Override
+ public long longValue() {
+ return value;
+ }
+
+ /**
+ * Returns the value of this MutableInt as a float.
+ *
+ * @return the numeric value represented by this object after conversion to type float.
+ */
+ @Override
+ public float floatValue() {
+ return value;
+ }
+
+ /**
+ * Returns the value of this MutableInt as a double.
+ *
+ * @return the numeric value represented by this object after conversion to type double.
+ */
+ @Override
+ public double doubleValue() {
+ return value;
+ }
+
+ //-----------------------------------------------------------------------
+ /**
+ * Gets this mutable as an instance of Integer.
+ *
+ * @return a Integer instance containing the value from this mutable, never null
+ */
+ public Integer toInteger() {
+ return Integer.valueOf(intValue());
+ }
+
+ //-----------------------------------------------------------------------
+ /**
+ * Compares this object to the specified object. The result is true if and only if the argument is
+ * not null and is a MutableInt object that contains the same int value
+ * as this object.
+ *
+ * @param obj the object to compare with, null returns false
+ * @return true if the objects are the same; false otherwise.
+ */
+ @Override
+ public boolean equals(Object obj) {
+ if (obj instanceof MutableInt) {
+ return value == ((MutableInt) obj).intValue();
+ }
+ return false;
+ }
+
+ /**
+ * Returns a suitable hash code for this mutable.
+ *
+ * @return a suitable hash code
+ */
+ @Override
+ public int hashCode() {
+ return value;
+ }
+
+ //-----------------------------------------------------------------------
+ /**
+ * Compares this mutable to another in ascending order.
+ *
+ * @param other the other mutable to compare to, not null
+ * @return negative if this is less, zero if equal, positive if greater
+ */
+ public int compareTo(MutableInt other) {
+ int anotherVal = other.value;
+ return value < anotherVal ? -1 : (value == anotherVal ? 0 : 1);
+ }
+
+ //-----------------------------------------------------------------------
+ /**
+ * Returns the String value of this mutable.
+ *
+ * @return the mutable value as a string
+ */
+ @Override
+ public String toString() {
+ return String.valueOf(value);
+ }
+
+}
diff --git a/Bridge/src/main/apacheCommonsLang/external/org/apache/commons/lang3/mutable/package.html b/Bridge/src/main/apacheCommonsLang/external/org/apache/commons/lang3/mutable/package.html
new file mode 100644
index 00000000..2f7436af
--- /dev/null
+++ b/Bridge/src/main/apacheCommonsLang/external/org/apache/commons/lang3/mutable/package.html
@@ -0,0 +1,29 @@
+
+
+
+
+
+
+
+Provides typed mutable wrappers to primitive values and Object.
+@since 2.1
+These classes are not thread-safe.
+
+
diff --git a/Bridge/src/main/apacheCommonsLang/external/org/apache/commons/lang3/overview.html b/Bridge/src/main/apacheCommonsLang/external/org/apache/commons/lang3/overview.html
new file mode 100644
index 00000000..f8433aae
--- /dev/null
+++ b/Bridge/src/main/apacheCommonsLang/external/org/apache/commons/lang3/overview.html
@@ -0,0 +1,23 @@
+
+
+
+
+This document is the API specification for the Apache Commons Lang library.
+
+
+
diff --git a/Bridge/src/main/apacheCommonsLang/external/org/apache/commons/lang3/package.html b/Bridge/src/main/apacheCommonsLang/external/org/apache/commons/lang3/package.html
new file mode 100644
index 00000000..1625c62d
--- /dev/null
+++ b/Bridge/src/main/apacheCommonsLang/external/org/apache/commons/lang3/package.html
@@ -0,0 +1,25 @@
+
+
+
+Provides highly reusable static utility methods, chiefly concerned
+with adding value to the {@link java.lang} classes.
+@since 1.0
+Most of these classes are immutable and thus thread-safe.
+However Charset is not currently guaranteed thread-safe under all circumstances.
+
+
diff --git a/Bridge/src/main/apacheCommonsLang/external/org/apache/commons/lang3/reflect/MemberUtils.java b/Bridge/src/main/apacheCommonsLang/external/org/apache/commons/lang3/reflect/MemberUtils.java
new file mode 100644
index 00000000..c268fd76
--- /dev/null
+++ b/Bridge/src/main/apacheCommonsLang/external/org/apache/commons/lang3/reflect/MemberUtils.java
@@ -0,0 +1,185 @@
+/*
+ * 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 external.org.apache.commons.lang3.reflect;
+
+import java.lang.reflect.AccessibleObject;
+import java.lang.reflect.Member;
+import java.lang.reflect.Modifier;
+
+import external.org.apache.commons.lang3.ClassUtils;
+
+/**
+ * Contains common code for working with Methods/Constructors, extracted and
+ * refactored from MethodUtils when it was imported from Commons
+ * BeanUtils.
+ *
+ * @since 2.5
+ * @version $Id: MemberUtils.java 1143537 2011-07-06 19:30:22Z joehni $
+ */
+public abstract class MemberUtils {
+ // TODO extract an interface to implement compareParameterSets(...)?
+
+ private static final int ACCESS_TEST = Modifier.PUBLIC | Modifier.PROTECTED | Modifier.PRIVATE;
+
+ /** Array of primitive number types ordered by "promotability" */
+ private static final Class>[] ORDERED_PRIMITIVE_TYPES = { Byte.TYPE, Short.TYPE,
+ Character.TYPE, Integer.TYPE, Long.TYPE, Float.TYPE, Double.TYPE };
+
+ /**
+ * XXX Default access superclass workaround
+ *
+ * When a public class has a default access superclass with public members,
+ * these members are accessible. Calling them from compiled code works fine.
+ * Unfortunately, on some JVMs, using reflection to invoke these members
+ * seems to (wrongly) prevent access even when the modifier is public.
+ * Calling setAccessible(true) solves the problem but will only work from
+ * sufficiently privileged code. Better workarounds would be gratefully
+ * accepted.
+ * @param o the AccessibleObject to set as accessible
+ */
+ static void setAccessibleWorkaround(AccessibleObject o) {
+ if (o == null || o.isAccessible()) {
+ return;
+ }
+ Member m = (Member) o;
+ if (Modifier.isPublic(m.getModifiers())
+ && isPackageAccess(m.getDeclaringClass().getModifiers())) {
+ try {
+ o.setAccessible(true);
+ } catch (SecurityException e) { // NOPMD
+ // ignore in favor of subsequent IllegalAccessException
+ }
+ }
+ }
+
+ /**
+ * Returns whether a given set of modifiers implies package access.
+ * @param modifiers to test
+ * @return true unless package/protected/private modifier detected
+ */
+ static boolean isPackageAccess(int modifiers) {
+ return (modifiers & ACCESS_TEST) == 0;
+ }
+
+ /**
+ * Returns whether a Member is accessible.
+ * @param m Member to check
+ * @return true if m is accessible
+ */
+ static boolean isAccessible(Member m) {
+ return m != null && Modifier.isPublic(m.getModifiers()) && !m.isSynthetic();
+ }
+
+ /**
+ * Compares the relative fitness of two sets of parameter types in terms of
+ * matching a third set of runtime parameter types, such that a list ordered
+ * by the results of the comparison would return the best match first
+ * (least).
+ *
+ * @param left the "left" parameter set
+ * @param right the "right" parameter set
+ * @param actual the runtime parameter types to match against
+ * left/right
+ * @return int consistent with compare semantics
+ */
+ public static int compareParameterTypes(Class>[] left, Class>[] right, Class>[] actual) {
+ float leftCost = getTotalTransformationCost(actual, left);
+ float rightCost = getTotalTransformationCost(actual, right);
+ return leftCost < rightCost ? -1 : rightCost < leftCost ? 1 : 0;
+ }
+
+ /**
+ * Returns the sum of the object transformation cost for each class in the
+ * source argument list.
+ * @param srcArgs The source arguments
+ * @param destArgs The destination arguments
+ * @return The total transformation cost
+ */
+ private static float getTotalTransformationCost(Class>[] srcArgs, Class>[] destArgs) {
+ float totalCost = 0.0f;
+ for (int i = 0; i < srcArgs.length; i++) {
+ Class> srcClass, destClass;
+ srcClass = srcArgs[i];
+ destClass = destArgs[i];
+ totalCost += getObjectTransformationCost(srcClass, destClass);
+ }
+ return totalCost;
+ }
+
+ /**
+ * Gets the number of steps required needed to turn the source class into
+ * the destination class. This represents the number of steps in the object
+ * hierarchy graph.
+ * @param srcClass The source class
+ * @param destClass The destination class
+ * @return The cost of transforming an object
+ */
+ private static float getObjectTransformationCost(Class> srcClass, Class> destClass) {
+ if (destClass.isPrimitive()) {
+ return getPrimitivePromotionCost(srcClass, destClass);
+ }
+ float cost = 0.0f;
+ while (srcClass != null && !destClass.equals(srcClass)) {
+ if (destClass.isInterface() && ClassUtils.isAssignable(srcClass, destClass)) {
+ // slight penalty for interface match.
+ // we still want an exact match to override an interface match,
+ // but
+ // an interface match should override anything where we have to
+ // get a superclass.
+ cost += 0.25f;
+ break;
+ }
+ cost++;
+ srcClass = srcClass.getSuperclass();
+ }
+ /*
+ * If the destination class is null, we've travelled all the way up to
+ * an Object match. We'll penalize this by adding 1.5 to the cost.
+ */
+ if (srcClass == null) {
+ cost += 1.5f;
+ }
+ return cost;
+ }
+
+ /**
+ * Gets the number of steps required to promote a primitive number to another
+ * type.
+ * @param srcClass the (primitive) source class
+ * @param destClass the (primitive) destination class
+ * @return The cost of promoting the primitive
+ */
+ private static float getPrimitivePromotionCost(final Class> srcClass, final Class> destClass) {
+ float cost = 0.0f;
+ Class> cls = srcClass;
+ if (!cls.isPrimitive()) {
+ // slight unwrapping penalty
+ cost += 0.1f;
+ cls = ClassUtils.wrapperToPrimitive(cls);
+ }
+ for (int i = 0; cls != destClass && i < ORDERED_PRIMITIVE_TYPES.length; i++) {
+ if (cls == ORDERED_PRIMITIVE_TYPES[i]) {
+ cost += 0.1f;
+ if (i < ORDERED_PRIMITIVE_TYPES.length - 1) {
+ cls = ORDERED_PRIMITIVE_TYPES[i + 1];
+ }
+ }
+ }
+ return cost;
+ }
+
+}
diff --git a/Bridge/src/main/apacheCommonsLang/external/org/apache/commons/lang3/reflect/MethodUtils.java b/Bridge/src/main/apacheCommonsLang/external/org/apache/commons/lang3/reflect/MethodUtils.java
new file mode 100644
index 00000000..d72a1ebe
--- /dev/null
+++ b/Bridge/src/main/apacheCommonsLang/external/org/apache/commons/lang3/reflect/MethodUtils.java
@@ -0,0 +1,537 @@
+/*
+ * 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 external.org.apache.commons.lang3.reflect;
+
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.lang.reflect.Modifier;
+
+import external.org.apache.commons.lang3.ArrayUtils;
+import external.org.apache.commons.lang3.ClassUtils;
+
+/**
+ * Utility reflection methods focused on methods, originally from Commons BeanUtils.
+ * Differences from the BeanUtils version may be noted, especially where similar functionality
+ * already existed within Lang.
+ *
+ *
+ * Known Limitations
+ * Accessing Public Methods In A Default Access Superclass
+ * There is an issue when invoking public methods contained in a default access superclass on JREs prior to 1.4.
+ * Reflection locates these methods fine and correctly assigns them as public.
+ * However, an IllegalAccessException is thrown if the method is invoked.
+ *
+ * MethodUtils contains a workaround for this situation.
+ * It will attempt to call setAccessible on this method.
+ * If this call succeeds, then the method can be invoked as normal.
+ * This call will only succeed when the application has sufficient security privileges.
+ * If this call fails then the method may fail.
+ *
+ * @since 2.5
+ * @version $Id: MethodUtils.java 1166253 2011-09-07 16:27:42Z ggregory $
+ */
+public class MethodUtils {
+
+ /**
+ * MethodUtils instances should NOT be constructed in standard programming.
+ * Instead, the class should be used as
+ * MethodUtils.getAccessibleMethod(method).
+ *
+ * This constructor is public to permit tools that require a JavaBean
+ * instance to operate.
+ */
+ public MethodUtils() {
+ super();
+ }
+
+ /**
+ * Invokes a named method whose parameter type matches the object type.
+ *
+ * This method delegates the method search to {@link #getMatchingAccessibleMethod(Class, String, Class[])}.
+ *
+ * This method supports calls to methods taking primitive parameters
+ * via passing in wrapping classes. So, for example, a Boolean object
+ * would match a boolean primitive.
+ *
+ * This is a convenient wrapper for
+ * {@link #invokeMethod(Object object,String methodName, Object[] args, Class[] parameterTypes)}.
+ *
+ *
+ * @param object invoke method on this object
+ * @param methodName get method with this name
+ * @param args use these arguments - treat null as empty array
+ * @return The value returned by the invoked method
+ *
+ * @throws NoSuchMethodException if there is no such accessible method
+ * @throws InvocationTargetException wraps an exception thrown by the method invoked
+ * @throws IllegalAccessException if the requested method is not accessible via reflection
+ */
+ public static Object invokeMethod(Object object, String methodName,
+ Object... args) throws NoSuchMethodException,
+ IllegalAccessException, InvocationTargetException {
+ if (args == null) {
+ args = ArrayUtils.EMPTY_OBJECT_ARRAY;
+ }
+ int arguments = args.length;
+ Class>[] parameterTypes = new Class[arguments];
+ for (int i = 0; i < arguments; i++) {
+ parameterTypes[i] = args[i].getClass();
+ }
+ return invokeMethod(object, methodName, args, parameterTypes);
+ }
+
+ /**
+ * Invokes a named method whose parameter type matches the object type.
+ *
+ * This method delegates the method search to {@link #getMatchingAccessibleMethod(Class, String, Class[])}.
+ *
+ * This method supports calls to methods taking primitive parameters
+ * via passing in wrapping classes. So, for example, a Boolean object
+ * would match a boolean primitive.
+ *
+ * @param object invoke method on this object
+ * @param methodName get method with this name
+ * @param args use these arguments - treat null as empty array
+ * @param parameterTypes match these parameters - treat null as empty array
+ * @return The value returned by the invoked method
+ *
+ * @throws NoSuchMethodException if there is no such accessible method
+ * @throws InvocationTargetException wraps an exception thrown by the method invoked
+ * @throws IllegalAccessException if the requested method is not accessible via reflection
+ */
+ public static Object invokeMethod(Object object, String methodName,
+ Object[] args, Class>[] parameterTypes)
+ throws NoSuchMethodException, IllegalAccessException,
+ InvocationTargetException {
+ if (parameterTypes == null) {
+ parameterTypes = ArrayUtils.EMPTY_CLASS_ARRAY;
+ }
+ if (args == null) {
+ args = ArrayUtils.EMPTY_OBJECT_ARRAY;
+ }
+ Method method = getMatchingAccessibleMethod(object.getClass(),
+ methodName, parameterTypes);
+ if (method == null) {
+ throw new NoSuchMethodException("No such accessible method: "
+ + methodName + "() on object: "
+ + object.getClass().getName());
+ }
+ return method.invoke(object, args);
+ }
+
+ /**
+ * Invokes a method whose parameter types match exactly the object
+ * types.
+ *
+ * This uses reflection to invoke the method obtained from a call to
+ * getAccessibleMethod().
+ *
+ * @param object invoke method on this object
+ * @param methodName get method with this name
+ * @param args use these arguments - treat null as empty array
+ * @return The value returned by the invoked method
+ *
+ * @throws NoSuchMethodException if there is no such accessible method
+ * @throws InvocationTargetException wraps an exception thrown by the
+ * method invoked
+ * @throws IllegalAccessException if the requested method is not accessible
+ * via reflection
+ */
+ public static Object invokeExactMethod(Object object, String methodName,
+ Object... args) throws NoSuchMethodException,
+ IllegalAccessException, InvocationTargetException {
+ if (args == null) {
+ args = ArrayUtils.EMPTY_OBJECT_ARRAY;
+ }
+ int arguments = args.length;
+ Class>[] parameterTypes = new Class[arguments];
+ for (int i = 0; i < arguments; i++) {
+ parameterTypes[i] = args[i].getClass();
+ }
+ return invokeExactMethod(object, methodName, args, parameterTypes);
+ }
+
+ /**
+ * Invokes a method whose parameter types match exactly the parameter
+ * types given.
+ *
+ * This uses reflection to invoke the method obtained from a call to
+ * getAccessibleMethod().
+ *
+ * @param object invoke method on this object
+ * @param methodName get method with this name
+ * @param args use these arguments - treat null as empty array
+ * @param parameterTypes match these parameters - treat null as empty array
+ * @return The value returned by the invoked method
+ *
+ * @throws NoSuchMethodException if there is no such accessible method
+ * @throws InvocationTargetException wraps an exception thrown by the
+ * method invoked
+ * @throws IllegalAccessException if the requested method is not accessible
+ * via reflection
+ */
+ public static Object invokeExactMethod(Object object, String methodName,
+ Object[] args, Class>[] parameterTypes)
+ throws NoSuchMethodException, IllegalAccessException,
+ InvocationTargetException {
+ if (args == null) {
+ args = ArrayUtils.EMPTY_OBJECT_ARRAY;
+ }
+ if (parameterTypes == null) {
+ parameterTypes = ArrayUtils.EMPTY_CLASS_ARRAY;
+ }
+ Method method = getAccessibleMethod(object.getClass(), methodName,
+ parameterTypes);
+ if (method == null) {
+ throw new NoSuchMethodException("No such accessible method: "
+ + methodName + "() on object: "
+ + object.getClass().getName());
+ }
+ return method.invoke(object, args);
+ }
+
+ /**
+ * Invokes a static method whose parameter types match exactly the parameter
+ * types given.
+ *
+ * This uses reflection to invoke the method obtained from a call to
+ * {@link #getAccessibleMethod(Class, String, Class[])}.
+ *
+ * @param cls invoke static method on this class
+ * @param methodName get method with this name
+ * @param args use these arguments - treat null as empty array
+ * @param parameterTypes match these parameters - treat null as empty array
+ * @return The value returned by the invoked method
+ *
+ * @throws NoSuchMethodException if there is no such accessible method
+ * @throws InvocationTargetException wraps an exception thrown by the
+ * method invoked
+ * @throws IllegalAccessException if the requested method is not accessible
+ * via reflection
+ */
+ public static Object invokeExactStaticMethod(Class> cls, String methodName,
+ Object[] args, Class>[] parameterTypes)
+ throws NoSuchMethodException, IllegalAccessException,
+ InvocationTargetException {
+ if (args == null) {
+ args = ArrayUtils.EMPTY_OBJECT_ARRAY;
+ }
+ if (parameterTypes == null) {
+ parameterTypes = ArrayUtils.EMPTY_CLASS_ARRAY;
+ }
+ Method method = getAccessibleMethod(cls, methodName, parameterTypes);
+ if (method == null) {
+ throw new NoSuchMethodException("No such accessible method: "
+ + methodName + "() on class: " + cls.getName());
+ }
+ return method.invoke(null, args);
+ }
+
+ /**
+ * Invokes a named static method whose parameter type matches the object type.
+ *
+ * This method delegates the method search to {@link #getMatchingAccessibleMethod(Class, String, Class[])}.
+ *
+ * This method supports calls to methods taking primitive parameters
+ * via passing in wrapping classes. So, for example, a Boolean class
+ * would match a boolean primitive.
+ *
+ * This is a convenient wrapper for
+ * {@link #invokeStaticMethod(Class objectClass,String methodName,Object [] args,Class[] parameterTypes)}.
+ *
+ *
+ * @param cls invoke static method on this class
+ * @param methodName get method with this name
+ * @param args use these arguments - treat null as empty array
+ * @return The value returned by the invoked method
+ *
+ * @throws NoSuchMethodException if there is no such accessible method
+ * @throws InvocationTargetException wraps an exception thrown by the
+ * method invoked
+ * @throws IllegalAccessException if the requested method is not accessible
+ * via reflection
+ */
+ public static Object invokeStaticMethod(Class> cls, String methodName,
+ Object... args) throws NoSuchMethodException,
+ IllegalAccessException, InvocationTargetException {
+ if (args == null) {
+ args = ArrayUtils.EMPTY_OBJECT_ARRAY;
+ }
+ int arguments = args.length;
+ Class>[] parameterTypes = new Class[arguments];
+ for (int i = 0; i < arguments; i++) {
+ parameterTypes[i] = args[i].getClass();
+ }
+ return invokeStaticMethod(cls, methodName, args, parameterTypes);
+ }
+
+ /**
+ * Invokes a named static method whose parameter type matches the object type.
+ *
+ * This method delegates the method search to {@link #getMatchingAccessibleMethod(Class, String, Class[])}.
+ *
+ * This method supports calls to methods taking primitive parameters
+ * via passing in wrapping classes. So, for example, a Boolean class
+ * would match a boolean primitive.
+ *
+ *
+ * @param cls invoke static method on this class
+ * @param methodName get method with this name
+ * @param args use these arguments - treat null as empty array
+ * @param parameterTypes match these parameters - treat null as empty array
+ * @return The value returned by the invoked method
+ *
+ * @throws NoSuchMethodException if there is no such accessible method
+ * @throws InvocationTargetException wraps an exception thrown by the
+ * method invoked
+ * @throws IllegalAccessException if the requested method is not accessible
+ * via reflection
+ */
+ public static Object invokeStaticMethod(Class> cls, String methodName,
+ Object[] args, Class>[] parameterTypes)
+ throws NoSuchMethodException, IllegalAccessException,
+ InvocationTargetException {
+ if (parameterTypes == null) {
+ parameterTypes = ArrayUtils.EMPTY_CLASS_ARRAY;
+ }
+ if (args == null) {
+ args = ArrayUtils.EMPTY_OBJECT_ARRAY;
+ }
+ Method method = getMatchingAccessibleMethod(cls, methodName,
+ parameterTypes);
+ if (method == null) {
+ throw new NoSuchMethodException("No such accessible method: "
+ + methodName + "() on class: " + cls.getName());
+ }
+ return method.invoke(null, args);
+ }
+
+ /**
+ * Invokes a static method whose parameter types match exactly the object
+ * types.
+ *
+ * This uses reflection to invoke the method obtained from a call to
+ * {@link #getAccessibleMethod(Class, String, Class[])}.
+ *
+ * @param cls invoke static method on this class
+ * @param methodName get method with this name
+ * @param args use these arguments - treat null as empty array
+ * @return The value returned by the invoked method
+ *
+ * @throws NoSuchMethodException if there is no such accessible method
+ * @throws InvocationTargetException wraps an exception thrown by the
+ * method invoked
+ * @throws IllegalAccessException if the requested method is not accessible
+ * via reflection
+ */
+ public static Object invokeExactStaticMethod(Class> cls, String methodName,
+ Object... args) throws NoSuchMethodException,
+ IllegalAccessException, InvocationTargetException {
+ if (args == null) {
+ args = ArrayUtils.EMPTY_OBJECT_ARRAY;
+ }
+ int arguments = args.length;
+ Class>[] parameterTypes = new Class[arguments];
+ for (int i = 0; i < arguments; i++) {
+ parameterTypes[i] = args[i].getClass();
+ }
+ return invokeExactStaticMethod(cls, methodName, args, parameterTypes);
+ }
+
+ /**
+ * Returns an accessible method (that is, one that can be invoked via
+ * reflection) with given name and parameters. If no such method
+ * can be found, return null.
+ * This is just a convenient wrapper for
+ * {@link #getAccessibleMethod(Method method)}.
+ *
+ * @param cls get method from this class
+ * @param methodName get method with this name
+ * @param parameterTypes with these parameters types
+ * @return The accessible method
+ */
+ public static Method getAccessibleMethod(Class> cls, String methodName,
+ Class>... parameterTypes) {
+ try {
+ return getAccessibleMethod(cls.getMethod(methodName,
+ parameterTypes));
+ } catch (NoSuchMethodException e) {
+ return null;
+ }
+ }
+
+ /**
+ * Returns an accessible method (that is, one that can be invoked via
+ * reflection) that implements the specified Method. If no such method
+ * can be found, return null.
+ *
+ * @param method The method that we wish to call
+ * @return The accessible method
+ */
+ public static Method getAccessibleMethod(Method method) {
+ if (!MemberUtils.isAccessible(method)) {
+ return null;
+ }
+ // If the declaring class is public, we are done
+ Class> cls = method.getDeclaringClass();
+ if (Modifier.isPublic(cls.getModifiers())) {
+ return method;
+ }
+ String methodName = method.getName();
+ Class>[] parameterTypes = method.getParameterTypes();
+
+ // Check the implemented interfaces and subinterfaces
+ method = getAccessibleMethodFromInterfaceNest(cls, methodName,
+ parameterTypes);
+
+ // Check the superclass chain
+ if (method == null) {
+ method = getAccessibleMethodFromSuperclass(cls, methodName,
+ parameterTypes);
+ }
+ return method;
+ }
+
+ /**
+ * Returns an accessible method (that is, one that can be invoked via
+ * reflection) by scanning through the superclasses. If no such method
+ * can be found, return null.
+ *
+ * @param cls Class to be checked
+ * @param methodName Method name of the method we wish to call
+ * @param parameterTypes The parameter type signatures
+ * @return the accessible method or null if not found
+ */
+ private static Method getAccessibleMethodFromSuperclass(Class> cls,
+ String methodName, Class>... parameterTypes) {
+ Class> parentClass = cls.getSuperclass();
+ while (parentClass != null) {
+ if (Modifier.isPublic(parentClass.getModifiers())) {
+ try {
+ return parentClass.getMethod(methodName, parameterTypes);
+ } catch (NoSuchMethodException e) {
+ return null;
+ }
+ }
+ parentClass = parentClass.getSuperclass();
+ }
+ return null;
+ }
+
+ /**
+ * Returns an accessible method (that is, one that can be invoked via
+ * reflection) that implements the specified method, by scanning through
+ * all implemented interfaces and subinterfaces. If no such method
+ * can be found, return null.
+ *
+ * There isn't any good reason why this method must be private.
+ * It is because there doesn't seem any reason why other classes should
+ * call this rather than the higher level methods.
+ *
+ * @param cls Parent class for the interfaces to be checked
+ * @param methodName Method name of the method we wish to call
+ * @param parameterTypes The parameter type signatures
+ * @return the accessible method or null if not found
+ */
+ private static Method getAccessibleMethodFromInterfaceNest(Class> cls,
+ String methodName, Class>... parameterTypes) {
+ Method method = null;
+
+ // Search up the superclass chain
+ for (; cls != null; cls = cls.getSuperclass()) {
+
+ // Check the implemented interfaces of the parent class
+ Class>[] interfaces = cls.getInterfaces();
+ for (int i = 0; i < interfaces.length; i++) {
+ // Is this interface public?
+ if (!Modifier.isPublic(interfaces[i].getModifiers())) {
+ continue;
+ }
+ // Does the method exist on this interface?
+ try {
+ method = interfaces[i].getDeclaredMethod(methodName,
+ parameterTypes);
+ } catch (NoSuchMethodException e) { // NOPMD
+ /*
+ * Swallow, if no method is found after the loop then this
+ * method returns null.
+ */
+ }
+ if (method != null) {
+ break;
+ }
+ // Recursively check our parent interfaces
+ method = getAccessibleMethodFromInterfaceNest(interfaces[i],
+ methodName, parameterTypes);
+ if (method != null) {
+ break;
+ }
+ }
+ }
+ return method;
+ }
+
+ /**
+ * Finds an accessible method that matches the given name and has compatible parameters.
+ * Compatible parameters mean that every method parameter is assignable from
+ * the given parameters.
+ * In other words, it finds a method with the given name
+ * that will take the parameters given.
+ *
+ *
This method is used by
+ * {@link
+ * #invokeMethod(Object object, String methodName, Object[] args, Class[] parameterTypes)}.
+ *
+ *
This method can match primitive parameter by passing in wrapper classes.
+ * For example, a Boolean will match a primitive boolean
+ * parameter.
+ *
+ * @param cls find method in this class
+ * @param methodName find method with this name
+ * @param parameterTypes find method with most compatible parameters
+ * @return The accessible method
+ */
+ public static Method getMatchingAccessibleMethod(Class> cls,
+ String methodName, Class>... parameterTypes) {
+ try {
+ Method method = cls.getMethod(methodName, parameterTypes);
+ MemberUtils.setAccessibleWorkaround(method);
+ return method;
+ } catch (NoSuchMethodException e) { // NOPMD - Swallow the exception
+ }
+ // search through all methods
+ Method bestMatch = null;
+ Method[] methods = cls.getMethods();
+ for (Method method : methods) {
+ // compare name and parameters
+ if (method.getName().equals(methodName) && ClassUtils.isAssignable(parameterTypes, method.getParameterTypes(), true)) {
+ // get accessible version of method
+ Method accessibleMethod = getAccessibleMethod(method);
+ if (accessibleMethod != null && (bestMatch == null || MemberUtils.compareParameterTypes(
+ accessibleMethod.getParameterTypes(),
+ bestMatch.getParameterTypes(),
+ parameterTypes) < 0)) {
+ bestMatch = accessibleMethod;
+ }
+ }
+ }
+ if (bestMatch != null) {
+ MemberUtils.setAccessibleWorkaround(bestMatch);
+ }
+ return bestMatch;
+ }
+}
diff --git a/Bridge/src/main/apacheCommonsLang/external/org/apache/commons/lang3/reflect/package.html b/Bridge/src/main/apacheCommonsLang/external/org/apache/commons/lang3/reflect/package.html
new file mode 100644
index 00000000..618b07a8
--- /dev/null
+++ b/Bridge/src/main/apacheCommonsLang/external/org/apache/commons/lang3/reflect/package.html
@@ -0,0 +1,29 @@
+
+
+
+
+
+
+
+Accumulates common high-level uses of the java.lang.reflect APIs.
+@since 3.0
+These classes are immutable, and therefore thread-safe.
+
+
diff --git a/Bridge/src/main/apacheCommonsLang/external/org/apache/commons/lang3/tuple/ImmutablePair.java b/Bridge/src/main/apacheCommonsLang/external/org/apache/commons/lang3/tuple/ImmutablePair.java
new file mode 100644
index 00000000..d3085be1
--- /dev/null
+++ b/Bridge/src/main/apacheCommonsLang/external/org/apache/commons/lang3/tuple/ImmutablePair.java
@@ -0,0 +1,103 @@
+/*
+ * 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 external.org.apache.commons.lang3.tuple;
+
+/**
+ * An immutable pair consisting of two {@code Object} elements.
+ *
+ * Although the implementation is immutable, there is no restriction on the objects
+ * that may be stored. If mutable objects are stored in the pair, then the pair
+ * itself effectively becomes mutable. The class is also not {@code final}, so a subclass
+ * could add undesirable behaviour.
+ *
+ * #ThreadSafe# if the objects are threadsafe
+ *
+ * @param the left element type
+ * @param the right element type
+ *
+ * @since Lang 3.0
+ * @version $Id: ImmutablePair.java 1127544 2011-05-25 14:35:42Z scolebourne $
+ */
+public final class ImmutablePair extends Pair {
+
+ /** Serialization version */
+ private static final long serialVersionUID = 4954918890077093841L;
+
+ /** Left object */
+ public final L left;
+ /** Right object */
+ public final R right;
+
+ /**
+ * Obtains an immutable pair of from two objects inferring the generic types.
+ *
+ * This factory allows the pair to be created using inference to
+ * obtain the generic types.
+ *
+ * @param the left element type
+ * @param the right element type
+ * @param left the left element, may be null
+ * @param right the right element, may be null
+ * @return a pair formed from the two parameters, not null
+ */
+ public static ImmutablePair of(L left, R right) {
+ return new ImmutablePair(left, right);
+ }
+
+ /**
+ * Create a new pair instance.
+ *
+ * @param left the left value, may be null
+ * @param right the right value, may be null
+ */
+ public ImmutablePair(L left, R right) {
+ super();
+ this.left = left;
+ this.right = right;
+ }
+
+ //-----------------------------------------------------------------------
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public L getLeft() {
+ return left;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public R getRight() {
+ return right;
+ }
+
+ /**
+ * Throws {@code UnsupportedOperationException}.
+ *
+ * This pair is immutable, so this operation is not supported.
+ *
+ * @param value the value to set
+ * @return never
+ * @throws UnsupportedOperationException as this operation is not supported
+ */
+ public R setValue(R value) {
+ throw new UnsupportedOperationException();
+ }
+
+}
diff --git a/Bridge/src/main/apacheCommonsLang/external/org/apache/commons/lang3/tuple/Pair.java b/Bridge/src/main/apacheCommonsLang/external/org/apache/commons/lang3/tuple/Pair.java
new file mode 100644
index 00000000..e43bd8ab
--- /dev/null
+++ b/Bridge/src/main/apacheCommonsLang/external/org/apache/commons/lang3/tuple/Pair.java
@@ -0,0 +1,177 @@
+/*
+ * 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 external.org.apache.commons.lang3.tuple;
+
+import java.io.Serializable;
+import java.util.Map;
+
+
+import external.org.apache.commons.lang3.ObjectUtils;
+import external.org.apache.commons.lang3.builder.CompareToBuilder;
+
+/**
+ * A pair consisting of two elements.
+ *
+ * This class is an abstract implementation defining the basic API.
+ * It refers to the elements as 'left' and 'right'. It also implements the
+ * {@code Map.Entry} interface where the key is 'left' and the value is 'right'.
+ *
+ * Subclass implementations may be mutable or immutable.
+ * However, there is no restriction on the type of the stored objects that may be stored.
+ * If mutable objects are stored in the pair, then the pair itself effectively becomes mutable.
+ *
+ * @param the left element type
+ * @param the right element type
+ *
+ * @since Lang 3.0
+ * @version $Id: Pair.java 1142401 2011-07-03 08:30:12Z bayard $
+ */
+public abstract class Pair implements Map.Entry, Comparable>, Serializable {
+
+ /** Serialization version */
+ private static final long serialVersionUID = 4954918890077093841L;
+
+ /**
+ * Obtains an immutable pair of from two objects inferring the generic types.
+ *
+ * This factory allows the pair to be created using inference to
+ * obtain the generic types.
+ *
+ * @param the left element type
+ * @param the right element type
+ * @param left the left element, may be null
+ * @param right the right element, may be null
+ * @return a pair formed from the two parameters, not null
+ */
+ public static Pair of(L left, R right) {
+ return new ImmutablePair(left, right);
+ }
+
+ //-----------------------------------------------------------------------
+ /**
+ * Gets the left element from this pair.
+ *
+ * When treated as a key-value pair, this is the key.
+ *
+ * @return the left element, may be null
+ */
+ public abstract L getLeft();
+
+ /**
+ * Gets the right element from this pair.
+ *
+ * When treated as a key-value pair, this is the value.
+ *
+ * @return the right element, may be null
+ */
+ public abstract R getRight();
+
+ /**
+ * Gets the key from this pair.
+ *
+ * This method implements the {@code Map.Entry} interface returning the
+ * left element as the key.
+ *
+ * @return the left element as the key, may be null
+ */
+ public final L getKey() {
+ return getLeft();
+ }
+
+ /**
+ * Gets the value from this pair.
+ *
+ * This method implements the {@code Map.Entry} interface returning the
+ * right element as the value.
+ *
+ * @return the right element as the value, may be null
+ */
+ public R getValue() {
+ return getRight();
+ }
+
+ //-----------------------------------------------------------------------
+ /**
+ * Compares the pair based on the left element followed by the right element.
+ * The types must be {@code Comparable}.
+ *
+ * @param other the other pair, not null
+ * @return negative if this is less, zero if equal, positive if greater
+ */
+ public int compareTo(Pair other) {
+ return new CompareToBuilder().append(getLeft(), other.getLeft())
+ .append(getRight(), other.getRight()).toComparison();
+ }
+
+ /**
+ * Compares this pair to another based on the two elements.
+ *
+ * @param obj the object to compare to, null returns false
+ * @return true if the elements of the pair are equal
+ */
+ @Override
+ public boolean equals(Object obj) {
+ if (obj == this) {
+ return true;
+ }
+ if (obj instanceof Map.Entry, ?>) {
+ Map.Entry, ?> other = (Map.Entry, ?>) obj;
+ return ObjectUtils.equals(getKey(), other.getKey())
+ && ObjectUtils.equals(getValue(), other.getValue());
+ }
+ return false;
+ }
+
+ /**
+ * Returns a suitable hash code.
+ * The hash code follows the definition in {@code Map.Entry}.
+ *
+ * @return the hash code
+ */
+ @Override
+ public int hashCode() {
+ // see Map.Entry API specification
+ return (getKey() == null ? 0 : getKey().hashCode()) ^
+ (getValue() == null ? 0 : getValue().hashCode());
+ }
+
+ /**
+ * Returns a String representation of this pair using the format {@code ($left,$right)}.
+ *
+ * @return a string describing this object, not null
+ */
+ @Override
+ public String toString() {
+ return new StringBuilder().append('(').append(getLeft()).append(',').append(getRight()).append(')').toString();
+ }
+
+ /**
+ * Formats the receiver using the given format.
+ *
+ * This uses {@link java.util.Formattable} to perform the formatting. Two variables may
+ * be used to embed the left and right elements. Use {@code %1$s} for the left
+ * element (key) and {@code %2$s} for the right element (value).
+ * The default format used by {@code toString()} is {@code (%1$s,%2$s)}.
+ *
+ * @param format the format string, optionally containing {@code %1$s} and {@code %2$s}, not null
+ * @return the formatted string, not null
+ */
+ public String toString(String format) {
+ return String.format(format, getLeft(), getRight());
+ }
+
+}
diff --git a/Bridge/src/main/apacheCommonsLang/external/org/apache/commons/lang3/tuple/package.html b/Bridge/src/main/apacheCommonsLang/external/org/apache/commons/lang3/tuple/package.html
new file mode 100644
index 00000000..db858539
--- /dev/null
+++ b/Bridge/src/main/apacheCommonsLang/external/org/apache/commons/lang3/tuple/package.html
@@ -0,0 +1,22 @@
+
+
+
+Tuple classes, starting with a Pair class in version 3.0.
+@since 3.0
+
+
diff --git a/Bridge/src/main/java/android/app/AndroidAppHelper.java b/Bridge/src/main/java/android/app/AndroidAppHelper.java
new file mode 100644
index 00000000..c9160c5a
--- /dev/null
+++ b/Bridge/src/main/java/android/app/AndroidAppHelper.java
@@ -0,0 +1,223 @@
+package android.app;
+
+import android.content.SharedPreferences;
+import android.content.pm.ApplicationInfo;
+import android.content.res.CompatibilityInfo;
+import android.content.res.Configuration;
+import android.content.res.Resources;
+import android.os.Build;
+import android.os.IBinder;
+import android.view.Display;
+
+import java.lang.ref.WeakReference;
+import java.util.Map;
+
+import de.robv.android.xposed.XSharedPreferences;
+import de.robv.android.xposed.XposedBridge;
+
+import static de.robv.android.xposed.XposedHelpers.findClass;
+import static de.robv.android.xposed.XposedHelpers.findFieldIfExists;
+import static de.robv.android.xposed.XposedHelpers.findMethodExactIfExists;
+import static de.robv.android.xposed.XposedHelpers.getObjectField;
+import static de.robv.android.xposed.XposedHelpers.newInstance;
+import static de.robv.android.xposed.XposedHelpers.setFloatField;
+
+/**
+ * Contains various methods for information about the current app.
+ *
+ * For historical reasons, this class is in the {@code android.app} package. It can't be moved
+ * without breaking compatibility with existing modules.
+ */
+public final class AndroidAppHelper {
+ private AndroidAppHelper() {}
+
+ private static final Class> CLASS_RESOURCES_KEY;
+ private static final boolean HAS_IS_THEMEABLE;
+ private static final boolean HAS_THEME_CONFIG_PARAMETER;
+
+ static {
+ CLASS_RESOURCES_KEY = (Build.VERSION.SDK_INT < 19) ?
+ findClass("android.app.ActivityThread$ResourcesKey", null)
+ : findClass("android.content.res.ResourcesKey", null);
+
+ HAS_IS_THEMEABLE = findFieldIfExists(CLASS_RESOURCES_KEY, "mIsThemeable") != null;
+ HAS_THEME_CONFIG_PARAMETER = HAS_IS_THEMEABLE && Build.VERSION.SDK_INT >= 21
+ && findMethodExactIfExists("android.app.ResourcesManager", null, "getThemeConfig") != null;
+ }
+
+ @SuppressWarnings({ "unchecked", "rawtypes" })
+ private static Map getResourcesMap(ActivityThread activityThread) {
+ if (Build.VERSION.SDK_INT >= 24) {
+ Object resourcesManager = getObjectField(activityThread, "mResourcesManager");
+ return (Map) getObjectField(resourcesManager, "mResourceImpls");
+ } else if (Build.VERSION.SDK_INT >= 19) {
+ Object resourcesManager = getObjectField(activityThread, "mResourcesManager");
+ return (Map) getObjectField(resourcesManager, "mActiveResources");
+ } else {
+ return (Map) getObjectField(activityThread, "mActiveResources");
+ }
+ }
+
+ /* For SDK 15 & 16 */
+ private static Object createResourcesKey(String resDir, float scale) {
+ try {
+ if (HAS_IS_THEMEABLE)
+ return newInstance(CLASS_RESOURCES_KEY, resDir, scale, false);
+ else
+ return newInstance(CLASS_RESOURCES_KEY, resDir, scale);
+ } catch (Throwable t) {
+ XposedBridge.log(t);
+ return null;
+ }
+ }
+
+ /* For SDK 17 & 18 & 23 */
+ private static Object createResourcesKey(String resDir, int displayId, Configuration overrideConfiguration, float scale) {
+ try {
+ if (HAS_THEME_CONFIG_PARAMETER)
+ return newInstance(CLASS_RESOURCES_KEY, resDir, displayId, overrideConfiguration, scale, false, null);
+ else if (HAS_IS_THEMEABLE)
+ return newInstance(CLASS_RESOURCES_KEY, resDir, displayId, overrideConfiguration, scale, false);
+ else
+ return newInstance(CLASS_RESOURCES_KEY, resDir, displayId, overrideConfiguration, scale);
+ } catch (Throwable t) {
+ XposedBridge.log(t);
+ return null;
+ }
+ }
+
+ /* For SDK 19 - 22 */
+ private static Object createResourcesKey(String resDir, int displayId, Configuration overrideConfiguration, float scale, IBinder token) {
+ try {
+ if (HAS_THEME_CONFIG_PARAMETER)
+ return newInstance(CLASS_RESOURCES_KEY, resDir, displayId, overrideConfiguration, scale, false, null, token);
+ else if (HAS_IS_THEMEABLE)
+ return newInstance(CLASS_RESOURCES_KEY, resDir, displayId, overrideConfiguration, scale, false, token);
+ else
+ return newInstance(CLASS_RESOURCES_KEY, resDir, displayId, overrideConfiguration, scale, token);
+ } catch (Throwable t) {
+ XposedBridge.log(t);
+ return null;
+ }
+ }
+
+ /* For SDK 24+ */
+ private static Object createResourcesKey(String resDir, String[] splitResDirs, String[] overlayDirs, String[] libDirs, int displayId, Configuration overrideConfiguration, CompatibilityInfo compatInfo) {
+ try {
+ return newInstance(CLASS_RESOURCES_KEY, resDir, splitResDirs, overlayDirs, libDirs, displayId, overrideConfiguration, compatInfo);
+ } catch (Throwable t) {
+ XposedBridge.log(t);
+ return null;
+ }
+ }
+
+ /** @hide */
+ public static void addActiveResource(String resDir, float scale, boolean isThemeable, Resources resources) {
+ addActiveResource(resDir, resources);
+ }
+
+ /** @hide */
+ public static void addActiveResource(String resDir, Resources resources) {
+ ActivityThread thread = ActivityThread.currentActivityThread();
+ if (thread == null) {
+ return;
+ }
+
+ Object resourcesKey;
+ if (Build.VERSION.SDK_INT >= 24) {
+ CompatibilityInfo compatInfo = (CompatibilityInfo) newInstance(CompatibilityInfo.class);
+ setFloatField(compatInfo, "applicationScale", resources.hashCode());
+ resourcesKey = createResourcesKey(resDir, null, null, null, Display.DEFAULT_DISPLAY, null, compatInfo);
+ } else if (Build.VERSION.SDK_INT == 23) {
+ resourcesKey = createResourcesKey(resDir, Display.DEFAULT_DISPLAY, null, resources.hashCode());
+ } else if (Build.VERSION.SDK_INT >= 19) {
+ resourcesKey = createResourcesKey(resDir, Display.DEFAULT_DISPLAY, null, resources.hashCode(), null);
+ } else if (Build.VERSION.SDK_INT >= 17) {
+ resourcesKey = createResourcesKey(resDir, Display.DEFAULT_DISPLAY, null, resources.hashCode());
+ } else {
+ resourcesKey = createResourcesKey(resDir, resources.hashCode());
+ }
+
+ if (resourcesKey != null) {
+ if (Build.VERSION.SDK_INT >= 24) {
+ Object resImpl = getObjectField(resources, "mResourcesImpl");
+ getResourcesMap(thread).put(resourcesKey, new WeakReference<>(resImpl));
+ } else {
+ getResourcesMap(thread).put(resourcesKey, new WeakReference<>(resources));
+ }
+ }
+ }
+
+ /**
+ * Returns the name of the current process. It's usually the same as the main package name.
+ */
+ public static String currentProcessName() {
+ String processName = ActivityThread.currentPackageName();
+ if (processName == null)
+ return "android";
+ return processName;
+ }
+
+ /**
+ * Returns information about the main application in the current process.
+ *
+ * In a few cases, multiple apps might run in the same process, e.g. the SystemUI and the
+ * Keyguard which both have {@code android:process="com.android.systemui"} set in their
+ * manifest. In those cases, the first application that was initialized will be returned.
+ */
+ public static ApplicationInfo currentApplicationInfo() {
+ ActivityThread am = ActivityThread.currentActivityThread();
+ if (am == null)
+ return null;
+
+ Object boundApplication = getObjectField(am, "mBoundApplication");
+ if (boundApplication == null)
+ return null;
+
+ return (ApplicationInfo) getObjectField(boundApplication, "appInfo");
+ }
+
+ /**
+ * Returns the Android package name of the main application in the current process.
+ *
+ *
In a few cases, multiple apps might run in the same process, e.g. the SystemUI and the
+ * Keyguard which both have {@code android:process="com.android.systemui"} set in their
+ * manifest. In those cases, the first application that was initialized will be returned.
+ */
+ public static String currentPackageName() {
+ ApplicationInfo ai = currentApplicationInfo();
+ return (ai != null) ? ai.packageName : "android";
+ }
+
+ /**
+ * Returns the main {@link android.app.Application} object in the current process.
+ *
+ *
In a few cases, multiple apps might run in the same process, e.g. the SystemUI and the
+ * Keyguard which both have {@code android:process="com.android.systemui"} set in their
+ * manifest. In those cases, the first application that was initialized will be returned.
+ */
+ public static Application currentApplication() {
+ return ActivityThread.currentApplication();
+ }
+
+ /** @deprecated Use {@link XSharedPreferences} instead. */
+ @SuppressWarnings("UnusedParameters")
+ @Deprecated
+ public static SharedPreferences getSharedPreferencesForPackage(String packageName, String prefFileName, int mode) {
+ return new XSharedPreferences(packageName, prefFileName);
+ }
+
+ /** @deprecated Use {@link XSharedPreferences} instead. */
+ @Deprecated
+ public static SharedPreferences getDefaultSharedPreferencesForPackage(String packageName) {
+ return new XSharedPreferences(packageName);
+ }
+
+ /** @deprecated Use {@link XSharedPreferences#reload} instead. */
+ @Deprecated
+ public static void reloadSharedPreferencesIfNeeded(SharedPreferences pref) {
+ if (pref instanceof XSharedPreferences) {
+ ((XSharedPreferences) pref).reload();
+ }
+ }
+}
diff --git a/Bridge/src/main/java/android/app/package-info.java b/Bridge/src/main/java/android/app/package-info.java
new file mode 100644
index 00000000..98b6207b
--- /dev/null
+++ b/Bridge/src/main/java/android/app/package-info.java
@@ -0,0 +1,4 @@
+/**
+ * Contains {@link android.app.AndroidAppHelper} with various methods for information about the current app.
+ */
+package android.app;
diff --git a/Bridge/src/main/java/android/content/res/XModuleResources.java b/Bridge/src/main/java/android/content/res/XModuleResources.java
new file mode 100644
index 00000000..57464b35
--- /dev/null
+++ b/Bridge/src/main/java/android/content/res/XModuleResources.java
@@ -0,0 +1,54 @@
+package android.content.res;
+
+import android.app.AndroidAppHelper;
+import android.util.DisplayMetrics;
+
+import de.robv.android.xposed.IXposedHookInitPackageResources;
+import de.robv.android.xposed.IXposedHookZygoteInit;
+import de.robv.android.xposed.IXposedHookZygoteInit.StartupParam;
+import de.robv.android.xposed.callbacks.XC_InitPackageResources.InitPackageResourcesParam;
+
+/**
+ * Provides access to resources from a certain path (usually the module's own path).
+ */
+public class XModuleResources extends Resources {
+ private XModuleResources(AssetManager assets, DisplayMetrics metrics, Configuration config) {
+ super(assets, metrics, config);
+ }
+
+ /**
+ * Creates a new instance.
+ *
+ *
This is usually called with {@link StartupParam#modulePath} from
+ * {@link IXposedHookZygoteInit#initZygote} and {@link InitPackageResourcesParam#res} from
+ * {@link IXposedHookInitPackageResources#handleInitPackageResources} (or {@code null} for
+ * system-wide replacements).
+ *
+ * @param path The path to the APK from which the resources should be loaded.
+ * @param origRes The resources object from which settings like the display metrics and the
+ * configuration should be copied. May be {@code null}.
+ */
+ public static XModuleResources createInstance(String path, XResources origRes) {
+ if (path == null)
+ throw new IllegalArgumentException("path must not be null");
+
+ AssetManager assets = new AssetManager();
+ assets.addAssetPath(path);
+
+ XModuleResources res;
+ if (origRes != null)
+ res = new XModuleResources(assets, origRes.getDisplayMetrics(), origRes.getConfiguration());
+ else
+ res = new XModuleResources(assets, null, null);
+
+ AndroidAppHelper.addActiveResource(path, res);
+ return res;
+ }
+
+ /**
+ * Creates an {@link XResForwarder} instance that forwards requests to {@code id} in this resource.
+ */
+ public XResForwarder fwd(int id) {
+ return new XResForwarder(this, id);
+ }
+}
diff --git a/Bridge/src/main/java/android/content/res/XResForwarder.java b/Bridge/src/main/java/android/content/res/XResForwarder.java
new file mode 100644
index 00000000..7d659052
--- /dev/null
+++ b/Bridge/src/main/java/android/content/res/XResForwarder.java
@@ -0,0 +1,34 @@
+package android.content.res;
+
+/**
+ * Instances of this class can be used for {@link XResources#setReplacement(String, String, String, Object)}
+ * and its variants. They forward the resource request to a different {@link android.content.res.Resources}
+ * instance with a possibly different ID.
+ *
+ *
Usually, instances aren't created directly but via {@link XModuleResources#fwd}.
+ */
+public class XResForwarder {
+ private final Resources res;
+ private final int id;
+
+ /**
+ * Creates a new instance.
+ *
+ * @param res The target {@link android.content.res.Resources} instance to forward requests to.
+ * @param id The target resource ID.
+ */
+ public XResForwarder(Resources res, int id) {
+ this.res = res;
+ this.id = id;
+ }
+
+ /** Returns the target {@link android.content.res.Resources} instance. */
+ public Resources getResources() {
+ return res;
+ }
+
+ /** Returns the target resource ID. */
+ public int getId() {
+ return id;
+ }
+}
diff --git a/Bridge/src/main/java/android/content/res/XResources.java b/Bridge/src/main/java/android/content/res/XResources.java
new file mode 100644
index 00000000..e8d67258
--- /dev/null
+++ b/Bridge/src/main/java/android/content/res/XResources.java
@@ -0,0 +1,1738 @@
+package android.content.res;
+
+import android.content.Context;
+import android.content.pm.PackageParser;
+import android.content.pm.PackageParser.PackageParserException;
+import android.graphics.Color;
+import android.graphics.Movie;
+import android.graphics.drawable.ColorDrawable;
+import android.graphics.drawable.Drawable;
+import android.os.Build;
+import android.text.Html;
+import android.util.AttributeSet;
+import android.util.DisplayMetrics;
+import android.util.Log;
+import android.util.SparseArray;
+import android.util.TypedValue;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+
+import org.xmlpull.v1.XmlPullParser;
+
+import java.io.File;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.LinkedList;
+import java.util.WeakHashMap;
+
+import de.robv.android.xposed.IXposedHookZygoteInit;
+import de.robv.android.xposed.XC_MethodHook;
+import de.robv.android.xposed.XC_MethodHook.MethodHookParam;
+import de.robv.android.xposed.XposedBridge;
+import de.robv.android.xposed.XposedBridge.CopyOnWriteSortedSet;
+import de.robv.android.xposed.callbacks.XC_LayoutInflated;
+import de.robv.android.xposed.callbacks.XC_LayoutInflated.LayoutInflatedParam;
+import de.robv.android.xposed.callbacks.XCallback;
+import xposed.dummy.XResourcesSuperClass;
+import xposed.dummy.XTypedArraySuperClass;
+
+import static de.robv.android.xposed.XposedHelpers.decrementMethodDepth;
+import static de.robv.android.xposed.XposedHelpers.findAndHookMethod;
+import static de.robv.android.xposed.XposedHelpers.getIntField;
+import static de.robv.android.xposed.XposedHelpers.getLongField;
+import static de.robv.android.xposed.XposedHelpers.getObjectField;
+import static de.robv.android.xposed.XposedHelpers.incrementMethodDepth;
+
+/**
+ * {@link android.content.res.Resources} subclass that allows replacing individual resources.
+ *
+ *
Xposed replaces the standard resources with this class, which overrides the methods used for
+ * retrieving individual resources and adds possibilities to replace them. These replacements can
+ * be set using the methods made available via the API methods in this class.
+ */
+@SuppressWarnings("JniMissingFunction")
+public class XResources extends XResourcesSuperClass {
+ private static final SparseArray> sReplacements = new SparseArray<>();
+ private static final SparseArray> sResourceNames = new SparseArray<>();
+
+ private static final byte[] sSystemReplacementsCache = new byte[256]; // bitmask: 0x000700ff => 2048 bit => 256 bytes
+ private byte[] mReplacementsCache; // bitmask: 0x0007007f => 1024 bit => 128 bytes
+ private static final HashMap sReplacementsCacheMap = new HashMap<>();
+ private static final SparseArray sColorStateListCache = new SparseArray<>(0);
+
+ private static final SparseArray>> sLayoutCallbacks = new SparseArray<>();
+ private static final WeakHashMap sXmlInstanceDetails = new WeakHashMap<>();
+
+ private static final String EXTRA_XML_INSTANCE_DETAILS = "xmlInstanceDetails";
+ private static final ThreadLocal> sIncludedLayouts = new ThreadLocal>() {
+ @Override
+ protected LinkedList initialValue() {
+ return new LinkedList<>();
+ }
+ };
+
+ private static final HashMap sResDirLastModified = new HashMap<>();
+ private static final HashMap sResDirPackageNames = new HashMap<>();
+ private static ThreadLocal sLatestResKey = null;
+
+ private boolean mIsObjectInited;
+ private String mResDir;
+ private String mPackageName;
+
+ /** Dummy, will never be called (objects are transferred to this class only). */
+ private XResources() {
+ throw new UnsupportedOperationException();
+ }
+
+ /** @hide */
+ public void initObject(String resDir) {
+ if (mIsObjectInited)
+ throw new IllegalStateException("Object has already been initialized");
+
+ this.mResDir = resDir;
+ this.mPackageName = getPackageName(resDir);
+
+ if (resDir != null) {
+ synchronized (sReplacementsCacheMap) {
+ mReplacementsCache = sReplacementsCacheMap.get(resDir);
+ if (mReplacementsCache == null) {
+ mReplacementsCache = new byte[128];
+ sReplacementsCacheMap.put(resDir, mReplacementsCache);
+ }
+ }
+ }
+
+ this.mIsObjectInited = true;
+ }
+
+ /** @hide */
+ public boolean isFirstLoad() {
+ synchronized (sReplacements) {
+ if (mResDir == null)
+ return false;
+
+ Long lastModification = new File(mResDir).lastModified();
+ Long oldModified = sResDirLastModified.get(mResDir);
+ if (lastModification.equals(oldModified))
+ return false;
+
+ sResDirLastModified.put(mResDir, lastModification);
+
+ if (oldModified == null)
+ return true;
+
+ // file was changed meanwhile => remove old replacements
+ for (int i = 0; i < sReplacements.size(); i++) {
+ sReplacements.valueAt(i).remove(mResDir);
+ }
+ Arrays.fill(mReplacementsCache, (byte) 0);
+ return true;
+ }
+ }
+
+ /** @hide */
+ public static void setPackageNameForResDir(String packageName, String resDir) {
+ synchronized (sResDirPackageNames) {
+ sResDirPackageNames.put(resDir, packageName);
+ }
+ }
+
+ /**
+ * Returns the name of the package that these resources belong to, or "android" for system resources.
+ */
+ public String getPackageName() {
+ return mPackageName;
+ }
+
+ private static String getPackageName(String resDir) {
+ if (resDir == null)
+ return "android";
+
+ String packageName;
+ synchronized (sResDirPackageNames) {
+ packageName = sResDirPackageNames.get(resDir);
+ }
+
+ if (packageName != null)
+ return packageName;
+
+ PackageParser.PackageLite pkgInfo;
+ if (Build.VERSION.SDK_INT >= 21) {
+ try {
+ pkgInfo = PackageParser.parsePackageLite(new File(resDir), 0);
+ } catch (PackageParserException e) {
+ throw new IllegalStateException("Could not determine package name for " + resDir, e);
+ }
+ } else {
+ pkgInfo = PackageParser.parsePackageLite(resDir, 0);
+ }
+ if (pkgInfo != null && pkgInfo.packageName != null) {
+ Log.w(XposedBridge.TAG, "Package name for " + resDir + " had to be retrieved via parser");
+ packageName = pkgInfo.packageName;
+ setPackageNameForResDir(packageName, resDir);
+ return packageName;
+ }
+
+ throw new IllegalStateException("Could not determine package name for " + resDir);
+ }
+
+ /**
+ * Special case of {@link #getPackageName} during object creation.
+ *
+ * For a short moment during/after the creation of a new {@link android.content.res Resources}
+ * object, it isn't an instance of {@link XResources} yet. For any hooks that need information
+ * about the just created object during this particular stage, this method will return the
+ * package name.
+ *
+ *
If you call this method outside of {@code getTopLevelResources()}, it
+ * throws an {@code IllegalStateException}.
+ */
+ public static String getPackageNameDuringConstruction() {
+ Object key;
+ if (sLatestResKey == null || (key = sLatestResKey.get()) == null)
+ throw new IllegalStateException("This method can only be called during getTopLevelResources()");
+
+ String resDir = (String) getObjectField(key, "mResDir");
+ return getPackageName(resDir);
+ }
+
+ /** @hide */
+ public static void init(ThreadLocal latestResKey) throws Exception {
+ sLatestResKey = latestResKey;
+
+ findAndHookMethod(LayoutInflater.class, "inflate", XmlPullParser.class, ViewGroup.class, boolean.class, new XC_MethodHook() {
+ @Override
+ protected void afterHookedMethod(MethodHookParam param) throws Throwable {
+ if (param.hasThrowable())
+ return;
+
+ XMLInstanceDetails details;
+ synchronized (sXmlInstanceDetails) {
+ details = sXmlInstanceDetails.get(param.args[0]);
+ }
+ if (details != null) {
+ LayoutInflatedParam liparam = new LayoutInflatedParam(details.callbacks);
+ liparam.view = (View) param.getResult();
+ liparam.resNames = details.resNames;
+ liparam.variant = details.variant;
+ liparam.res = details.res;
+ XCallback.callAll(liparam);
+ }
+ }
+ });
+
+ final XC_MethodHook parseIncludeHook = new XC_MethodHook() {
+ @Override
+ protected void beforeHookedMethod(MethodHookParam param) throws Throwable {
+ sIncludedLayouts.get().push(param);
+ }
+
+ @Override
+ protected void afterHookedMethod(MethodHookParam param) throws Throwable {
+ sIncludedLayouts.get().pop();
+
+ if (param.hasThrowable())
+ return;
+
+ // filled in by our implementation of getLayout()
+ XMLInstanceDetails details = (XMLInstanceDetails) param.getObjectExtra(EXTRA_XML_INSTANCE_DETAILS);
+ if (details != null) {
+ LayoutInflatedParam liparam = new LayoutInflatedParam(details.callbacks);
+ ViewGroup group = (ViewGroup) param.args[(Build.VERSION.SDK_INT < 23) ? 1 : 2];
+ liparam.view = group.getChildAt(group.getChildCount() - 1);
+ liparam.resNames = details.resNames;
+ liparam.variant = details.variant;
+ liparam.res = details.res;
+ XCallback.callAll(liparam);
+ }
+ }
+ };
+ if (Build.VERSION.SDK_INT < 21) {
+ findAndHookMethod(LayoutInflater.class, "parseInclude", XmlPullParser.class, View.class,
+ AttributeSet.class, parseIncludeHook);
+ } else if (Build.VERSION.SDK_INT < 23) {
+ findAndHookMethod(LayoutInflater.class, "parseInclude", XmlPullParser.class, View.class,
+ AttributeSet.class, boolean.class, parseIncludeHook);
+ } else {
+ findAndHookMethod(LayoutInflater.class, "parseInclude", XmlPullParser.class, Context.class,
+ View.class, AttributeSet.class, parseIncludeHook);
+ }
+ }
+
+ /**
+ * Wrapper for information about an indiviual resource.
+ */
+ public static class ResourceNames {
+ /** The resource ID. */
+ public final int id;
+ /** The resource package name as returned by {@link #getResourcePackageName}. */
+ public final String pkg;
+ /** The resource entry name as returned by {@link #getResourceEntryName}. */
+ public final String name;
+ /** The resource type name as returned by {@link #getResourceTypeName}. */
+ public final String type;
+ /** The full resource nameas returned by {@link #getResourceName}. */
+ public final String fullName;
+
+ private ResourceNames(int id, String pkg, String name, String type) {
+ this.id = id;
+ this.pkg = pkg;
+ this.name = name;
+ this.type = type;
+ this.fullName = pkg + ":" + type + "/" + name;
+ }
+
+ /**
+ * Returns whether all non-null parameters match the values of this object.
+ */
+ public boolean equals(String pkg, String name, String type, int id) {
+ return (pkg == null || pkg.equals(this.pkg))
+ && (name == null || name.equals(this.name))
+ && (type == null || type.equals(this.type))
+ && (id == 0 || id == this.id);
+ }
+ }
+
+ private ResourceNames getResourceNames(int id) {
+ return new ResourceNames(
+ id,
+ getResourcePackageName(id),
+ getResourceTypeName(id),
+ getResourceEntryName(id));
+ }
+
+ private static ResourceNames getSystemResourceNames(int id) {
+ Resources sysRes = getSystem();
+ return new ResourceNames(
+ id,
+ sysRes.getResourcePackageName(id),
+ sysRes.getResourceTypeName(id),
+ sysRes.getResourceEntryName(id));
+ }
+
+ private static void putResourceNames(String resDir, ResourceNames resNames) {
+ int id = resNames.id;
+ synchronized (sResourceNames) {
+ HashMap inner = sResourceNames.get(id);
+ if (inner == null) {
+ inner = new HashMap<>();
+ sResourceNames.put(id, inner);
+ }
+ synchronized (inner) {
+ inner.put(resDir, resNames);
+ }
+ }
+ }
+
+ // =======================================================
+ // DEFINING REPLACEMENTS
+ // =======================================================
+
+ /**
+ * Sets a replacement for an individual resource. See {@link #setReplacement(String, String, String, Object)}.
+ *
+ * @param id The ID of the resource which should be replaced.
+ * @param replacement The replacement, see above.
+ */
+ public void setReplacement(int id, Object replacement) {
+ setReplacement(id, replacement, this);
+ }
+
+ /**
+ * Sets a replacement for an individual resource. See {@link #setReplacement(String, String, String, Object)}.
+ *
+ * @deprecated Use {@link #setReplacement(String, String, String, Object)} instead.
+ *
+ * @param fullName The full resource name, e.g. {@code com.example.myapplication:string/app_name}.
+ * See {@link #getResourceName}.
+ * @param replacement The replacement.
+ */
+ @Deprecated
+ public void setReplacement(String fullName, Object replacement) {
+ int id = getIdentifier(fullName, null, null);
+ if (id == 0)
+ throw new NotFoundException(fullName);
+ setReplacement(id, replacement, this);
+ }
+
+ /**
+ * Sets a replacement for an individual resource. If called more than once for the same ID, the
+ * replacement from the last call is used. Setting the replacement to {@code null} removes it.
+ *
+ * The allowed replacements depend on the type of the source. All types accept an
+ * {@link XResForwarder} object, which is usually created with {@link XModuleResources#fwd}.
+ * The resource request will then be forwarded to another {@link android.content.res.Resources}
+ * object. In addition to that, the following replacement types are accepted:
+ *
+ *
+ *
+ * Resource type Additional allowed replacement types (*) Returned from (**)
+ *
+ *
+ *
+ * Animation
+ * none
+ * {@link #getAnimation}
+ *
+ *
+ * Bool
+ * {@link Boolean}
+ * {@link #getBoolean}
+ *
+ *
+ * Color
+ * {@link Integer} (you might want to use {@link Color#parseColor})
+ * {@link #getColor}
+ * {@link #getDrawable} (creates a {@link ColorDrawable})
+ * {@link #getColorStateList} (calls {@link android.content.res.ColorStateList#valueOf})
+ *
+ *
+ *
+ * Color State List
+ * {@link android.content.res.ColorStateList}
+ * {@link Integer} (calls {@link android.content.res.ColorStateList#valueOf})
+ *
+ * {@link #getColorStateList}
+ *
+ *
+ * Dimension
+ * {@link DimensionReplacement} (since v50)
+ * {@link #getDimension}
+ * {@link #getDimensionPixelOffset}
+ * {@link #getDimensionPixelSize}
+ *
+ *
+ *
+ * Drawable
+ * (including mipmap )
+ * {@link DrawableLoader}
+ * {@link Integer} (creates a {@link ColorDrawable})
+ *
+ * {@link #getDrawable}
+ * {@link #getDrawableForDensity}
+ *
+ *
+ *
+ * Fraction
+ * none
+ * {@link #getFraction}
+ *
+ *
+ * Integer
+ * {@link Integer}
+ * {@link #getInteger}
+ *
+ *
+ * Integer Array
+ * {@code int[]}
+ * {@link #getIntArray}
+ *
+ *
+ * Layout
+ * none, but see {@link #hookLayout}
+ * {@link #getLayout}
+ *
+ *
+ * Movie
+ * none
+ * {@link #getMovie}
+ *
+ *
+ * Quantity Strings (Plurals)
+ * none
+ * {@link #getQuantityString}
+ * {@link #getQuantityText}
+ *
+ *
+ *
+ * String
+ * {@link String}
+ * {@link CharSequence} (for styled texts, see also {@link Html#fromHtml})
+ *
+ * {@link #getString}
+ * {@link #getText}
+ *
+ *
+ *
+ * String Array
+ * {@code String[]}
+ * {@code CharSequence[]} (for styled texts, see also {@link Html#fromHtml})
+ *
+ * {@link #getStringArray}
+ * {@link #getTextArray}
+ *
+ *
+ *
+ * XML
+ * none
+ * {@link #getXml}
+ * {@link #getQuantityText}
+ *
+ *
+ *
+ *
+ *
+ *
+ * Other resource types, such as
+ * styles/themes ,
+ * {@linkplain #openRawResource raw resources} and
+ * typed arrays
+ * can't be replaced.
+ *
+ *
+ * * Auto-boxing allows you to use literals like {@code 123} where an {@link Integer} is
+ * accepted, so you don't neeed to call methods like {@link Integer#valueOf(int)} manually.
+ * ** Some of these methods have multiple variants, only one of them is mentioned here.
+ *
+ *
+ * @param pkg The package name, e.g. {@code com.example.myapplication}.
+ * See {@link #getResourcePackageName}.
+ * @param type The type name, e.g. {@code string}.
+ * See {@link #getResourceTypeName}.
+ * @param name The entry name, e.g. {@code app_name}.
+ * See {@link #getResourceEntryName}.
+ * @param replacement The replacement.
+ */
+ public void setReplacement(String pkg, String type, String name, Object replacement) {
+ int id = getIdentifier(name, type, pkg);
+ if (id == 0)
+ throw new NotFoundException(pkg + ":" + type + "/" + name);
+ setReplacement(id, replacement, this);
+ }
+
+ /**
+ * Sets a replacement for an individual Android framework resource (in the {@code android} package).
+ * See {@link #setSystemWideReplacement(String, String, String, Object)}.
+ *
+ * @param id The ID of the resource which should be replaced.
+ * @param replacement The replacement.
+ */
+ public static void setSystemWideReplacement(int id, Object replacement) {
+ setReplacement(id, replacement, null);
+ }
+
+ /**
+ * Sets a replacement for an individual Android framework resource (in the {@code android} package).
+ * See {@link #setSystemWideReplacement(String, String, String, Object)}.
+ *
+ * @deprecated Use {@link #setSystemWideReplacement(String, String, String, Object)} instead.
+ *
+ * @param fullName The full resource name, e.g. {@code android:string/yes}.
+ * See {@link #getResourceName}.
+ * @param replacement The replacement.
+ */
+ @Deprecated
+ public static void setSystemWideReplacement(String fullName, Object replacement) {
+ int id = getSystem().getIdentifier(fullName, null, null);
+ if (id == 0)
+ throw new NotFoundException(fullName);
+ setReplacement(id, replacement, null);
+ }
+
+ /**
+ * Sets a replacement for an individual Android framework resource (in the {@code android} package).
+ *
+ *
Some resources are part of the Android framework and can be used in any app. They're
+ * accessible via {@link android.R android.R} and are not bound to a specific
+ * {@link android.content.res.Resources} instance. Such resources can be replaced in
+ * {@link IXposedHookZygoteInit#initZygote initZygote()} for all apps. As there is no
+ * {@link XResources} object easily available in that scope, this static method can be used
+ * to set resource replacements. All other details (e.g. how certain types can be replaced) are
+ * mentioned in {@link #setReplacement(String, String, String, Object)}.
+ *
+ * @param pkg The package name, should always be {@code android} here.
+ * See {@link #getResourcePackageName}.
+ * @param type The type name, e.g. {@code string}.
+ * See {@link #getResourceTypeName}.
+ * @param name The entry name, e.g. {@code yes}.
+ * See {@link #getResourceEntryName}.
+ * @param replacement The replacement.
+ */
+ public static void setSystemWideReplacement(String pkg, String type, String name, Object replacement) {
+ int id = getSystem().getIdentifier(name, type, pkg);
+ if (id == 0)
+ throw new NotFoundException(pkg + ":" + type + "/" + name);
+ setReplacement(id, replacement, null);
+ }
+
+ private static void setReplacement(int id, Object replacement, XResources res) {
+ String resDir = (res != null) ? res.mResDir : null;
+ if (id == 0)
+ throw new IllegalArgumentException("id 0 is not an allowed resource identifier");
+ else if (resDir == null && id >= 0x7f000000)
+ throw new IllegalArgumentException("ids >= 0x7f000000 are app specific and cannot be set for the framework");
+
+ if (replacement instanceof Drawable)
+ throw new IllegalArgumentException("Drawable replacements are deprecated since Xposed 2.1. Use DrawableLoader instead.");
+
+ // Cache that we have a replacement for this ID, false positives are accepted to save memory.
+ if (id < 0x7f000000) {
+ int cacheKey = (id & 0x00070000) >> 11 | (id & 0xf8) >> 3;
+ synchronized (sSystemReplacementsCache) {
+ sSystemReplacementsCache[cacheKey] |= 1 << (id & 7);
+ }
+ } else {
+ int cacheKey = (id & 0x00070000) >> 12 | (id & 0x78) >> 3;
+ synchronized (res.mReplacementsCache) {
+ res.mReplacementsCache[cacheKey] |= 1 << (id & 7);
+ }
+ }
+
+ synchronized (sReplacements) {
+ HashMap inner = sReplacements.get(id);
+ if (inner == null) {
+ inner = new HashMap<>();
+ sReplacements.put(id, inner);
+ }
+ inner.put(resDir, replacement);
+ }
+ }
+
+ // =======================================================
+ // RETURNING REPLACEMENTS
+ // =======================================================
+
+ private Object getReplacement(int id) {
+ if (id <= 0)
+ return null;
+
+ // Check the cache whether it's worth looking for replacements
+ if (id < 0x7f000000) {
+ int cacheKey = (id & 0x00070000) >> 11 | (id & 0xf8) >> 3;
+ if ((sSystemReplacementsCache[cacheKey] & (1 << (id & 7))) == 0)
+ return null;
+ } else if (mResDir != null) {
+ int cacheKey = (id & 0x00070000) >> 12 | (id & 0x78) >> 3;
+ if ((mReplacementsCache[cacheKey] & (1 << (id & 7))) == 0)
+ return null;
+ }
+
+ HashMap inner;
+ synchronized (sReplacements) {
+ inner = sReplacements.get(id);
+ }
+
+ if (inner == null)
+ return null;
+
+ synchronized (inner) {
+ Object result = inner.get(mResDir);
+ if (result != null || mResDir == null)
+ return result;
+ return inner.get(null);
+ }
+ }
+
+ /** @hide */
+ @Override
+ public XmlResourceParser getAnimation(int id) throws NotFoundException {
+ Object replacement = getReplacement(id);
+ if (replacement instanceof XResForwarder) {
+ Resources repRes = ((XResForwarder) replacement).getResources();
+ int repId = ((XResForwarder) replacement).getId();
+
+ boolean loadedFromCache = isXmlCached(repRes, repId);
+ XmlResourceParser result = repRes.getAnimation(repId);
+
+ if (!loadedFromCache) {
+ long parseState = (Build.VERSION.SDK_INT >= 21)
+ ? getLongField(result, "mParseState")
+ : getIntField(result, "mParseState");
+ rewriteXmlReferencesNative(parseState, this, repRes);
+ }
+
+ return result;
+ }
+ return super.getAnimation(id);
+ }
+
+ /** @hide */
+ @Override
+ public boolean getBoolean(int id) throws NotFoundException {
+ Object replacement = getReplacement(id);
+ if (replacement instanceof Boolean) {
+ return (Boolean) replacement;
+ } else if (replacement instanceof XResForwarder) {
+ Resources repRes = ((XResForwarder) replacement).getResources();
+ int repId = ((XResForwarder) replacement).getId();
+ return repRes.getBoolean(repId);
+ }
+ return super.getBoolean(id);
+ }
+
+ /** @hide */
+ @Override
+ public int getColor(int id) throws NotFoundException {
+ Object replacement = getReplacement(id);
+ if (replacement instanceof Integer) {
+ return (Integer) replacement;
+ } else if (replacement instanceof XResForwarder) {
+ Resources repRes = ((XResForwarder) replacement).getResources();
+ int repId = ((XResForwarder) replacement).getId();
+ return repRes.getColor(repId);
+ }
+ return super.getColor(id);
+ }
+
+ /** @hide */
+ @Override
+ public ColorStateList getColorStateList(int id) throws NotFoundException {
+ Object replacement = getReplacement(id);
+ if (replacement instanceof ColorStateList) {
+ return (ColorStateList) replacement;
+ } else if (replacement instanceof Integer) {
+ int color = (Integer) replacement;
+ synchronized (sColorStateListCache) {
+ ColorStateList result = sColorStateListCache.get(color);
+ if (result == null) {
+ result = ColorStateList.valueOf(color);
+ sColorStateListCache.put(color, result);
+ }
+ return result;
+ }
+ } else if (replacement instanceof XResForwarder) {
+ Resources repRes = ((XResForwarder) replacement).getResources();
+ int repId = ((XResForwarder) replacement).getId();
+ return repRes.getColorStateList(repId);
+ }
+ return super.getColorStateList(id);
+ }
+
+ /** @hide */
+ @Override
+ public float getDimension(int id) throws NotFoundException {
+ Object replacement = getReplacement(id);
+ if (replacement instanceof DimensionReplacement) {
+ return ((DimensionReplacement) replacement).getDimension(getDisplayMetrics());
+ } else if (replacement instanceof XResForwarder) {
+ Resources repRes = ((XResForwarder) replacement).getResources();
+ int repId = ((XResForwarder) replacement).getId();
+ return repRes.getDimension(repId);
+ }
+ return super.getDimension(id);
+ }
+
+ /** @hide */
+ @Override
+ public int getDimensionPixelOffset(int id) throws NotFoundException {
+ Object replacement = getReplacement(id);
+ if (replacement instanceof DimensionReplacement) {
+ return ((DimensionReplacement) replacement).getDimensionPixelOffset(getDisplayMetrics());
+ } else if (replacement instanceof XResForwarder) {
+ Resources repRes = ((XResForwarder) replacement).getResources();
+ int repId = ((XResForwarder) replacement).getId();
+ return repRes.getDimensionPixelOffset(repId);
+ }
+ return super.getDimensionPixelOffset(id);
+ }
+
+ /** @hide */
+ @Override
+ public int getDimensionPixelSize(int id) throws NotFoundException {
+ Object replacement = getReplacement(id);
+ if (replacement instanceof DimensionReplacement) {
+ return ((DimensionReplacement) replacement).getDimensionPixelSize(getDisplayMetrics());
+ } else if (replacement instanceof XResForwarder) {
+ Resources repRes = ((XResForwarder) replacement).getResources();
+ int repId = ((XResForwarder) replacement).getId();
+ return repRes.getDimensionPixelSize(repId);
+ }
+ return super.getDimensionPixelSize(id);
+ }
+
+ /** @hide */
+ @Override
+ public Drawable getDrawable(int id) throws NotFoundException {
+ try {
+ if (incrementMethodDepth("getDrawable") == 1) {
+ Object replacement = getReplacement(id);
+ if (replacement instanceof DrawableLoader) {
+ try {
+ Drawable result = ((DrawableLoader) replacement).newDrawable(this, id);
+ if (result != null)
+ return result;
+ } catch (Throwable t) { XposedBridge.log(t); }
+ } else if (replacement instanceof Integer) {
+ return new ColorDrawable((Integer) replacement);
+ } else if (replacement instanceof XResForwarder) {
+ Resources repRes = ((XResForwarder) replacement).getResources();
+ int repId = ((XResForwarder) replacement).getId();
+ return repRes.getDrawable(repId);
+ }
+ }
+ return super.getDrawable(id);
+ } finally {
+ decrementMethodDepth("getDrawable");
+ }
+ }
+
+ /** @hide */
+ @Override
+ public Drawable getDrawable(int id, Theme theme) throws NotFoundException {
+ try {
+ if (incrementMethodDepth("getDrawable") == 1) {
+ Object replacement = getReplacement(id);
+ if (replacement instanceof DrawableLoader) {
+ try {
+ Drawable result = ((DrawableLoader) replacement).newDrawable(this, id);
+ if (result != null)
+ return result;
+ } catch (Throwable t) { XposedBridge.log(t); }
+ } else if (replacement instanceof Integer) {
+ return new ColorDrawable((Integer) replacement);
+ } else if (replacement instanceof XResForwarder) {
+ Resources repRes = ((XResForwarder) replacement).getResources();
+ int repId = ((XResForwarder) replacement).getId();
+ return repRes.getDrawable(repId);
+ }
+ }
+ return super.getDrawable(id, theme);
+ } finally {
+ decrementMethodDepth("getDrawable");
+ }
+ }
+
+ /** @hide */
+ @Override
+ public Drawable getDrawable(int id, Theme theme, boolean supportComposedIcons) throws NotFoundException {
+ try {
+ if (incrementMethodDepth("getDrawable") == 1) {
+ Object replacement = getReplacement(id);
+ if (replacement instanceof DrawableLoader) {
+ try {
+ Drawable result = ((DrawableLoader) replacement).newDrawable(this, id);
+ if (result != null)
+ return result;
+ } catch (Throwable t) { XposedBridge.log(t); }
+ } else if (replacement instanceof Integer) {
+ return new ColorDrawable((Integer) replacement);
+ } else if (replacement instanceof XResForwarder) {
+ Resources repRes = ((XResForwarder) replacement).getResources();
+ int repId = ((XResForwarder) replacement).getId();
+ return repRes.getDrawable(repId);
+ }
+ }
+ return super.getDrawable(id, theme, supportComposedIcons);
+ } finally {
+ decrementMethodDepth("getDrawable");
+ }
+ }
+
+ /** @hide */
+ @Override
+ public Drawable getDrawableForDensity(int id, int density) throws NotFoundException {
+ try {
+ if (incrementMethodDepth("getDrawableForDensity") == 1) {
+ Object replacement = getReplacement(id);
+ if (replacement instanceof DrawableLoader) {
+ try {
+ Drawable result = ((DrawableLoader) replacement).newDrawableForDensity(this, id, density);
+ if (result != null)
+ return result;
+ } catch (Throwable t) { XposedBridge.log(t); }
+ } else if (replacement instanceof Integer) {
+ return new ColorDrawable((Integer) replacement);
+ } else if (replacement instanceof XResForwarder) {
+ Resources repRes = ((XResForwarder) replacement).getResources();
+ int repId = ((XResForwarder) replacement).getId();
+ return repRes.getDrawableForDensity(repId, density);
+ }
+ }
+ return super.getDrawableForDensity(id, density);
+ } finally {
+ decrementMethodDepth("getDrawableForDensity");
+ }
+ }
+
+ /** @hide */
+ @Override
+ public Drawable getDrawableForDensity(int id, int density, Theme theme) throws NotFoundException {
+ try {
+ if (incrementMethodDepth("getDrawableForDensity") == 1) {
+ Object replacement = getReplacement(id);
+ if (replacement instanceof DrawableLoader) {
+ try {
+ Drawable result = ((DrawableLoader) replacement).newDrawableForDensity(this, id, density);
+ if (result != null)
+ return result;
+ } catch (Throwable t) { XposedBridge.log(t); }
+ } else if (replacement instanceof Integer) {
+ return new ColorDrawable((Integer) replacement);
+ } else if (replacement instanceof XResForwarder) {
+ Resources repRes = ((XResForwarder) replacement).getResources();
+ int repId = ((XResForwarder) replacement).getId();
+ return repRes.getDrawableForDensity(repId, density);
+ }
+ }
+ return super.getDrawableForDensity(id, density, theme);
+ } finally {
+ decrementMethodDepth("getDrawableForDensity");
+ }
+ }
+
+ /** @hide */
+ @Override
+ public Drawable getDrawableForDensity(int id, int density, Theme theme, boolean supportComposedIcons) throws NotFoundException {
+ try {
+ if (incrementMethodDepth("getDrawableForDensity") == 1) {
+ Object replacement = getReplacement(id);
+ if (replacement instanceof DrawableLoader) {
+ try {
+ Drawable result = ((DrawableLoader) replacement).newDrawableForDensity(this, id, density);
+ if (result != null)
+ return result;
+ } catch (Throwable t) { XposedBridge.log(t); }
+ } else if (replacement instanceof Integer) {
+ return new ColorDrawable((Integer) replacement);
+ } else if (replacement instanceof XResForwarder) {
+ Resources repRes = ((XResForwarder) replacement).getResources();
+ int repId = ((XResForwarder) replacement).getId();
+ return repRes.getDrawableForDensity(repId, density);
+ }
+ }
+ return super.getDrawableForDensity(id, density, theme, supportComposedIcons);
+ } finally {
+ decrementMethodDepth("getDrawableForDensity");
+ }
+ }
+
+ /** @hide */
+ @Override
+ public float getFraction(int id, int base, int pbase) {
+ Object replacement = getReplacement(id);
+ if (replacement instanceof XResForwarder) {
+ Resources repRes = ((XResForwarder) replacement).getResources();
+ int repId = ((XResForwarder) replacement).getId();
+ return repRes.getFraction(repId, base, pbase);
+ }
+ return super.getFraction(id, base, pbase);
+ }
+
+ /** @hide */
+ @Override
+ public int getInteger(int id) throws NotFoundException {
+ Object replacement = getReplacement(id);
+ if (replacement instanceof Integer) {
+ return (Integer) replacement;
+ } else if (replacement instanceof XResForwarder) {
+ Resources repRes = ((XResForwarder) replacement).getResources();
+ int repId = ((XResForwarder) replacement).getId();
+ return repRes.getInteger(repId);
+ }
+ return super.getInteger(id);
+ }
+
+ /** @hide */
+ @Override
+ public int[] getIntArray(int id) throws NotFoundException {
+ Object replacement = getReplacement(id);
+ if (replacement instanceof int[]) {
+ return (int[]) replacement;
+ } else if (replacement instanceof XResForwarder) {
+ Resources repRes = ((XResForwarder) replacement).getResources();
+ int repId = ((XResForwarder) replacement).getId();
+ return repRes.getIntArray(repId);
+ }
+ return super.getIntArray(id);
+ }
+
+ /** @hide */
+ @Override
+ public XmlResourceParser getLayout(int id) throws NotFoundException {
+ XmlResourceParser result;
+ Object replacement = getReplacement(id);
+ if (replacement instanceof XResForwarder) {
+ Resources repRes = ((XResForwarder) replacement).getResources();
+ int repId = ((XResForwarder) replacement).getId();
+
+ boolean loadedFromCache = isXmlCached(repRes, repId);
+ result = repRes.getLayout(repId);
+
+ if (!loadedFromCache) {
+ long parseState = (Build.VERSION.SDK_INT >= 21)
+ ? getLongField(result, "mParseState")
+ : getIntField(result, "mParseState");
+ rewriteXmlReferencesNative(parseState, this, repRes);
+ }
+ } else {
+ result = super.getLayout(id);
+ }
+
+ // Check whether this layout is hooked
+ HashMap> inner;
+ synchronized (sLayoutCallbacks) {
+ inner = sLayoutCallbacks.get(id);
+ }
+ if (inner != null) {
+ CopyOnWriteSortedSet callbacks;
+ synchronized (inner) {
+ callbacks = inner.get(mResDir);
+ if (callbacks == null && mResDir != null)
+ callbacks = inner.get(null);
+ }
+ if (callbacks != null) {
+ String variant = "layout";
+ TypedValue value = (TypedValue) getObjectField(this, "mTmpValue");
+ getValue(id, value, true);
+ if (value.type == TypedValue.TYPE_STRING) {
+ String[] components = value.string.toString().split("/", 3);
+ if (components.length == 3)
+ variant = components[1];
+ else
+ XposedBridge.log("Unexpected resource path \"" + value.string.toString()
+ + "\" for resource id 0x" + Integer.toHexString(id));
+ } else {
+ XposedBridge.log(new NotFoundException("Could not find file name for resource id 0x") + Integer.toHexString(id));
+ }
+
+ synchronized (sXmlInstanceDetails) {
+ synchronized (sResourceNames) {
+ HashMap resNamesInner = sResourceNames.get(id);
+ if (resNamesInner != null) {
+ synchronized (resNamesInner) {
+ XMLInstanceDetails details = new XMLInstanceDetails(resNamesInner.get(mResDir), variant, callbacks);
+ sXmlInstanceDetails.put(result, details);
+
+ // if we were called inside LayoutInflater.parseInclude, store the details for it
+ MethodHookParam top = sIncludedLayouts.get().peek();
+ if (top != null)
+ top.setObjectExtra(EXTRA_XML_INSTANCE_DETAILS, details);
+ }
+ }
+ }
+ }
+ }
+ }
+
+ return result;
+ }
+
+ /** @hide */
+ @Override
+ public Movie getMovie(int id) throws NotFoundException {
+ Object replacement = getReplacement(id);
+ if (replacement instanceof XResForwarder) {
+ Resources repRes = ((XResForwarder) replacement).getResources();
+ int repId = ((XResForwarder) replacement).getId();
+ return repRes.getMovie(repId);
+ }
+ return super.getMovie(id);
+ }
+
+ /** @hide */
+ @Override
+ public CharSequence getQuantityText(int id, int quantity) throws NotFoundException {
+ Object replacement = getReplacement(id);
+ if (replacement instanceof XResForwarder) {
+ Resources repRes = ((XResForwarder) replacement).getResources();
+ int repId = ((XResForwarder) replacement).getId();
+ return repRes.getQuantityText(repId, quantity);
+ }
+ return super.getQuantityText(id, quantity);
+ }
+ // these are handled by getQuantityText:
+ // public String getQuantityString(int id, int quantity);
+ // public String getQuantityString(int id, int quantity, Object... formatArgs);
+
+ /** @hide */
+ @Override
+ public String[] getStringArray(int id) throws NotFoundException {
+ Object replacement = getReplacement(id);
+ if (replacement instanceof String[]) {
+ return (String[]) replacement;
+ } else if (replacement instanceof XResForwarder) {
+ Resources repRes = ((XResForwarder) replacement).getResources();
+ int repId = ((XResForwarder) replacement).getId();
+ return repRes.getStringArray(repId);
+ }
+ return super.getStringArray(id);
+ }
+
+ /** @hide */
+ @Override
+ public CharSequence getText(int id) throws NotFoundException {
+ Object replacement = getReplacement(id);
+ if (replacement instanceof CharSequence) {
+ return (CharSequence) replacement;
+ } else if (replacement instanceof XResForwarder) {
+ Resources repRes = ((XResForwarder) replacement).getResources();
+ int repId = ((XResForwarder) replacement).getId();
+ return repRes.getText(repId);
+ }
+ return super.getText(id);
+ }
+ // these are handled by getText:
+ // public String getString(int id);
+ // public String getString(int id, Object... formatArgs);
+
+ /** @hide */
+ @Override
+ public CharSequence getText(int id, CharSequence def) {
+ Object replacement = getReplacement(id);
+ if (replacement instanceof CharSequence) {
+ return (CharSequence) replacement;
+ } else if (replacement instanceof XResForwarder) {
+ Resources repRes = ((XResForwarder) replacement).getResources();
+ int repId = ((XResForwarder) replacement).getId();
+ return repRes.getText(repId, def);
+ }
+ return super.getText(id, def);
+ }
+
+ /** @hide */
+ @Override
+ public CharSequence[] getTextArray(int id) throws NotFoundException {
+ Object replacement = getReplacement(id);
+ if (replacement instanceof CharSequence[]) {
+ return (CharSequence[]) replacement;
+ } else if (replacement instanceof XResForwarder) {
+ Resources repRes = ((XResForwarder) replacement).getResources();
+ int repId = ((XResForwarder) replacement).getId();
+ return repRes.getTextArray(repId);
+ }
+ return super.getTextArray(id);
+ }
+
+ /** @hide */
+ @Override
+ public XmlResourceParser getXml(int id) throws NotFoundException {
+ Object replacement = getReplacement(id);
+ if (replacement instanceof XResForwarder) {
+ Resources repRes = ((XResForwarder) replacement).getResources();
+ int repId = ((XResForwarder) replacement).getId();
+
+ boolean loadedFromCache = isXmlCached(repRes, repId);
+ XmlResourceParser result = repRes.getXml(repId);
+
+ if (!loadedFromCache) {
+ long parseState = (Build.VERSION.SDK_INT >= 21)
+ ? getLongField(result, "mParseState")
+ : getIntField(result, "mParseState");
+ rewriteXmlReferencesNative(parseState, this, repRes);
+ }
+
+ return result;
+ }
+ return super.getXml(id);
+ }
+
+ private static boolean isXmlCached(Resources res, int id) {
+ int[] mCachedXmlBlockIds = (int[]) getObjectField(res, "mCachedXmlBlockIds");
+ synchronized (mCachedXmlBlockIds) {
+ for (int cachedId : mCachedXmlBlockIds) {
+ if (cachedId == id)
+ return true;
+ }
+ }
+ return false;
+ }
+
+ private static native void rewriteXmlReferencesNative(long parserPtr, XResources origRes, Resources repRes);
+
+ /**
+ * Used to replace reference IDs in XMLs.
+ *
+ * When resource requests are forwarded to modules, the may include references to resources with the same
+ * name as in the original resources, but the IDs generated by aapt will be different. rewriteXmlReferencesNative
+ * walks through all references and calls this function to find out the original ID, which it then writes to
+ * the compiled XML file in the memory.
+ */
+ private static int translateResId(int id, XResources origRes, Resources repRes) {
+ try {
+ String entryName = repRes.getResourceEntryName(id);
+ String entryType = repRes.getResourceTypeName(id);
+ String origPackage = origRes.mPackageName;
+ int origResId = 0;
+ try {
+ // look for a resource with the same name and type in the original package
+ origResId = origRes.getIdentifier(entryName, entryType, origPackage);
+ } catch (NotFoundException ignored) {}
+
+ boolean repResDefined = false;
+ try {
+ final TypedValue tmpValue = new TypedValue();
+ repRes.getValue(id, tmpValue, false);
+ // if a resource has not been defined (i.e. only a resource ID has been created), it will equal "false"
+ // this means a boolean "false" value is not detected of it is directly referenced in an XML file
+ repResDefined = !(tmpValue.type == TypedValue.TYPE_INT_BOOLEAN && tmpValue.data == 0);
+ } catch (NotFoundException ignored) {}
+
+ if (!repResDefined && origResId == 0 && !entryType.equals("id")) {
+ XposedBridge.log(entryType + "/" + entryName + " is neither defined in module nor in original resources");
+ return 0;
+ }
+
+ // exists only in module, so create a fake resource id
+ if (origResId == 0)
+ origResId = getFakeResId(repRes, id);
+
+ // IDs will never be loaded, no need to set a replacement
+ if (repResDefined && !entryType.equals("id"))
+ origRes.setReplacement(origResId, new XResForwarder(repRes, id));
+
+ return origResId;
+ } catch (Exception e) {
+ XposedBridge.log(e);
+ return id;
+ }
+ }
+
+ /**
+ * Generates a fake resource ID.
+ *
+ * The parameter is just hashed, it doesn't have a deeper meaning. However, it's recommended
+ * to use values with a low risk for conflicts, such as a full resource name. Calling this
+ * method multiple times will return the same ID.
+ *
+ * @param resName A used for hashing, see above.
+ * @return The fake resource ID.
+ */
+ public static int getFakeResId(String resName) {
+ return 0x7e000000 | (resName.hashCode() & 0x00ffffff);
+ }
+
+ /**
+ * Generates a fake resource ID.
+ *
+ *
This variant uses the result of {@link #getResourceName} to create the hash that the ID is
+ * based on. The given resource doesn't need to match the {@link XResources} instance for which
+ * the fake resource ID is going to be used.
+ *
+ * @param res The {@link android.content.res.Resources} object to be used for hashing.
+ * @param id The resource ID to be used for hashing.
+ * @return The fake resource ID.
+ */
+ public static int getFakeResId(Resources res, int id) {
+ return getFakeResId(res.getResourceName(id));
+ }
+
+ /**
+ * Makes any individual resource available from another {@link android.content.res.Resources}
+ * instance available in this {@link XResources} instance.
+ *
+ *
This method combines calls to {@link #getFakeResId(Resources, int)} and
+ * {@link #setReplacement(int, Object)} to generate a fake resource ID and set up a replacement
+ * for it which forwards to the given resource.
+ *
+ *
The returned ID can only be used to retrieve the resource, it won't work for methods like
+ * {@link #getResourceName} etc.
+ *
+ * @param res The target {@link android.content.res.Resources} instance.
+ * @param id The target resource ID.
+ * @return The fake resource ID (see above).
+ */
+ public int addResource(Resources res, int id) {
+ int fakeId = getFakeResId(res, id);
+ synchronized (sReplacements) {
+ if (sReplacements.indexOfKey(fakeId) < 0)
+ setReplacement(fakeId, new XResForwarder(res, id));
+ }
+ return fakeId;
+ }
+
+ /**
+ * Similar to {@link #translateResId}, but used to determine the original ID of attribute names.
+ */
+ private static int translateAttrId(String attrName, XResources origRes) {
+ String origPackage = origRes.mPackageName;
+ int origAttrId = 0;
+ try {
+ origAttrId = origRes.getIdentifier(attrName, "attr", origPackage);
+ } catch (NotFoundException e) {
+ XposedBridge.log("Attribute " + attrName + " not found in original resources");
+ }
+ return origAttrId;
+ }
+
+ // =======================================================
+ // XTypedArray class
+ // =======================================================
+ /**
+ * {@link android.content.res.TypedArray} replacement that replaces values on-the-fly.
+ * Mainly used when inflating layouts.
+ * @hide
+ */
+ public static class XTypedArray extends XTypedArraySuperClass {
+ /** Dummy, will never be called (objects are transferred to this class only). */
+ private XTypedArray() {
+ super(null, null, null, 0);
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public boolean getBoolean(int index, boolean defValue) {
+ Object replacement = ((XResources) getResources()).getReplacement(getResourceId(index, 0));
+ if (replacement instanceof Boolean) {
+ return (Boolean) replacement;
+ } else if (replacement instanceof XResForwarder) {
+ Resources repRes = ((XResForwarder) replacement).getResources();
+ int repId = ((XResForwarder) replacement).getId();
+ return repRes.getBoolean(repId);
+ }
+ return super.getBoolean(index, defValue);
+ }
+
+ @Override
+ public int getColor(int index, int defValue) {
+ Object replacement = ((XResources) getResources()).getReplacement(getResourceId(index, 0));
+ if (replacement instanceof Integer) {
+ return (Integer) replacement;
+ } else if (replacement instanceof XResForwarder) {
+ Resources repRes = ((XResForwarder) replacement).getResources();
+ int repId = ((XResForwarder) replacement).getId();
+ return repRes.getColor(repId);
+ }
+ return super.getColor(index, defValue);
+ }
+
+ @Override
+ public ColorStateList getColorStateList(int index) {
+ Object replacement = ((XResources) getResources()).getReplacement(getResourceId(index, 0));
+ if (replacement instanceof ColorStateList) {
+ return (ColorStateList) replacement;
+ } else if (replacement instanceof Integer) {
+ int color = (Integer) replacement;
+ synchronized (sColorStateListCache) {
+ ColorStateList result = sColorStateListCache.get(color);
+ if (result == null) {
+ result = ColorStateList.valueOf(color);
+ sColorStateListCache.put(color, result);
+ }
+ return result;
+ }
+ } else if (replacement instanceof XResForwarder) {
+ Resources repRes = ((XResForwarder) replacement).getResources();
+ int repId = ((XResForwarder) replacement).getId();
+ return repRes.getColorStateList(repId);
+ }
+ return super.getColorStateList(index);
+ }
+
+ @Override
+ public float getDimension(int index, float defValue) {
+ Object replacement = ((XResources) getResources()).getReplacement(getResourceId(index, 0));
+ if (replacement instanceof XResForwarder) {
+ Resources repRes = ((XResForwarder) replacement).getResources();
+ int repId = ((XResForwarder) replacement).getId();
+ return repRes.getDimension(repId);
+ }
+ return super.getDimension(index, defValue);
+ }
+
+ @Override
+ public int getDimensionPixelOffset(int index, int defValue) {
+ Object replacement = ((XResources) getResources()).getReplacement(getResourceId(index, 0));
+ if (replacement instanceof XResForwarder) {
+ Resources repRes = ((XResForwarder) replacement).getResources();
+ int repId = ((XResForwarder) replacement).getId();
+ return repRes.getDimensionPixelOffset(repId);
+ }
+ return super.getDimensionPixelOffset(index, defValue);
+ }
+
+ @Override
+ public int getDimensionPixelSize(int index, int defValue) {
+ Object replacement = ((XResources) getResources()).getReplacement(getResourceId(index, 0));
+ if (replacement instanceof XResForwarder) {
+ Resources repRes = ((XResForwarder) replacement).getResources();
+ int repId = ((XResForwarder) replacement).getId();
+ return repRes.getDimensionPixelSize(repId);
+ }
+ return super.getDimensionPixelSize(index, defValue);
+ }
+
+ @Override
+ public Drawable getDrawable(int index) {
+ final int resId = getResourceId(index, 0);
+ XResources xres = (XResources) getResources();
+ Object replacement = xres.getReplacement(resId);
+ if (replacement instanceof DrawableLoader) {
+ try {
+ Drawable result = ((DrawableLoader) replacement).newDrawable(xres, resId);
+ if (result != null)
+ return result;
+ } catch (Throwable t) { XposedBridge.log(t); }
+ } else if (replacement instanceof Integer) {
+ return new ColorDrawable((Integer) replacement);
+ } else if (replacement instanceof XResForwarder) {
+ Resources repRes = ((XResForwarder) replacement).getResources();
+ int repId = ((XResForwarder) replacement).getId();
+ return repRes.getDrawable(repId);
+ }
+ return super.getDrawable(index);
+ }
+
+ @Override
+ public float getFloat(int index, float defValue) {
+ Object replacement = ((XResources) getResources()).getReplacement(getResourceId(index, 0));
+ if (replacement instanceof XResForwarder) {
+ Resources repRes = ((XResForwarder) replacement).getResources();
+ int repId = ((XResForwarder) replacement).getId();
+ // dimensions seem to be the only way to define floats by references
+ return repRes.getDimension(repId);
+ }
+ return super.getFloat(index, defValue);
+ }
+
+ @Override
+ public float getFraction(int index, int base, int pbase, float defValue) {
+ Object replacement = ((XResources) getResources()).getReplacement(getResourceId(index, 0));
+ if (replacement instanceof XResForwarder) {
+ Resources repRes = ((XResForwarder) replacement).getResources();
+ int repId = ((XResForwarder) replacement).getId();
+ // dimensions seem to be the only way to define floats by references
+ return repRes.getFraction(repId, base, pbase);
+ }
+ return super.getFraction(index, base, pbase, defValue);
+ }
+
+ @Override
+ public int getInt(int index, int defValue) {
+ Object replacement = ((XResources) getResources()).getReplacement(getResourceId(index, 0));
+ if (replacement instanceof Integer) {
+ return (Integer) replacement;
+ } else if (replacement instanceof XResForwarder) {
+ Resources repRes = ((XResForwarder) replacement).getResources();
+ int repId = ((XResForwarder) replacement).getId();
+ return repRes.getInteger(repId);
+ }
+ return super.getInt(index, defValue);
+ }
+
+ @Override
+ public int getInteger(int index, int defValue) {
+ Object replacement = ((XResources) getResources()).getReplacement(getResourceId(index, 0));
+ if (replacement instanceof Integer) {
+ return (Integer) replacement;
+ } else if (replacement instanceof XResForwarder) {
+ Resources repRes = ((XResForwarder) replacement).getResources();
+ int repId = ((XResForwarder) replacement).getId();
+ return repRes.getInteger(repId);
+ }
+ return super.getInteger(index, defValue);
+ }
+
+ @Override
+ public int getLayoutDimension(int index, int defValue) {
+ Object replacement = ((XResources) getResources()).getReplacement(getResourceId(index, 0));
+ if (replacement instanceof XResForwarder) {
+ Resources repRes = ((XResForwarder) replacement).getResources();
+ int repId = ((XResForwarder) replacement).getId();
+ return repRes.getDimensionPixelSize(repId);
+ }
+ return super.getLayoutDimension(index, defValue);
+ }
+
+ @Override
+ public int getLayoutDimension(int index, String name) {
+ Object replacement = ((XResources) getResources()).getReplacement(getResourceId(index, 0));
+ if (replacement instanceof XResForwarder) {
+ Resources repRes = ((XResForwarder) replacement).getResources();
+ int repId = ((XResForwarder) replacement).getId();
+ return repRes.getDimensionPixelSize(repId);
+ }
+ return super.getLayoutDimension(index, name);
+ }
+
+ @Override
+ public String getString(int index) {
+ Object replacement = ((XResources) getResources()).getReplacement(getResourceId(index, 0));
+ if (replacement instanceof CharSequence) {
+ return replacement.toString();
+ } else if (replacement instanceof XResForwarder) {
+ Resources repRes = ((XResForwarder) replacement).getResources();
+ int repId = ((XResForwarder) replacement).getId();
+ return repRes.getString(repId);
+ }
+ return super.getString(index);
+ }
+
+ @Override
+ public CharSequence getText(int index) {
+ Object replacement = ((XResources) getResources()).getReplacement(getResourceId(index, 0));
+ if (replacement instanceof CharSequence) {
+ return (CharSequence) replacement;
+ } else if (replacement instanceof XResForwarder) {
+ Resources repRes = ((XResForwarder) replacement).getResources();
+ int repId = ((XResForwarder) replacement).getId();
+ return repRes.getText(repId);
+ }
+ return super.getText(index);
+ }
+
+ @Override
+ public CharSequence[] getTextArray(int index) {
+ Object replacement = ((XResources) getResources()).getReplacement(getResourceId(index, 0));
+ if (replacement instanceof CharSequence[]) {
+ return (CharSequence[]) replacement;
+ } else if (replacement instanceof XResForwarder) {
+ Resources repRes = ((XResForwarder) replacement).getResources();
+ int repId = ((XResForwarder) replacement).getId();
+ return repRes.getTextArray(repId);
+ }
+ return super.getTextArray(index);
+ }
+ }
+
+
+ // =======================================================
+ // DrawableLoader class
+ // =======================================================
+ /**
+ * Callback for drawable replacements. Instances of this class can passed to
+ * {@link #setReplacement(String, String, String, Object)} and its variants.
+ *
+ *
Make sure to always return new {@link Drawable} instances, as drawables
+ * usually can't be reused.
+ */
+ @SuppressWarnings("UnusedParameters")
+ public static abstract class DrawableLoader {
+ /**
+ * Constructor.
+ */
+ public DrawableLoader() {}
+
+ /**
+ * Called when the hooked drawable resource has been requested.
+ *
+ * @param res The {@link XResources} object in which the hooked drawable resides.
+ * @param id The resource ID which has been requested.
+ * @return The {@link Drawable} which should be used as replacement. {@code null} is ignored.
+ * @throws Throwable Everything the callback throws is caught and logged.
+ */
+ public abstract Drawable newDrawable(XResources res, int id) throws Throwable;
+
+ /**
+ * Like {@link #newDrawable}, but called for {@link #getDrawableForDensity}. The default
+ * implementation is to use the result of {@link #newDrawable}.
+ *
+ * @param res The {@link XResources} object in which the hooked drawable resides.
+ * @param id The resource ID which has been requested.
+ * @param density The desired screen density indicated by the resource as found in
+ * {@link DisplayMetrics}.
+ * @return The {@link Drawable} which should be used as replacement. {@code null} is ignored.
+ * @throws Throwable Everything the callback throws is caught and logged.
+ */
+ public Drawable newDrawableForDensity(XResources res, int id, int density) throws Throwable {
+ return newDrawable(res, id);
+ }
+ }
+
+
+ // =======================================================
+ // DimensionReplacement class
+ // =======================================================
+ /**
+ * Callback for dimension replacements. Instances of this class can passed to
+ * {@link #setReplacement(String, String, String, Object)} and its variants.
+ */
+ public static class DimensionReplacement {
+ private final float mValue;
+ private final int mUnit;
+
+ /**
+ * Creates an instance that can be used for {@link #setReplacement(String, String, String, Object)}
+ * to replace a dimension resource.
+ *
+ * @param value The value of the replacement, in the unit specified with the next parameter.
+ * @param unit One of the {@code COMPLEX_UNIT_*} constants in {@link TypedValue}.
+ */
+ public DimensionReplacement(float value, int unit) {
+ mValue = value;
+ mUnit = unit;
+ }
+
+ /** Called by {@link android.content.res.Resources#getDimension}. */
+ public float getDimension(DisplayMetrics metrics) {
+ return TypedValue.applyDimension(mUnit, mValue, metrics);
+ }
+
+ /** Called by {@link android.content.res.Resources#getDimensionPixelOffset}. */
+ public int getDimensionPixelOffset(DisplayMetrics metrics) {
+ return (int) TypedValue.applyDimension(mUnit, mValue, metrics);
+ }
+
+ /** Called by {@link android.content.res.Resources#getDimensionPixelSize}. */
+ public int getDimensionPixelSize(DisplayMetrics metrics) {
+ final float f = TypedValue.applyDimension(mUnit, mValue, metrics);
+ final int res = (int)(f+0.5f);
+ if (res != 0) return res;
+ if (mValue == 0) return 0;
+ if (mValue > 0) return 1;
+ return -1;
+ }
+ }
+
+ // =======================================================
+ // INFLATING LAYOUTS
+ // =======================================================
+
+ private class XMLInstanceDetails {
+ public final ResourceNames resNames;
+ public final String variant;
+ public final CopyOnWriteSortedSet callbacks;
+ public final XResources res = XResources.this;
+
+ private XMLInstanceDetails(ResourceNames resNames, String variant, CopyOnWriteSortedSet callbacks) {
+ this.resNames = resNames;
+ this.variant = variant;
+ this.callbacks = callbacks;
+ }
+ }
+
+ /**
+ * Hook the inflation of a layout.
+ *
+ * @param id The ID of the resource which should be replaced.
+ * @param callback The callback to be executed when the layout has been inflated.
+ * @return An object which can be used to remove the callback again.
+ */
+ public XC_LayoutInflated.Unhook hookLayout(int id, XC_LayoutInflated callback) {
+ return hookLayoutInternal(mResDir, id, getResourceNames(id), callback);
+ }
+
+ /**
+ * Hook the inflation of a layout.
+ *
+ * @deprecated Use {@link #hookLayout(String, String, String, XC_LayoutInflated)} instead.
+ *
+ * @param fullName The full resource name, e.g. {@code com.android.systemui:layout/statusbar}.
+ * See {@link #getResourceName}.
+ * @param callback The callback to be executed when the layout has been inflated.
+ * @return An object which can be used to remove the callback again.
+ */
+ @Deprecated
+ public XC_LayoutInflated.Unhook hookLayout(String fullName, XC_LayoutInflated callback) {
+ int id = getIdentifier(fullName, null, null);
+ if (id == 0)
+ throw new NotFoundException(fullName);
+ return hookLayout(id, callback);
+ }
+
+ /**
+ * Hook the inflation of a layout.
+ *
+ * @param pkg The package name, e.g. {@code com.android.systemui}.
+ * See {@link #getResourcePackageName}.
+ * @param type The type name, e.g. {@code layout}.
+ * See {@link #getResourceTypeName}.
+ * @param name The entry name, e.g. {@code statusbar}.
+ * See {@link #getResourceEntryName}.
+ * @param callback The callback to be executed when the layout has been inflated.
+ * @return An object which can be used to remove the callback again.
+ */
+ public XC_LayoutInflated.Unhook hookLayout(String pkg, String type, String name, XC_LayoutInflated callback) {
+ int id = getIdentifier(name, type, pkg);
+ if (id == 0)
+ throw new NotFoundException(pkg + ":" + type + "/" + name);
+ return hookLayout(id, callback);
+ }
+
+ /**
+ * Hook the inflation of an Android framework layout (in the {@code android} package).
+ * See {@link #hookSystemWideLayout(String, String, String, XC_LayoutInflated)}.
+ *
+ * @param id The ID of the resource which should be replaced.
+ * @param callback The callback to be executed when the layout has been inflated.
+ * @return An object which can be used to remove the callback again.
+ */
+ public static XC_LayoutInflated.Unhook hookSystemWideLayout(int id, XC_LayoutInflated callback) {
+ if (id >= 0x7f000000)
+ throw new IllegalArgumentException("ids >= 0x7f000000 are app specific and cannot be set for the framework");
+ return hookLayoutInternal(null, id, getSystemResourceNames(id), callback);
+ }
+
+ /**
+ * Hook the inflation of an Android framework layout (in the {@code android} package).
+ * See {@link #hookSystemWideLayout(String, String, String, XC_LayoutInflated)}.
+ *
+ * @deprecated Use {@link #hookSystemWideLayout(String, String, String, XC_LayoutInflated)} instead.
+ *
+ * @param fullName The full resource name, e.g. {@code android:layout/simple_list_item_1}.
+ * See {@link #getResourceName}.
+ * @param callback The callback to be executed when the layout has been inflated.
+ * @return An object which can be used to remove the callback again.
+ */
+ @Deprecated
+ public static XC_LayoutInflated.Unhook hookSystemWideLayout(String fullName, XC_LayoutInflated callback) {
+ int id = getSystem().getIdentifier(fullName, null, null);
+ if (id == 0)
+ throw new NotFoundException(fullName);
+ return hookSystemWideLayout(id, callback);
+ }
+
+ /**
+ * Hook the inflation of an Android framework layout (in the {@code android} package).
+ *
+ * Some layouts are part of the Android framework and can be used in any app. They're
+ * accessible via {@link android.R.layout android.R.layout} and are not bound to a specific
+ * {@link android.content.res.Resources} instance. Such resources can be replaced in
+ * {@link IXposedHookZygoteInit#initZygote initZygote()} for all apps. As there is no
+ * {@link XResources} object easily available in that scope, this static method can be used
+ * to hook layouts.
+ *
+ * @param pkg The package name, e.g. {@code android}.
+ * See {@link #getResourcePackageName}.
+ * @param type The type name, e.g. {@code layout}.
+ * See {@link #getResourceTypeName}.
+ * @param name The entry name, e.g. {@code simple_list_item_1}.
+ * See {@link #getResourceEntryName}.
+ * @param callback The callback to be executed when the layout has been inflated.
+ * @return An object which can be used to remove the callback again.
+ */
+ public static XC_LayoutInflated.Unhook hookSystemWideLayout(String pkg, String type, String name, XC_LayoutInflated callback) {
+ int id = getSystem().getIdentifier(name, type, pkg);
+ if (id == 0)
+ throw new NotFoundException(pkg + ":" + type + "/" + name);
+ return hookSystemWideLayout(id, callback);
+ }
+
+ private static XC_LayoutInflated.Unhook hookLayoutInternal(String resDir, int id, ResourceNames resNames, XC_LayoutInflated callback) {
+ if (id == 0)
+ throw new IllegalArgumentException("id 0 is not an allowed resource identifier");
+
+ HashMap> inner;
+ synchronized (sLayoutCallbacks) {
+ inner = sLayoutCallbacks.get(id);
+ if (inner == null) {
+ inner = new HashMap<>();
+ sLayoutCallbacks.put(id, inner);
+ }
+ }
+
+ CopyOnWriteSortedSet callbacks;
+ synchronized (inner) {
+ callbacks = inner.get(resDir);
+ if (callbacks == null) {
+ callbacks = new CopyOnWriteSortedSet<>();
+ inner.put(resDir, callbacks);
+ }
+ }
+
+ callbacks.add(callback);
+
+ putResourceNames(resDir, resNames);
+
+ return callback.new Unhook(resDir, id);
+ }
+
+ /** @hide */
+ public static void unhookLayout(String resDir, int id, XC_LayoutInflated callback) {
+ HashMap> inner;
+ synchronized (sLayoutCallbacks) {
+ inner = sLayoutCallbacks.get(id);
+ if (inner == null)
+ return;
+ }
+
+ CopyOnWriteSortedSet callbacks;
+ synchronized (inner) {
+ callbacks = inner.get(resDir);
+ if (callbacks == null)
+ return;
+ }
+
+ callbacks.remove(callback);
+ }
+}
diff --git a/Bridge/src/main/java/android/content/res/package-info.java b/Bridge/src/main/java/android/content/res/package-info.java
new file mode 100644
index 00000000..4016398e
--- /dev/null
+++ b/Bridge/src/main/java/android/content/res/package-info.java
@@ -0,0 +1,4 @@
+/**
+ * Contains classes that are required for replacing resources.
+ */
+package android.content.res;
diff --git a/Bridge/src/main/java/com/elderdrivers/riru/common/KeepAll.java b/Bridge/src/main/java/com/elderdrivers/riru/common/KeepAll.java
new file mode 100644
index 00000000..173ad0b9
--- /dev/null
+++ b/Bridge/src/main/java/com/elderdrivers/riru/common/KeepAll.java
@@ -0,0 +1,4 @@
+package com.elderdrivers.riru.common;
+
+public interface KeepAll {
+}
diff --git a/Bridge/src/main/java/com/elderdrivers/riru/common/KeepMembers.java b/Bridge/src/main/java/com/elderdrivers/riru/common/KeepMembers.java
new file mode 100644
index 00000000..6f83c576
--- /dev/null
+++ b/Bridge/src/main/java/com/elderdrivers/riru/common/KeepMembers.java
@@ -0,0 +1,4 @@
+package com.elderdrivers.riru.common;
+
+public interface KeepMembers {
+}
diff --git a/Bridge/src/main/java/com/elderdrivers/riru/xposed/Main.java b/Bridge/src/main/java/com/elderdrivers/riru/xposed/Main.java
new file mode 100644
index 00000000..cac9a190
--- /dev/null
+++ b/Bridge/src/main/java/com/elderdrivers/riru/xposed/Main.java
@@ -0,0 +1,83 @@
+package com.elderdrivers.riru.xposed;
+
+import android.annotation.SuppressLint;
+import android.os.Build;
+
+import com.elderdrivers.riru.common.KeepAll;
+import com.elderdrivers.riru.xposed.core.HookMethodResolver;
+import com.elderdrivers.riru.xposed.entry.Router;
+
+import java.lang.reflect.Method;
+
+@SuppressLint("DefaultLocale")
+public class Main implements KeepAll {
+
+ // private static String sForkAndSpecializePramsStr = "";
+// private static String sForkSystemServerPramsStr = "";
+ public static String sAppDataDir = "";
+
+ static {
+ init(Build.VERSION.SDK_INT);
+ HookMethodResolver.init();
+ }
+
+ ///////////////////////////////////////////////////////////////////////////////////////////////
+ // entry points
+ ///////////////////////////////////////////////////////////////////////////////////////////////
+
+ @Deprecated
+ public static void forkAndSpecializePre(int uid, int gid, int[] gids, int debugFlags,
+ int[][] rlimits, int mountExternal, String seInfo,
+ String niceName, int[] fdsToClose, int[] fdsToIgnore,
+ boolean startChildZygote, String instructionSet, String appDataDir) {
+// sForkAndSpecializePramsStr = String.format(
+// "Zygote#forkAndSpecialize(%d, %d, %s, %d, %s, %d, %s, %s, %s, %s, %s, %s, %s)",
+// uid, gid, Arrays.toString(gids), debugFlags, Arrays.toString(rlimits),
+// mountExternal, seInfo, niceName, Arrays.toString(fdsToClose),
+// Arrays.toString(fdsToIgnore), startChildZygote, instructionSet, appDataDir);
+ }
+
+ public static void forkAndSpecializePost(int pid, String appDataDir) {
+// Utils.logD(sForkAndSpecializePramsStr + " = " + pid);
+ if (pid == 0) {
+ // in app process
+ sAppDataDir = appDataDir;
+ Router.onProcessForked(false);
+ } else {
+ // in zygote process, res is child zygote pid
+ // don't print log here, see https://github.com/RikkaApps/Riru/blob/77adfd6a4a6a81bfd20569c910bc4854f2f84f5e/riru-core/jni/main/jni_native_method.cpp#L55-L66
+ }
+ }
+
+ public static void forkSystemServerPre(int uid, int gid, int[] gids, int debugFlags, int[][] rlimits,
+ long permittedCapabilities, long effectiveCapabilities) {
+// sForkSystemServerPramsStr = String.format("Zygote#forkSystemServer(%d, %d, %s, %d, %s, %d, %d)",
+// uid, gid, Arrays.toString(gids), debugFlags, Arrays.toString(rlimits),
+// permittedCapabilities, effectiveCapabilities);
+ }
+
+ public static void forkSystemServerPost(int pid) {
+// Utils.logD(sForkSystemServerPramsStr + " = " + pid);
+ if (pid == 0) {
+ // in system_server process
+ sAppDataDir = "/data/data/android/";
+ Router.onProcessForked(true);
+ } else {
+ // in zygote process, res is child zygote pid
+ // don't print log here, see https://github.com/RikkaApps/Riru/blob/77adfd6a4a6a81bfd20569c910bc4854f2f84f5e/riru-core/jni/main/jni_native_method.cpp#L55-L66
+ }
+ }
+
+ ///////////////////////////////////////////////////////////////////////////////////////////////
+ // native methods
+ ///////////////////////////////////////////////////////////////////////////////////////////////
+
+ public static native boolean backupAndHookNative(Object target, Method hook, Method backup);
+
+ public static native void ensureMethodCached(Method hook, Method backup);
+
+ // JNI.ToReflectedMethod() could return either Method or Constructor
+ public static native Object findMethodNative(Class targetClass, String methodName, String methodSig);
+
+ private static native void init(int SDK_version);
+}
diff --git a/Bridge/src/main/java/com/elderdrivers/riru/xposed/core/HookMain.java b/Bridge/src/main/java/com/elderdrivers/riru/xposed/core/HookMain.java
new file mode 100644
index 00000000..db1188c7
--- /dev/null
+++ b/Bridge/src/main/java/com/elderdrivers/riru/xposed/core/HookMain.java
@@ -0,0 +1,167 @@
+package com.elderdrivers.riru.xposed.core;
+
+import com.elderdrivers.riru.xposed.util.Utils;
+
+import java.lang.reflect.Constructor;
+import java.lang.reflect.Method;
+import java.lang.reflect.Modifier;
+import java.util.ArrayList;
+import java.util.Arrays;
+
+import static com.elderdrivers.riru.xposed.Main.backupAndHookNative;
+import static com.elderdrivers.riru.xposed.Main.findMethodNative;
+
+public class HookMain {
+
+ public static void doHookDefault(ClassLoader patchClassLoader, ClassLoader originClassLoader, String hookInfoClassName) {
+ try {
+ Class> hookInfoClass = Class.forName(hookInfoClassName, true, patchClassLoader);
+ String[] hookItemNames = (String[]) hookInfoClass.getField("hookItemNames").get(null);
+ for (String hookItemName : hookItemNames) {
+ doHookItemDefault(patchClassLoader, hookItemName, originClassLoader);
+ }
+ } catch (Throwable e) {
+ Utils.logE("error when hooking all in: " + hookInfoClassName, e);
+ }
+ }
+
+ private static void doHookItemDefault(ClassLoader patchClassLoader, String hookItemName, ClassLoader originClassLoader) {
+ try {
+ Utils.logD("Start hooking with item " + hookItemName);
+ Class> hookItem = Class.forName(hookItemName, true, patchClassLoader);
+
+ String className = (String) hookItem.getField("className").get(null);
+ String methodName = (String) hookItem.getField("methodName").get(null);
+ String methodSig = (String) hookItem.getField("methodSig").get(null);
+
+ if (className == null || className.equals("")) {
+ Utils.logW("No target class. Skipping...");
+ return;
+ }
+ Class> clazz = null;
+ try {
+ clazz = Class.forName(className, true, originClassLoader);
+ } catch (ClassNotFoundException cnfe) {
+ Utils.logE(className + " not found in " + originClassLoader);
+ return;
+ }
+ if (Modifier.isAbstract(clazz.getModifiers())) {
+ Utils.logW("Hook may fail for abstract class: " + className);
+ }
+
+ Method hook = null;
+ Method backup = null;
+ for (Method method : hookItem.getDeclaredMethods()) {
+ if (method.getName().equals("hook") && Modifier.isStatic(method.getModifiers())) {
+ hook = method;
+ } else if (method.getName().equals("backup") && Modifier.isStatic(method.getModifiers())) {
+ backup = method;
+ }
+ }
+ if (hook == null) {
+ Utils.logE("Cannot find hook for " + methodName);
+ return;
+ }
+ findAndBackupAndHook(clazz, methodName, methodSig, hook, backup);
+ } catch (Throwable e) {
+ Utils.logE("error when hooking " + hookItemName, e);
+ }
+ }
+
+ public static void findAndHook(Class targetClass, String methodName, String methodSig, Method hook) {
+ hook(findMethod(targetClass, methodName, methodSig), hook);
+ }
+
+ public static void findAndBackupAndHook(Class targetClass, String methodName, String methodSig,
+ Method hook, Method backup) {
+ backupAndHook(findMethod(targetClass, methodName, methodSig), hook, backup);
+ }
+
+ public static void hook(Object target, Method hook) {
+ backupAndHook(target, hook, null);
+ }
+
+ public static void backupAndHook(Object target, Method hook, Method backup) {
+ if (target == null) {
+ throw new IllegalArgumentException("null target method");
+ }
+ if (hook == null) {
+ throw new IllegalArgumentException("null hook method");
+ }
+
+ if (!Modifier.isStatic(hook.getModifiers())) {
+ throw new IllegalArgumentException("Hook must be a static method: " + hook);
+ }
+ checkCompatibleMethods(target, hook, "Original", "Hook");
+ if (backup != null) {
+ if (!Modifier.isStatic(backup.getModifiers())) {
+ throw new IllegalArgumentException("Backup must be a static method: " + backup);
+ }
+ // backup is just a placeholder and the constraint could be less strict
+ checkCompatibleMethods(target, backup, "Original", "Backup");
+ }
+ if (backup != null) {
+ HookMethodResolver.resolveMethod(hook, backup);
+ }
+ if (!backupAndHookNative(target, hook, backup)) {
+ throw new RuntimeException("Failed to hook " + target + " with " + hook);
+ }
+ }
+
+ private static Object findMethod(Class cls, String methodName, String methodSig) {
+ if (cls == null) {
+ throw new IllegalArgumentException("null class");
+ }
+ if (methodName == null) {
+ throw new IllegalArgumentException("null method name");
+ }
+ if (methodSig == null) {
+ throw new IllegalArgumentException("null method signature");
+ }
+ return findMethodNative(cls, methodName, methodSig);
+ }
+
+ private static void checkCompatibleMethods(Object original, Method replacement, String originalName, String replacementName) {
+ ArrayList> originalParams;
+ if (original instanceof Method) {
+ originalParams = new ArrayList<>(Arrays.asList(((Method) original).getParameterTypes()));
+ } else if (original instanceof Constructor) {
+ originalParams = new ArrayList<>(Arrays.asList(((Constructor>) original).getParameterTypes()));
+ } else {
+ throw new IllegalArgumentException("Type of target method is wrong");
+ }
+
+ ArrayList> replacementParams = new ArrayList<>(Arrays.asList(replacement.getParameterTypes()));
+
+ if (original instanceof Method
+ && !Modifier.isStatic(((Method) original).getModifiers())) {
+ originalParams.add(0, ((Method) original).getDeclaringClass());
+ } else if (original instanceof Constructor) {
+ originalParams.add(0, ((Constructor>) original).getDeclaringClass());
+ }
+
+
+ if (!Modifier.isStatic(replacement.getModifiers())) {
+ replacementParams.add(0, replacement.getDeclaringClass());
+ }
+
+ if (original instanceof Method
+ && !replacement.getReturnType().isAssignableFrom(((Method) original).getReturnType())) {
+ throw new IllegalArgumentException("Incompatible return types. " + originalName + ": " + ((Method) original).getReturnType() + ", " + replacementName + ": " + replacement.getReturnType());
+ } else if (original instanceof Constructor) {
+ if (replacement.getReturnType().equals(Void.class)) {
+ throw new IllegalArgumentException("Incompatible return types. " + "" + ": " + "V" + ", " + replacementName + ": " + replacement.getReturnType());
+ }
+ }
+
+ if (originalParams.size() != replacementParams.size()) {
+ throw new IllegalArgumentException("Number of arguments don't match. " + originalName + ": " + originalParams.size() + ", " + replacementName + ": " + replacementParams.size());
+ }
+
+ for (int i = 0; i < originalParams.size(); i++) {
+ if (!replacementParams.get(i).isAssignableFrom(originalParams.get(i))) {
+ throw new IllegalArgumentException("Incompatible argument #" + i + ": " + originalName + ": " + originalParams.get(i) + ", " + replacementName + ": " + replacementParams.get(i));
+ }
+ }
+ }
+}
diff --git a/Bridge/src/main/java/com/elderdrivers/riru/xposed/core/HookMethodResolver.java b/Bridge/src/main/java/com/elderdrivers/riru/xposed/core/HookMethodResolver.java
new file mode 100644
index 00000000..ab97aba8
--- /dev/null
+++ b/Bridge/src/main/java/com/elderdrivers/riru/xposed/core/HookMethodResolver.java
@@ -0,0 +1,156 @@
+package com.elderdrivers.riru.xposed.core;
+
+import android.os.Build;
+import android.util.Log;
+
+import com.elderdrivers.riru.xposed.Main;
+import com.elderdrivers.riru.xposed.util.Utils;
+
+import java.lang.reflect.Field;
+import java.lang.reflect.Method;
+
+/**
+ * create by Swift Gan on 14/01/2019
+ * To ensure method in resolved cache
+ */
+
+public class HookMethodResolver {
+
+ public static Class artMethodClass;
+
+ public static Field resolvedMethodsField;
+ public static Field dexCacheField;
+ public static Field dexMethodIndexField;
+ public static Field artMethodField;
+
+ public static boolean canResolvedInJava = false;
+ public static boolean isArtMethod = false;
+
+ public static long resolvedMethodsAddress = 0;
+ public static int dexMethodIndex = 0;
+
+ public static Method testMethod;
+ public static Object testArtMethod;
+
+ public static void init() {
+ checkSupport();
+ }
+
+ private static void checkSupport() {
+ try {
+
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
+ isArtMethod = false;
+ canResolvedInJava = false;
+ return;
+ }
+
+ testMethod = HookMethodResolver.class.getDeclaredMethod("init");
+ artMethodField = getField(Method.class, "artMethod");
+
+ testArtMethod = artMethodField.get(testMethod);
+
+ if (hasJavaArtMethod() && testArtMethod.getClass() == artMethodClass) {
+ checkSupportForArtMethod();
+ isArtMethod = true;
+ } else if (testArtMethod instanceof Long) {
+ checkSupportForArtMethodId();
+ isArtMethod = false;
+ } else {
+ canResolvedInJava = false;
+ }
+
+ } catch (Throwable throwable) {
+ Utils.logE("error when checkSupport", throwable);
+ }
+ }
+
+ // may 5.0
+ private static void checkSupportForArtMethod() throws Exception {
+ dexMethodIndexField = getField(artMethodClass, "dexMethodIndex");
+ dexCacheField = getField(Class.class, "dexCache");
+ Object dexCache = dexCacheField.get(testMethod.getDeclaringClass());
+ resolvedMethodsField = getField(dexCache.getClass(), "resolvedMethods");
+ if (resolvedMethodsField.get(dexCache) instanceof Object[]) {
+ canResolvedInJava = true;
+ }
+ }
+
+ // may 6.0
+ private static void checkSupportForArtMethodId() throws Exception {
+ dexMethodIndexField = getField(Method.class, "dexMethodIndex");
+ dexMethodIndex = (int) dexMethodIndexField.get(testMethod);
+ dexCacheField = getField(Class.class, "dexCache");
+ Object dexCache = dexCacheField.get(testMethod.getDeclaringClass());
+ resolvedMethodsField = getField(dexCache.getClass(), "resolvedMethods");
+ Object resolvedMethods = resolvedMethodsField.get(dexCache);
+ if (resolvedMethods instanceof Long) {
+ canResolvedInJava = false;
+ resolvedMethodsAddress = (long) resolvedMethods;
+ } else if (resolvedMethods instanceof long[]) {
+ canResolvedInJava = true;
+ }
+ }
+
+ public static void resolveMethod(Method hook, Method backup) {
+ if (canResolvedInJava && artMethodField != null) {
+ // in java
+ try {
+ resolveInJava(hook, backup);
+ } catch (Exception e) {
+ // in native
+ resolveInNative(hook, backup);
+ }
+ } else {
+ // in native
+ resolveInNative(hook, backup);
+ }
+ }
+
+ private static void resolveInJava(Method hook, Method backup) throws Exception {
+ Object dexCache = dexCacheField.get(hook.getDeclaringClass());
+ if (isArtMethod) {
+ Object artMethod = artMethodField.get(backup);
+ int dexMethodIndex = (int) dexMethodIndexField.get(artMethod);
+ Object resolvedMethods = resolvedMethodsField.get(dexCache);
+ ((Object[])resolvedMethods)[dexMethodIndex] = artMethod;
+ } else {
+ int dexMethodIndex = (int) dexMethodIndexField.get(backup);
+ Object resolvedMethods = resolvedMethodsField.get(dexCache);
+ long artMethod = (long) artMethodField.get(backup);
+ ((long[])resolvedMethods)[dexMethodIndex] = artMethod;
+ }
+ }
+
+ private static void resolveInNative(Method hook, Method backup) {
+ Main.ensureMethodCached(hook, backup);
+ }
+
+ public static Field getField(Class topClass, String fieldName) throws NoSuchFieldException {
+ while (topClass != null && topClass != Object.class) {
+ try {
+ Field field = topClass.getDeclaredField(fieldName);
+ field.setAccessible(true);
+ return field;
+ } catch (Exception e) {
+ }
+ topClass = topClass.getSuperclass();
+ }
+ throw new NoSuchFieldException(fieldName);
+ }
+
+ public static boolean hasJavaArtMethod() {
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
+ return false;
+ }
+ if (artMethodClass != null)
+ return true;
+ try {
+ artMethodClass = Class.forName("java.lang.reflect.ArtMethod");
+ return true;
+ } catch (ClassNotFoundException e) {
+ return false;
+ }
+ }
+
+}
\ No newline at end of file
diff --git a/Bridge/src/main/java/com/elderdrivers/riru/xposed/dexmaker/DexLog.java b/Bridge/src/main/java/com/elderdrivers/riru/xposed/dexmaker/DexLog.java
new file mode 100644
index 00000000..3802e49c
--- /dev/null
+++ b/Bridge/src/main/java/com/elderdrivers/riru/xposed/dexmaker/DexLog.java
@@ -0,0 +1,37 @@
+package com.elderdrivers.riru.xposed.dexmaker;
+
+import android.util.Log;
+
+import com.elderdrivers.riru.xposed.BuildConfig;
+
+public class DexLog {
+
+ public static final String TAG = "EdXposed-dexmaker";
+
+ public static int v(String s) {
+ return Log.v(TAG, s);
+ }
+
+ public static int i(String s) {
+ return Log.i(TAG, s);
+ }
+
+ public static int d(String s) {
+ if (BuildConfig.DEBUG) {
+ return Log.d(TAG, s);
+ }
+ return 0;
+ }
+
+ public static int w(String s) {
+ return Log.w(TAG, s);
+ }
+
+ public static int e(String s) {
+ return Log.e(TAG, s);
+ }
+
+ public static int e(String s, Throwable t) {
+ return Log.e(TAG, s, t);
+ }
+}
diff --git a/Bridge/src/main/java/com/elderdrivers/riru/xposed/dexmaker/DexMakerUtils.java b/Bridge/src/main/java/com/elderdrivers/riru/xposed/dexmaker/DexMakerUtils.java
new file mode 100644
index 00000000..94374a98
--- /dev/null
+++ b/Bridge/src/main/java/com/elderdrivers/riru/xposed/dexmaker/DexMakerUtils.java
@@ -0,0 +1,186 @@
+package com.elderdrivers.riru.xposed.dexmaker;
+
+import external.com.android.dx.Code;
+import external.com.android.dx.Local;
+import external.com.android.dx.TypeId;
+
+import java.util.HashMap;
+import java.util.Map;
+
+public class DexMakerUtils {
+
+ public static void autoBoxIfNecessary(Code code, Local target, Local source) {
+ String boxMethod = "valueOf";
+ TypeId> boxTypeId;
+ TypeId typeId = source.getType();
+ if (typeId.equals(TypeId.BOOLEAN)) {
+ boxTypeId = TypeId.get(Boolean.class);
+ code.invokeStatic(boxTypeId.getMethod(boxTypeId, boxMethod, TypeId.BOOLEAN), target, source);
+ } else if (typeId.equals(TypeId.BYTE)) {
+ boxTypeId = TypeId.get(Byte.class);
+ code.invokeStatic(boxTypeId.getMethod(boxTypeId, boxMethod, TypeId.BYTE), target, source);
+ } else if (typeId.equals(TypeId.CHAR)) {
+ boxTypeId = TypeId.get(Character.class);
+ code.invokeStatic(boxTypeId.getMethod(boxTypeId, boxMethod, TypeId.CHAR), target, source);
+ } else if (typeId.equals(TypeId.DOUBLE)) {
+ boxTypeId = TypeId.get(Double.class);
+ code.invokeStatic(boxTypeId.getMethod(boxTypeId, boxMethod, TypeId.DOUBLE), target, source);
+ } else if (typeId.equals(TypeId.FLOAT)) {
+ boxTypeId = TypeId.get(Float.class);
+ code.invokeStatic(boxTypeId.getMethod(boxTypeId, boxMethod, TypeId.FLOAT), target, source);
+ } else if (typeId.equals(TypeId.INT)) {
+ boxTypeId = TypeId.get(Integer.class);
+ code.invokeStatic(boxTypeId.getMethod(boxTypeId, boxMethod, TypeId.INT), target, source);
+ } else if (typeId.equals(TypeId.LONG)) {
+ boxTypeId = TypeId.get(Long.class);
+ code.invokeStatic(boxTypeId.getMethod(boxTypeId, boxMethod, TypeId.LONG), target, source);
+ } else if (typeId.equals(TypeId.SHORT)) {
+ boxTypeId = TypeId.get(Short.class);
+ code.invokeStatic(boxTypeId.getMethod(boxTypeId, boxMethod, TypeId.SHORT), target, source);
+ } else if (typeId.equals(TypeId.VOID)) {
+ code.loadConstant(target, null);
+ } else {
+ code.move(target, source);
+ }
+ }
+
+ public static void autoUnboxIfNecessary(Code code, Local target, Local source) {
+ String unboxMethod;
+ TypeId typeId = target.getType();
+ TypeId> boxTypeId;
+ if (typeId.equals(TypeId.BOOLEAN)) {
+ unboxMethod = "booleanValue";
+ boxTypeId = TypeId.get(Boolean.class);
+ code.invokeVirtual(boxTypeId.getMethod(TypeId.BOOLEAN, unboxMethod), target, source);
+ } else if (typeId.equals(TypeId.BYTE)) {
+ unboxMethod = "byteValue";
+ boxTypeId = TypeId.get(Byte.class);
+ code.invokeVirtual(boxTypeId.getMethod(TypeId.BYTE, unboxMethod), target, source);
+ } else if (typeId.equals(TypeId.CHAR)) {
+ unboxMethod = "charValue";
+ boxTypeId = TypeId.get(Character.class);
+ code.invokeVirtual(boxTypeId.getMethod(TypeId.CHAR, unboxMethod), target, source);
+ } else if (typeId.equals(TypeId.DOUBLE)) {
+ unboxMethod = "doubleValue";
+ boxTypeId = TypeId.get(Double.class);
+ code.invokeVirtual(boxTypeId.getMethod(TypeId.DOUBLE, unboxMethod), target, source);
+ } else if (typeId.equals(TypeId.FLOAT)) {
+ unboxMethod = "floatValue";
+ boxTypeId = TypeId.get(Float.class);
+ code.invokeVirtual(boxTypeId.getMethod(TypeId.FLOAT, unboxMethod), target, source);
+ } else if (typeId.equals(TypeId.INT)) {
+ unboxMethod = "intValue";
+ boxTypeId = TypeId.get(Integer.class);
+ code.invokeVirtual(boxTypeId.getMethod(TypeId.INT, unboxMethod), target, source);
+ } else if (typeId.equals(TypeId.LONG)) {
+ unboxMethod = "longValue";
+ boxTypeId = TypeId.get(Long.class);
+ code.invokeVirtual(boxTypeId.getMethod(TypeId.LONG, unboxMethod), target, source);
+ } else if (typeId.equals(TypeId.SHORT)) {
+ unboxMethod = "shortValue";
+ boxTypeId = TypeId.get(Short.class);
+ code.invokeVirtual(boxTypeId.getMethod(TypeId.SHORT, unboxMethod), target, source);
+ } else if (typeId.equals(TypeId.VOID)) {
+ code.loadConstant(target, null);
+ } else {
+ code.move(target, source);
+ }
+ }
+
+ public static Map createResultLocals(Code code) {
+ HashMap resultMap = new HashMap<>();
+ Local booleanLocal = code.newLocal(TypeId.BOOLEAN);
+ Local byteLocal = code.newLocal(TypeId.BYTE);
+ Local charLocal = code.newLocal(TypeId.CHAR);
+ Local doubleLocal = code.newLocal(TypeId.DOUBLE);
+ Local floatLocal = code.newLocal(TypeId.FLOAT);
+ Local intLocal = code.newLocal(TypeId.INT);
+ Local longLocal = code.newLocal(TypeId.LONG);
+ Local shortLocal = code.newLocal(TypeId.SHORT);
+ Local voidLocal = code.newLocal(TypeId.VOID);
+ Local objectLocal = code.newLocal(TypeId.OBJECT);
+
+ Local booleanObjLocal = code.newLocal(TypeId.get("Ljava/lang/Boolean;"));
+ Local byteObjLocal = code.newLocal(TypeId.get("Ljava/lang/Byte;"));
+ Local charObjLocal = code.newLocal(TypeId.get("Ljava/lang/Character;"));
+ Local doubleObjLocal = code.newLocal(TypeId.get("Ljava/lang/Double;"));
+ Local floatObjLocal = code.newLocal(TypeId.get("Ljava/lang/Float;"));
+ Local intObjLocal = code.newLocal(TypeId.get("Ljava/lang/Integer;"));
+ Local longObjLocal = code.newLocal(TypeId.get("Ljava/lang/Long;"));
+ Local shortObjLocal = code.newLocal(TypeId.get("Ljava/lang/Short;"));
+ Local voidObjLocal = code.newLocal(TypeId.get("Ljava/lang/Void;"));
+
+ // backup need initialized locals
+ code.loadConstant(booleanLocal, Boolean.valueOf(false));
+ code.loadConstant(byteLocal, Byte.valueOf("0"));
+ code.loadConstant(charLocal, Character.valueOf('\0'));
+ code.loadConstant(floatLocal, Float.valueOf(0));
+ code.loadConstant(intLocal, 0);
+ code.loadConstant(longLocal, Long.valueOf(0));
+ code.loadConstant(shortLocal, Short.valueOf("0"));
+ code.loadConstant(voidLocal, null);
+ code.loadConstant(objectLocal, null);
+ // all to null
+ code.loadConstant(booleanObjLocal, null);
+ code.loadConstant(byteObjLocal, null);
+ code.loadConstant(charObjLocal, null);
+ code.loadConstant(floatObjLocal, null);
+ code.loadConstant(intObjLocal, null);
+ code.loadConstant(longObjLocal, null);
+ code.loadConstant(shortObjLocal, null);
+ code.loadConstant(voidObjLocal, null);
+ // package all
+ resultMap.put(TypeId.BOOLEAN, booleanLocal);
+ resultMap.put(TypeId.BYTE, byteLocal);
+ resultMap.put(TypeId.CHAR, charLocal);
+ resultMap.put(TypeId.DOUBLE, doubleLocal);
+ resultMap.put(TypeId.FLOAT, floatLocal);
+ resultMap.put(TypeId.INT, intLocal);
+ resultMap.put(TypeId.LONG, longLocal);
+ resultMap.put(TypeId.SHORT, shortLocal);
+ resultMap.put(TypeId.VOID, voidLocal);
+ resultMap.put(TypeId.OBJECT, objectLocal);
+
+ resultMap.put(TypeId.get("Ljava/lang/Boolean;"), booleanObjLocal);
+ resultMap.put(TypeId.get("Ljava/lang/Byte;"), byteObjLocal);
+ resultMap.put(TypeId.get("Ljava/lang/Character;"), charObjLocal);
+ resultMap.put(TypeId.get("Ljava/lang/Double;"), doubleObjLocal);
+ resultMap.put(TypeId.get("Ljava/lang/Float;"), floatObjLocal);
+ resultMap.put(TypeId.get("Ljava/lang/Integer;"), intObjLocal);
+ resultMap.put(TypeId.get("Ljava/lang/Long;"), longObjLocal);
+ resultMap.put(TypeId.get("Ljava/lang/Short;"), shortObjLocal);
+ resultMap.put(TypeId.get("Ljava/lang/Void;"), voidObjLocal);
+
+ return resultMap;
+ }
+
+ public static TypeId getObjTypeIdIfPrimitive(TypeId typeId) {
+ if (typeId.equals(TypeId.BOOLEAN)) {
+ return TypeId.get("Ljava/lang/Boolean;");
+ } else if (typeId.equals(TypeId.BYTE)) {
+ return TypeId.get("Ljava/lang/Byte;");
+ } else if (typeId.equals(TypeId.CHAR)) {
+ return TypeId.get("Ljava/lang/Character;");
+ } else if (typeId.equals(TypeId.DOUBLE)) {
+ return TypeId.get("Ljava/lang/Double;");
+ } else if (typeId.equals(TypeId.FLOAT)) {
+ return TypeId.get("Ljava/lang/Float;");
+ } else if (typeId.equals(TypeId.INT)) {
+ return TypeId.get("Ljava/lang/Integer;");
+ } else if (typeId.equals(TypeId.LONG)) {
+ return TypeId.get("Ljava/lang/Long;");
+ } else if (typeId.equals(TypeId.SHORT)) {
+ return TypeId.get("Ljava/lang/Short;");
+ } else if (typeId.equals(TypeId.VOID)) {
+ return TypeId.get("Ljava/lang/Void;");
+ } else {
+ return typeId;
+ }
+ }
+
+ public static void returnRightValue(Code code, Class> returnType, Map resultLocals) {
+ String unboxMethod;
+ TypeId> boxTypeId;
+ code.returnValue(resultLocals.get(returnType));
+ }
+}
diff --git a/Bridge/src/main/java/com/elderdrivers/riru/xposed/dexmaker/DynamicBridge.java b/Bridge/src/main/java/com/elderdrivers/riru/xposed/dexmaker/DynamicBridge.java
new file mode 100644
index 00000000..f8518e9c
--- /dev/null
+++ b/Bridge/src/main/java/com/elderdrivers/riru/xposed/dexmaker/DynamicBridge.java
@@ -0,0 +1,107 @@
+package com.elderdrivers.riru.xposed.dexmaker;
+
+import android.app.AndroidAppHelper;
+import android.os.Build;
+
+import com.elderdrivers.riru.xposed.Main;
+
+import java.io.File;
+import java.lang.reflect.Constructor;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Member;
+import java.lang.reflect.Method;
+import java.lang.reflect.Modifier;
+import java.util.HashMap;
+
+import de.robv.android.xposed.XposedBridge;
+
+public final class DynamicBridge {
+
+ private static HashMap hookedInfo = new HashMap<>();
+
+ public static synchronized void hookMethod(Member hookMethod, XposedBridge.AdditionalHookInfo additionalHookInfo) {
+
+ if (hookMethod.toString().contains("com.tencent.wcdb.database")) {
+ DexLog.w("wcdb not permitted.");
+ return;
+ }
+ if (!checkMember(hookMethod)) {
+ return;
+ }
+
+ if (hookedInfo.containsKey(hookMethod)) {
+ DexLog.w("already hook method:" + hookMethod.toString());
+ return;
+ }
+
+ DexLog.d("start to generate class for: " + hookMethod);
+ try {
+ // for Android Oreo and later use InMemoryClassLoader
+ String dexDirPath = "";
+ if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) {
+ // under Android Oreo, using DexClassLoader
+ String dataDir = Main.sAppDataDir;
+ String processName = AndroidAppHelper.currentProcessName();
+ File dexDir = new File(dataDir, "cache/edhookers/" + processName + "/");
+ dexDir.mkdirs();
+ dexDirPath = dexDir.getAbsolutePath();
+ }
+ HookerDexMaker dexMaker = new HookerDexMaker();
+ dexMaker.start(hookMethod, additionalHookInfo,
+ hookMethod.getDeclaringClass().getClassLoader(), dexDirPath);
+ hookedInfo.put(hookMethod, dexMaker);
+ } catch (Exception e) {
+ DexLog.e("error occur when generating dex", e);
+ }
+ }
+
+ private static boolean checkMember(Member member) {
+
+ if (member instanceof Method) {
+ return true;
+ } else if (member instanceof Constructor>) {
+ return true;
+ } else if (member.getDeclaringClass().isInterface()) {
+ DexLog.e("Cannot hook interfaces: " + member.toString());
+ return false;
+ } else if (Modifier.isAbstract(member.getModifiers())) {
+ DexLog.e("Cannot hook abstract methods: " + member.toString());
+ return false;
+ } else {
+ DexLog.e("Only methods and constructors can be hooked: " + member.toString());
+ return false;
+ }
+ }
+
+ public static Object invokeOriginalMethod(Member method, Object thisObject, Object[] args)
+ throws InvocationTargetException, IllegalAccessException {
+ HookerDexMaker dexMaker = hookedInfo.get(method);
+ if (dexMaker == null) {
+ throw new IllegalStateException("method not hooked, cannot call original method.");
+ }
+ Method callBackup = dexMaker.getCallBackupMethod();
+ if (callBackup == null) {
+ throw new IllegalStateException("original method is null, something must be wrong!");
+ }
+ if (!Modifier.isStatic(callBackup.getModifiers())) {
+ throw new IllegalStateException("original method is not static, something must be wrong!");
+ }
+ callBackup.setAccessible(true);
+ if (args == null) {
+ args = new Object[0];
+ }
+ final int argsSize = args.length;
+ if (Modifier.isStatic(method.getModifiers())) {
+ return callBackup.invoke(null, args);
+ } else {
+ Object[] newArgs = new Object[argsSize + 1];
+ newArgs[0] = thisObject;
+ for (int i = 1; i < newArgs.length; i++) {
+ newArgs[i] = args[i - 1];
+ }
+ return callBackup.invoke(null, newArgs);
+ }
+ }
+}
+
+
diff --git a/Bridge/src/main/java/com/elderdrivers/riru/xposed/dexmaker/HookerDexMaker.java b/Bridge/src/main/java/com/elderdrivers/riru/xposed/dexmaker/HookerDexMaker.java
new file mode 100644
index 00000000..da36f495
--- /dev/null
+++ b/Bridge/src/main/java/com/elderdrivers/riru/xposed/dexmaker/HookerDexMaker.java
@@ -0,0 +1,540 @@
+package com.elderdrivers.riru.xposed.dexmaker;
+
+import android.os.Build;
+import android.text.TextUtils;
+
+import external.com.android.dx.BinaryOp;
+import external.com.android.dx.Code;
+import external.com.android.dx.Comparison;
+import external.com.android.dx.DexMaker;
+import external.com.android.dx.FieldId;
+import external.com.android.dx.Label;
+import external.com.android.dx.Local;
+import external.com.android.dx.MethodId;
+import external.com.android.dx.TypeId;
+import com.elderdrivers.riru.xposed.core.HookMain;
+
+import java.io.File;
+import java.lang.reflect.Constructor;
+import java.lang.reflect.Member;
+import java.lang.reflect.Method;
+import java.lang.reflect.Modifier;
+import java.nio.ByteBuffer;
+import java.util.Map;
+import java.util.concurrent.atomic.AtomicLong;
+
+import dalvik.system.InMemoryDexClassLoader;
+import de.robv.android.xposed.XC_MethodHook;
+import de.robv.android.xposed.XposedBridge;
+
+import static com.elderdrivers.riru.xposed.dexmaker.DexMakerUtils.autoBoxIfNecessary;
+import static com.elderdrivers.riru.xposed.dexmaker.DexMakerUtils.autoUnboxIfNecessary;
+import static com.elderdrivers.riru.xposed.dexmaker.DexMakerUtils.createResultLocals;
+import static com.elderdrivers.riru.xposed.dexmaker.DexMakerUtils.getObjTypeIdIfPrimitive;
+
+public class HookerDexMaker {
+
+ public static final String METHOD_NAME_BACKUP = "backup";
+ public static final String METHOD_NAME_HOOK = "hook";
+ public static final String METHOD_NAME_CALL_BACKUP = "callBackup";
+ public static final String METHOD_NAME_SETUP = "setup";
+ private static final String CLASS_DESC_PREFIX = "L";
+ private static final String CLASS_NAME_PREFIX = "EdHooker";
+ private static final String FIELD_NAME_HOOK_INFO = "additionalHookInfo";
+ private static final String FIELD_NAME_METHOD = "method";
+ private static final String PARAMS_FIELD_NAME_METHOD = "method";
+ private static final String PARAMS_FIELD_NAME_THIS_OBJECT = "thisObject";
+ private static final String PARAMS_FIELD_NAME_ARGS = "args";
+ private static final String CALLBACK_METHOD_NAME_BEFORE = "callBeforeHookedMethod";
+ private static final String CALLBACK_METHOD_NAME_AFTER = "callAfterHookedMethod";
+ private static final String PARAMS_METHOD_NAME_IS_EARLY_RETURN = "isEarlyReturn";
+
+ public static final TypeId objArrayTypeId = TypeId.get(Object[].class);
+ private static final TypeId throwableTypeId = TypeId.get(Throwable.class);
+ private static final TypeId memberTypeId = TypeId.get(Member.class);
+ private static final TypeId callbackTypeId = TypeId.get(XC_MethodHook.class);
+ private static final TypeId