org.jboss.errai.common.client.logging.util.StringFormat.java Source code

Java tutorial

Introduction

Here is the source code for org.jboss.errai.common.client.logging.util.StringFormat.java

Source

/*
 * Copyright (C) 2015 Red Hat, Inc. and/or its affiliates.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *       http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.jboss.errai.common.client.logging.util;

import java.util.Arrays;
import java.util.Date;

import com.google.gwt.i18n.client.LocaleInfo;
import com.google.gwt.i18n.client.NumberFormat;
import com.google.gwt.i18n.client.TimeZone;
import com.google.gwt.regexp.shared.MatchResult;
import com.google.gwt.regexp.shared.RegExp;
import com.google.gwt.user.datepicker.client.CalendarUtil;

/**
 * A utility class for replacing the {@link String#format(String, Object...)} method.
 * 
 * @author Max Barkley <mbarkley@redhat.com>
 */
public class StringFormat {

    private static enum Conversion {
        bool, hexStr, str, uniChar, decInt, octInt, hexInt, sciInt, decNum, compNum, hexNum, date, escLit, line
    }

    private static final String argIndex = "(?:(\\d+)\\$)?";
    private static final String flags = "([-#+ 0,(]*)?";
    private static final String width = "(\\d+)?";
    private static final String prec = "(?:\\.(\\d+))?";
    private static final String dateSuffix = "([HIklMSLNpzZsQBbhAaCYyjmdeRTrDFc])?";
    // Includes date/time suffix
    private static final String conv = "([bBhHsScCdoxXeEfgGaA%n]|(?:[tT]" + dateSuffix + "))";

    private static RegExp convPattern = RegExp.compile("%" + argIndex + flags + width + prec + conv);

    /**
     * This method emulates {@link String#format(String, Object...)} with some
     * notable differences:
     * <ul>
     * <li>flags are unsupported (and are silently ignored)</li>
     * <li>the '{@code a}' and '{@code A}' conversions are unsupported</li>
     * <li>the other scientific notation flags use {@link NumberFormat} and thus
     * produce slightly different output</li>
     * </ul>
     * 
     * @param format
     *          The format string.
     * @param args
     *          The values available for format string conversions.
     * @return A formatted string, similar to the result of calling
     *         {@link String#format(String, Object...)} with {@code format} and
     *         {@code args}.
     * @see String#format(String, Object...)
     */
    public static String format(String format, Object... args) {
        StringBuffer buffer = new StringBuffer(format.length());
        final String originalFormat = format;
        int count = 0;

        // Special case: user does StringFormat.format("...", null)
        if (args == null) {
            args = new Object[] { null };
        }

        int i = 0;
        while (i < format.length()) {
            if (format.charAt(i) == '%') {
                final String subStr = format.substring(i);
                final MatchResult match = convPattern.exec(subStr);
                if (match == null || match.getIndex() != 0) {
                    throw new IllegalArgumentException(
                            "Bad conversion at index " + i + " in format String: " + format);
                }

                if (match.getGroup(2) != null && !match.getGroup(2).equals("")) {
                    throw new UnsupportedOperationException("Flags are not yet supported in this implementation.");
                }

                // TODO: check preconditions and possibly throw IllegalFormatException
                final Object arg;
                final int width;
                final int prec;
                final String suffix;
                final Conversion conv = getConversion(match.getGroup(5).charAt(0));
                final boolean autoIndexed = match.getGroup(1) == null || match.getGroup(1).equals("");

                if (conv.equals(Conversion.escLit) || conv.equals(Conversion.line)) {
                    arg = null;
                } else if (autoIndexed) {
                    arg = args[count];
                } else {
                    arg = args[Integer.valueOf(match.getGroup(1)) - 1];
                }

                if (match.getGroup(3) == null || match.getGroup(3).equals("")) {
                    width = 0;
                } else {
                    width = Integer.valueOf(match.getGroup(3));
                }

                if (match.getGroup(4) == null || match.getGroup(4).equals("")) {
                    prec = Integer.MAX_VALUE;
                } else {
                    prec = Integer.valueOf(match.getGroup(4));
                }

                if (match.getGroup(6) == null || match.getGroup(6).equals("")) {
                    suffix = "";
                } else {
                    suffix = match.getGroup(6);
                }

                final boolean upper = match.getGroup(5).toUpperCase().equals(match.getGroup(5));
                String replacement;
                try {
                    replacement = buildReplacement(conv, upper, width, prec, suffix, arg);
                } catch (Exception e) {
                    throw new IllegalArgumentException("Error processing substitution " + (count + 1)
                            + ".\nFormat: " + originalFormat + "\nArgs: " + Arrays.toString(args), e);
                }

                buffer.append(replacement);
                i += match.getGroup(0).length();

                // Auto-index is incremented for non-explicitly indexed conversions
                if (autoIndexed)
                    count += 1;
            } else {
                buffer.append(format.charAt(i++));
            }
        }

        return buffer.toString();
    }

