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

org.h2.expression.function.ToChar Maven / Gradle / Ivy

There is a newer version: 1.0.0-beta2
Show newest version
/*
 * Copyright 2004-2019 H2 Group. Multiple-Licensed under the MPL 2.0,
 * and the EPL 1.0 (https://h2database.com/html/license.html).
 * Initial Developer: Daniel Gredler
 */
package org.h2.expression.function;

import java.math.BigDecimal;
import java.math.RoundingMode;
import java.text.DateFormatSymbols;
import java.text.DecimalFormat;
import java.text.DecimalFormatSymbols;
import java.text.SimpleDateFormat;
import java.util.Arrays;
import java.util.Currency;
import java.util.Locale;

import org.h2.api.ErrorCode;
import org.h2.message.DbException;
import org.h2.util.DateTimeUtils;
import org.h2.util.StringUtils;
import org.h2.util.TimeZoneProvider;
import org.h2.value.Value;
import org.h2.value.ValueTimeTimeZone;
import org.h2.value.ValueTimestamp;
import org.h2.value.ValueTimestampTimeZone;

/**
 * Emulates Oracle's TO_CHAR function.
 */
public class ToChar {

    /**
     * The beginning of the Julian calendar.
     */
    static final int JULIAN_EPOCH = -2_440_588;

    private static final int[] ROMAN_VALUES = { 1000, 900, 500, 400, 100, 90, 50, 40, 10, 9,
            5, 4, 1 };

    private static final String[] ROMAN_NUMERALS = { "M", "CM", "D", "CD", "C", "XC",
            "L", "XL", "X", "IX", "V", "IV", "I" };

    /**
     * The month field.
     */
    static final int MONTHS = 0;

    /**
     * The month field (short form).
     */
    static final int SHORT_MONTHS = 1;

    /**
     * The weekday field.
     */
    static final int WEEKDAYS = 2;

    /**
     * The weekday field (short form).
     */
    static final int SHORT_WEEKDAYS = 3;

    /**
     * The AM / PM field.
     */
    static final int AM_PM = 4;

    private static volatile String[][] NAMES;

    private ToChar() {
        // utility class
    }

