public static final Pattern COMB_DIACRITICAL = Pattern.compile("\\p{InCombiningDiacriticalMarks}+"); public static final String EMPTY = ""; public static final Pattern HYPEN_SPACE = Pattern.compile("[-\\s]+"); public static final int ND_FACTOR = 1000 * 60 * 60 * 23; public static final Pattern ONLY_SLUG_CHARS = Pattern.compile("[^-\\w\\s]"); public static final String ORDINAL_FMT = "%d%s"; public static final Pattern PUNCTUATION = Pattern.compile("\\p{Punct}+"); public static final String SPACE = " "; public static final Pattern SPLIT_CAMEL = Pattern
        .compile("(?<=[A-Z])(?=[A-Z][a-z])|(?<=[^A-Z])(?=[A-Z])|(?<=[A-Za-z])(?=[^A-Za-z])"); public static final BigDecimal THOUSAND = BigDecimal.valueOf(1000); public static final Map<BigDecimal, String> bigDecExponents = new LinkedHashMap<BigDecimal, String>(); public static final Map<Long, String> binPrefixes = new LinkedHashMap<Long, String>(); public static final Joiner commaJoiner = Joiner.on(", ").skipNulls(); public static final Map<Long, String> metricPrefixes = new LinkedHashMap<Long, String>(); public static final Map<Long, String> nanoTimePrefixes = new LinkedHashMap<Long, String>(); public static final List<String> titleIgnoredWords = Arrays.asList(new String[] {
        "a", "an", "and", "but", "nor", "it", "the", "to", "with", "in", "on", "of",
        "up", "or", "at", "into", "onto", "by", "from", "then", "for", "via", "versus"
}); public static final Pattern titleWordSperator = Pattern.compile(".+(\\||-|/).+"); public class MessageFormat extends ExtendedMessageFormat
{

    private static final long serialVersionUID = -5384364921909539710L;

    private final static Map<String, FormatFactory> formatFactories = loadFormatFactories();

    private static Map<String, FormatFactory> loadFormatFactories()
    {

        Map<String, FormatFactory> factories = new HashMap<String, FormatFactory>();
        ServiceLoader<FormatProvider> ldr = ServiceLoader.load(FormatProvider.class);

        for (FormatProvider provider : ldr)
        {
            registerProvider(factories, provider);
        }

        return factories;

    }

    private static void registerProvider(Map<String, FormatFactory> factories, FormatProvider provider)
    {

        String formatName = provider.getFormatName();
        FormatFactory factory = provider.getFactory();

        if (formatName.indexOf('|') > -1)
        {
            String[] names = formatName.split("\\|");
            for (String name : names)
            {
                factories.put(name, factory);
            }
        } else
        {
            factories.put(formatName, factory);
        }

    }

    public MessageFormat(String pattern)
    {

        super(pattern, formatFactories);

    }

    public MessageFormat(String pattern, Locale locale)
    {

        super(pattern, locale, formatFactories);

    }

    public MessageFormat(String pattern, Locale locale, Map<String, ? extends FormatFactory> registry)
    {

        super(pattern, locale, registry);

    }

    public MessageFormat(String pattern, Map<String, ? extends FormatFactory> registry)
    {

        super(pattern, registry);

    }

    /**
     * Formats the current pattern with the given arguments.
     * 
     * @param arguments
     *            The formatting arguments
     * @return Formatted message
     */
    public String render(Object... arguments)
    {

        return format(arguments);

    }

    /**
     * Formats the current pattern with the given arguments.
     * 
     * @param buffer
     *            The StringBuffer
     * @param arguments
     *            The formatting arguments
     * @return StringBuffer with the formatted message
     */
    public StringBuffer render(StringBuffer buffer, Object... arguments)
    {

        return format(arguments, buffer, null);

    }

} public interface ContextFactory
{

    Context createContext();

} public class DefaultContext implements Context, StandardContext
{

    private static final String BUNDLE_LOCATION = "i18n.Humanize";

    private static final String ORDINAL_SUFFIXES = "ordinal.suffixes";

    private static final String TIME_SUFFIXES = "time.suffixes";

    private static final String CURRENCY = "currency";

    private static final String DECIMAL = "decimal";

    private static final String NUMBER = "number";

    private static final String DIGITS = "digits";

    private static final String PERCENT = "percent";

    private static final String DATE_FORMAT = "date";

    private static final String DATE_TIME_FORMAT = "date.time";

    private static final String SIMPLE_DATE = "simple.date";

    private static final String PRETTY_TIME = "pretty.time";

    private static final String MASK = "mask";

    private final static CacheProvider sharedCache = loadCacheProvider();

    private static CacheProvider loadCacheProvider()
    {
        ServiceLoader<CacheProvider> ldr = ServiceLoader.load(CacheProvider.class);

        for (CacheProvider provider : ldr)
        {
            return provider;
        }

        throw new RuntimeException("No CacheProvider was found");
    }

    private final CacheProvider localCache = loadCacheProvider();

    private Locale locale;

    public DefaultContext()
    {
        this(Locale.getDefault());
    }

    public DefaultContext(Locale locale)
    {
        setLocale(locale);
    }

    @Override
    public String digitStrings(int index)
    {
        return getStringByIndex(DIGITS, index);
    }

    @Override
    public String formatDate(int style, Date value)
    {
        return getDateFormat(style).format(value);
    }

    @Override
    public String formatDateTime(Date date)
    {
        return getDateTimeFormat().format(date);
    }

    @Override
    public String formatDateTime(int dateStyle, int timeStyle, Date date)
    {
        return getDateTimeFormat(dateStyle, timeStyle).format(date);
    }

    @Override
    public String formatDecimal(Number value)
    {
        return getNumberFormat().format(value);
    }

    @Override
    public String formatMessage(String key, Object... args)
    {
        MessageFormat fmt = getMessageFormat();
        fmt.applyPattern(getBundle().getString(key));
        return fmt.render(args);
    }

    @Override
    public String formatRelativeDate(Date reference, Date duration)
    {
        return getPrettyTimeFormat().format(reference, duration);
    }

    @Override
    public String formatRelativeDate(Date reference, Date duration, long precision)
    {
        return getPrettyTimeFormat().format(reference, duration, precision);
    }

    @Override
    public ResourceBundle getBundle()
    {
        return sharedCache.getBundle(locale, new Callable<ResourceBundle>()
        {
            @Override
            public ResourceBundle call() throws Exception
            {
                return ResourceBundle.getBundle(BUNDLE_LOCATION, locale, new UTF8Control());
            }
        });
    }

    @Override
    public DecimalFormat getCurrencyFormat()
    {
        return sharedCache.getFormat(CURRENCY, locale, new Callable<DecimalFormat>()
        {
            @Override
            public DecimalFormat call() throws Exception
            {
                return (DecimalFormat) NumberFormat.getCurrencyInstance(locale);
            }
        });
    }

    @Override
    public DateFormat getDateFormat(final int style)
    {
        String name = DATE_FORMAT + style;

        return localCache.getFormat(name, locale, new Callable<DateFormat>()
        {
            @Override
            public DateFormat call() throws Exception
            {
                return DateFormat.getDateInstance(style, locale);
            }
        });
    }

    @Override
    public DateFormat getDateFormat(final String pattern)
    {
        return localCache.getFormat(SIMPLE_DATE + pattern.hashCode(), locale, new Callable<DateFormat>()
        {
            @Override
            public DateFormat call() throws Exception
            {
                return new SimpleDateFormat(pattern, locale);
            }
        });
    }

    @Override
    public DateFormat getDateTimeFormat()
    {
        return getDateTimeFormat(DateFormat.SHORT, DateFormat.SHORT);
    }

    @Override
    public DateFormat getDateTimeFormat(final int dateStyle, final int timeStyle)
    {
        String name = DATE_TIME_FORMAT + dateStyle + timeStyle;

        return localCache.getFormat(name, locale, new Callable<DateFormat>()
        {
            @Override
            public DateFormat call() throws Exception
            {
                return DateFormat.getDateTimeInstance(dateStyle, timeStyle, locale);
            }
        });
    }

    @Override
    public DecimalFormat getDecimalFormat()
    {
        return localCache.getFormat(DECIMAL, locale, new Callable<DecimalFormat>()
        {
            @Override
            public DecimalFormat call() throws Exception
            {
                return (DecimalFormat) DecimalFormat.getInstance(locale);
            }
        });
    }

    @Override
    public Locale getLocale()
    {
        return locale;
    }

    @Override
    public MaskFormat getMaskFormat()
    {
        return localCache.getFormat(MASK, Locale.ROOT, new Callable<MaskFormat>()
        {
            @Override
            public MaskFormat call() throws Exception
            {
                return new MaskFormat("");
            }
        });
    }

    @Override
    public String getMessage(String key)
    {
        return getBundle().getString(key);
    }

    @Override
    public MessageFormat getMessageFormat()
    {
        return new MessageFormat(EMPTY, locale);
    }

    @Override
    public NumberFormat getNumberFormat()
    {
        return sharedCache.getFormat(NUMBER, locale, new Callable<NumberFormat>()
        {
            @Override
            public NumberFormat call() throws Exception
            {
                return NumberFormat.getInstance(locale);
            }
        });
    }

    @Override
    public DecimalFormat getPercentFormat()
    {
        return sharedCache.getFormat(PERCENT, locale, new Callable<DecimalFormat>()
        {
            @Override
            public DecimalFormat call() throws Exception
            {
                return (DecimalFormat) NumberFormat.getPercentInstance(locale);
            }
        });
    }

    @Override
    public PrettyTimeFormat getPrettyTimeFormat()
    {
        return sharedCache.getFormat(PRETTY_TIME, locale, new Callable<PrettyTimeFormat>()
        {
            @Override
            public PrettyTimeFormat call() throws Exception
            {
                return new PrettyTimeFormat(locale);
            }
        });
    }

    @Override
    public String ordinalSuffix(int index)
    {
        return getStringByIndex(ORDINAL_SUFFIXES, index);
    }

    @Override
    public void setLocale(Locale locale)
    {
        this.locale = locale;
    }

    @Override
    public String timeSuffix(int index)
    {
        return getStringByIndex(TIME_SUFFIXES, index);
    }

    protected String getStringByIndex(final String cacheName, final int index)
    {
        return getStrings(cacheName)[index];
    }

    protected Collection<String> getStringList(final String cacheName)
    {
        return Arrays.asList(getStrings(cacheName));
    }

    protected String[] getStrings(final String cacheName)
    {
        return sharedCache.getStrings(cacheName, locale, new Callable<String[]>()
        {
            @Override
            public String[] call() throws Exception
            {
                return getBundle().getString(cacheName).split(SPACE);
            }
        });
    }

} public class DefaultContextFactory implements ContextFactory
{

    @Override
    public Context createContext()
    {

        return new DefaultContext();
    }

} public class MaskFormat extends Format implements FormatProvider
{

    private static final long serialVersionUID = -2072270263539296713L;

    private static final char DEFAULT_PLACEHOLDER = '_';

    public static FormatFactory factory()
    {

        return new FormatFactory()
        {
            @Override
            public Format getFormat(String name, String args, Locale locale)
            {

                return new MaskFormat(args);

            }
        };

    }

    public static String format(String mask, String str)
    {

        return format(mask, str, DEFAULT_PLACEHOLDER);

    }

    public static String format(String mask, String str, char placeholder)
    {

        return new MaskFormat(mask, placeholder).format(str);

    }

    public static String parse(String mask, String source) throws ParseException
    {

        return parse(mask, source, DEFAULT_PLACEHOLDER);

    }

    public static String parse(String mask, String source, char placeholder) throws ParseException
    {

        return new MaskFormat(mask, placeholder).parse(source);

    }

    private String mask;

    private char placeholder;

    public MaskFormat()
    {

        // Empty constructor for FormatProvider

    }

    public MaskFormat(String mask)
    {

        this(mask, DEFAULT_PLACEHOLDER);

    }

    public MaskFormat(String mask, char placeholder)
    {

        this.mask = mask;
        this.placeholder = placeholder;

    }

    /*
     * (non-Javadoc)
     * 
     * @see java.text.Format#format(java.lang.Object, java.lang.StringBuffer,
     * java.text.FieldPosition)
     */
    public StringBuffer format(Object obj, StringBuffer toAppendTo, FieldPosition pos)
    {

        if (obj == null)
            return null;

        return toAppendTo.append(this.format(obj.toString()));

    }

    public String format(String str)
    {

        if (isEmptyInput(mask, str))
        {
            return str;
        }

        StringBuilder result = new StringBuilder();

        int msgIndex = 0;
        char maskChar;
        boolean isPlaceHolder;

        int i = 0;
        for (; i < mask.length(); i++)
        {

            maskChar = mask.charAt(i);
            isPlaceHolder = isPlaceholder(maskChar);

            if (!(isPlaceHolder || isDeleteholder(maskChar)))
            {
                result.append(isEscapeChar(maskChar) ? mask.charAt(++i) : maskChar);
            } else
            {
                if (isPlaceHolder)
                {
                    result.append(str.charAt(msgIndex));
                }
                if (++msgIndex == str.length())
                {
                    break;
                }
            }

        }

        // Append tail
        while (++i < mask.length())
        {
            result.append(mask.charAt(i));
        }

        return result.toString();

    }

    @Override
    public FormatFactory getFactory()
    {

        return factory();

    }

    @Override
    public String getFormatName()
    {

        return "mask";

    }

    public String getMask()
    {

        return mask;
    }

    public char getPlaceholder()
    {

        return placeholder;
    }

    public String parse(String source) throws ParseException
    {

        return parse(source, (ParsePosition) null);

    }

    public String parse(String source, ParsePosition pos) throws ParseException
    {

        if (isEmptyInput(mask, source))
        {
            return source;
        }

        StringBuilder sb = new StringBuilder(mask.length());

        for (int i = 0; i < mask.length() && i < source.length(); i++)
        {

            char maskChar = mask.charAt(i);
            if (isPlaceholder(maskChar))
            {
                sb.append(source.charAt(i));
            } else if (!isEscapeChar(maskChar) && maskChar != source.charAt(i))
            {
                throw new ParseException(String.format("Error parsing String: '%s' at %d", source, i), i);
            }

        }

        return sb.toString();

    }

    /*
     * (non-Javadoc)
     * 
     * @see java.text.Format#parseObject(java.lang.String)
     */
    public Object parseObject(String source) throws ParseException
    {

        ParsePosition pos = new ParsePosition(0);
        Object result = parseObject(source, pos);
        if (pos.getErrorIndex() >= 0)
        {
            throw new ParseException("Format.parseObject(String) failed", pos.getErrorIndex());
        }
        return result;
    }

    /*
     * (non-Javadoc)
     * 
     * @see java.text.Format#parseObject(java.lang.String,
     * java.text.ParsePosition)
     */
    public Object parseObject(String source, ParsePosition pos)
    {

        try
        {
            return parse(source, pos);
        } catch (ParseException e)
        {
            pos.setIndex(0);
            pos.setErrorIndex(e.getErrorOffset());
        }
        return null;

    }

    public void setMask(String mask)
    {

        this.mask = mask;

    }

    public void setPlaceholder(char placeholder)
    {

        this.placeholder = placeholder;
    }

    private boolean isDeleteholder(char c)
    {

        return c == '#';

    }

    private boolean isEmptyInput(String mask, String str)
    {

        return (mask == null || mask.length() == 0 || str == null || str.length() == 0);

    }

    private boolean isEscapeChar(char c)
    {

        return c == '\\';

    }

    private boolean isPlaceholder(char c)
    {

        return c == this.placeholder;

    }

} public class InterpolationHelper
{

    public static String interpolate(String text, Pattern pattern, Replacer replacer)
    {

        Matcher matcher = pattern.matcher(text);
        StringBuffer sb = new StringBuffer();

        while (matcher.find())
        {
            String replacement = replacer.replace(matcher.group(1));
            matcher.appendReplacement(sb, Strings.nullToEmpty(replacement));
        }

        matcher.appendTail(sb);

        return (sb.length() > 0) ? sb.toString() : text;
    }

    public static String replaceSupplementary(String text, Replacer replacer)
    {
        return interpolate(text, Constants.NOT_IN_BMP, replacer);
    }
} public interface Replacer
{

    String replace(String replacement);

} public class Pace
{

    public enum Accuracy
    {
        NONE, APROX, LESS_THAN
    }

    private final long value;
    private final String accuracy;
    private final String timeUnit;

    public static final Pace EMPTY = new Pace(0, Accuracy.NONE, TimeMillis.SECOND);

    public Pace(long value, Accuracy accuracy, TimeMillis timeUnit)
    {
        this.value = value;
        this.accuracy = accuracy.name().toLowerCase();
        this.timeUnit = timeUnit.key();
    }

    public String getAccuracy()
    {
        return accuracy;
    }

    public String getTimeUnit()
    {
        return timeUnit;
    }

    public long getValue()
    {
        return value;
    }

    public String toString()
    {
        return MoreObjects.toStringHelper(this)
                .add("value", value)
                .add("accuracy", accuracy)
                .add("timeUnit", timeUnit)
                .toString();
    }

} public enum Accuracy
{
    NONE, APROX, LESS_THAN
} public class PrettyTimeFormat extends Format implements FormatProvider
{

    private static final long serialVersionUID = -1398312177396430967L;

    public static FormatFactory factory()
    {

        return new FormatFactory()
        {
            @Override
            public Format getFormat(String name, String args, Locale locale)
            {
                // TODO support unrounded in args?
                return new PrettyTimeFormat(locale);
            }
        };

    }

    private transient PrettyTime prettyTime;

    private final Locale locale;

    public PrettyTimeFormat()
    {
        this(Locale.getDefault());
    }

    public PrettyTimeFormat(Locale locale)
    {
        this.prettyTime = new PrettyTime(locale);
        this.locale = locale;
    }

    public Duration approximateDuration(Date then)
    {
        return prettyTime.approximateDuration(then);
    }

    public List<Duration> calculatePreciseDuration(Date then)
    {
        return prettyTime.calculatePreciseDuration(then);
    }

    public List<TimeUnit> clearUnits()
    {
        return prettyTime.clearUnits();
    }

    /**
     * Convenience format method.
     * 
     * @param ref
     *            The date of reference.
     * @param then
     *            The future date.
     * @return a relative format date as text representation
     */
    public String format(Date ref, Date then)
    {
        return prettyTime.format(DurationHelper.calculateDuration(ref, then, prettyTime.getUnits()));
    }

    /**
     * Convenience format method for precise durations.
     * 
     * @param ref
     *            The date of reference.
     * @param then
     *            The future date.
     * @param precision
     *            The precision to retain in milliseconds.
     * @return a relative format date as text representation or an empty string
     *         if no durations are retained
     */
    public String format(Date ref, Date then, long precision)
    {
        List<Duration> durations = DurationHelper.calculatePreciseDuration(ref, then, prettyTime.getUnits());
        List<Duration> retained = retainPrecision(durations, precision);
        return retained.isEmpty() ? "" : prettyTime.format(retained);
    }

    public String format(Duration duration)
    {
        return prettyTime.format(duration);
    }

    public String format(List<Duration> durations)
    {
        return prettyTime.format(durations);
    }

    @Override
    @SuppressWarnings("unchecked")
    public StringBuffer format(Object obj, StringBuffer toAppendTo, FieldPosition pos)
    {
        if (Duration.class.isAssignableFrom(obj.getClass()))
        {
            return toAppendTo.append(prettyTime.format((Duration) obj));
        }

        if (Date.class.isAssignableFrom(obj.getClass()))
        {
            return toAppendTo.append(prettyTime.format((Date) obj));
        }

        if (List.class.isAssignableFrom(obj.getClass()))
        {
            return toAppendTo.append(prettyTime.format((List<Duration>) obj));
        }

        if (Number.class.isAssignableFrom(obj.getClass()))
        {
            return toAppendTo.append(prettyTime.format(new Date(((Number) obj).longValue())));
        }

        throw new IllegalArgumentException(String.format("Class %s is not suitable for PrettyTimeFormat",
                obj.getClass()));
    }

    public String formatUnrounded(Date then)
    {
        return prettyTime.formatUnrounded(then);
    }

    public String formatUnrounded(Duration duration)
    {
        return prettyTime.formatUnrounded(duration);
    }

    @Override
    public FormatFactory getFactory()
    {
        return factory();
    }

    public TimeFormat getFormat(TimeUnit unit)
    {
        return prettyTime.getFormat(unit);
    }

    @Override
    public String getFormatName()
    {
        return "prettytime";
    }

    /**
     * Gets the underlying {@link PrettyTime} instance.
     * 
     * @return the underlying {@link PrettyTime} instance.
     */
    public PrettyTime getPrettyTime()
    {
        return prettyTime;
    }

    public List<TimeUnit> getUnits()
    {
        return prettyTime.getUnits();
    }

    @Override
    public Object parseObject(String source, ParsePosition pos)
    {
        throw new UnsupportedOperationException();
    }

    public PrettyTime registerUnit(TimeUnit unit, TimeFormat format)
    {
        return prettyTime.registerUnit(unit, format);
    }

    private void readObject(ObjectInputStream ois) throws ClassNotFoundException, IOException
    {
        ois.defaultReadObject();
        this.prettyTime = new PrettyTime(locale);
    }

    private List<Duration> retainPrecision(final List<Duration> durations, final long precision)
    {
        return ImmutableList.copyOf(Iterables.filter(durations, new Predicate<Duration>()
        {
            @Override
            public boolean apply(Duration it)
            {
                return it.getUnit().getMillisPerUnit() >= precision;
            }
        }));
    }

} public enum TimeMillis
{
    SECOND(1000L),
    MINUTE(60000L),
    HOUR(3600000L),
    DAY(86400000L),
    WEEK(604800000L),
    // ideal months
    MONTH(2628000000L);

    private long millis;

    TimeMillis(long millis)
    {
        this.millis = millis;
    }

    public String key()
    {
        return this.name().toLowerCase();
    }

    public long millis()
    {
        return this.millis;
    }
} public enum TimeStyle
{
    STANDARD
    {
        public String format(DefaultContext ctx, boolean neg, int h, int m, int s)
        {

            return String.format("%s%d:%02d:%02d", neg ? '-' : "", h, m, s);

        }
    },
    FRENCH_DECIMAL
    {
        public String format(DefaultContext ctx, boolean neg, int h, int m, int s)
        {

            String r;

            if (h == 0)
            {
                r = (m == 0) ? String.format("%d%s", s, ctx.timeSuffix(2)) :
                        (s == 0) ? String.format("%d%s", m, ctx.timeSuffix(1)) :
                                String.format("%d%s %d%s", m, ctx.timeSuffix(1), s, ctx.timeSuffix(2));
            } else
            {
                r = (m == 0) ?
                        ((s == 0) ? String.format("%d%s", h, ctx.timeSuffix(0)) :
                                String.format("%d%s %d%s", h, ctx.timeSuffix(0), s, ctx.timeSuffix(2))) :
                        (s == 0) ?
                                String.format("%d%s %d%s", h, ctx.timeSuffix(0), m, ctx.timeSuffix(1)) :
                                String.format("%d%s %d%s %d%s", h, ctx.timeSuffix(0), m, ctx.timeSuffix(1), s,
                                        ctx.timeSuffix(2));
            }

            return (neg ? '-' : "") + r;
        }
    };

    public abstract String format(DefaultContext defaultContext, boolean neg, int h, int m, int s);

} public final static class PaceParameters
{
    /**
     * Builder method.
     * 
     * @param one
     *            Format for a single element
     * @return a new pace parameters instance
     */
    public static PaceParameters begin(String one)
    {
        PaceParameters p = new PaceParameters();
        return p.one(one);
    }

    /**
     * Pluralization formats
     */
    public PluralizeParams plural;

    /**
     * The interval of the pace in milliseconds.
     * 
     * @see TimeMillis
     */
    public long interval;

    private PaceParameters()
    {
        //
    }

    public void checkArguments()
    {
        Preconditions.checkArgument(plural != null, "Plural parameters are required");
    }

    public PaceParameters exts(Object... exts)
    {
        this.plural.exts(exts);
        return this;
    }

    public PaceParameters interval(long interval)
    {
        this.interval = interval;
        return this;
    }

    public PaceParameters interval(TimeMillis interval)
    {
        this.interval = interval.millis();
        return this;
    }

    public PaceParameters many(String many)
    {
        this.plural.many(many);
        return this;
    }

    public PaceParameters none(String none)
    {
        this.plural.none(none);
        return this;
    }

    public PaceParameters one(String one)
    {
        this.plural = PluralizeParams.begin(one);
        return this;
    }

    public PaceParameters plural(PluralizeParams plural)
    {
        this.plural = plural;
        return this;
    }
} public final static class PluralizeParams
{
    /**
     * Builder method.
     * 
     * @param one
     *            Format for a single element
     * @return a new pluralize parameters instance
     */
    public static PluralizeParams begin(String one)
    {
        PluralizeParams p = new PluralizeParams();
        return p.one(one);
    }

    /**
     * Format for a single element
     */
    public String one;

    /**
     * Format for many elements
     */
    public String many;

    /**
     * Format for no element
     */
    public String none;

    /**
     * Extended parameters for the specified formats
     */
    public Object[] exts;

    private PluralizeParams()
    {
        //
    }

    public PluralizeParams exts(Object... exts)
    {
        this.exts = exts;
        return this;
    }

    public PluralizeParams many(String many)
    {
        this.many = many;
        return this;
    }

    public PluralizeParams none(String none)
    {
        this.none = none;
        return this;
    }

    public PluralizeParams one(String one)
    {
        this.one = one;
        return this;
    }

} public final static class SlugifyParams
{
    /**
     * Builder method.
     * 
     * @return a new slugify parameters instance
     */
    public static SlugifyParams begin()
    {
        return new SlugifyParams();
    }

    /**
     * Separator slug
     */
    public String separator;

    /**
     * Controls the transformation to lowercase
     */
    public boolean isToLowerCase;

    private SlugifyParams()
    {
        this.separator = DEFAULT_SLUG_SEPARATOR;
        this.isToLowerCase = true;
    }

    public SlugifyParams separator(String separator)
    {
        this.separator = separator;
        return this;
    }

    public SlugifyParams toLowerCase(boolean isToLowerCase)
    {
        this.isToLowerCase = isToLowerCase;
        return this;
    }

}
import static humanize.util.Constants.COMB_DIACRITICAL;
import static humanize.util.Constants.EMPTY;
import static humanize.util.Constants.HYPEN_SPACE;
import static humanize.util.Constants.ND_FACTOR;
import static humanize.util.Constants.ONLY_SLUG_CHARS;
import static humanize.util.Constants.ORDINAL_FMT;
import static humanize.util.Constants.PUNCTUATION;
import static humanize.util.Constants.SPACE;
import static humanize.util.Constants.SPLIT_CAMEL;
import static humanize.util.Constants.THOUSAND;
import static humanize.util.Constants.bigDecExponents;
import static humanize.util.Constants.binPrefixes;
import static humanize.util.Constants.commaJoiner;
import static humanize.util.Constants.metricPrefixes;
import static humanize.util.Constants.nanoTimePrefixes;
import static humanize.util.Constants.titleIgnoredWords;
import static humanize.util.Constants.titleWordSperator;
import humanize.spi.Expose;
import humanize.spi.MessageFormat;
import humanize.spi.context.ContextFactory;
import humanize.spi.context.DefaultContext;
import humanize.spi.context.DefaultContextFactory;
import humanize.text.MaskFormat;
import humanize.text.util.InterpolationHelper;
import humanize.text.util.Replacer;
import humanize.time.Pace;
import humanize.time.Pace.Accuracy;
import humanize.time.PrettyTimeFormat;
import humanize.time.TimeMillis;
import humanize.util.Constants.TimeStyle;
import humanize.util.Parameters.PaceParameters;
import humanize.util.Parameters.PluralizeParams;
import humanize.util.Parameters.SlugifyParams;
import java.math.BigDecimal;
import java.math.MathContext;
import java.math.RoundingMode;
import java.text.BreakIterator;
import java.text.ChoiceFormat;
import java.text.Collator;
import java.text.DateFormat;
import java.text.DecimalFormat;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Arrays;
import java.util.Calendar;
import java.util.Collection;
import java.util.Date;
import java.util.Locale;
import java.util.Map;
import java.util.Map.Entry;
import java.util.ResourceBundle;
import java.util.ServiceLoader;
import java.util.concurrent.Callable;
import java.util.regex.Matcher;
import javax.xml.bind.DatatypeConverter;
import me.xuender.unidecode.Unidecode;
import com.google.common.base.CharMatcher;
import com.google.common.base.Preconditions;
import com.google.common.base.Strings;
import com.google.common.collect.ObjectArrays;
    /**
     * <p>
     * Rounds a number to significant figures.
     * </p>
     * 
     * @param num
     *            The number to be rounded
     * @param precision
     *            The number of significant digits
     * @return The number rounded to significant figures
     */
    public static Number roundToSignificantFigures(Number num, int precision)
    {
        return new BigDecimal(num.doubleValue())
                .round(new MathContext(precision, RoundingMode.HALF_EVEN));
    }

    /**
     * <p>
     * Sort of poor man's transliteration, i.e. normalizes and strips
     * diacritical marks.
     * </p>
     * 
     * <table border="0" cellspacing="0" cellpadding="3" width="100%">
     * <tr>
     * <th class="colFirst">Input</th>
     * <th class="colLast">Output</th>
     * </tr>
     * <tr>
     * <td>"J'étudie le français"</td>
     * <td>"J'etudie le francais"</td>
     * </tr>
     * <tr>
     * <td>"Lo siento, no hablo español."</td>
     * <td>"Lo siento, no hablo espanol."</td>
     * </tr>
     * </table>
     * 
     * @param text
     *            The text to be simplified.
     * @return simplified text.
     */
    @Expose
    public static String simplify(final String text)
    {
        String normalized = java.text.Normalizer.normalize(text, java.text.Normalizer.Form.NFD);
        return COMB_DIACRITICAL.matcher(normalized).replaceAll("");
    }

    /**
     * @param text
     *            The text to be slugified
     * @return a slugified representation of text specified
     */
    @Expose
    public static String slugify(final String text)
    {
        return slugify(text, SlugifyParams.begin());
    }

    /**
     * <p>
     * Transforms a text into a representation suitable to be used in an URL.
     * </p>
     * 
     * <table border="0" cellspacing="0" cellpadding="3" width="100%">
     * <tr>
     * <th class="colFirst">Input</th>
     * <th class="colLast">Output</th>
     * </tr>
     * <tr>
     * <td>"J'étudie le français"</td>
     * <td>"j-etudie-le-francais"</td>
     * </tr>
     * <tr>
     * <td>"Lo siento, no hablo español"</td>
     * <td>"lo-siento-no-hablo-espanol"</td>
     * </tr>
     * <tr>
     * <td>"\nsome@mail.com\n"</td>
     * <td>"some-mail-com"</td>
     * </tr>
     * </table>
     * 
     * @param text
     *            The text to be slugified
     * @param params
     *            The slugify parameterization object
     * @return a slugified representation of text specified
     */
    public static String slugify(final String text, final SlugifyParams params)
    {
        String result = unidecode(text);
        result = PUNCTUATION.matcher(result).replaceAll("-");
        result = ONLY_SLUG_CHARS.matcher(result).replaceAll("");
        result = CharMatcher.WHITESPACE.trimFrom(result);
        result = HYPEN_SPACE.matcher(result).replaceAll(params.separator);
        result = CharMatcher.INVISIBLE.removeFrom(result);
        return params.isToLowerCase ? result.toLowerCase() : result;
    }

    /**
     * <p>
     * Converts a big number to a friendly text representation. Accepts values
     * ranging from thousands to googols. Uses BigDecimal.
     * </p>
     * 
     * @param value
     *            Number to be converted
     * @return Friendly text representation of the given value
     */
    public static String spellBigNumber(final Number value)
    {
        BigDecimal v = new BigDecimal(value.toString());

        if (THOUSAND.compareTo(v.abs()) > 0)
            return value.toString();

        boolean isPlural = needPlural(v.unscaledValue().intValue());

        for (BigDecimal bigNum : bigDecExponents.keySet())