    private static String buildReplacement(Conversion conv, boolean upper, int width, int prec, String suffix,
            Object arg) {
        String res = null;
        switch (conv) {
        case bool:
            if (arg instanceof Boolean) {
                if ((Boolean) arg) {
                    res = "true";
                } else {
                    res = "false";
                }
            } else if (arg != null) {
                res = "true";
            } else {
                res = "false";
            }
            break;
        case date:
            if (suffix == null || suffix.length() != 1)
                throw new IllegalArgumentException("Must provide suffix with date conversion.");
            if (arg instanceof Long)
                arg = new Date((Long) arg);
            res = processDate((Date) arg, upper, suffix.charAt(0));
            break;
        case decInt:
            res = String.valueOf((Integer) arg);
            break;
        case decNum:
            if (arg instanceof Float) {
                res = String.valueOf((Float) arg);
            } else {
                res = String.valueOf((Double) arg);
            }
            break;
        case hexInt:
            res = Integer.toHexString((Integer) arg);
            break;
        case hexStr:
            if (arg == null)
                res = "null";
            else
                res = Integer.toHexString(arg.hashCode());
            break;
        case octInt:
            res = Integer.toOctalString((Integer) arg);
            break;
        case str:
            if (arg == null)
                res = "null";
            else
                res = arg.toString();
            break;
        case uniChar:
            if (arg instanceof Integer)
                arg = Character.valueOf((char) ((Integer) arg).intValue());
            res = Character.toString((Character) arg);
            break;
        case compNum:
        case sciInt:
            if (arg instanceof Float) {
                arg = Double.valueOf((Float) arg);
            }
            if (Integer.MAX_VALUE == prec)
                prec = 6;
            final StringBuilder formatString = new StringBuilder(prec + 5);
            formatString.append("0.");
            for (int i = 0; i < prec; i++)
                formatString.append('0');
            formatString.append("E00");

            res = NumberFormat.getFormat(formatString.toString()).format((Double) arg);
            if (!upper)
                res = res.toLowerCase();

            return padOrTrunc(res, width, Integer.MAX_VALUE);
        // TODO
        case hexNum:
            throw new UnsupportedOperationException();
        case line:
            return "\n";
        case escLit:
            return "%";
        }
        res = padOrTrunc(res, width, prec);
        if (upper)
            return res.toUpperCase();
        else
            return res;
    }

    @SuppressWarnings("deprecation")
    private static String processDate(Date date, boolean upper, char suffix) {
        String retVal = null;
        switch (suffix) {
        case 'k':
            retVal = String.valueOf(date.getHours());
            break;
        case 'H':
            retVal = String.valueOf(padInt(date.getHours(), 2));
            break;
        case 'l':
            retVal = String.valueOf(date.getHours() % 12);
            break;
        case 'I':
            retVal = String.valueOf(padInt(date.getHours() % 12, 2));
            break;
        case 'M':
            retVal = String.valueOf(padInt(date.getMinutes(), 2));
            break;
        case 'S':
            retVal = String.valueOf(padInt(date.getSeconds(), 2));
            break;
        case 'L':
            retVal = String.valueOf(padInt((int) (date.getTime() % 1000), 3));
            break;
        case 'N':
            retVal = String.valueOf(padInt((int) ((date.getTime() % 1000) * 1000000), 9));
            break;
        case 'p':
            retVal = LocaleInfo.getCurrentLocale().getDateTimeFormatInfo().ampms()[date.getHours() / 12];
            break;
        case 's':
            retVal = String.valueOf(date.getTime() / 1000);
            break;
        case 'Q':
            retVal = String.valueOf(date.getTime());
            break;
        case 'C':
            retVal = String.valueOf(padInt((date.getYear() + 1900) / 100, 2));
            break;
        case 'Y':
            retVal = String.valueOf(padInt(date.getYear() + 1900, 4));
            break;
        case 'y':
            retVal = String.valueOf(padInt(date.getYear() % 100, 2));
            break;
        case 'j':
            final Date lastYear = new Date(date.getTime());
            lastYear.setYear(date.getYear() - 1);
            lastYear.setMonth(11);
            lastYear.setDate(31);

            retVal = String.valueOf(padInt(CalendarUtil.getDaysBetween(lastYear, date), 3));
            break;
        case 'z':
            retVal = TimeZone.createTimeZone(date.getTimezoneOffset()).getRFCTimeZoneString(date);
            break;
        case 'm':
            retVal = String.valueOf(padInt(date.getMonth() + 1, 2));
            break;
        case 'd':
            retVal = String.valueOf(padInt(date.getDate(), 2));
            break;
        case 'e':
            retVal = String.valueOf(date.getDate());
            break;
        case 'R':
            retVal = processDate(date, false, 'H') + ":" + processDate(date, false, 'M');
            break;
        case 'T':
            retVal = processDate(date, false, 'R') + ":" + processDate(date, false, 'S');
            break;
        case 'r':
            retVal = processDate(date, false, 'I') + ":" + processDate(date, false, 'M') + ":"
                    + processDate(date, upper, 'S') + " " + processDate(date, true, 'p');
            break;
        case 'D':
            retVal = processDate(date, false, 'm') + "/" + processDate(date, false, 'd') + "/"
                    + processDate(date, false, 'y');
            break;
        case 'F':
            retVal = processDate(date, false, 'Y') + "-" + processDate(date, false, 'm') + "-"
                    + processDate(date, false, 'd');
            break;
        case 'Z':
            retVal = TimeZone.createTimeZone(date.getTimezoneOffset()).getShortName(date);
            break;
        case 'B':
            retVal = LocaleInfo.getCurrentLocale().getDateTimeFormatInfo().monthsFull()[date.getMonth()];
            break;
        case 'b':
        case 'h':
            retVal = LocaleInfo.getCurrentLocale().getDateTimeFormatInfo().monthsShort()[date.getMonth()];
            break;
        case 'A':
            retVal = LocaleInfo.getCurrentLocale().getDateTimeFormatInfo().weekdaysFull()[date.getDay()];
            break;
        case 'a':
            retVal = LocaleInfo.getCurrentLocale().getDateTimeFormatInfo().weekdaysShort()[date.getDay()];
            break;
        case 'c':
            retVal = processDate(date, false, 'a') + " " + processDate(date, false, 'b') + " "
                    + processDate(date, false, 'd') + " " + processDate(date, false, 'T') + " "
                    + processDate(date, false, 'Z') + " " + processDate(date, false, 'Y');
            break;
        default:
            throw new IllegalArgumentException("Invalid date suffix: " + suffix);
        }

        if (upper)
            return retVal.toUpperCase();
        else
            return retVal;
    }

    private static String padInt(int num, int width) {
        final StringBuilder builder = new StringBuilder(width);
        // num %= (int) Math.pow(10, width);
        for (int d = width - 1; d >= 0; d--) {
            int div = (int) Math.pow(10, d);
            builder.append(num / div);
            num %= div;
        }

        return builder.toString();
    }

    private static String padOrTrunc(String res, int min, int max) {
        if (res.length() < min) {
            final StringBuilder builder = new StringBuilder(min);
            for (int i = 0; i < min - res.length(); i++) {
                builder.append(" ");
            }
            builder.append(res);
            return builder.toString();
        } else if (res.length() > max) {
            return res.substring(0, max);
        } else {
            return res;
        }
    }

    private static Conversion getConversion(char c) {
        switch (c) {
        case 'b':
        case 'B':
            return Conversion.bool;
        case 'h':
        case 'H':
            return Conversion.hexStr;
        case 's':
        case 'S':
            return Conversion.str;
        case 'c':
        case 'C':
            return Conversion.uniChar;
        case 'd':
            return Conversion.decInt;
        case 'o':
            return Conversion.octInt;
        case 'x':
        case 'X':
            return Conversion.hexInt;
        case 'e':
        case 'E':
            return Conversion.sciInt;
        case 'f':
            return Conversion.decNum;
        case 'g':
        case 'G':
            return Conversion.compNum;
        case 'a':
        case 'A':
            return Conversion.hexNum;
        case 'T':
        case 't':
            return Conversion.date;
        case '%':
            return Conversion.escLit;
        case 'n':
            return Conversion.line;
        default:
            throw new IllegalArgumentException(c + " is not a valid conversion character.");
        }
    }

}