    /**
     * Emulates Oracle's TO_CHAR(number) function.
     *
     * 

* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
InputOutputClosest {@link DecimalFormat} Equivalent
,Grouping separator.,
.Decimal separator..
$Leading dollar sign.$
0Leading or trailing zeroes.0
9Digit.#
BBlanks integer part of a fixed point number less than 1.#
CISO currency symbol.\u00A4
DLocal decimal separator..
EEEEReturns a value in scientific notation.E
FMReturns values with no leading or trailing spaces.None.
GLocal grouping separator.,
LLocal currency symbol.\u00A4
MINegative values get trailing minus sign, * positive get trailing space.-
PRNegative values get enclosing angle brackets, * positive get spaces.None.
RNReturns values in Roman numerals.None.
SReturns values with leading/trailing +/- signs.None.
TMReturns smallest number of characters possible.None.
UReturns the dual currency symbol.None.
VReturns a value multiplied by 10^n.None.
XHex value.None.
* See also TO_CHAR(number) and number format models * in the Oracle documentation. * * @param number the number to format * @param format the format pattern to use (if any) * @param nlsParam the NLS parameter (if any) * @return the formatted number */ public static String toChar(BigDecimal number, String format, @SuppressWarnings("unused") String nlsParam) { // short-circuit logic for formats that don't follow common logic below String formatUp = format != null ? StringUtils.toUpperEnglish(format) : null; if (formatUp == null || formatUp.equals("TM") || formatUp.equals("TM9")) { String s = number.toPlainString(); return s.startsWith("0.") ? s.substring(1) : s; } else if (formatUp.equals("TME")) { int pow = number.precision() - number.scale() - 1; number = number.movePointLeft(pow); return number.toPlainString() + "E" + (pow < 0 ? '-' : '+') + (Math.abs(pow) < 10 ? "0" : "") + Math.abs(pow); } else if (formatUp.equals("RN")) { boolean lowercase = format.startsWith("r"); String rn = StringUtils.pad(toRomanNumeral(number.intValue()), 15, " ", false); return lowercase ? rn.toLowerCase() : rn; } else if (formatUp.equals("FMRN")) { boolean lowercase = format.charAt(2) == 'r'; String rn = toRomanNumeral(number.intValue()); return lowercase ? rn.toLowerCase() : rn; } else if (formatUp.endsWith("X")) { return toHex(number, format); } String originalFormat = format; DecimalFormatSymbols symbols = DecimalFormatSymbols.getInstance(); char localGrouping = symbols.getGroupingSeparator(); char localDecimal = symbols.getDecimalSeparator(); boolean leadingSign = formatUp.startsWith("S"); if (leadingSign) { format = format.substring(1); } boolean trailingSign = formatUp.endsWith("S"); if (trailingSign) { format = format.substring(0, format.length() - 1); } boolean trailingMinus = formatUp.endsWith("MI"); if (trailingMinus) { format = format.substring(0, format.length() - 2); } boolean angleBrackets = formatUp.endsWith("PR"); if (angleBrackets) { format = format.substring(0, format.length() - 2); } int v = formatUp.indexOf('V'); if (v >= 0) { int digits = 0; for (int i = v + 1; i < format.length(); i++) { char c = format.charAt(i); if (c == '0' || c == '9') { digits++; } } number = number.movePointRight(digits); format = format.substring(0, v) + format.substring(v + 1); } Integer power; if (format.endsWith("EEEE")) { power = number.precision() - number.scale() - 1; number = number.movePointLeft(power); format = format.substring(0, format.length() - 4); } else { power = null; } int maxLength = 1; boolean fillMode = !formatUp.startsWith("FM"); if (!fillMode) { format = format.substring(2); } // blanks flag doesn't seem to actually do anything format = format.replaceAll("[Bb]", ""); // if we need to round the number to fit into the format specified, // go ahead and do that first int separator = findDecimalSeparator(format); int formatScale = calculateScale(format, separator); if (formatScale < number.scale()) { number = number.setScale(formatScale, RoundingMode.HALF_UP); } // any 9s to the left of the decimal separator but to the right of a // 0 behave the same as a 0, e.g. "09999.99" -> "00000.99" for (int i = format.indexOf('0'); i >= 0 && i < separator; i++) { if (format.charAt(i) == '9') { format = format.substring(0, i) + "0" + format.substring(i + 1); } } StringBuilder output = new StringBuilder(); String unscaled = (number.abs().compareTo(BigDecimal.ONE) < 0 ? zeroesAfterDecimalSeparator(number) : "") + number.unscaledValue().abs().toString(); // start at the decimal point and fill in the numbers to the left, // working our way from right to left int i = separator - 1; int j = unscaled.length() - number.scale() - 1; for (; i >= 0; i--) { char c = format.charAt(i); maxLength++; if (c == '9' || c == '0') { if (j >= 0) { char digit = unscaled.charAt(j); output.insert(0, digit); j--; } else if (c == '0' && power == null) { output.insert(0, '0'); } } else if (c == ',') { // only add the grouping separator if we have more numbers if (j >= 0 || (i > 0 && format.charAt(i - 1) == '0')) { output.insert(0, c); } } else if (c == 'G' || c == 'g') { // only add the grouping separator if we have more numbers if (j >= 0 || (i > 0 && format.charAt(i - 1) == '0')) { output.insert(0, localGrouping); } } else if (c == 'C' || c == 'c') { Currency currency = Currency.getInstance(Locale.getDefault()); output.insert(0, currency.getCurrencyCode()); maxLength += 6; } else if (c == 'L' || c == 'l' || c == 'U' || c == 'u') { Currency currency = Currency.getInstance(Locale.getDefault()); output.insert(0, currency.getSymbol()); maxLength += 9; } else if (c == '$') { Currency currency = Currency.getInstance(Locale.getDefault()); String cs = currency.getSymbol(); output.insert(0, cs); } else { throw DbException.get( ErrorCode.INVALID_TO_CHAR_FORMAT, originalFormat); } } // if the format (to the left of the decimal point) was too small // to hold the number, return a big "######" string if (j >= 0) { return StringUtils.pad("", format.length() + 1, "#", true); } if (separator < format.length()) { // add the decimal point maxLength++; char pt = format.charAt(separator); if (pt == 'd' || pt == 'D') { output.append(localDecimal); } else { output.append(pt); } // start at the decimal point and fill in the numbers to the right, // working our way from left to right i = separator + 1; j = unscaled.length() - number.scale(); for (; i < format.length(); i++) { char c = format.charAt(i); maxLength++; if (c == '9' || c == '0') { if (j < unscaled.length()) { char digit = unscaled.charAt(j); output.append(digit); j++; } else { if (c == '0' || fillMode) { output.append('0'); } } } else { throw DbException.get( ErrorCode.INVALID_TO_CHAR_FORMAT, originalFormat); } } } addSign(output, number.signum(), leadingSign, trailingSign, trailingMinus, angleBrackets, fillMode); if (power != null) { output.append('E'); output.append(power < 0 ? '-' : '+'); output.append(Math.abs(power) < 10 ? "0" : ""); output.append(Math.abs(power)); } if (fillMode) { if (power != null) { output.insert(0, ' '); } else { while (output.length() < maxLength) { output.insert(0, ' '); } } } return output.toString(); } private static String zeroesAfterDecimalSeparator(BigDecimal number) { final String numberStr = number.toPlainString(); final int idx = numberStr.indexOf('.'); if (idx < 0) { return ""; } int i = idx + 1; boolean allZeroes = true; int length = numberStr.length(); for (; i < length; i++) { if (numberStr.charAt(i) != '0') { allZeroes = false; break; } } final char[] zeroes = new char[allZeroes ? length - idx - 1: i - 1 - idx]; Arrays.fill(zeroes, '0'); return String.valueOf(zeroes); } private static void addSign(StringBuilder output, int signum, boolean leadingSign, boolean trailingSign, boolean trailingMinus, boolean angleBrackets, boolean fillMode) { if (angleBrackets) { if (signum < 0) { output.insert(0, '<'); output.append('>'); } else if (fillMode) { output.insert(0, ' '); output.append(' '); } } else { String sign; if (signum == 0) { sign = ""; } else if (signum < 0) { sign = "-"; } else { if (leadingSign || trailingSign) { sign = "+"; } else if (fillMode) { sign = " "; } else { sign = ""; } } if (trailingMinus || trailingSign) { output.append(sign); } else { output.insert(0, sign); } } } private static int findDecimalSeparator(String format) { int index = format.indexOf('.'); if (index == -1) { index = format.indexOf('D'); if (index == -1) { index = format.indexOf('d'); if (index == -1) { index = format.length(); } } } return index; } private static int calculateScale(String format, int separator) { int scale = 0; for (int i = separator; i < format.length(); i++) { char c = format.charAt(i); if (c == '0' || c == '9') { scale++; } } return scale; } private static String toRomanNumeral(int number) { StringBuilder result = new StringBuilder(); for (int i = 0; i < ROMAN_VALUES.length; i++) { int value = ROMAN_VALUES[i]; String numeral = ROMAN_NUMERALS[i]; while (number >= value) { result.append(numeral); number -= value; } } return result.toString(); } private static String toHex(BigDecimal number, String format) { boolean fillMode = !StringUtils.toUpperEnglish(format).startsWith("FM"); boolean uppercase = !format.contains("x"); boolean zeroPadded = format.startsWith("0"); int digits = 0; for (int i = 0; i < format.length(); i++) { char c = format.charAt(i); if (c == '0' || c == 'X' || c == 'x') { digits++; } } int i = number.setScale(0, RoundingMode.HALF_UP).intValue(); String hex = Integer.toHexString(i); if (digits < hex.length()) { hex = StringUtils.pad("", digits + 1, "#", true); } else { if (uppercase) { hex = StringUtils.toUpperEnglish(hex); } if (zeroPadded) { hex = StringUtils.pad(hex, digits, "0", false); } if (fillMode) { hex = StringUtils.pad(hex, format.length() + 1, " ", false); } } return hex; } /** * Get the date (month / weekday / ...) names. * * @param names the field * @return the names */ static String[] getDateNames(int names) { String[][] result = NAMES; if (result == null) { result = new String[5][]; DateFormatSymbols dfs = DateFormatSymbols.getInstance(); result[MONTHS] = dfs.getMonths(); String[] months = dfs.getShortMonths(); for (int i = 0; i < 12; i++) { String month = months[i]; if (month.endsWith(".")) { months[i] = month.substring(0, month.length() - 1); } } result[SHORT_MONTHS] = months; result[WEEKDAYS] = dfs.getWeekdays(); result[SHORT_WEEKDAYS] = dfs.getShortWeekdays(); result[AM_PM] = dfs.getAmPmStrings(); NAMES = result; } return result[names]; } /** * Returns time zone display name or ID for the specified date-time value. * * @param value * value * @param tzd * if {@code true} return TZD (time zone region with Daylight Saving * Time information included), if {@code false} return TZR (time zone * region) * @return time zone display name or ID */ private static String getTimeZone(Value value, boolean tzd) { if (value instanceof ValueTimestampTimeZone) { return DateTimeUtils.timeZoneNameFromOffsetSeconds(((ValueTimestampTimeZone) value) .getTimeZoneOffsetSeconds()); } else if (value instanceof ValueTimeTimeZone) { return DateTimeUtils.timeZoneNameFromOffsetSeconds(((ValueTimeTimeZone) value) .getTimeZoneOffsetSeconds()); } else { TimeZoneProvider tz = DateTimeUtils.getTimeZone(); if (tzd) { ValueTimestamp v = (ValueTimestamp) value.convertTo(Value.TIMESTAMP); return tz.getShortId(tz.getEpochSecondsFromLocal(v.getDateValue(), v.getTimeNanos())); } return tz.getId(); } } /** * Emulates Oracle's TO_CHAR(datetime) function. * *

* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
InputOutputClosest {@link SimpleDateFormat} Equivalent
- / , . ; : "text"Reproduced verbatim.'text'
A.D. AD B.C. BCEra designator, with or without periods.G
A.M. AM P.M. PMAM/PM marker.a
CC SCCCentury.None.
DDay of week.u
DAYName of day.EEEE
DYAbbreviated day name.EEE
DDDay of month.d
DDDDay of year.D
DLLong date format.EEEE, MMMM d, yyyy
DSShort date format.MM/dd/yyyy
EAbbreviated era name (Japanese, Chinese, Thai)None.
EEFull era name (Japanese, Chinese, Thai)None.
FF[1-9]Fractional seconds.S
FMReturns values with no leading or trailing spaces.None.
FXRequires exact matches between character data and format model.None.
HH HH12Hour in AM/PM (1-12).hh
HH24Hour in day (0-23).HH
IWWeek in year.w
WWWeek in year.w
WWeek in month.W
IYYY IYY IY ILast 4/3/2/1 digit(s) of ISO year.yyyy yyy yy y
RRRR RRLast 4/2 digits of year.yyyy yy
Y,YYYYear with comma.None.
YEAR SYEARYear spelled out (S prefixes BC years with minus sign).None.
YYYY SYYYY4-digit year (S prefixes BC years with minus sign).yyyy
YYY YY YLast 3/2/1 digit(s) of year.yyy yy y
JJulian day (number of days since January 1, 4712 BC).None.
MIMinute in hour.mm
MMMonth in year.MM
MONAbbreviated name of month.MMM
MONTHName of month, padded with spaces.MMMM
RMRoman numeral month.None.
QQuarter of year.None.
SSSeconds in minute.ss
SSSSSSeconds in day.None.
TSShort time format.h:mm:ss aa
TZDDaylight savings time zone abbreviation.z
TZRTime zone region information.zzzz
XLocal radix character.None.
*

* See also TO_CHAR(datetime) and datetime format models * in the Oracle documentation. * * @param value the date-time value to format * @param format the format pattern to use (if any) * @param nlsParam the NLS parameter (if any) * @return the formatted timestamp */ public static String toCharDateTime(Value value, String format, @SuppressWarnings("unused") String nlsParam) { long[] a = DateTimeUtils.dateAndTimeFromValue(value); long dateValue = a[0]; long timeNanos = a[1]; int year = DateTimeUtils.yearFromDateValue(dateValue); int monthOfYear = DateTimeUtils.monthFromDateValue(dateValue); int dayOfMonth = DateTimeUtils.dayFromDateValue(dateValue); int posYear = Math.abs(year); long second = timeNanos / 1_000_000_000; int nanos = (int) (timeNanos - second * 1_000_000_000); int minute = (int) (second / 60); second -= minute * 60; int hour = minute / 60; minute -= hour * 60; int h12 = (hour + 11) % 12 + 1; boolean isAM = hour < 12; if (format == null) { format = "DD-MON-YY HH.MI.SS.FF PM"; } StringBuilder output = new StringBuilder(); boolean fillMode = true; for (int i = 0, length = format.length(); i < length;) { Capitalization cap; // AD / BC if ((cap = containsAt(format, i, "A.D.", "B.C.")) != null) { String era = year > 0 ? "A.D." : "B.C."; output.append(cap.apply(era)); i += 4; } else if ((cap = containsAt(format, i, "AD", "BC")) != null) { String era = year > 0 ? "AD" : "BC"; output.append(cap.apply(era)); i += 2; // AM / PM } else if ((cap = containsAt(format, i, "A.M.", "P.M.")) != null) { String am = isAM ? "A.M." : "P.M."; output.append(cap.apply(am)); i += 4; } else if ((cap = containsAt(format, i, "AM", "PM")) != null) { String am = isAM ? "AM" : "PM"; output.append(cap.apply(am)); i += 2; // Long/short date/time format } else if (containsAt(format, i, "DL") != null) { String day = getDateNames(WEEKDAYS)[DateTimeUtils.getSundayDayOfWeek(dateValue)]; String month = getDateNames(MONTHS)[monthOfYear - 1]; output.append(day).append(", ").append(month).append(' ').append(dayOfMonth).append(", "); StringUtils.appendZeroPadded(output, 4, posYear); i += 2; } else if (containsAt(format, i, "DS") != null) { StringUtils.appendZeroPadded(output, 2, monthOfYear); output.append('/'); StringUtils.appendZeroPadded(output, 2, dayOfMonth); output.append('/'); StringUtils.appendZeroPadded(output, 4, posYear); i += 2; } else if (containsAt(format, i, "TS") != null) { output.append(h12).append(':'); StringUtils.appendZeroPadded(output, 2, minute); output.append(':'); StringUtils.appendZeroPadded(output, 2, second); output.append(' '); output.append(getDateNames(AM_PM)[isAM ? 0 : 1]); i += 2; // Day } else if (containsAt(format, i, "DDD") != null) { output.append(DateTimeUtils.getDayOfYear(dateValue)); i += 3; } else if (containsAt(format, i, "DD") != null) { StringUtils.appendZeroPadded(output, 2, dayOfMonth); i += 2; } else if ((cap = containsAt(format, i, "DY")) != null) { String day = getDateNames(SHORT_WEEKDAYS)[DateTimeUtils.getSundayDayOfWeek(dateValue)]; output.append(cap.apply(day)); i += 2; } else if ((cap = containsAt(format, i, "DAY")) != null) { String day = getDateNames(WEEKDAYS)[DateTimeUtils.getSundayDayOfWeek(dateValue)]; if (fillMode) { day = StringUtils.pad(day, "Wednesday".length(), " ", true); } output.append(cap.apply(day)); i += 3; } else if (containsAt(format, i, "D") != null) { output.append(DateTimeUtils.getSundayDayOfWeek(dateValue)); i += 1; } else if (containsAt(format, i, "J") != null) { output.append(DateTimeUtils.absoluteDayFromDateValue(dateValue) - JULIAN_EPOCH); i += 1; // Hours } else if (containsAt(format, i, "HH24") != null) { StringUtils.appendZeroPadded(output, 2, hour); i += 4; } else if (containsAt(format, i, "HH12") != null) { StringUtils.appendZeroPadded(output, 2, h12); i += 4; } else if (containsAt(format, i, "HH") != null) { StringUtils.appendZeroPadded(output, 2, h12); i += 2; // Minutes } else if (containsAt(format, i, "MI") != null) { StringUtils.appendZeroPadded(output, 2, minute); i += 2; // Seconds } else if (containsAt(format, i, "SSSSS") != null) { int seconds = (int) (timeNanos / 1_000_000_000); output.append(seconds); i += 5; } else if (containsAt(format, i, "SS") != null) { StringUtils.appendZeroPadded(output, 2, second); i += 2; // Fractional seconds } else if (containsAt(format, i, "FF1", "FF2", "FF3", "FF4", "FF5", "FF6", "FF7", "FF8", "FF9") != null) { int x = format.charAt(i + 2) - '0'; int ff = (int) (nanos * Math.pow(10, x - 9)); StringUtils.appendZeroPadded(output, x, ff); i += 3; } else if (containsAt(format, i, "FF") != null) { StringUtils.appendZeroPadded(output, 9, nanos); i += 2; // Time zone } else if (containsAt(format, i, "TZR") != null) { output.append(getTimeZone(value, false)); i += 3; } else if (containsAt(format, i, "TZD") != null) { output.append(getTimeZone(value, true)); i += 3; // Week } else if (containsAt(format, i, "WW") != null) { StringUtils.appendZeroPadded(output, 2, (DateTimeUtils.getDayOfYear(dateValue) - 1) / 7 + 1); i += 2; } else if (containsAt(format, i, "IW") != null) { StringUtils.appendZeroPadded(output, 2, DateTimeUtils.getIsoWeekOfYear(dateValue)); i += 2; } else if (containsAt(format, i, "W") != null) { output.append((dayOfMonth - 1) / 7 + 1); i += 1; // Year } else if (containsAt(format, i, "Y,YYY") != null) { output.append(new DecimalFormat("#,###").format(posYear)); i += 5; } else if (containsAt(format, i, "SYYYY") != null) { // Should be <= 0, but Oracle prints negative years with off-by-one difference if (year < 0) { output.append('-'); } StringUtils.appendZeroPadded(output, 4, posYear); i += 5; } else if (containsAt(format, i, "YYYY", "RRRR") != null) { StringUtils.appendZeroPadded(output, 4, posYear); i += 4; } else if (containsAt(format, i, "IYYY") != null) { StringUtils.appendZeroPadded(output, 4, Math.abs(DateTimeUtils.getIsoWeekYear(dateValue))); i += 4; } else if (containsAt(format, i, "YYY") != null) { StringUtils.appendZeroPadded(output, 3, posYear % 1000); i += 3; } else if (containsAt(format, i, "IYY") != null) { StringUtils.appendZeroPadded(output, 3, Math.abs(DateTimeUtils.getIsoWeekYear(dateValue)) % 1000); i += 3; } else if (containsAt(format, i, "YY", "RR") != null) { StringUtils.appendZeroPadded(output, 2, posYear % 100); i += 2; } else if (containsAt(format, i, "IY") != null) { StringUtils.appendZeroPadded(output, 2, Math.abs(DateTimeUtils.getIsoWeekYear(dateValue)) % 100); i += 2; } else if (containsAt(format, i, "Y") != null) { output.append(posYear % 10); i += 1; } else if (containsAt(format, i, "I") != null) { output.append(Math.abs(DateTimeUtils.getIsoWeekYear(dateValue)) % 10); i += 1; // Month / quarter } else if ((cap = containsAt(format, i, "MONTH")) != null) { String month = getDateNames(MONTHS)[monthOfYear - 1]; if (fillMode) { month = StringUtils.pad(month, "September".length(), " ", true); } output.append(cap.apply(month)); i += 5; } else if ((cap = containsAt(format, i, "MON")) != null) { String month = getDateNames(SHORT_MONTHS)[monthOfYear - 1]; output.append(cap.apply(month)); i += 3; } else if (containsAt(format, i, "MM") != null) { StringUtils.appendZeroPadded(output, 2, monthOfYear); i += 2; } else if ((cap = containsAt(format, i, "RM")) != null) { output.append(cap.apply(toRomanNumeral(monthOfYear))); i += 2; } else if (containsAt(format, i, "Q") != null) { int q = 1 + ((monthOfYear - 1) / 3); output.append(q); i += 1; // Local radix character } else if (containsAt(format, i, "X") != null) { char c = DecimalFormatSymbols.getInstance().getDecimalSeparator(); output.append(c); i += 1; // Format modifiers } else if (containsAt(format, i, "FM") != null) { fillMode = !fillMode; i += 2; } else if (containsAt(format, i, "FX") != null) { i += 2; // Literal text } else if (containsAt(format, i, "\"") != null) { for (i = i + 1; i < format.length(); i++) { char c = format.charAt(i); if (c != '"') { output.append(c); } else { i++; break; } } } else if (format.charAt(i) == '-' || format.charAt(i) == '/' || format.charAt(i) == ',' || format.charAt(i) == '.' || format.charAt(i) == ';' || format.charAt(i) == ':' || format.charAt(i) == ' ') { output.append(format.charAt(i)); i += 1; // Anything else } else { throw DbException.get(ErrorCode.INVALID_TO_CHAR_FORMAT, format); } } return output.toString(); } /** * Returns a capitalization strategy if the specified string contains any of * the specified substrings at the specified index. The capitalization * strategy indicates the casing of the substring that was found. If none of * the specified substrings are found, this method returns null * . * * @param s the string to check * @param index the index to check at * @param substrings the substrings to check for within the string * @return a capitalization strategy if the specified string contains any of * the specified substrings at the specified index, * null otherwise */ private static Capitalization containsAt(String s, int index, String... substrings) { for (String substring : substrings) { if (index + substring.length() <= s.length()) { boolean found = true; Boolean up1 = null; Boolean up2 = null; for (int i = 0; i < substring.length(); i++) { char c1 = s.charAt(index + i); char c2 = substring.charAt(i); if (c1 != c2 && Character.toUpperCase(c1) != Character.toUpperCase(c2)) { found = false; break; } else if (Character.isLetter(c1)) { if (up1 == null) { up1 = Character.isUpperCase(c1); } else if (up2 == null) { up2 = Character.isUpperCase(c1); } } } if (found) { return Capitalization.toCapitalization(up1, up2); } } } return null; } /** Represents a capitalization / casing strategy. */ public enum Capitalization { /** * All letters are uppercased. */ UPPERCASE, /** * All letters are lowercased. */ LOWERCASE, /** * The string is capitalized (first letter uppercased, subsequent * letters lowercased). */ CAPITALIZE; /** * Returns the capitalization / casing strategy which should be used * when the first and second letters have the specified casing. * * @param up1 whether or not the first letter is uppercased * @param up2 whether or not the second letter is uppercased * @return the capitalization / casing strategy which should be used * when the first and second letters have the specified casing */ static Capitalization toCapitalization(Boolean up1, Boolean up2) { if (up1 == null) { return Capitalization.CAPITALIZE; } else if (up2 == null) { return up1 ? Capitalization.UPPERCASE : Capitalization.LOWERCASE; } else if (up1) { return up2 ? Capitalization.UPPERCASE : Capitalization.CAPITALIZE; } else { return Capitalization.LOWERCASE; } } /** * Applies this capitalization strategy to the specified string. * * @param s the string to apply this strategy to * @return the resultant string */ public String apply(String s) { if (s == null || s.isEmpty()) { return s; } switch (this) { case UPPERCASE: return StringUtils.toUpperEnglish(s); case LOWERCASE: return StringUtils.toLowerEnglish(s); case CAPITALIZE: return Character.toUpperCase(s.charAt(0)) + (s.length() > 1 ? StringUtils.toLowerEnglish(s).substring(1) : ""); default: throw new IllegalArgumentException( "Unknown capitalization strategy: " + this); } } } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy