All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.github.underscore.string.U Maven / Gradle / Ivy

/*
 * The MIT License (MIT)
 *
 * Copyright 2016-2018 Valentyn Kolesnikov
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 * THE SOFTWARE.
 */
package com.github.underscore.string;

import com.github.underscore.BinaryOperator;
import com.github.underscore.BiFunction;
import com.github.underscore.Consumer;
import com.github.underscore.Function;
import com.github.underscore.Function3;
import com.github.underscore.Predicate;
import com.github.underscore.PredicateIndexed;
import com.github.underscore.Tuple;
import com.github.underscore.Optional;
import java.util.*;

public class U extends com.github.underscore.U {
    private static final int DEFAULT_TRUNC_LENGTH = 30;
    private static final String DEFAULT_TRUNC_OMISSION = "...";
    private static final String NULL = "null";
    private static final String ELEMENT = "";
    private static final String CLOSED_ELEMENT = "";
    private static final String EMPTY_ELEMENT = ELEMENT + CLOSED_ELEMENT;
    private static final String NULL_ELEMENT = ELEMENT + NULL + CLOSED_ELEMENT;
    private static final java.util.regex.Pattern RE_LATIN_1 = java.util.regex.Pattern.compile(
        "[\\xc0-\\xd6\\xd8-\\xde\\xdf-\\xf6\\xf8-\\xff]");
    private static Map deburredLetters = new LinkedHashMap() { {
        put("\u00c0", "A"); put("\u00c1", "A"); put("\u00c2", "A"); put("\u00c3", "A");
        put("\u00c4", "A"); put("\u00c5", "A");
        put("\u00e0", "a"); put("\u00e1", "a"); put("\u00e2", "a"); put("\u00e3", "a");
        put("\u00e4", "a"); put("\u00e5", "a");
        put("\u00c7", "C"); put("\u00e7", "c");
        put("\u00d0", "D"); put("\u00f0", "d");
        put("\u00c8", "E"); put("\u00c9", "E"); put("\u00ca", "E"); put("\u00cb", "E");
        put("\u00e8", "e"); put("\u00e9", "e"); put("\u00ea", "e"); put("\u00eb", "e");
        put("\u00cC", "I"); put("\u00cd", "I"); put("\u00ce", "I"); put("\u00cf", "I");
        put("\u00eC", "i"); put("\u00ed", "i"); put("\u00ee", "i"); put("\u00ef", "i");
        put("\u00d1", "N"); put("\u00f1", "n");
        put("\u00d2", "O"); put("\u00d3", "O"); put("\u00d4", "O"); put("\u00d5", "O");
        put("\u00d6", "O"); put("\u00d8", "O");
        put("\u00f2", "o"); put("\u00f3", "o"); put("\u00f4", "o"); put("\u00f5", "o");
        put("\u00f6", "o"); put("\u00f8", "o");
        put("\u00d9", "U"); put("\u00da", "U"); put("\u00db", "U"); put("\u00dc", "U");
        put("\u00f9", "u"); put("\u00fa", "u"); put("\u00fb", "u"); put("\u00fc", "u");
        put("\u00dd", "Y"); put("\u00fd", "y"); put("\u00ff", "y");
        put("\u00c6", "Ae"); put("\u00e6", "ae");
        put("\u00de", "Th"); put("\u00fe", "th");
        put("\u00df", "ss");
    } };
    private static String upper = "[A-Z\\xc0-\\xd6\\xd8-\\xde\\u0400-\\u04FF]";
    private static String lower = "[a-z\\xdf-\\xf6\\xf8-\\xff]+";
    private static java.util.regex.Pattern reWords = java.util.regex.Pattern.compile(
        upper + "+(?=" + upper + lower + ")|" + upper + "?" + lower + "|" + upper + "+|[0-9]+");

    public U(final Iterable iterable) {
        super(iterable);
    }

    public U(final String string) {
        super(string);
    }

    public static class Chain extends com.github.underscore.U.Chain {
        public Chain(final T item) {
            super(item);
        }
        public Chain(final List list) {
            super(list);
        }

        public Chain first() {
            return new Chain(U.first(value()));
        }

        public Chain first(int n) {
            return new Chain(U.first(value(), n));
        }

        public Chain firstOrNull() {
            return new Chain(U.firstOrNull(value()));
        }

        public Chain firstOrNull(final Predicate pred) {
            return new Chain(U.firstOrNull(value(), pred));
        }

        public Chain initial() {
            return new Chain(U.initial(value()));
        }

        public Chain initial(int n) {
            return new Chain(U.initial(value(), n));
        }

        public Chain last() {
            return new Chain(U.last(value()));
        }

        public Chain last(int n) {
            return new Chain(U.last(value(), n));
        }

        public Chain lastOrNull() {
            return new Chain(U.lastOrNull(value()));
        }

        public Chain lastOrNull(final Predicate pred) {
            return new Chain(U.lastOrNull(value(), pred));
        }

        public Chain rest() {
            return new Chain(U.rest(value()));
        }

        public Chain rest(int n) {
            return new Chain(U.rest(value(), n));
        }

        public Chain compact() {
            return new Chain(U.compact(value()));
        }

        public Chain compact(final T falsyValue) {
            return new Chain(U.compact(value(), falsyValue));
        }

        @SuppressWarnings("unchecked")
        public Chain flatten() {
            return new Chain((List) U.flatten(value()));
        }

        public  Chain map(final Function func) {
            return new Chain(U.map(value(), func));
        }

        public  Chain mapIndexed(final BiFunction func) {
            return new Chain(U.mapIndexed(value(), func));
        }

        public Chain filter(final Predicate pred) {
            return new Chain(U.filter(value(), pred));
        }

        public Chain filterIndexed(final PredicateIndexed pred) {
            return new Chain(U.filterIndexed(value(), pred));
        }

        public Chain rejectIndexed(final PredicateIndexed pred) {
            return new Chain(U.rejectIndexed(value(), pred));
        }

        public Chain reject(final Predicate pred) {
            return new Chain(U.reject(value(), pred));
        }

        public Chain filterFalse(final Predicate pred) {
            return new Chain(U.filterFalse(value(), pred));
        }

        public  Chain reduce(final BiFunction func, final F zeroElem) {
            return new Chain(U.reduce(value(), func, zeroElem));
        }

        public Chain> reduce(final BinaryOperator func) {
            return new Chain>(U.reduce(value(), func));
        }

        public  Chain reduceRight(final BiFunction func, final F zeroElem) {
            return new Chain(U.reduceRight(value(), func, zeroElem));
        }

        public Chain> reduceRight(final BinaryOperator func) {
            return new Chain>(U.reduceRight(value(), func));
        }

        public Chain> find(final Predicate pred) {
            return new Chain>(U.find(value(), pred));
        }

        public Chain> findLast(final Predicate pred) {
            return new Chain>(U.findLast(value(), pred));
        }

        @SuppressWarnings("unchecked")
        public Chain max() {
            return new Chain(U.max((Collection) value()));
        }

        public > Chain max(final Function func) {
            return new Chain(U.max(value(), func));
        }

        @SuppressWarnings("unchecked")
        public Chain min() {
            return new Chain(U.min((Collection) value()));
        }

        public > Chain min(final Function func) {
            return new Chain(U.min(value(), func));
        }

        @SuppressWarnings("unchecked")
        public Chain sort() {
            return new Chain(U.sort((List) value()));
        }

        @SuppressWarnings("unchecked")
        public > Chain sortWith(final Comparator comparator) {
            return new Chain(U.sortWith((List) value(), comparator));
        }

        public > Chain sortBy(final Function func) {
            return new Chain(U.sortBy(value(), func));
        }

        @SuppressWarnings("unchecked")
        public  Chain> sortBy(final K key) {
            return new Chain>(U.sortBy((List>) value(), key));
        }

        public  Chain>> groupBy(final Function func) {
            return new Chain>>(U.groupBy(value(), func));
        }

        public Chain>> indexBy(final String property) {
            return new Chain>>(U.indexBy(value(), property));
        }

        public  Chain> countBy(final Function func) {
            return new Chain>(U.countBy(value(), func));
        }

        public Chain shuffle() {
            return new Chain(U.shuffle(value()));
        }

        public Chain sample() {
            return new Chain(U.sample(value()));
        }

        public Chain sample(final int howMany) {
            return new Chain(U.newArrayList(U.sample(value(), howMany)));
        }

        public Chain tap(final Consumer func) {
            U.tap(value(), func);
            return new Chain(value());
        }

        public Chain forEach(final Consumer func) {
            U.forEach(value(), func);
            return new Chain(value());
        }

        public Chain forEachRight(final Consumer func) {
            U.forEachRight(value(), func);
            return new Chain(value());
        }

        public Chain every(final Predicate pred) {
            return new Chain(U.every(value(), pred));
        }

        public Chain some(final Predicate pred) {
            return new Chain(U.some(value(), pred));
        }

        public Chain contains(final T elem) {
            return new Chain(U.contains(value(), elem));
        }

        public Chain invoke(final String methodName, final List args) {
            return new Chain(U.invoke(value(), methodName, args));
        }

        public Chain invoke(final String methodName) {
            return new Chain(U.invoke(value(), methodName));
        }

        public Chain pluck(final String propertyName) {
            return new Chain(U.pluck(value(), propertyName));
        }

        public  Chain where(final List> properties) {
            return new Chain(U.where(value(), properties));
        }

        public  Chain> findWhere(final List> properties) {
            return new Chain>(U.findWhere(value(), properties));
        }

        public Chain uniq() {
            return new Chain(U.uniq(value()));
        }

        @SuppressWarnings("unchecked")
        public  Chain uniq(final Function func) {
            return new Chain(U.newArrayList(U.uniq(value(), func)));
        }

        public Chain distinct() {
            return new Chain(U.uniq(value()));
        }

        @SuppressWarnings("unchecked")
        public  Chain distinctBy(final Function func) {
            return new Chain(U.newArrayList((Iterable) U.uniq(value(), func)));
        }

        @SuppressWarnings("unchecked")
        public Chain union(final List ... lists) {
            return new Chain(U.union(value(), lists));
        }

        @SuppressWarnings("unchecked")
        public Chain intersection(final List ... lists) {
            return new Chain(U.intersection(value(), lists));
        }

        @SuppressWarnings("unchecked")
        public Chain difference(final List ... lists) {
            return new Chain(U.difference(value(), lists));
        }

        public Chain range(final int stop) {
            return new Chain(newIntegerList(U.range(stop)));
        }

        public Chain range(final int start, final int stop) {
            return new Chain(newIntegerList(U.range(start, stop)));
        }

        public Chain range(final int start, final int stop, final int step) {
            return new Chain(newIntegerList(U.range(start, stop, step)));
        }

        public Chain> chunk(final int size) {
            return new Chain>(U.chunk(value(), size));
        }

        @SuppressWarnings("unchecked")
        public Chain concat(final List ... lists) {
            return new Chain(U.concat(value(), lists));
        }

        public Chain slice(final int start) {
            return new Chain(U.slice(value(), start));
        }

        public Chain slice(final int start, final int end) {
            return new Chain(U.slice(value(), start, end));
        }

        public Chain reverse() {
            return new Chain(U.reverse(value()));
        }

        public Chain join() {
            return new Chain(U.join(value()));
        }

        public Chain join(final String separator) {
            return new Chain(U.join(value(), separator));
        }

        public Chain skip(final int numberToSkip) {
            return new Chain(value().subList(numberToSkip, value().size()));
        }

        public Chain limit(final int size) {
            return new Chain(value().subList(0, size));
        }

        @SuppressWarnings("unchecked")
        public  Chain> toMap() {
            return new Chain>(U.toMap((Iterable>) value()));
        }

        public Chain camelCase() {
            return new Chain(U.camelCase((String) item()));
        }

        public Chain lowerFirst() {
            return new Chain(U.lowerFirst((String) item()));
        }

        public Chain upperFirst() {
            return new Chain(U.upperFirst((String) item()));
        }

        public Chain capitalize() {
            return new Chain(U.capitalize((String) item()));
        }

        public Chain deburr() {
            return new Chain(U.deburr((String) item()));
        }

        public Chain endsWith(final String target) {
            return new Chain(U.endsWith((String) item(), target));
        }

        public Chain endsWith(final String target, final Integer position) {
            return new Chain(U.endsWith((String) item(), target, position));
        }

        public Chain kebabCase() {
            return new Chain(U.kebabCase((String) item()));
        }

        public Chain repeat(final int length) {
            return new Chain(U.repeat((String) item(), length));
        }

        public Chain pad(final int length) {
            return new Chain(U.pad((String) item(), length));
        }

        public Chain pad(final int length, final String chars) {
            return new Chain(U.pad((String) item(), length, chars));
        }

        public Chain padStart(final int length) {
            return new Chain(U.padStart((String) item(), length));
        }

        public Chain padStart(final int length, final String chars) {
            return new Chain(U.padStart((String) item(), length, chars));
        }

        public Chain padEnd(final int length) {
            return new Chain(U.padEnd((String) item(), length));
        }

        public Chain padEnd(final int length, final String chars) {
            return new Chain(U.padEnd((String) item(), length, chars));
        }

        public Chain snakeCase() {
            return new Chain(U.snakeCase((String) item()));
        }

        public Chain startCase() {
            return new Chain(U.startCase((String) item()));
        }

        public Chain startsWith(final String target) {
            return new Chain(U.startsWith((String) item(), target));
        }

        public Chain startsWith(final String target, final Integer position) {
            return new Chain(U.startsWith((String) item(), target, position));
        }

        public Chain trim() {
            return new Chain(U.trim((String) item()));
        }

        public Chain trim(final String chars) {
            return new Chain(U.trim((String) item(), chars));
        }

        public Chain trimStart() {
            return new Chain(U.trimStart((String) item()));
        }

        public Chain trimStart(final String chars) {
            return new Chain(U.trimStart((String) item(), chars));
        }

        public Chain trimEnd() {
            return new Chain(U.trimEnd((String) item()));
        }

        public Chain trunc() {
            return new Chain(U.trunc((String) item()));
        }

        public Chain trunc(final int length) {
            return new Chain(U.trunc((String) item(), length));
        }

        public Chain trimEnd(final String chars) {
            return new Chain(U.trimEnd((String) item(), chars));
        }

        public Chain uncapitalize() {
            return new Chain(U.uncapitalize((String) item()));
        }

        public Chain words() {
            return new Chain(U.words((String) item()));
        }

        public Chain toJson() {
            return new Chain(U.toJson((Collection) value()));
        }

        public Chain fromJson() {
            return new Chain(U.fromJson((String) item()));
        }

        public Chain toXml() {
            return new Chain(U.toXml((Collection) value()));
        }

        public Chain fromXml() {
            return new Chain(U.fromXml((String) item()));
        }

        public Chain toJsonJavaString() {
            return new Chain(U.toJsonJavaString((Collection) value()));
        }
    }

    public static Chain chain(final String item) {
        return new U.Chain(item);
    }

    public static  Chain chain(final List list) {
        return new U.Chain(list);
    }

    public static  Chain chain(final Iterable iterable) {
        return new U.Chain(newArrayList(iterable));
    }

    public static  Chain chain(final Iterable iterable, int size) {
        return new U.Chain(newArrayList(iterable, size));
    }

    @SuppressWarnings("unchecked")
    public static  Chain chain(final T ... list) {
        return new U.Chain(Arrays.asList(list));
    }

    public static Chain chain(final int[] array) {
        return new U.Chain(newIntegerList(array));
    }

    @SuppressWarnings("unchecked")
    public Chain chain() {
        return new U.Chain(newArrayList(value()));
    }

    public static String camelCase(final String string) {
        return createCompounder(new Function3() {
            public String apply(final String result, final String word, final Integer index) {
                final String localWord = word.toLowerCase(Locale.getDefault());
                return result + (index > 0 ? (word.substring(0, 1).toUpperCase(Locale.getDefault())
                    + word.substring(1)) : localWord);
            }
        }).apply(string);
    }

    public static String lowerFirst(final String string) {
        return createCaseFirst("toLowerCase").apply(string);
    }

    public static String upperFirst(final String string) {
        return createCaseFirst("toUpperCase").apply(string);
    }

    public static String capitalize(final String string) {
        return upperFirst(baseToString(string).toLowerCase());
    }

    public static String uncapitalize(final String string) {
        return lowerFirst(baseToString(string).toLowerCase());
    }

    private static String baseToString(String value) {
        return value == null ? "" : value;
    }

    public static String deburr(final String string) {
        final String localString = baseToString(string);
        final StringBuilder sb = new StringBuilder();
        for (final String str : localString.split("")) {
            if (RE_LATIN_1.matcher(str).matches()) {
                sb.append(deburredLetters.get(str));
            } else {
                sb.append(str);
            }
        }
        return sb.toString();
    }

    public static List words(final String string) {
        final String localString = baseToString(string);
        final List result = new ArrayList();
        final java.util.regex.Matcher matcher = reWords.matcher(localString);
        while (matcher.find()) {
            result.add(matcher.group());
        }
        return result;
    }

    private static Function createCompounder(
        final Function3 callback) {
        return new Function() {
            public String apply(final String string) {
                int index = -1;
                List array = words(deburr(string));
                int length = array.size();
                String result = "";

                while (++index < length) {
                    result = callback.apply(result, array.get(index), index);
                }
                return result;
            }
        };
    }

    private static Function createCaseFirst(final String methodName) {
        return new Function() {
            public String apply(final String string) {
                final String localString = baseToString(string);
                final String chr = localString.isEmpty() ? "" : localString.substring(0, 1);
                final String trailing = localString.length() > 1 ? localString.substring(1) : "";
                return U.invoke(Arrays.asList(chr), methodName).get(0) + trailing;
            }
        };
    }

    public static boolean endsWith(final String string, final String target) {
        return endsWith(string, target, null);
    }

    public static boolean endsWith(final String string, final String target, final Integer position) {
        if (string == null || target == null) {
            return false;
        }
        final String localString = baseToString(string);

        final int length = localString.length();
        final int localPosition = position == null ? length
          : Math.min(position < 0 ? 0 : position, length);

        final int localPosition2 = localPosition - target.length();
      return localPosition2 >= 0 && localString.indexOf(target, localPosition2) == localPosition2;
    }

    public static String kebabCase(final String string) {
        return createCompounder(new Function3() {
            public String apply(final String result, final String word, final Integer index) {
                return result + (index > 0 ? "-" : "") + word.toLowerCase(Locale.getDefault());
            }
        }).apply(string);
    }

    public static String repeat(final String string, final int length) {
        final StringBuilder result = new StringBuilder();
        final StringBuilder localString = new StringBuilder(baseToString(string));
        if (length < 1 || string == null) {
            return result.toString();
        }
        int n = length;
        do {
            if (n % 2 != 0) {
                result.append(localString);
            }
            n = (int) Math.floor(n / (double) 2);
            localString.append(localString.toString());
        } while (n > 0);
        return result.toString();
    }

    private static String createPadding(final String string, final int length, final String chars) {
        final int strLength = string.length();
        final int padLength = length - strLength;
        final String localChars = chars == null ? " " : chars;
        return repeat(localChars, (int) Math.ceil(padLength / (double) localChars.length())).substring(0, padLength);
    }

    public static String pad(final String string, final int length) {
        return pad(string, length, null);
    }

    public static String pad(final String string, final int length, final String chars) {
        final String localString = baseToString(string);
        final int strLength = string.length();
        if (strLength >= length) {
            return localString;
        }
        final double mid = (length - strLength) / (double) 2;
        final int leftLength = (int) Math.floor(mid);
        final int rightLength = (int) Math.ceil(mid);
        final String localChars = createPadding("", rightLength, chars);
        return localChars.substring(0, leftLength) + string + localChars;
    }

    private static Function3 createPadDir(final boolean fromRight) {
        return new Function3() {
            public String apply(String string, Integer length, String chars) {
                final String localString = baseToString(string);
                return (fromRight ? localString : "") + createPadding(localString, length, chars)
                    + (fromRight ? "" : localString);
            }
        };
    }

    public static String padStart(final String string, final Integer length) {
         return createPadDir(false).apply(string, length, null);
    }

    public static String padStart(final String string, final Integer length, final String chars) {
         return createPadDir(false).apply(string, length, chars);
    }

    public static String padEnd(final String string, final Integer length) {
         return createPadDir(true).apply(string, length, null);
    }

    public static String padEnd(final String string, final Integer length, final String chars) {
         return createPadDir(true).apply(string, length, chars);
    }

    public static String snakeCase(final String string) {
        return createCompounder(new Function3() {
            public String apply(final String result, final String word, final Integer index) {
                return result + (index > 0 ? "_" : "") + word.toLowerCase(Locale.getDefault());
            }
        }).apply(string);
    }

    public static String startCase(final String string) {
        return createCompounder(new Function3() {
            public String apply(final String result, final String word, final Integer index) {
                return result + (index > 0 ? " " : "") + word.substring(0, 1).toUpperCase(Locale.getDefault())
                    + word.substring(1);
            }
        }).apply(string);
    }

    public static boolean startsWith(final String string, final String target) {
        return startsWith(string, target, null);
    }

    public static boolean startsWith(final String string, final String target, final Integer position) {
        if (string == null || target == null) {
            return false;
        }
        final String localString = baseToString(string);

        final int length = localString.length();
        final int localPosition = position == null ? 0
          : Math.min(position < 0 ? 0 : position, length);

        return localString.lastIndexOf(target, localPosition) == localPosition;
    }

    private static int charsLeftIndex(final String string, final String chars) {
        int index = 0;
        final int length = string.length();
        while (index < length && chars.indexOf(string.charAt(index)) > -1) {
            index += 1;
        }
        return index == length ? -1 : index;
    }

    private static int charsRightIndex(final String string, final String chars) {
        int index = string.length() - 1;
        while (index >= 0 && chars.indexOf(string.charAt(index)) > -1) {
            index -= 1;
        }
        return index;
    }

    public static String trim(final String string) {
        return trim(string, null);
    }

    public static String trim(final String string, final String chars) {
        final String localString = baseToString(string);
        if (localString.isEmpty()) {
            return localString;
        }
        final String localChars;
        if (chars == null) {
            localChars = " ";
        } else {
            localChars = chars;
        }
        final int leftIndex = charsLeftIndex(localString, localChars);
        final int rightIndex = charsRightIndex(localString, localChars);
        return leftIndex > -1 ? localString.substring(leftIndex, rightIndex + 1) : localString;
    }

    public static String trimStart(final String string) {
        return trimStart(string, null);
    }

    public static String trimStart(final String string, final String chars) {
        final String localString = baseToString(string);
        if (localString.isEmpty()) {
            return localString;
        }
        final String localChars;
        if (chars == null) {
            localChars = " ";
        } else {
            localChars = chars;
        }
        final int leftIndex = charsLeftIndex(localString, localChars);
        return leftIndex > -1 ? localString.substring(leftIndex, localString.length()) : localString;
    }

    public static String trimEnd(final String string) {
        return trimEnd(string, null);
    }

    public static String trimEnd(final String string, final String chars) {
        final String localString = baseToString(string);
        if (localString.isEmpty()) {
            return localString;
        }
        final String localChars;
        if (chars == null) {
            localChars = " ";
        } else {
            localChars = chars;
        }
        final int rightIndex = charsRightIndex(localString, localChars);
        return rightIndex > -1 ? localString.substring(0, rightIndex + 1) : localString;
    }

    public static String trunc(final String string) {
        return trunc(string, DEFAULT_TRUNC_LENGTH);
    }

    public static String trunc(final String string, final Integer length) {
        final String localString = baseToString(string);
        final String omission = DEFAULT_TRUNC_OMISSION;
        if (length >= localString.length()) {
            return localString;
        }
        final int end = length - omission.length();
        final String result = string.substring(0, end);
        return result + omission;
    }

    public static class JsonStringBuilder {
        private final StringBuilder builder;
        private int ident;

        public JsonStringBuilder() {
            builder = new StringBuilder();
        }

        public JsonStringBuilder append(final char character) {
            builder.append(character);
            return this;
        }

        public JsonStringBuilder append(final String string) {
            builder.append(string);
            return this;
        }

        public JsonStringBuilder fillSpaces() {
            for (int index = 0; index < ident; index += 1) {
                builder.append(' ');
            }
            return this;
        }

        public JsonStringBuilder incIdent() {
            ident += 2;
            return this;
        }

        public JsonStringBuilder decIdent() {
            ident -= 2;
            return this;
        }

        public JsonStringBuilder newLine() {
            builder.append("\n");
            return this;
        }

        public String toString() {
            return builder.toString();
        }
    }

    public static class JsonArray {
        public static void writeJson(Collection collection, JsonStringBuilder builder) {
            if (collection == null) {
                builder.append(NULL);
                return;
            }

            Iterator iter = collection.iterator();

            builder.append('[').incIdent().newLine();
            while (iter.hasNext()) {
                Object value = iter.next();
                if (value == null) {
                    builder.fillSpaces().append(NULL);
                    continue;
                }

                builder.fillSpaces();
                JsonValue.writeJson(value, builder);
                if (iter.hasNext()) {
                    builder.append(',').newLine();
                }
            }
            builder.newLine().decIdent().fillSpaces().append(']');
        }

        public static void writeJson(byte[] array, JsonStringBuilder builder) {
            if (array == null) {
                builder.append(NULL);
            } else if (array.length == 0) {
                builder.append("[]");
            } else {
                builder.append('[').incIdent().newLine();
                builder.fillSpaces().append(String.valueOf(array[0]));

                for (int i = 1; i < array.length; i++) {
                    builder.append(',').newLine().fillSpaces();
                    builder.append(String.valueOf(array[i]));
                }

                builder.newLine().decIdent().fillSpaces().append(']');
            }
        }

        public static void writeJson(short[] array, JsonStringBuilder builder) {
            if (array == null) {
                builder.append(NULL);
            } else if (array.length == 0) {
                builder.append("[]");
            } else {
                builder.append('[').incIdent().newLine();
                builder.fillSpaces().append(String.valueOf(array[0]));

                for (int i = 1; i < array.length; i++) {
                    builder.append(',').newLine().fillSpaces();
                    builder.append(String.valueOf(array[i]));
                }

                builder.newLine().decIdent().fillSpaces().append(']');
            }
        }

        public static void writeJson(int[] array, JsonStringBuilder builder) {
            if (array == null) {
                builder.append(NULL);
            } else if (array.length == 0) {
                builder.append("[]");
            } else {
                builder.append('[').incIdent().newLine();
                builder.fillSpaces().append(String.valueOf(array[0]));

                for (int i = 1; i < array.length; i++) {
                    builder.append(',').newLine().fillSpaces();
                    builder.append(String.valueOf(array[i]));
                }

                builder.newLine().decIdent().fillSpaces().append(']');
            }
        }

        public static void writeJson(long[] array, JsonStringBuilder builder) {
            if (array == null) {
                builder.append(NULL);
            } else if (array.length == 0) {
                builder.append("[]");
            } else {
                builder.append('[').incIdent().newLine();
                builder.fillSpaces().append(String.valueOf(array[0]));

                for (int i = 1; i < array.length; i++) {
                    builder.append(',').newLine().fillSpaces();
                    builder.append(String.valueOf(array[i]));
                }

                builder.newLine().decIdent().fillSpaces().append(']');
            }
        }

        public static void writeJson(float[] array, JsonStringBuilder builder) {
            if (array == null) {
                builder.append(NULL);
            } else if (array.length == 0) {
                builder.append("[]");
            } else {
                builder.append('[').incIdent().newLine();
                builder.fillSpaces().append(String.valueOf(array[0]));

                for (int i = 1; i < array.length; i++) {
                    builder.append(',').newLine().fillSpaces();
                    builder.append(String.valueOf(array[i]));
                }

                builder.newLine().decIdent().fillSpaces().append(']');
            }
        }

        public static void writeJson(double[] array, JsonStringBuilder builder) {
            if (array == null) {
                builder.append(NULL);
            } else if (array.length == 0) {
                builder.append("[]");
            } else {
                builder.append('[').incIdent().newLine();
                builder.fillSpaces().append(String.valueOf(array[0]));

                for (int i = 1; i < array.length; i++) {
                    builder.append(',').newLine().fillSpaces();
                    builder.append(String.valueOf(array[i]));
                }

                builder.newLine().decIdent().fillSpaces().append(']');
            }
        }

        public static void writeJson(boolean[] array, JsonStringBuilder builder) {
            if (array == null) {
                builder.append(NULL);
            } else if (array.length == 0) {
                builder.append("[]");
            } else {
                builder.append('[').incIdent().newLine();
                builder.fillSpaces().append(String.valueOf(array[0]));

                for (int i = 1; i < array.length; i++) {
                    builder.append(',').newLine().fillSpaces();
                    builder.append(String.valueOf(array[i]));
                }

                builder.newLine().decIdent().fillSpaces().append(']');
            }
        }

        public static void writeJson(char[] array, JsonStringBuilder builder) {
            if (array == null) {
                builder.append(NULL);
            } else if (array.length == 0) {
                builder.append("[]");
            } else {
                builder.append('[').incIdent().newLine();
                builder.fillSpaces().append('\"').append(String.valueOf(array[0])).append('\"');

                for (int i = 1; i < array.length; i++) {
                    builder.append(',').newLine().fillSpaces();
                    builder.append('\"').append(String.valueOf(array[i])).append('\"');
                }

                builder.newLine().decIdent().fillSpaces().append(']');
            }
        }

        public static void writeJson(Object[] array, JsonStringBuilder builder) {
            if (array == null) {
                builder.append(NULL);
            } else if (array.length == 0) {
                builder.append("[]");
            } else {
                builder.append('[').newLine().incIdent().fillSpaces();
                JsonValue.writeJson(array[0], builder);

                for (int i = 1; i < array.length; i++) {
                    builder.append(',').newLine().fillSpaces();
                    JsonValue.writeJson(array[i], builder);
                }

                builder.newLine().decIdent().fillSpaces().append(']');
            }
        }
    }

    public static class JsonObject {
        public static void writeJson(Map map, JsonStringBuilder builder) {
            if (map == null) {
                builder.append(NULL);
                return;
            }

            Iterator iter = map.entrySet().iterator();

            builder.append('{').newLine().incIdent();
            while (iter.hasNext()) {
                Map.Entry entry = (Map.Entry) iter.next();
                builder.fillSpaces().append('\"');
                builder.append(escape(String.valueOf(entry.getKey())));
                builder.append('\"');
                builder.append(':').append(' ');
                JsonValue.writeJson(entry.getValue(), builder);
                if (iter.hasNext()) {
                    builder.append(',').newLine();
                }
            }
            builder.newLine().decIdent().fillSpaces().append('}');
        }
    }

    public static class JsonValue {
        public static void writeJson(Object value, JsonStringBuilder builder) {
            if (value == null) {
                builder.append(NULL);
            } else if (value instanceof String) {
                builder.append('"').append(escape((String) value)).append('"');
            } else if (value instanceof Double) {
                if (((Double) value).isInfinite() || ((Double) value).isNaN()) {
                    builder.append(NULL);
                } else {
                    builder.append(value.toString());
                }
            } else if (value instanceof Float) {
                if (((Float) value).isInfinite() || ((Float) value).isNaN()) {
                    builder.append(NULL);
                } else {
                    builder.append(value.toString());
                }
            } else if (value instanceof Number) {
                builder.append(value.toString());
            } else if (value instanceof Boolean) {
                builder.append(value.toString());
            } else if (value instanceof Map) {
                JsonObject.writeJson((Map) value, builder);
            } else if (value instanceof Collection) {
                JsonArray.writeJson((Collection) value, builder);
            } else if (value instanceof byte[]) {
                JsonArray.writeJson((byte[]) value, builder);
            } else if (value instanceof short[]) {
                JsonArray.writeJson((short[]) value, builder);
            } else if (value instanceof int[]) {
                JsonArray.writeJson((int[]) value, builder);
            } else if (value instanceof long[]) {
                JsonArray.writeJson((long[]) value, builder);
            } else if (value instanceof float[]) {
                JsonArray.writeJson((float[]) value, builder);
            } else if (value instanceof double[]) {
                JsonArray.writeJson((double[]) value, builder);
            } else if (value instanceof boolean[]) {
                JsonArray.writeJson((boolean[]) value, builder);
            } else if (value instanceof char[]) {
                JsonArray.writeJson((char[]) value, builder);
            } else if (value instanceof Object[]) {
                JsonArray.writeJson((Object[]) value, builder);
            } else {
                builder.append(value.toString());
            }
        }

        public static String escape(String s) {
            if (s == null) {
                return null;
            }
            StringBuilder sb = new StringBuilder();
            escape(s, sb);
            return sb.toString();
        }

        private static void escape(String s, StringBuilder sb) {
            final int len = s.length();
            for (int i = 0; i < len; i++) {
                char ch = s.charAt(i);
                switch (ch) {
                case '"':
                    sb.append("\\\"");
                    break;
                case '\\':
                    sb.append("\\\\");
                    break;
                case '\b':
                    sb.append("\\b");
                    break;
                case '\f':
                    sb.append("\\f");
                    break;
                case '\n':
                    sb.append("\\n");
                    break;
                case '\r':
                    sb.append("\\r");
                    break;
                case '\t':
                    sb.append("\\t");
                    break;
                case '/':
                    sb.append("\\/");
                    break;
                default:
                    if (ch <= '\u001F' || ch >= '\u007F' && ch <= '\u009F'
                        || ch >= '\u2000' && ch <= '\u20FF') {
                        String ss = Integer.toHexString(ch);
                        sb.append("\\u");
                        for (int k = 0; k < 4 - ss.length(); k++) {
                            sb.append('0');
                        }
                        sb.append(ss.toUpperCase());
                    } else {
                        sb.append(ch);
                    }
                    break;
                }
            }
        }
    }

    public static String toJson(Collection collection) {
        final JsonStringBuilder builder = new JsonStringBuilder();

        JsonArray.writeJson(collection, builder);
        return builder.toString();
    }

    public String toJson() {
        return toJson((Collection) getIterable());
    }

    public static String toJson(Map map) {
        final JsonStringBuilder builder = new JsonStringBuilder();

        JsonObject.writeJson(map, builder);
        return builder.toString();
    }

    public static class XmlStringBuilder {
        protected final StringBuilder builder;
        private int ident;

        public XmlStringBuilder() {
            builder = new StringBuilder("\n\n");
            ident = 2;
        }

        public XmlStringBuilder(StringBuilder builder, int ident) {
            this.builder = builder;
            this.ident = ident;
        }

        public XmlStringBuilder append(final String string) {
            builder.append(string);
            return this;
        }

        public XmlStringBuilder fillSpaces() {
            for (int index = 0; index < ident; index += 1) {
                builder.append(' ');
            }
            return this;
        }

        public XmlStringBuilder incIdent() {
            ident += 2;
            return this;
        }

        public XmlStringBuilder decIdent() {
            ident -= 2;
            return this;
        }

        public XmlStringBuilder newLine() {
            builder.append("\n");
            return this;
        }

        public String toString() {
            return builder.toString() + "\n";
        }
    }

    public static class XmlStringBuilderWithoutRoot extends XmlStringBuilder {
        public XmlStringBuilderWithoutRoot() {
            super(new StringBuilder("\n"), 0);
        }

        public String toString() {
            return builder.toString();
        }
    }

    public static class XmlArray {
        public static void writeXml(Collection collection, XmlStringBuilder builder) {
            if (collection == null) {
                builder.append(NULL);
                return;
            }

            Iterator iter = collection.iterator();

            while (iter.hasNext()) {
                Object value = iter.next();
                if (value == null) {
                    builder.fillSpaces().append(NULL_ELEMENT);
                    continue;
                }

                builder.fillSpaces().append(ELEMENT);
                XmlValue.writeXml(value, builder);
                builder.append(CLOSED_ELEMENT);
                if (iter.hasNext()) {
                    builder.newLine();
                }
            }
        }

        public static void writeXml(byte[] array, XmlStringBuilder builder) {
            if (array == null) {
                builder.fillSpaces().append(NULL_ELEMENT);
            } else if (array.length == 0) {
                builder.fillSpaces().append(EMPTY_ELEMENT);
            } else {
                for (int i = 0; i < array.length; i++) {
                    builder.fillSpaces().append(ELEMENT);
                    builder.append(String.valueOf(array[i]));
                    builder.append(CLOSED_ELEMENT);
                    if (i != array.length - 1) {
                        builder.newLine();
                    }
                }
            }
        }

        public static void writeXml(short[] array, XmlStringBuilder builder) {
            if (array == null) {
                builder.fillSpaces().append(NULL_ELEMENT);
            } else if (array.length == 0) {
                builder.fillSpaces().append(EMPTY_ELEMENT);
            } else {
                for (int i = 0; i < array.length; i++) {
                    builder.fillSpaces().append(ELEMENT);
                    builder.append(String.valueOf(array[i]));
                    builder.append(CLOSED_ELEMENT);
                    if (i != array.length - 1) {
                        builder.newLine();
                    }
                }
            }
        }

        public static void writeXml(int[] array, XmlStringBuilder builder) {
            if (array == null) {
                builder.fillSpaces().append(NULL_ELEMENT);
            } else if (array.length == 0) {
                builder.fillSpaces().append(EMPTY_ELEMENT);
            } else {
                for (int i = 0; i < array.length; i++) {
                    builder.fillSpaces().append(ELEMENT);
                    builder.append(String.valueOf(array[i]));
                    builder.append(CLOSED_ELEMENT);
                    if (i != array.length - 1) {
                        builder.newLine();
                    }
                }
            }
        }

        public static void writeXml(long[] array, XmlStringBuilder builder) {
            if (array == null) {
                builder.fillSpaces().append(NULL_ELEMENT);
            } else if (array.length == 0) {
                builder.fillSpaces().append(EMPTY_ELEMENT);
            } else {
                for (int i = 0; i < array.length; i++) {
                    builder.fillSpaces().append(ELEMENT);
                    builder.append(String.valueOf(array[i]));
                    builder.append(CLOSED_ELEMENT);
                    if (i != array.length - 1) {
                        builder.newLine();
                    }
                }
            }
        }

        public static void writeXml(float[] array, XmlStringBuilder builder) {
            if (array == null) {
                builder.fillSpaces().append(NULL_ELEMENT);
            } else if (array.length == 0) {
                builder.fillSpaces().append(EMPTY_ELEMENT);
            } else {
                for (int i = 0; i < array.length; i++) {
                    builder.fillSpaces().append(ELEMENT);
                    builder.append(String.valueOf(array[i]));
                    builder.append(CLOSED_ELEMENT);
                    if (i != array.length - 1) {
                        builder.newLine();
                    }
                }
            }
        }

        public static void writeXml(double[] array, XmlStringBuilder builder) {
            if (array == null) {
                builder.fillSpaces().append(NULL_ELEMENT);
            } else if (array.length == 0) {
                builder.fillSpaces().append(EMPTY_ELEMENT);
            } else {
                for (int i = 0; i < array.length; i++) {
                    builder.fillSpaces().append(ELEMENT);
                    builder.append(String.valueOf(array[i]));
                    builder.append(CLOSED_ELEMENT);
                    if (i != array.length - 1) {
                        builder.newLine();
                    }
                }
            }
        }

        public static void writeXml(boolean[] array, XmlStringBuilder builder) {
            if (array == null) {
                builder.fillSpaces().append(NULL_ELEMENT);
            } else if (array.length == 0) {
                builder.fillSpaces().append(EMPTY_ELEMENT);
            } else {
                for (int i = 0; i < array.length; i++) {
                    builder.fillSpaces().append(ELEMENT);
                    builder.append(String.valueOf(array[i]));
                    builder.append(CLOSED_ELEMENT);
                    if (i != array.length - 1) {
                        builder.newLine();
                    }
                }
            }
        }

        public static void writeXml(char[] array, XmlStringBuilder builder) {
            if (array == null) {
                builder.fillSpaces().append(NULL_ELEMENT);
            } else if (array.length == 0) {
                builder.fillSpaces().append(EMPTY_ELEMENT);
            } else {
                for (int i = 0; i < array.length; i++) {
                    builder.fillSpaces().append(ELEMENT);
                    builder.append(String.valueOf(array[i]));
                    builder.append(CLOSED_ELEMENT);
                    if (i != array.length - 1) {
                        builder.newLine();
                    }
                }
            }
        }

        public static void writeXml(Object[] array, XmlStringBuilder builder) {
            if (array == null) {
                builder.fillSpaces().append(NULL_ELEMENT);
            } else if (array.length == 0) {
                builder.fillSpaces().append(EMPTY_ELEMENT);
            } else {
                for (int i = 0; i < array.length; i++) {
                    builder.fillSpaces().append(ELEMENT);
                    XmlValue.writeXml(array[i], builder);
                    builder.append(CLOSED_ELEMENT);
                    if (i != array.length - 1) {
                        builder.newLine();
                    }
                }
            }
        }
    }

    public static class XmlObject {
        public static void writeXml(Map map, XmlStringBuilder builder) {
            if (map == null) {
                builder.append(NULL);
                return;
            }

            Iterator iter = map.entrySet().iterator();
            while (iter.hasNext()) {
                Map.Entry entry = (Map.Entry) iter.next();
                builder.fillSpaces().append("<").append(escape(String.valueOf(entry.getKey()))).append(">");
                XmlValue.writeXml(entry.getValue(), builder);
                builder.append("");
                if (iter.hasNext()) {
                    builder.newLine();
                }
            }
        }
    }

    public static class XmlValue {
        public static void writeXml(Object value, XmlStringBuilder builder) {
            if (value == null) {
                builder.append(NULL);
            } else if (value instanceof String) {
                builder.append(escape((String) value));
            } else if (value instanceof Double) {
                if (((Double) value).isInfinite() || ((Double) value).isNaN()) {
                    builder.append(NULL);
                } else {
                    builder.append(value.toString());
                }
            } else if (value instanceof Float) {
                if (((Float) value).isInfinite() || ((Float) value).isNaN()) {
                    builder.append(NULL);
                } else {
                    builder.append(value.toString());
                }
            } else if (value instanceof Number) {
                builder.append(value.toString());
            } else if (value instanceof Boolean) {
                builder.append(value.toString());
            } else if (value instanceof Map) {
                builder.newLine().incIdent();
                XmlObject.writeXml((Map) value, builder);
                builder.newLine().decIdent().fillSpaces();
            } else if (value instanceof Collection) {
                builder.newLine().incIdent();
                XmlArray.writeXml((Collection) value, builder);
                builder.newLine().decIdent().fillSpaces();
            } else if (value instanceof byte[]) {
                builder.newLine().incIdent();
                XmlArray.writeXml((byte[]) value, builder);
                builder.newLine().decIdent().fillSpaces();
            } else if (value instanceof short[]) {
                builder.newLine().incIdent();
                XmlArray.writeXml((short[]) value, builder);
                builder.newLine().decIdent().fillSpaces();
            } else if (value instanceof int[]) {
                builder.newLine().incIdent();
                XmlArray.writeXml((int[]) value, builder);
                builder.newLine().decIdent().fillSpaces();
            } else if (value instanceof long[]) {
                builder.newLine().incIdent();
                XmlArray.writeXml((long[]) value, builder);
                builder.newLine().decIdent().fillSpaces();
            } else if (value instanceof float[]) {
                builder.newLine().incIdent();
                XmlArray.writeXml((float[]) value, builder);
                builder.newLine().decIdent().fillSpaces();
            } else if (value instanceof double[]) {
                builder.newLine().incIdent();
                XmlArray.writeXml((double[]) value, builder);
                builder.newLine().decIdent().fillSpaces();
            } else if (value instanceof boolean[]) {
                builder.newLine().incIdent();
                XmlArray.writeXml((boolean[]) value, builder);
                builder.newLine().decIdent().fillSpaces();
            } else if (value instanceof char[]) {
                builder.newLine().incIdent();
                XmlArray.writeXml((char[]) value, builder);
                builder.newLine().decIdent().fillSpaces();
            } else if (value instanceof Object[]) {
                builder.newLine().incIdent();
                XmlArray.writeXml((Object[]) value, builder);
                builder.newLine().decIdent().fillSpaces();
            } else {
                builder.append(value.toString());
            }
        }

        public static String escape(String s) {
            if (s == null) {
                return null;
            }
            StringBuilder sb = new StringBuilder();
            escape(s, sb);
            return sb.toString();
        }

        private static void escape(String s, StringBuilder sb) {
            final int len = s.length();
            for (int i = 0; i < len; i++) {
                char ch = s.charAt(i);
                switch (ch) {
                case '"':
                    sb.append(""");
                    break;
                case '\'':
                    sb.append("'");
                    break;
                case '&':
                    sb.append("&");
                    break;
                case '<':
                    sb.append("<");
                    break;
                case '>':
                    sb.append(">");
                    break;
                case '\\':
                    sb.append("\\\\");
                    break;
                case '\b':
                    sb.append("\\b");
                    break;
                case '\f':
                    sb.append("\\f");
                    break;
                case '\n':
                    sb.append("\\n");
                    break;
                case '\r':
                    sb.append("\\r");
                    break;
                case '\t':
                    sb.append("\\t");
                    break;
                case '/':
                    sb.append("\\/");
                    break;
                default:
                    if (ch <= '\u001F' || ch >= '\u007F' && ch <= '\u009F'
                        || ch >= '\u2000' && ch <= '\u20FF') {
                        String ss = Integer.toHexString(ch);
                        sb.append("&#x");
                        for (int k = 0; k < 4 - ss.length(); k++) {
                            sb.append('0');
                        }
                        sb.append(ss.toUpperCase()).append(";");
                    } else {
                        sb.append(ch);
                    }
                    break;
                }
            }
        }
    }

    public static String toXml(Collection collection) {
        final XmlStringBuilder builder = new XmlStringBuilder();

        XmlArray.writeXml(collection, builder);
        return builder.toString();
    }

    public String toXml() {
        return toXml((Collection) getIterable());
    }

    public static String toXml(Map map) {
        final XmlStringBuilder builder;
        if (map != null && map.size() == 1) {
            builder = new XmlStringBuilderWithoutRoot();
        } else {
            builder = new XmlStringBuilder();
        }

        XmlObject.writeXml(map, builder);
        return builder.toString();
    }

    public static class ParseException extends RuntimeException {
        private final int offset;
        private final int line;
        private final int column;

        public ParseException(String message, int offset, int line, int column) {
            super(message + " at " + line + ":" + column);
            this.offset = offset;
            this.line = line;
            this.column = column;
        }

        public int getOffset() {
            return offset;
        }

        public int getLine() {
            return line;
        }

        public int getColumn() {
            return column;
        }
    }

    public static class JsonParser {
        private final String json;
        private int index;
        private int line;
        private int lineOffset;
        private int current;
        private StringBuilder captureBuffer;
        private int captureStart;

        public JsonParser(String string) {
            this.json = string;
            line = 1;
            captureStart = -1;
        }

        public Object parse() {
            read();
            skipWhiteSpace();
            final Object result = readValue();
            skipWhiteSpace();
            if (!isEndOfText()) {
                throw error("Unexpected character");
            }
            return result;
        }

        private Object readValue() {
            switch (current) {
            case 'n':
                return readNull();
            case 't':
                return readTrue();
            case 'f':
                return readFalse();
            case '"':
                return readString();
            case '[':
                return readArray();
            case '{':
                return readObject();
            case '-':
            case '0':
            case '1':
            case '2':
            case '3':
            case '4':
            case '5':
            case '6':
            case '7':
            case '8':
            case '9':
                return readNumber();
            default:
                throw expected("value");
            }
        }

        private List readArray() {
            read();
            List array = newArrayList();
            skipWhiteSpace();
            if (readChar(']')) {
                return array;
            }
            do {
                skipWhiteSpace();
                array.add(readValue());
                skipWhiteSpace();
            } while (readChar(','));
            if (!readChar(']')) {
                throw expected("',' or ']'");
            }
            return array;
        }

        private Map readObject() {
            read();
            Map object = newLinkedHashMap();
            skipWhiteSpace();
            if (readChar('}')) {
                return object;
            }
            do {
                skipWhiteSpace();
                String name = readName();
                skipWhiteSpace();
                if (!readChar(':')) {
                    throw expected("':'");
                }
                skipWhiteSpace();
                object.put(name, readValue());
                skipWhiteSpace();
            } while (readChar(','));
            if (!readChar('}')) {
                throw expected("',' or '}'");
            }
            return object;
        }

        private String readName() {
            if (current != '"') {
                throw expected("name");
            }
            return readString();
        }

        private String readNull() {
            read();
            readRequiredChar('u');
            readRequiredChar('l');
            readRequiredChar('l');
            return null;
        }

        private Boolean readTrue() {
            read();
            readRequiredChar('r');
            readRequiredChar('u');
            readRequiredChar('e');
            return Boolean.TRUE;
        }

        private Boolean readFalse() {
            read();
            readRequiredChar('a');
            readRequiredChar('l');
            readRequiredChar('s');
            readRequiredChar('e');
            return Boolean.FALSE;
        }

        private void readRequiredChar(char ch) {
            if (!readChar(ch)) {
                throw expected("'" + ch + "'");
            }
        }

        private String readString() {
            read();
            startCapture();
            while (current != '"') {
                if (current == '\\') {
                    pauseCapture();
                    readEscape();
                    startCapture();
                } else if (current < 0x20) {
                    throw expected("valid string character");
                } else {
                    read();
                }
            }
            String string = endCapture();
            read();
            return string;
        }

        private void readEscape() {
            read();
            switch (current) {
            case '"':
            case '/':
            case '\\':
                captureBuffer.append((char) current);
                break;
            case 'b':
                captureBuffer.append('\b');
                break;
            case 'f':
                captureBuffer.append('\f');
                break;
            case 'n':
                captureBuffer.append('\n');
                break;
            case 'r':
                captureBuffer.append('\r');
                break;
            case 't':
                captureBuffer.append('\t');
                break;
            case 'u':
                char[] hexChars = new char[4];
                boolean isHexCharsDigits = true;
                for (int i = 0; i < 4; i++) {
                    read();
                    if (!isHexDigit()) {
                        isHexCharsDigits = false;
                    }
                    hexChars[i] = (char) current;
                }
                if (isHexCharsDigits) {
                    captureBuffer.append((char) Integer.parseInt(new String(hexChars), 16));
                } else {
                    captureBuffer.append("\\u").append(hexChars[0]).append(hexChars[1]).append(hexChars[2])
                        .append(hexChars[3]);
                }
                break;
            default:
                throw expected("valid escape sequence");
            }
            read();
        }

        private Number readNumber() {
            startCapture();
            readChar('-');
            int firstDigit = current;
            if (!readDigit()) {
                throw expected("digit");
            }
            if (firstDigit != '0') {
                while (readDigit()) {
                }
            }
            readFraction();
            readExponent();
            final String number = endCapture();
            if (number.contains(".") || number.contains("e") || number.contains("E")) {
                return Double.valueOf(number);
            } else {
                return Long.valueOf(number);
            }
        }

        private boolean readFraction() {
            if (!readChar('.')) {
                return false;
            }
            if (!readDigit()) {
                throw expected("digit");
            }
            while (readDigit()) {
            }
            return true;
        }

        private boolean readExponent() {
            if (!readChar('e') && !readChar('E')) {
                return false;
            }
            if (!readChar('+')) {
                readChar('-');
            }
            if (!readDigit()) {
                throw expected("digit");
            }
            while (readDigit()) {
            }
            return true;
        }

        private boolean readChar(char ch) {
            if (current != ch) {
                return false;
            }
            read();
            return true;
        }

        private boolean readDigit() {
            if (!isDigit()) {
                return false;
            }
            read();
            return true;
        }

        private void skipWhiteSpace() {
            while (isWhiteSpace()) {
                read();
            }
        }

        private void read() {
            if (index == json.length()) {
                current = -1;
                return;
            }
            if (current == '\n') {
                line++;
                lineOffset = index;
            }
            current = json.charAt(index++);
        }

        private void startCapture() {
            if (captureBuffer == null) {
                captureBuffer = new StringBuilder();
            }
            captureStart = index - 1;
        }

        private void pauseCapture() {
            captureBuffer.append(json.substring(captureStart, index - 1));
            captureStart = -1;
        }

        private String endCapture() {
            int end = current == -1 ? index : index - 1;
            String captured;
            if (captureBuffer.length() > 0) {
                captureBuffer.append(json.substring(captureStart, end));
                captured = captureBuffer.toString();
                captureBuffer.setLength(0);
            } else {
                captured = json.substring(captureStart, end);
            }
            captureStart = -1;
            return captured;
        }

        private ParseException expected(String expected) {
            if (isEndOfText()) {
                return error("Unexpected end of input");
            }
            return error("Expected " + expected);
        }

        private ParseException error(String message) {
            int absIndex = index;
            int column = absIndex - lineOffset;
            int offset = isEndOfText() ? absIndex : absIndex - 1;
            return new ParseException(message, offset, line, column - 1);
        }

        private boolean isWhiteSpace() {
            return current == ' ' || current == '\t' || current == '\n' || current == '\r';
        }

        private boolean isDigit() {
            return current >= '0' && current <= '9';
        }

        private boolean isHexDigit() {
            return isDigit() || current >= 'a' && current <= 'f' || current >= 'A'
                    && current <= 'F';
        }

        private boolean isEndOfText() {
            return current == -1;
        }

    }

    public static Object fromJson(String string) {
        return new JsonParser(string).parse();
    }

    public Object fromJson() {
        return fromJson(getString().get());
    }

    @SuppressWarnings("unchecked")
    private static Object getValue(final Object value) {
        if (value instanceof Map && ((Map) value).entrySet().iterator().hasNext()) {
            final Map.Entry entry = ((Map) value).entrySet().iterator().next();
            if (entry.getKey().equals("#text") || entry.getKey().equals("element")) {
                return entry.getValue();
            }
        }
        return value;
    }

    @SuppressWarnings("unchecked")
    private static Map createMap(final org.w3c.dom.Node node) {
        final Map map = newLinkedHashMap();
        final org.w3c.dom.NodeList nodeList = node.getChildNodes();
        for (int index = 0; index < nodeList.getLength(); index++) {
            final org.w3c.dom.Node currentNode = nodeList.item(index);
            final String name = currentNode.getNodeName();
            final Object value;
            if (currentNode.getNodeType() == org.w3c.dom.Node.ELEMENT_NODE) {
                value = createMap(currentNode);
            } else {
                value = currentNode.getTextContent();
            }
            if ("#text".equals(name) && value.toString().trim().isEmpty()) {
                continue;
            }
            if (map.containsKey(name)) {
                final Object object = map.get(name);
                if (object instanceof List) {
                    ((List) object).add(getValue(value));
                } else {
                    final List objects = newArrayList();
                    objects.add(object);
                    objects.add(getValue(value));
                    map.put(name, objects);
                }
            } else {
                map.put(name, getValue(value));
            }
        }
        return map;
    }

    public static Object fromXml(final String xml) {
        try {
            final java.io.InputStream stream = new java.io.ByteArrayInputStream(xml.getBytes("UTF-8"));
            final javax.xml.parsers.DocumentBuilderFactory factory =
                javax.xml.parsers.DocumentBuilderFactory.newInstance();
            factory.setNamespaceAware(true);
            final org.w3c.dom.Document document = factory.newDocumentBuilder().parse(stream);
            return createMap(document);
        } catch (Exception ex) {
            throw new IllegalArgumentException(ex);
        }
    }

    public Object fromXml() {
        return fromXml(getString().get());
    }

    public static class JsonJavaStringBuilder {
        private final StringBuilder builder;
        private int ident;

        public JsonJavaStringBuilder() {
            builder = new StringBuilder("\"");
        }

        public JsonJavaStringBuilder append(final char character) {
            builder.append(character);
            return this;
        }

        public JsonJavaStringBuilder append(final String string) {
            builder.append(string);
            return this;
        }

        public JsonJavaStringBuilder fillSpaces() {
            for (int index = 0; index < ident; index += 1) {
                builder.append(' ');
            }
            return this;
        }

        public JsonJavaStringBuilder incIdent() {
            ident += 2;
            return this;
        }

        public JsonJavaStringBuilder decIdent() {
            ident -= 2;
            return this;
        }

        public JsonJavaStringBuilder newLine() {
            builder.append("\\n\"\n + \"");
            return this;
        }

        public String toString() {
            return builder.toString() + "\";";
        }
    }

    public static class JsonJavaArray {
        public static void writeJson(Collection collection, JsonJavaStringBuilder builder) {
            if (collection == null) {
                builder.append(NULL);
                return;
            }

            Iterator iter = collection.iterator();

            builder.append('[').incIdent().newLine();
            while (iter.hasNext()) {
                Object value = iter.next();
                if (value == null) {
                    builder.fillSpaces().append(NULL);
                    continue;
                }

                builder.fillSpaces();
                JsonJavaValue.writeJson(value, builder);
                if (iter.hasNext()) {
                    builder.append(',').newLine();
                }
            }
            builder.newLine().decIdent().fillSpaces().append(']');
        }

        public static void writeJson(byte[] array, JsonJavaStringBuilder builder) {
            if (array == null) {
                builder.append(NULL);
            } else if (array.length == 0) {
                builder.append("[]");
            } else {
                builder.append('[').incIdent().newLine();
                builder.fillSpaces().append(String.valueOf(array[0]));

                for (int i = 1; i < array.length; i++) {
                    builder.append(',').newLine().fillSpaces();
                    builder.append(String.valueOf(array[i]));
                }

                builder.newLine().decIdent().fillSpaces().append(']');
            }
        }

        public static void writeJson(short[] array, JsonJavaStringBuilder builder) {
            if (array == null) {
                builder.append(NULL);
            } else if (array.length == 0) {
                builder.append("[]");
            } else {
                builder.append('[').incIdent().newLine();
                builder.fillSpaces().append(String.valueOf(array[0]));

                for (int i = 1; i < array.length; i++) {
                    builder.append(',').newLine().fillSpaces();
                    builder.append(String.valueOf(array[i]));
                }

                builder.newLine().decIdent().fillSpaces().append(']');
            }
        }

        public static void writeJson(int[] array, JsonJavaStringBuilder builder) {
            if (array == null) {
                builder.append(NULL);
            } else if (array.length == 0) {
                builder.append("[]");
            } else {
                builder.append('[').incIdent().newLine();
                builder.fillSpaces().append(String.valueOf(array[0]));

                for (int i = 1; i < array.length; i++) {
                    builder.append(',').newLine().fillSpaces();
                    builder.append(String.valueOf(array[i]));
                }

                builder.newLine().decIdent().fillSpaces().append(']');
            }
        }

        public static void writeJson(long[] array, JsonJavaStringBuilder builder) {
            if (array == null) {
                builder.append(NULL);
            } else if (array.length == 0) {
                builder.append("[]");
            } else {
                builder.append('[').incIdent().newLine();
                builder.fillSpaces().append(String.valueOf(array[0]));

                for (int i = 1; i < array.length; i++) {
                    builder.append(',').newLine().fillSpaces();
                    builder.append(String.valueOf(array[i]));
                }

                builder.newLine().decIdent().fillSpaces().append(']');
            }
        }

        public static void writeJson(float[] array, JsonJavaStringBuilder builder) {
            if (array == null) {
                builder.append(NULL);
            } else if (array.length == 0) {
                builder.append("[]");
            } else {
                builder.append('[').incIdent().newLine();
                builder.fillSpaces().append(String.valueOf(array[0]));

                for (int i = 1; i < array.length; i++) {
                    builder.append(',').newLine().fillSpaces();
                    builder.append(String.valueOf(array[i]));
                }

                builder.newLine().decIdent().fillSpaces().append(']');
            }
        }

        public static void writeJson(double[] array, JsonJavaStringBuilder builder) {
            if (array == null) {
                builder.append(NULL);
            } else if (array.length == 0) {
                builder.append("[]");
            } else {
                builder.append('[').incIdent().newLine();
                builder.fillSpaces().append(String.valueOf(array[0]));

                for (int i = 1; i < array.length; i++) {
                    builder.append(',').newLine().fillSpaces();
                    builder.append(String.valueOf(array[i]));
                }

                builder.newLine().decIdent().fillSpaces().append(']');
            }
        }

        public static void writeJson(boolean[] array, JsonJavaStringBuilder builder) {
            if (array == null) {
                builder.append(NULL);
            } else if (array.length == 0) {
                builder.append("[]");
            } else {
                builder.append('[').incIdent().newLine();
                builder.fillSpaces().append(String.valueOf(array[0]));

                for (int i = 1; i < array.length; i++) {
                    builder.append(',').newLine().fillSpaces();
                    builder.append(String.valueOf(array[i]));
                }

                builder.newLine().decIdent().fillSpaces().append(']');
            }
        }

        public static void writeJson(char[] array, JsonJavaStringBuilder builder) {
            if (array == null) {
                builder.append(NULL);
            } else if (array.length == 0) {
                builder.append("[]");
            } else {
                builder.append('[').incIdent().newLine();
                builder.fillSpaces().append('\"').append(String.valueOf(array[0])).append('\"');

                for (int i = 1; i < array.length; i++) {
                    builder.append(',').newLine().fillSpaces();
                    builder.append('\"').append(String.valueOf(array[i])).append('\"');
                }

                builder.newLine().decIdent().fillSpaces().append(']');
            }
        }

        public static void writeJson(Object[] array, JsonJavaStringBuilder builder) {
            if (array == null) {
                builder.append(NULL);
            } else if (array.length == 0) {
                builder.append("[]");
            } else {
                builder.append('[').newLine().incIdent().fillSpaces();
                JsonJavaValue.writeJson(array[0], builder);

                for (int i = 1; i < array.length; i++) {
                    builder.append(',').newLine().fillSpaces();
                    JsonJavaValue.writeJson(array[i], builder);
                }

                builder.newLine().decIdent().fillSpaces().append(']');
            }
        }
    }

    public static class JsonJavaObject {
        public static void writeJson(Map map, JsonJavaStringBuilder builder) {
            if (map == null) {
                builder.append(NULL);
                return;
            }

            Iterator iter = map.entrySet().iterator();

            builder.append('{').newLine().incIdent();
            while (iter.hasNext()) {
                Map.Entry entry = (Map.Entry) iter.next();
                builder.fillSpaces().append("\\\"");
                builder.append(escape(String.valueOf(entry.getKey())));
                builder.append("\\\"");
                builder.append(':').append(' ');
                JsonJavaValue.writeJson(entry.getValue(), builder);
                if (iter.hasNext()) {
                    builder.append(',').newLine();
                }
            }
            builder.newLine().decIdent().fillSpaces().append('}');
        }
    }

    public static class JsonJavaValue {
        public static void writeJson(Object value, JsonJavaStringBuilder builder) {
            if (value == null) {
                builder.append(NULL);
            } else if (value instanceof String) {
                builder.append("\\\"").append(escape((String) value)).append("\\\"");
            } else if (value instanceof Double) {
                if (((Double) value).isInfinite() || ((Double) value).isNaN()) {
                    builder.append(NULL);
                } else {
                    builder.append(value.toString());
                }
            } else if (value instanceof Float) {
                if (((Float) value).isInfinite() || ((Float) value).isNaN()) {
                    builder.append(NULL);
                } else {
                    builder.append(value.toString());
                }
            } else if (value instanceof Number) {
                builder.append(value.toString());
            } else if (value instanceof Boolean) {
                builder.append(value.toString());
            } else if (value instanceof Map) {
                JsonJavaObject.writeJson((Map) value, builder);
            } else if (value instanceof Collection) {
                JsonJavaArray.writeJson((Collection) value, builder);
            } else if (value instanceof byte[]) {
                JsonJavaArray.writeJson((byte[]) value, builder);
            } else if (value instanceof short[]) {
                JsonJavaArray.writeJson((short[]) value, builder);
            } else if (value instanceof int[]) {
                JsonJavaArray.writeJson((int[]) value, builder);
            } else if (value instanceof long[]) {
                JsonJavaArray.writeJson((long[]) value, builder);
            } else if (value instanceof float[]) {
                JsonJavaArray.writeJson((float[]) value, builder);
            } else if (value instanceof double[]) {
                JsonJavaArray.writeJson((double[]) value, builder);
            } else if (value instanceof boolean[]) {
                JsonJavaArray.writeJson((boolean[]) value, builder);
            } else if (value instanceof char[]) {
                JsonJavaArray.writeJson((char[]) value, builder);
            } else if (value instanceof Object[]) {
                JsonJavaArray.writeJson((Object[]) value, builder);
            } else {
                builder.append(value.toString());
            }
        }

        public static String escape(String s) {
            if (s == null) {
                return null;
            }
            StringBuilder sb = new StringBuilder();
            escape(s, sb);
            return sb.toString();
        }

        private static void escape(String s, StringBuilder sb) {
            final int len = s.length();
            for (int i = 0; i < len; i++) {
                char ch = s.charAt(i);
                switch (ch) {
                case '"':
                    sb.append("\\\"");
                    break;
                case '\\':
                    sb.append("\\\\");
                    break;
                case '\b':
                    sb.append("\\b");
                    break;
                case '\f':
                    sb.append("\\f");
                    break;
                case '\n':
                    sb.append("\\n");
                    break;
                case '\r':
                    sb.append("\\r");
                    break;
                case '\t':
                    sb.append("\\t");
                    break;
                case '/':
                    sb.append("\\/");
                    break;
                default:
                    if (ch <= '\u001F' || ch >= '\u007F' && ch <= '\u009F'
                        || ch >= '\u2000' && ch <= '\u20FF') {
                        String ss = Integer.toHexString(ch);
                        sb.append("\\u");
                        for (int k = 0; k < 4 - ss.length(); k++) {
                            sb.append('0');
                        }
                        sb.append(ss.toUpperCase());
                    } else {
                        sb.append(ch);
                    }
                    break;
                }
            }
        }
    }

    public static String toJsonJavaString(Collection collection) {
        final JsonJavaStringBuilder builder = new JsonJavaStringBuilder();

        JsonJavaArray.writeJson(collection, builder);
        return builder.toString();
    }

    public String toJsonJavaString() {
        return toJsonJavaString((Collection) getIterable());
    }


    public static String toJsonJavaString(Map map) {
        final JsonJavaStringBuilder builder = new JsonJavaStringBuilder();

        JsonJavaObject.writeJson(map, builder);
        return builder.toString();
    }

    public String camelCase() {
        return camelCase(getString().get());
    }

    public String lowerFirst() {
        return lowerFirst(getString().get());
    }

    public String upperFirst() {
        return upperFirst(getString().get());
    }

    public String capitalize() {
        return capitalize(getString().get());
    }

    public String deburr() {
        return deburr(getString().get());
    }

    public boolean endsWith(final String target) {
        return endsWith(getString().get(), target);
    }

    public boolean endsWith(final String target, final Integer position) {
        return endsWith(getString().get(), target, position);
    }

    public String kebabCase() {
        return kebabCase(getString().get());
    }

    public String repeat(final int length) {
        return repeat(getString().get(), length);
    }

    public String pad(final int length) {
        return pad(getString().get(), length);
    }

    public String pad(final int length, final String chars) {
        return pad(getString().get(), length, chars);
    }

    public String padStart(final int length) {
        return padStart(getString().get(), length);
    }

    public String padStart(final int length, final String chars) {
        return padStart(getString().get(), length, chars);
    }

    public String padEnd(final int length) {
        return padEnd(getString().get(), length);
    }

    public String padEnd(final int length, final String chars) {
        return padEnd(getString().get(), length, chars);
    }

    public String snakeCase() {
        return snakeCase(getString().get());
    }

    public String startCase() {
        return startCase(getString().get());
    }

    public boolean startsWith(final String target) {
        return startsWith(getString().get(), target);
    }

    public boolean startsWith(final String target, final Integer position) {
        return startsWith(getString().get(), target, position);
    }

    public String trim() {
        return trim(getString().get());
    }

    public String trimWith(final String chars) {
        return trim(getString().get(), chars);
    }

    public String trimStart() {
        return trimStart(getString().get());
    }

    public String trimStartWith(final String chars) {
        return trimStart(getString().get(), chars);
    }

    public String trimEnd() {
        return trimEnd(getString().get());
    }

    public String trimEndWith(final String chars) {
        return trimEnd(getString().get(), chars);
    }

    public String trunc() {
        return trunc(getString().get());
    }

    public String trunc(final int length) {
        return trunc(getString().get(), length);
    }

    public String uncapitalize() {
        return uncapitalize(getString().get());
    }

    public List words() {
        return words(getString().get());
    }

    public static void main(String ... args) {
        final String message = "Underscore-java-string is a string plugin for underscore-java.\n\n"
            + "For docs, license, tests, and downloads, see: http://javadev.github.io/underscore-java";
        System.out.println(message);
    }
}