< prev index next >

src/java.base/share/classes/java/util/Formatter.java

Print this page

        

*** 34,43 **** --- 34,44 ---- import java.io.Flushable; import java.io.OutputStream; import java.io.OutputStreamWriter; import java.io.PrintStream; import java.io.UnsupportedEncodingException; + import java.lang.invoke.*; import java.math.BigDecimal; import java.math.BigInteger; import java.math.MathContext; import java.math.RoundingMode; import java.nio.charset.Charset;
*** 46,73 **** import java.text.DateFormatSymbols; import java.text.DecimalFormat; import java.text.DecimalFormatSymbols; import java.text.NumberFormat; import java.text.spi.NumberFormatProvider; - import java.util.regex.Matcher; - import java.util.regex.Pattern; - import java.util.Objects; import java.time.DateTimeException; import java.time.Instant; import java.time.ZoneId; import java.time.ZoneOffset; import java.time.temporal.ChronoField; import java.time.temporal.TemporalAccessor; import java.time.temporal.TemporalQueries; import java.time.temporal.UnsupportedTemporalTypeException; import jdk.internal.math.DoubleConsts; import jdk.internal.math.FormattedFloatingDecimal; import sun.util.locale.provider.LocaleProviderAdapter; import sun.util.locale.provider.ResourceBundleBasedAdapter; /** * An interpreter for printf-style format strings. This class provides support * for layout justification and alignment, common formats for numeric, string, * and date/time data, and locale-specific output. Common Java types such as * {@code byte}, {@link java.math.BigDecimal BigDecimal}, and {@link Calendar} --- 47,81 ---- import java.text.DateFormatSymbols; import java.text.DecimalFormat; import java.text.DecimalFormatSymbols; import java.text.NumberFormat; import java.text.spi.NumberFormatProvider; import java.time.DateTimeException; import java.time.Instant; import java.time.ZoneId; import java.time.ZoneOffset; import java.time.temporal.ChronoField; import java.time.temporal.TemporalAccessor; import java.time.temporal.TemporalQueries; import java.time.temporal.UnsupportedTemporalTypeException; + import java.lang.compiler.IntrinsicCandidate; + import java.util.regex.Matcher; + import java.util.regex.Pattern; + import java.util.stream.IntStream; + import jdk.internal.math.DoubleConsts; import jdk.internal.math.FormattedFloatingDecimal; import sun.util.locale.provider.LocaleProviderAdapter; import sun.util.locale.provider.ResourceBundleBasedAdapter; + import static java.lang.invoke.MethodHandles.*; + import static java.lang.invoke.MethodHandles.constant; + import static java.lang.invoke.MethodHandles.filterArguments; + import static java.lang.invoke.MethodType.methodType; + /** * An interpreter for printf-style format strings. This class provides support * for layout justification and alignment, common formats for numeric, string, * and date/time data, and locale-specific output. Common Java types such as * {@code byte}, {@link java.math.BigDecimal BigDecimal}, and {@link Calendar}
*** 1911,1931 **** * * @author Iris Clark * @since 1.5 */ public final class Formatter implements Closeable, Flushable { ! private Appendable a; ! private final Locale l; ! ! private IOException lastException; ! ! private final char zero; ! private static double scaleUp; ! ! // 1 (sign) + 19 (max # sig digits) + 1 ('.') + 1 ('e') + 1 (sign) ! // + 3 (max # exp digits) + 4 (error) = 30 ! private static final int MAX_FD_CHARS = 30; /** * Returns a charset object for the given charset name. * @throws NullPointerException is csn is null * @throws UnsupportedEncodingException if the charset is not supported --- 1919,1943 ---- * * @author Iris Clark * @since 1.5 */ public final class Formatter implements Closeable, Flushable { ! /** Receiving Appendable */ ! Appendable a; ! /** Formatter locale */ ! final Locale l; ! /** Last low level exception caught */ ! IOException lastException; ! /** Zero for the locale */ ! final char zero; ! /** Round up scaler */ ! static double SCALEUP = Math.scalb(1.0, 54); ! /** Maximum floating decimal digits ! * 1 (sign) + 19 (max # sig digits) + 1 ('.') + 1 ('e') + 1 (sign) ! * + 3 (max # exp digits) + 4 (error) = 30 ! */ ! static final int MAX_FD_CHARS = 30; /** * Returns a charset object for the given charset name. * @throws NullPointerException is csn is null * @throws UnsupportedEncodingException if the charset is not supported
*** 2603,2612 **** --- 2615,2625 ---- * If this formatter has been closed by invoking its {@link * #close()} method * * @return This formatter */ + @IntrinsicCandidate public Formatter format(String format, Object ... args) { return format(l, format, args); } /**
*** 2642,4247 **** * If this formatter has been closed by invoking its {@link * #close()} method * * @return This formatter */ public Formatter format(Locale l, String format, Object ... args) { ensureOpen(); - // index of last argument referenced int last = -1; // last ordinary index int lasto = -1; ! List<FormatString> fsa = parse(format); ! for (FormatString fs : fsa) { ! int index = fs.index(); try { switch (index) { ! case -2: // fixed string, "%n", or "%%" ! fs.print(null, l); ! break; ! case -1: // relative index ! if (last < 0 || (args != null && last > args.length - 1)) ! throw new MissingFormatArgumentException(fs.toString()); ! fs.print((args == null ? null : args[last]), l); ! break; ! case 0: // ordinary index ! lasto++; ! last = lasto; ! if (args != null && lasto > args.length - 1) ! throw new MissingFormatArgumentException(fs.toString()); ! fs.print((args == null ? null : args[lasto]), l); ! break; ! default: // explicit index ! last = index - 1; ! if (args != null && last > args.length - 1) ! throw new MissingFormatArgumentException(fs.toString()); ! fs.print((args == null ? null : args[last]), l); ! break; } } catch (IOException x) { lastException = x; } } return this; } ! // %[argument_index$][flags][width][.precision][t]conversion ! private static final String formatSpecifier ! = "%(\\d+\\$)?([-#+ 0,(\\<]*)?(\\d+)?(\\.\\d+)?([tT])?([a-zA-Z%])"; ! ! private static Pattern fsPattern = Pattern.compile(formatSpecifier); ! ! /** ! * Finds format specifiers in the format string. ! */ ! private List<FormatString> parse(String s) { ! ArrayList<FormatString> al = new ArrayList<>(); ! Matcher m = fsPattern.matcher(s); ! for (int i = 0, len = s.length(); i < len; ) { ! if (m.find(i)) { ! // Anything between the start of the string and the beginning ! // of the format specifier is either fixed text or contains ! // an invalid format string. ! if (m.start() != i) { ! // Make sure we didn't miss any invalid format specifiers ! checkText(s, i, m.start()); ! // Assume previous characters were fixed text ! al.add(new FixedString(s, i, m.start())); ! } ! ! al.add(new FormatSpecifier(s, m)); ! i = m.end(); ! } else { ! // No more valid format specifiers. Check for possible invalid ! // format specifiers. ! checkText(s, i, len); ! // The rest of the string is fixed text ! al.add(new FixedString(s, i, s.length())); break; ! } ! } ! return al; ! } ! ! private static void checkText(String s, int start, int end) { ! for (int i = start; i < end; i++) { ! // Any '%' found in the region starts an invalid format specifier. ! if (s.charAt(i) == '%') { ! char c = (i == end - 1) ? '%' : s.charAt(i + 1); ! throw new UnknownFormatConversionException(String.valueOf(c)); ! } ! } ! } ! ! private interface FormatString { ! int index(); ! void print(Object arg, Locale l) throws IOException; ! String toString(); ! } ! ! private class FixedString implements FormatString { ! private String s; ! private int start; ! private int end; ! FixedString(String s, int start, int end) { ! this.s = s; ! this.start = start; ! this.end = end; ! } ! public int index() { return -2; } ! public void print(Object arg, Locale l) ! throws IOException { a.append(s, start, end); } ! public String toString() { return s.substring(start, end); } ! } ! ! /** ! * Enum for {@code BigDecimal} formatting. ! */ ! public enum BigDecimalLayoutForm { ! /** ! * Format the {@code BigDecimal} in computerized scientific notation. ! */ ! SCIENTIFIC, ! ! /** ! * Format the {@code BigDecimal} as a decimal number. ! */ ! DECIMAL_FLOAT ! }; ! ! private class FormatSpecifier implements FormatString { ! private int index = -1; ! private Flags f = Flags.NONE; ! private int width; ! private int precision; ! private boolean dt = false; ! private char c; ! ! private int index(String s, int start, int end) { ! if (start >= 0) { ! try { ! // skip the trailing '$' ! index = Integer.parseInt(s, start, end - 1, 10); ! } catch (NumberFormatException x) { ! assert(false); ! } ! } else { ! index = 0; ! } ! return index; ! } ! ! public int index() { ! return index; ! } ! ! private Flags flags(String s, int start, int end) { ! f = Flags.parse(s, start, end); ! if (f.contains(Flags.PREVIOUS)) ! index = -1; ! return f; ! } ! ! private int width(String s, int start, int end) { ! width = -1; ! if (start >= 0) { ! try { ! width = Integer.parseInt(s, start, end, 10); ! if (width < 0) ! throw new IllegalFormatWidthException(width); ! } catch (NumberFormatException x) { ! assert(false); ! } ! } ! return width; ! } ! ! private int precision(String s, int start, int end) { ! precision = -1; ! if (start >= 0) { ! try { ! // skip the leading '.' ! precision = Integer.parseInt(s, start + 1, end, 10); ! if (precision < 0) ! throw new IllegalFormatPrecisionException(precision); ! } catch (NumberFormatException x) { ! assert(false); ! } ! } ! return precision; ! } ! ! private char conversion(char conv) { ! c = conv; ! if (!dt) { ! if (!Conversion.isValid(c)) { ! throw new UnknownFormatConversionException(String.valueOf(c)); ! } ! if (Character.isUpperCase(c)) { ! f.add(Flags.UPPERCASE); ! c = Character.toLowerCase(c); ! } ! if (Conversion.isText(c)) { ! index = -2; ! } ! } ! return c; ! } ! ! FormatSpecifier(String s, Matcher m) { ! index(s, m.start(1), m.end(1)); ! flags(s, m.start(2), m.end(2)); ! width(s, m.start(3), m.end(3)); ! precision(s, m.start(4), m.end(4)); ! ! int tTStart = m.start(5); ! if (tTStart >= 0) { ! dt = true; ! if (s.charAt(tTStart) == 'T') { ! f.add(Flags.UPPERCASE); ! } ! } ! conversion(s.charAt(m.start(6))); ! ! if (dt) ! checkDateTime(); ! else if (Conversion.isGeneral(c)) ! checkGeneral(); ! else if (Conversion.isCharacter(c)) ! checkCharacter(); ! else if (Conversion.isInteger(c)) ! checkInteger(); ! else if (Conversion.isFloat(c)) ! checkFloat(); ! else if (Conversion.isText(c)) ! checkText(); ! else ! throw new UnknownFormatConversionException(String.valueOf(c)); ! } ! ! public void print(Object arg, Locale l) throws IOException { ! if (dt) { ! printDateTime(arg, l); ! return; ! } ! switch(c) { ! case Conversion.DECIMAL_INTEGER: ! case Conversion.OCTAL_INTEGER: ! case Conversion.HEXADECIMAL_INTEGER: ! printInteger(arg, l); break; ! case Conversion.SCIENTIFIC: ! case Conversion.GENERAL: ! case Conversion.DECIMAL_FLOAT: ! case Conversion.HEXADECIMAL_FLOAT: ! printFloat(arg, l); break; ! case Conversion.CHARACTER: ! case Conversion.CHARACTER_UPPER: ! printCharacter(arg, l); break; ! case Conversion.BOOLEAN: ! printBoolean(arg, l); break; ! case Conversion.STRING: ! printString(arg, l); break; ! case Conversion.HASHCODE: ! printHashCode(arg, l); break; ! case Conversion.LINE_SEPARATOR: ! a.append(System.lineSeparator()); break; ! case Conversion.PERCENT_SIGN: ! print("%", l); break; default: assert false; - } } ! private void printInteger(Object arg, Locale l) throws IOException { ! if (arg == null) ! print("null", l); ! else if (arg instanceof Byte) ! print(((Byte)arg).byteValue(), l); ! else if (arg instanceof Short) ! print(((Short)arg).shortValue(), l); ! else if (arg instanceof Integer) ! print(((Integer)arg).intValue(), l); ! else if (arg instanceof Long) ! print(((Long)arg).longValue(), l); ! else if (arg instanceof BigInteger) ! print(((BigInteger)arg), l); ! else ! failConversion(c, arg); ! } ! private void printFloat(Object arg, Locale l) throws IOException { ! if (arg == null) ! print("null", l); ! else if (arg instanceof Float) ! print(((Float)arg).floatValue(), l); ! else if (arg instanceof Double) ! print(((Double)arg).doubleValue(), l); ! else if (arg instanceof BigDecimal) ! print(((BigDecimal)arg), l); ! else ! failConversion(c, arg); ! } ! private void printDateTime(Object arg, Locale l) throws IOException { ! if (arg == null) { ! print("null", l); ! return; ! } ! Calendar cal = null; ! ! // Instead of Calendar.setLenient(true), perhaps we should ! // wrap the IllegalArgumentException that might be thrown? ! if (arg instanceof Long) { ! // Note that the following method uses an instance of the ! // default time zone (TimeZone.getDefaultRef(). ! cal = Calendar.getInstance(l == null ? Locale.US : l); ! cal.setTimeInMillis((Long)arg); ! } else if (arg instanceof Date) { ! // Note that the following method uses an instance of the ! // default time zone (TimeZone.getDefaultRef(). ! cal = Calendar.getInstance(l == null ? Locale.US : l); ! cal.setTime((Date)arg); ! } else if (arg instanceof Calendar) { ! cal = (Calendar) ((Calendar) arg).clone(); ! cal.setLenient(true); ! } else if (arg instanceof TemporalAccessor) { ! print((TemporalAccessor) arg, c, l); ! return; ! } else { ! failConversion(c, arg); ! } ! // Use the provided locale so that invocations of ! // localizedMagnitude() use optimizations for null. ! print(cal, c, l); ! } ! ! private void printCharacter(Object arg, Locale l) throws IOException { ! if (arg == null) { ! print("null", l); ! return; ! } ! String s = null; ! if (arg instanceof Character) { ! s = ((Character)arg).toString(); ! } else if (arg instanceof Byte) { ! byte i = ((Byte)arg).byteValue(); ! if (Character.isValidCodePoint(i)) ! s = new String(Character.toChars(i)); ! else ! throw new IllegalFormatCodePointException(i); ! } else if (arg instanceof Short) { ! short i = ((Short)arg).shortValue(); ! if (Character.isValidCodePoint(i)) ! s = new String(Character.toChars(i)); ! else ! throw new IllegalFormatCodePointException(i); ! } else if (arg instanceof Integer) { ! int i = ((Integer)arg).intValue(); ! if (Character.isValidCodePoint(i)) ! s = new String(Character.toChars(i)); ! else ! throw new IllegalFormatCodePointException(i); ! } else { ! failConversion(c, arg); ! } ! print(s, l); } ! private void printString(Object arg, Locale l) throws IOException { ! if (arg instanceof Formattable) { ! Formatter fmt = Formatter.this; ! if (fmt.locale() != l) ! fmt = new Formatter(fmt.out(), l); ! ((Formattable)arg).formatTo(fmt, f.valueOf(), width, precision); ! } else { ! if (f.contains(Flags.ALTERNATE)) ! failMismatch(Flags.ALTERNATE, 's'); ! if (arg == null) ! print("null", l); ! else ! print(arg.toString(), l); ! } } ! private void printBoolean(Object arg, Locale l) throws IOException { ! String s; ! if (arg != null) ! s = ((arg instanceof Boolean) ! ? ((Boolean)arg).toString() ! : Boolean.toString(true)); else ! s = Boolean.toString(false); ! print(s, l); } ! private void printHashCode(Object arg, Locale l) throws IOException { ! String s = (arg == null ! ? "null" ! : Integer.toHexString(arg.hashCode())); ! print(s, l); } ! private void print(String s, Locale l) throws IOException { ! if (precision != -1 && precision < s.length()) ! s = s.substring(0, precision); ! if (f.contains(Flags.UPPERCASE)) ! s = toUpperCaseWithLocale(s, l); ! appendJustified(a, s); ! } ! private String toUpperCaseWithLocale(String s, Locale l) { ! return s.toUpperCase(Objects.requireNonNullElse(l, ! Locale.getDefault(Locale.Category.FORMAT))); ! } ! ! private Appendable appendJustified(Appendable a, CharSequence cs) throws IOException { ! if (width == -1) { ! return a.append(cs); ! } ! boolean padRight = f.contains(Flags.LEFT_JUSTIFY); ! int sp = width - cs.length(); ! if (padRight) { ! a.append(cs); ! } ! for (int i = 0; i < sp; i++) { ! a.append(' '); ! } ! if (!padRight) { ! a.append(cs); ! } ! return a; ! } ! public String toString() { ! StringBuilder sb = new StringBuilder("%"); ! // Flags.UPPERCASE is set internally for legal conversions. ! Flags dupf = f.dup().remove(Flags.UPPERCASE); ! sb.append(dupf.toString()); ! if (index > 0) ! sb.append(index).append('$'); ! if (width != -1) ! sb.append(width); ! if (precision != -1) ! sb.append('.').append(precision); ! if (dt) ! sb.append(f.contains(Flags.UPPERCASE) ? 'T' : 't'); ! sb.append(f.contains(Flags.UPPERCASE) ! ? Character.toUpperCase(c) : c); ! return sb.toString(); ! } ! private void checkGeneral() { ! if ((c == Conversion.BOOLEAN || c == Conversion.HASHCODE) ! && f.contains(Flags.ALTERNATE)) ! failMismatch(Flags.ALTERNATE, c); ! // '-' requires a width ! if (width == -1 && f.contains(Flags.LEFT_JUSTIFY)) ! throw new MissingFormatWidthException(toString()); ! checkBadFlags(Flags.PLUS, Flags.LEADING_SPACE, Flags.ZERO_PAD, ! Flags.GROUP, Flags.PARENTHESES); ! } ! private void checkDateTime() { ! if (precision != -1) ! throw new IllegalFormatPrecisionException(precision); ! if (!DateTime.isValid(c)) ! throw new UnknownFormatConversionException("t" + c); ! checkBadFlags(Flags.ALTERNATE, Flags.PLUS, Flags.LEADING_SPACE, ! Flags.ZERO_PAD, Flags.GROUP, Flags.PARENTHESES); ! // '-' requires a width ! if (width == -1 && f.contains(Flags.LEFT_JUSTIFY)) ! throw new MissingFormatWidthException(toString()); } ! ! private void checkCharacter() { ! if (precision != -1) ! throw new IllegalFormatPrecisionException(precision); ! checkBadFlags(Flags.ALTERNATE, Flags.PLUS, Flags.LEADING_SPACE, ! Flags.ZERO_PAD, Flags.GROUP, Flags.PARENTHESES); ! // '-' requires a width ! if (width == -1 && f.contains(Flags.LEFT_JUSTIFY)) ! throw new MissingFormatWidthException(toString()); } ! private void checkInteger() { ! checkNumeric(); ! if (precision != -1) ! throw new IllegalFormatPrecisionException(precision); ! ! if (c == Conversion.DECIMAL_INTEGER) ! checkBadFlags(Flags.ALTERNATE); ! else if (c == Conversion.OCTAL_INTEGER) ! checkBadFlags(Flags.GROUP); ! else ! checkBadFlags(Flags.GROUP); } ! private void checkBadFlags(Flags ... badFlags) { ! for (Flags badFlag : badFlags) ! if (f.contains(badFlag)) ! failMismatch(badFlag, c); } ! private void checkFloat() { ! checkNumeric(); ! if (c == Conversion.DECIMAL_FLOAT) { ! } else if (c == Conversion.HEXADECIMAL_FLOAT) { ! checkBadFlags(Flags.PARENTHESES, Flags.GROUP); ! } else if (c == Conversion.SCIENTIFIC) { ! checkBadFlags(Flags.GROUP); ! } else if (c == Conversion.GENERAL) { ! checkBadFlags(Flags.ALTERNATE); ! } } ! private void checkNumeric() { ! if (width != -1 && width < 0) ! throw new IllegalFormatWidthException(width); ! if (precision != -1 && precision < 0) ! throw new IllegalFormatPrecisionException(precision); ! // '-' and '0' require a width ! if (width == -1 ! && (f.contains(Flags.LEFT_JUSTIFY) || f.contains(Flags.ZERO_PAD))) ! throw new MissingFormatWidthException(toString()); ! // bad combination ! if ((f.contains(Flags.PLUS) && f.contains(Flags.LEADING_SPACE)) ! || (f.contains(Flags.LEFT_JUSTIFY) && f.contains(Flags.ZERO_PAD))) ! throw new IllegalFormatFlagsException(f.toString()); ! } ! private void checkText() { ! if (precision != -1) ! throw new IllegalFormatPrecisionException(precision); ! switch (c) { ! case Conversion.PERCENT_SIGN: ! if (f.valueOf() != Flags.LEFT_JUSTIFY.valueOf() ! && f.valueOf() != Flags.NONE.valueOf()) ! throw new IllegalFormatFlagsException(f.toString()); ! // '-' requires a width ! if (width == -1 && f.contains(Flags.LEFT_JUSTIFY)) ! throw new MissingFormatWidthException(toString()); ! break; ! case Conversion.LINE_SEPARATOR: ! if (width != -1) ! throw new IllegalFormatWidthException(width); ! if (f.valueOf() != Flags.NONE.valueOf()) ! throw new IllegalFormatFlagsException(f.toString()); ! break; ! default: ! assert false; ! } ! } ! private void print(byte value, Locale l) throws IOException { ! long v = value; ! if (value < 0 ! && (c == Conversion.OCTAL_INTEGER ! || c == Conversion.HEXADECIMAL_INTEGER)) { ! v += (1L << 8); ! assert v >= 0 : v; } ! print(v, l); } ! private void print(short value, Locale l) throws IOException { ! long v = value; ! if (value < 0 ! && (c == Conversion.OCTAL_INTEGER ! || c == Conversion.HEXADECIMAL_INTEGER)) { ! v += (1L << 16); ! assert v >= 0 : v; } ! print(v, l); } ! private void print(int value, Locale l) throws IOException { ! long v = value; ! if (value < 0 ! && (c == Conversion.OCTAL_INTEGER ! || c == Conversion.HEXADECIMAL_INTEGER)) { ! v += (1L << 32); ! assert v >= 0 : v; } ! print(v, l); } ! private void print(long value, Locale l) throws IOException { ! StringBuilder sb = new StringBuilder(); ! if (c == Conversion.DECIMAL_INTEGER) { ! boolean neg = value < 0; ! String valueStr = Long.toString(value, 10); ! ! // leading sign indicator ! leadingSign(sb, neg); ! ! // the value ! localizedMagnitude(sb, valueStr, neg ? 1 : 0, f, adjustWidth(width, f, neg), l); ! ! // trailing sign indicator ! trailingSign(sb, neg); ! } else if (c == Conversion.OCTAL_INTEGER) { ! checkBadFlags(Flags.PARENTHESES, Flags.LEADING_SPACE, ! Flags.PLUS); ! String s = Long.toOctalString(value); ! int len = (f.contains(Flags.ALTERNATE) ! ? s.length() + 1 ! : s.length()); ! ! // apply ALTERNATE (radix indicator for octal) before ZERO_PAD ! if (f.contains(Flags.ALTERNATE)) ! sb.append('0'); ! if (f.contains(Flags.ZERO_PAD)) { ! trailingZeros(sb, width - len); ! } ! sb.append(s); ! } else if (c == Conversion.HEXADECIMAL_INTEGER) { ! checkBadFlags(Flags.PARENTHESES, Flags.LEADING_SPACE, ! Flags.PLUS); ! String s = Long.toHexString(value); ! int len = (f.contains(Flags.ALTERNATE) ! ? s.length() + 2 ! : s.length()); ! ! // apply ALTERNATE (radix indicator for hex) before ZERO_PAD ! if (f.contains(Flags.ALTERNATE)) ! sb.append(f.contains(Flags.UPPERCASE) ? "0X" : "0x"); ! if (f.contains(Flags.ZERO_PAD)) { ! trailingZeros(sb, width - len); ! } ! if (f.contains(Flags.UPPERCASE)) ! s = toUpperCaseWithLocale(s, l); ! sb.append(s); ! } ! ! // justify based on width ! appendJustified(a, sb); ! } ! ! // neg := val < 0 ! private StringBuilder leadingSign(StringBuilder sb, boolean neg) { ! if (!neg) { ! if (f.contains(Flags.PLUS)) { ! sb.append('+'); ! } else if (f.contains(Flags.LEADING_SPACE)) { ! sb.append(' '); ! } ! } else { ! if (f.contains(Flags.PARENTHESES)) ! sb.append('('); ! else ! sb.append('-'); ! } ! return sb; ! } ! // neg := val < 0 ! private StringBuilder trailingSign(StringBuilder sb, boolean neg) { ! if (neg && f.contains(Flags.PARENTHESES)) ! sb.append(')'); ! return sb; ! } ! private void print(BigInteger value, Locale l) throws IOException { ! StringBuilder sb = new StringBuilder(); ! boolean neg = value.signum() == -1; ! BigInteger v = value.abs(); // leading sign indicator ! leadingSign(sb, neg); // the value ! if (c == Conversion.DECIMAL_INTEGER) { ! localizedMagnitude(sb, v.toString(), 0, f, adjustWidth(width, f, neg), l); ! } else if (c == Conversion.OCTAL_INTEGER) { ! String s = v.toString(8); ! ! int len = s.length() + sb.length(); ! if (neg && f.contains(Flags.PARENTHESES)) ! len++; ! ! // apply ALTERNATE (radix indicator for octal) before ZERO_PAD ! if (f.contains(Flags.ALTERNATE)) { ! len++; ! sb.append('0'); ! } ! if (f.contains(Flags.ZERO_PAD)) { ! trailingZeros(sb, width - len); ! } ! sb.append(s); ! } else if (c == Conversion.HEXADECIMAL_INTEGER) { ! String s = v.toString(16); ! ! int len = s.length() + sb.length(); ! if (neg && f.contains(Flags.PARENTHESES)) ! len++; ! ! // apply ALTERNATE (radix indicator for hex) before ZERO_PAD ! if (f.contains(Flags.ALTERNATE)) { ! len += 2; ! sb.append(f.contains(Flags.UPPERCASE) ? "0X" : "0x"); ! } ! if (f.contains(Flags.ZERO_PAD)) { ! trailingZeros(sb, width - len); ! } ! if (f.contains(Flags.UPPERCASE)) ! s = toUpperCaseWithLocale(s, l); ! sb.append(s); ! } // trailing sign indicator ! trailingSign(sb, (value.signum() == -1)); ! ! // justify based on width ! appendJustified(a, sb); ! } ! ! private void print(float value, Locale l) throws IOException { ! print((double) value, l); } ! private void print(double value, Locale l) throws IOException { ! StringBuilder sb = new StringBuilder(); ! boolean neg = Double.compare(value, 0.0) == -1; ! ! if (!Double.isNaN(value)) { ! double v = Math.abs(value); ! ! // leading sign indicator ! leadingSign(sb, neg); ! ! // the value ! if (!Double.isInfinite(v)) ! print(sb, v, l, f, c, precision, neg); ! else ! sb.append(f.contains(Flags.UPPERCASE) ! ? "INFINITY" : "Infinity"); ! ! // trailing sign indicator ! trailingSign(sb, neg); ! } else { ! sb.append(f.contains(Flags.UPPERCASE) ? "NAN" : "NaN"); ! } ! ! // justify based on width ! appendJustified(a, sb); ! } ! // !Double.isInfinite(value) && !Double.isNaN(value) ! private void print(StringBuilder sb, double value, Locale l, ! Flags f, char c, int precision, boolean neg) throws IOException ! { ! if (c == Conversion.SCIENTIFIC) { ! // Create a new FormattedFloatingDecimal with the desired ! // precision. ! int prec = (precision == -1 ? 6 : precision); ! FormattedFloatingDecimal fd ! = FormattedFloatingDecimal.valueOf(value, prec, ! FormattedFloatingDecimal.Form.SCIENTIFIC); ! StringBuilder mant = new StringBuilder().append(fd.getMantissa()); ! addZeros(mant, prec); ! // If the precision is zero and the '#' flag is set, add the ! // requested decimal point. ! if (f.contains(Flags.ALTERNATE) && (prec == 0)) { ! mant.append('.'); ! } ! char[] exp = (value == 0.0) ? new char[] {'+','0','0'} : fd.getExponent(); ! int newW = width; ! if (width != -1) { ! newW = adjustWidth(width - exp.length - 1, f, neg); ! } ! localizedMagnitude(sb, mant, 0, f, newW, l); ! sb.append(f.contains(Flags.UPPERCASE) ? 'E' : 'e'); ! char sign = exp[0]; ! assert(sign == '+' || sign == '-'); ! sb.append(sign); ! localizedMagnitudeExp(sb, exp, 1, l); ! } else if (c == Conversion.DECIMAL_FLOAT) { ! // Create a new FormattedFloatingDecimal with the desired ! // precision. ! int prec = (precision == -1 ? 6 : precision); ! FormattedFloatingDecimal fd ! = FormattedFloatingDecimal.valueOf(value, prec, ! FormattedFloatingDecimal.Form.DECIMAL_FLOAT); ! StringBuilder mant = new StringBuilder().append(fd.getMantissa()); ! addZeros(mant, prec); ! // If the precision is zero and the '#' flag is set, add the ! // requested decimal point. ! if (f.contains(Flags.ALTERNATE) && (prec == 0)) ! mant.append('.'); ! int newW = width; ! if (width != -1) ! newW = adjustWidth(width, f, neg); ! localizedMagnitude(sb, mant, 0, f, newW, l); ! } else if (c == Conversion.GENERAL) { ! int prec = precision; ! if (precision == -1) ! prec = 6; ! else if (precision == 0) ! prec = 1; ! ! char[] exp; ! StringBuilder mant = new StringBuilder(); ! int expRounded; ! if (value == 0.0) { ! exp = null; ! mant.append('0'); ! expRounded = 0; ! } else { ! FormattedFloatingDecimal fd = FormattedFloatingDecimal.valueOf(value, prec, ! FormattedFloatingDecimal.Form.GENERAL); ! exp = fd.getExponent(); ! mant.append(fd.getMantissa()); ! expRounded = fd.getExponentRounded(); ! } ! ! if (exp != null) { ! prec -= 1; ! } else { ! prec -= expRounded + 1; ! } ! ! addZeros(mant, prec); ! // If the precision is zero and the '#' flag is set, add the ! // requested decimal point. ! if (f.contains(Flags.ALTERNATE) && (prec == 0)) { ! mant.append('.'); ! } ! ! int newW = width; ! if (width != -1) { ! if (exp != null) ! newW = adjustWidth(width - exp.length - 1, f, neg); ! else ! newW = adjustWidth(width, f, neg); ! } ! localizedMagnitude(sb, mant, 0, f, newW, l); ! ! if (exp != null) { ! sb.append(f.contains(Flags.UPPERCASE) ? 'E' : 'e'); ! char sign = exp[0]; ! assert(sign == '+' || sign == '-'); ! sb.append(sign); ! localizedMagnitudeExp(sb, exp, 1, l); ! } ! } else if (c == Conversion.HEXADECIMAL_FLOAT) { ! int prec = precision; ! if (precision == -1) ! // assume that we want all of the digits ! prec = 0; ! else if (precision == 0) ! prec = 1; ! String s = hexDouble(value, prec); ! StringBuilder va = new StringBuilder(); ! boolean upper = f.contains(Flags.UPPERCASE); ! sb.append(upper ? "0X" : "0x"); ! if (f.contains(Flags.ZERO_PAD)) { ! trailingZeros(sb, width - s.length() - 2); ! } ! int idx = s.indexOf('p'); ! if (upper) { ! String tmp = s.substring(0, idx); ! // don't localize hex ! tmp = tmp.toUpperCase(Locale.ROOT); ! va.append(tmp); ! } else { ! va.append(s, 0, idx); ! } ! if (prec != 0) { ! addZeros(va, prec); ! } ! sb.append(va); ! sb.append(upper ? 'P' : 'p'); ! sb.append(s, idx+1, s.length()); } ! } ! ! // Add zeros to the requested precision. ! private void addZeros(StringBuilder sb, int prec) { ! // Look for the dot. If we don't find one, the we'll need to add ! // it before we add the zeros. ! int len = sb.length(); ! int i; ! for (i = 0; i < len; i++) { ! if (sb.charAt(i) == '.') { ! break; ! } } ! boolean needDot = false; ! if (i == len) { ! needDot = true; } ! // Determine existing precision. ! int outPrec = len - i - (needDot ? 0 : 1); ! assert (outPrec <= prec); ! if (outPrec == prec) { ! return; } ! // Add dot if previously determined to be necessary. ! if (needDot) { ! sb.append('.'); ! } ! // Add zeros. ! trailingZeros(sb, prec - outPrec); } ! // Method assumes that d > 0. ! private String hexDouble(double d, int prec) { ! // Let Double.toHexString handle simple cases ! if (!Double.isFinite(d) || d == 0.0 || prec == 0 || prec >= 13) { ! // remove "0x" ! return Double.toHexString(d).substring(2); ! } else { ! assert(prec >= 1 && prec <= 12); ! int exponent = Math.getExponent(d); ! boolean subnormal = (exponent == Double.MIN_EXPONENT - 1); ! // If this is subnormal input so normalize (could be faster to ! // do as integer operation). ! if (subnormal) { ! scaleUp = Math.scalb(1.0, 54); ! d *= scaleUp; ! // Calculate the exponent. This is not just exponent + 54 ! // since the former is not the normalized exponent. ! exponent = Math.getExponent(d); ! assert exponent >= Double.MIN_EXPONENT && exponent <= Double.MAX_EXPONENT: exponent; ! } ! int precision = 1 + prec*4; ! int shiftDistance = DoubleConsts.SIGNIFICAND_WIDTH - precision; ! assert(shiftDistance >= 1 && shiftDistance < DoubleConsts.SIGNIFICAND_WIDTH); ! long doppel = Double.doubleToLongBits(d); ! // Deterime the number of bits to keep. ! long newSignif = (doppel & (DoubleConsts.EXP_BIT_MASK ! | DoubleConsts.SIGNIF_BIT_MASK)) ! >> shiftDistance; ! // Bits to round away. ! long roundingBits = doppel & ~(~0L << shiftDistance); ! ! // To decide how to round, look at the low-order bit of the ! // working significand, the highest order discarded bit (the ! // round bit) and whether any of the lower order discarded bits ! // are nonzero (the sticky bit). ! boolean leastZero = (newSignif & 0x1L) == 0L; ! boolean round = ((1L << (shiftDistance - 1) ) & roundingBits) != 0L; ! boolean sticky = shiftDistance > 1 && (~(1L<< (shiftDistance - 1)) & roundingBits) != 0; ! if((leastZero && round && sticky) || (!leastZero && round)) { ! newSignif++; ! } ! long signBit = doppel & DoubleConsts.SIGN_BIT_MASK; ! newSignif = signBit | (newSignif << shiftDistance); ! double result = Double.longBitsToDouble(newSignif); ! if (Double.isInfinite(result) ) { ! // Infinite result generated by rounding ! return "1.0p1024"; ! } else { ! String res = Double.toHexString(result).substring(2); ! if (!subnormal) ! return res; ! else { ! // Create a normalized subnormal string. ! int idx = res.indexOf('p'); ! if (idx == -1) { ! // No 'p' character in hex string. ! assert false; ! return null; ! } else { ! // Get exponent and append at the end. ! String exp = res.substring(idx + 1); ! int iexp = Integer.parseInt(exp) -54; ! return res.substring(0, idx) + "p" + Integer.toString(iexp); - } } } } } ! private void print(BigDecimal value, Locale l) throws IOException { ! if (c == Conversion.HEXADECIMAL_FLOAT) ! failConversion(c, value); ! StringBuilder sb = new StringBuilder(); ! boolean neg = value.signum() == -1; ! BigDecimal v = value.abs(); ! // leading sign indicator ! leadingSign(sb, neg); ! // the value ! print(sb, v, l, f, c, precision, neg); ! // trailing sign indicator ! trailingSign(sb, neg); ! // justify based on width ! appendJustified(a, sb); ! } ! // value > 0 ! private void print(StringBuilder sb, BigDecimal value, Locale l, ! Flags f, char c, int precision, boolean neg) throws IOException ! { ! if (c == Conversion.SCIENTIFIC) { ! // Create a new BigDecimal with the desired precision. ! int prec = (precision == -1 ? 6 : precision); ! int scale = value.scale(); ! int origPrec = value.precision(); ! int nzeros = 0; ! int compPrec; ! ! if (prec > origPrec - 1) { ! compPrec = origPrec; ! nzeros = prec - (origPrec - 1); ! } else { ! compPrec = prec + 1; ! } ! MathContext mc = new MathContext(compPrec); ! BigDecimal v = new BigDecimal(value.unscaledValue(), scale, mc); ! BigDecimalLayout bdl = new BigDecimalLayout(v.unscaledValue(), v.scale(), ! BigDecimalLayoutForm.SCIENTIFIC); ! ! StringBuilder mant = bdl.mantissa(); ! // Add a decimal point if necessary. The mantissa may not ! // contain a decimal point if the scale is zero (the internal ! // representation has no fractional part) or the original ! // precision is one. Append a decimal point if '#' is set or if ! // we require zero padding to get to the requested precision. ! if ((origPrec == 1 || !bdl.hasDot()) ! && (nzeros > 0 || (f.contains(Flags.ALTERNATE)))) { ! mant.append('.'); ! } ! // Add trailing zeros in the case precision is greater than ! // the number of available digits after the decimal separator. ! trailingZeros(mant, nzeros); ! ! StringBuilder exp = bdl.exponent(); ! int newW = width; ! if (width != -1) { ! newW = adjustWidth(width - exp.length() - 1, f, neg); ! } ! localizedMagnitude(sb, mant, 0, f, newW, l); ! sb.append(f.contains(Flags.UPPERCASE) ? 'E' : 'e'); ! Flags flags = f.dup().remove(Flags.GROUP); ! char sign = exp.charAt(0); ! assert(sign == '+' || sign == '-'); ! sb.append(sign); ! sb.append(localizedMagnitude(null, exp, 1, flags, -1, l)); ! } else if (c == Conversion.DECIMAL_FLOAT) { ! // Create a new BigDecimal with the desired precision. ! int prec = (precision == -1 ? 6 : precision); ! int scale = value.scale(); ! ! if (scale > prec) { ! // more "scale" digits than the requested "precision" ! int compPrec = value.precision(); ! if (compPrec <= scale) { ! // case of 0.xxxxxx ! value = value.setScale(prec, RoundingMode.HALF_UP); ! } else { ! compPrec -= (scale - prec); ! value = new BigDecimal(value.unscaledValue(), ! scale, ! new MathContext(compPrec)); ! } ! } ! BigDecimalLayout bdl = new BigDecimalLayout( ! value.unscaledValue(), ! value.scale(), ! BigDecimalLayoutForm.DECIMAL_FLOAT); ! ! StringBuilder mant = bdl.mantissa(); ! int nzeros = (bdl.scale() < prec ? prec - bdl.scale() : 0); ! ! // Add a decimal point if necessary. The mantissa may not ! // contain a decimal point if the scale is zero (the internal ! // representation has no fractional part). Append a decimal ! // point if '#' is set or we require zero padding to get to the ! // requested precision. ! if (bdl.scale() == 0 && (f.contains(Flags.ALTERNATE) ! || nzeros > 0)) { ! mant.append('.'); ! } ! // Add trailing zeros if the precision is greater than the ! // number of available digits after the decimal separator. ! trailingZeros(mant, nzeros); ! ! localizedMagnitude(sb, mant, 0, f, adjustWidth(width, f, neg), l); ! } else if (c == Conversion.GENERAL) { ! int prec = precision; ! if (precision == -1) ! prec = 6; ! else if (precision == 0) ! prec = 1; ! ! BigDecimal tenToTheNegFour = BigDecimal.valueOf(1, 4); ! BigDecimal tenToThePrec = BigDecimal.valueOf(1, -prec); ! if ((value.equals(BigDecimal.ZERO)) || ((value.compareTo(tenToTheNegFour) != -1) ! && (value.compareTo(tenToThePrec) == -1))) { ! int e = - value.scale() + (value.unscaledValue().toString().length() - 1); ! // xxx.yyy ! // g precision (# sig digits) = #x + #y ! // f precision = #y ! // exponent = #x - 1 ! // => f precision = g precision - exponent - 1 ! // 0.000zzz ! // g precision (# sig digits) = #z ! // f precision = #0 (after '.') + #z ! // exponent = - #0 (after '.') - 1 ! // => f precision = g precision - exponent - 1 ! prec = prec - e - 1; ! print(sb, value, l, f, Conversion.DECIMAL_FLOAT, prec, ! neg); ! } else { ! print(sb, value, l, f, Conversion.SCIENTIFIC, prec - 1, neg); ! } ! } else if (c == Conversion.HEXADECIMAL_FLOAT) { ! // This conversion isn't supported. The error should be ! // reported earlier. ! assert false; } } ! private class BigDecimalLayout { ! private StringBuilder mant; ! private StringBuilder exp; ! private boolean dot = false; ! private int scale; ! ! public BigDecimalLayout(BigInteger intVal, int scale, BigDecimalLayoutForm form) { ! layout(intVal, scale, form); ! } ! ! public boolean hasDot() { ! return dot; ! } ! ! public int scale() { ! return scale; ! } ! ! public StringBuilder mantissa() { ! return mant; ! } ! ! // The exponent will be formatted as a sign ('+' or '-') followed ! // by the exponent zero-padded to include at least two digits. ! public StringBuilder exponent() { ! return exp; ! } ! ! private void layout(BigInteger intVal, int scale, BigDecimalLayoutForm form) { ! String coeff = intVal.toString(); ! this.scale = scale; ! ! // Construct a buffer, with sufficient capacity for all cases. ! // If E-notation is needed, length will be: +1 if negative, +1 ! // if '.' needed, +2 for "E+", + up to 10 for adjusted ! // exponent. Otherwise it could have +1 if negative, plus ! // leading "0.00000" ! int len = coeff.length(); ! mant = new StringBuilder(len + 14); ! ! if (scale == 0) { ! if (len > 1) { ! mant.append(coeff.charAt(0)); ! if (form == BigDecimalLayoutForm.SCIENTIFIC) { ! mant.append('.'); ! dot = true; ! mant.append(coeff, 1, len); ! exp = new StringBuilder("+"); ! if (len < 10) { ! exp.append('0').append(len - 1); ! } else { ! exp.append(len - 1); ! } } else { ! mant.append(coeff, 1, len); } } else { ! mant.append(coeff); ! if (form == BigDecimalLayoutForm.SCIENTIFIC) { ! exp = new StringBuilder("+00"); ! } } ! } else if (form == BigDecimalLayoutForm.DECIMAL_FLOAT) { ! // count of padding zeros ! ! if (scale >= len) { ! // 0.xxx form ! mant.append("0."); ! dot = true; ! trailingZeros(mant, scale - len); ! mant.append(coeff); ! } else { ! if (scale > 0) { ! // xx.xx form ! int pad = len - scale; ! mant.append(coeff, 0, pad); ! mant.append('.'); ! dot = true; ! mant.append(coeff, pad, len); ! } else { // scale < 0 ! // xx form ! mant.append(coeff, 0, len); ! if (intVal.signum() != 0) { ! trailingZeros(mant, -scale); ! } ! this.scale = 0; ! } } } else { ! // x.xxx form ! mant.append(coeff.charAt(0)); ! if (len > 1) { mant.append('.'); dot = true; ! mant.append(coeff, 1, len); ! } ! exp = new StringBuilder(); ! long adjusted = -(long) scale + (len - 1); ! if (adjusted != 0) { ! long abs = Math.abs(adjusted); ! // require sign ! exp.append(adjusted < 0 ? '-' : '+'); ! if (abs < 10) { ! exp.append('0'); } ! exp.append(abs); ! } else { ! exp.append("+00"); } } } } ! private int adjustWidth(int width, Flags f, boolean neg) { ! int newW = width; ! if (newW != -1 && neg && f.contains(Flags.PARENTHESES)) ! newW--; ! return newW; ! } ! // Add trailing zeros ! private void trailingZeros(StringBuilder sb, int nzeros) { ! for (int i = 0; i < nzeros; i++) { ! sb.append('0'); ! } } ! private void print(Calendar t, char c, Locale l) throws IOException { ! StringBuilder sb = new StringBuilder(); ! print(sb, t, c, l); ! ! // justify based on width ! if (f.contains(Flags.UPPERCASE)) { ! appendJustified(a, toUpperCaseWithLocale(sb.toString(), l)); ! } else { ! appendJustified(a, sb); ! } } ! private Appendable print(StringBuilder sb, Calendar t, char c, Locale l) ! throws IOException { ! if (sb == null) ! sb = new StringBuilder(); ! switch (c) { ! case DateTime.HOUR_OF_DAY_0: // 'H' (00 - 23) ! case DateTime.HOUR_0: // 'I' (01 - 12) ! case DateTime.HOUR_OF_DAY: // 'k' (0 - 23) -- like H ! case DateTime.HOUR: { // 'l' (1 - 12) -- like I int i = t.get(Calendar.HOUR_OF_DAY); ! if (c == DateTime.HOUR_0 || c == DateTime.HOUR) i = (i == 0 || i == 12 ? 12 : i % 12); ! Flags flags = (c == DateTime.HOUR_OF_DAY_0 ! || c == DateTime.HOUR_0 ! ? Flags.ZERO_PAD ! : Flags.NONE); sb.append(localizedMagnitude(null, i, flags, 2, l)); break; } ! case DateTime.MINUTE: { // 'M' (00 - 59) int i = t.get(Calendar.MINUTE); ! Flags flags = Flags.ZERO_PAD; sb.append(localizedMagnitude(null, i, flags, 2, l)); break; } ! case DateTime.NANOSECOND: { // 'N' (000000000 - 999999999) int i = t.get(Calendar.MILLISECOND) * 1000000; ! Flags flags = Flags.ZERO_PAD; sb.append(localizedMagnitude(null, i, flags, 9, l)); break; } ! case DateTime.MILLISECOND: { // 'L' (000 - 999) int i = t.get(Calendar.MILLISECOND); ! Flags flags = Flags.ZERO_PAD; sb.append(localizedMagnitude(null, i, flags, 3, l)); break; } ! case DateTime.MILLISECOND_SINCE_EPOCH: { // 'Q' (0 - 99...?) long i = t.getTimeInMillis(); ! Flags flags = Flags.NONE; ! sb.append(localizedMagnitude(null, i, flags, width, l)); break; } ! case DateTime.AM_PM: { // 'p' (am or pm) // Calendar.AM = 0, Calendar.PM = 1, LocaleElements defines upper String[] ampm = { "AM", "PM" }; if (l != null && l != Locale.US) { DateFormatSymbols dfs = DateFormatSymbols.getInstance(l); ampm = dfs.getAmPmStrings(); } String s = ampm[t.get(Calendar.AM_PM)]; sb.append(s.toLowerCase(Objects.requireNonNullElse(l, ! Locale.getDefault(Locale.Category.FORMAT)))); break; } ! case DateTime.SECONDS_SINCE_EPOCH: { // 's' (0 - 99...?) long i = t.getTimeInMillis() / 1000; ! Flags flags = Flags.NONE; ! sb.append(localizedMagnitude(null, i, flags, width, l)); break; } ! case DateTime.SECOND: { // 'S' (00 - 60 - leap second) int i = t.get(Calendar.SECOND); ! Flags flags = Flags.ZERO_PAD; sb.append(localizedMagnitude(null, i, flags, 2, l)); break; } ! case DateTime.ZONE_NUMERIC: { // 'z' ({-|+}####) - ls minus? int i = t.get(Calendar.ZONE_OFFSET) + t.get(Calendar.DST_OFFSET); boolean neg = i < 0; sb.append(neg ? '-' : '+'); if (neg) i = -i; int min = i / 60000; // combine minute and hour into a single integer int offset = (min / 60) * 100 + (min % 60); ! Flags flags = Flags.ZERO_PAD; sb.append(localizedMagnitude(null, offset, flags, 4, l)); break; } ! case DateTime.ZONE: { // 'Z' (symbol) TimeZone tz = t.getTimeZone(); sb.append(tz.getDisplayName((t.get(Calendar.DST_OFFSET) != 0), ! TimeZone.SHORT, ! Objects.requireNonNullElse(l, Locale.US))); break; } // Date ! case DateTime.NAME_OF_DAY_ABBREV: // 'a' ! case DateTime.NAME_OF_DAY: { // 'A' int i = t.get(Calendar.DAY_OF_WEEK); Locale lt = Objects.requireNonNullElse(l, Locale.US); DateFormatSymbols dfs = DateFormatSymbols.getInstance(lt); ! if (c == DateTime.NAME_OF_DAY) sb.append(dfs.getWeekdays()[i]); else sb.append(dfs.getShortWeekdays()[i]); break; } ! case DateTime.NAME_OF_MONTH_ABBREV: // 'b' ! case DateTime.NAME_OF_MONTH_ABBREV_X: // 'h' -- same b ! case DateTime.NAME_OF_MONTH: { // 'B' int i = t.get(Calendar.MONTH); Locale lt = Objects.requireNonNullElse(l, Locale.US); DateFormatSymbols dfs = DateFormatSymbols.getInstance(lt); ! if (c == DateTime.NAME_OF_MONTH) sb.append(dfs.getMonths()[i]); else sb.append(dfs.getShortMonths()[i]); break; } ! case DateTime.CENTURY: // 'C' (00 - 99) ! case DateTime.YEAR_2: // 'y' (00 - 99) ! case DateTime.YEAR_4: { // 'Y' (0000 - 9999) int i = t.get(Calendar.YEAR); int size = 2; ! switch (c) { ! case DateTime.CENTURY: ! i /= 100; ! break; ! case DateTime.YEAR_2: ! i %= 100; ! break; ! case DateTime.YEAR_4: ! size = 4; ! break; } ! Flags flags = Flags.ZERO_PAD; sb.append(localizedMagnitude(null, i, flags, size, l)); break; } ! case DateTime.DAY_OF_MONTH_0: // 'd' (01 - 31) ! case DateTime.DAY_OF_MONTH: { // 'e' (1 - 31) -- like d int i = t.get(Calendar.DATE); ! Flags flags = (c == DateTime.DAY_OF_MONTH_0 ! ? Flags.ZERO_PAD ! : Flags.NONE); sb.append(localizedMagnitude(null, i, flags, 2, l)); break; } ! case DateTime.DAY_OF_YEAR: { // 'j' (001 - 366) int i = t.get(Calendar.DAY_OF_YEAR); ! Flags flags = Flags.ZERO_PAD; sb.append(localizedMagnitude(null, i, flags, 3, l)); break; } ! case DateTime.MONTH: { // 'm' (01 - 12) int i = t.get(Calendar.MONTH) + 1; ! Flags flags = Flags.ZERO_PAD; sb.append(localizedMagnitude(null, i, flags, 2, l)); break; } // Composites ! case DateTime.TIME: // 'T' (24 hour hh:mm:ss - %tH:%tM:%tS) ! case DateTime.TIME_24_HOUR: { // 'R' (hh:mm same as %H:%M) char sep = ':'; ! print(sb, t, DateTime.HOUR_OF_DAY_0, l).append(sep); ! print(sb, t, DateTime.MINUTE, l); ! if (c == DateTime.TIME) { sb.append(sep); ! print(sb, t, DateTime.SECOND, l); } break; } ! case DateTime.TIME_12_HOUR: { // 'r' (hh:mm:ss [AP]M) char sep = ':'; ! print(sb, t, DateTime.HOUR_0, l).append(sep); ! print(sb, t, DateTime.MINUTE, l).append(sep); ! print(sb, t, DateTime.SECOND, l).append(' '); // this may be in wrong place for some locales StringBuilder tsb = new StringBuilder(); ! print(tsb, t, DateTime.AM_PM, l); sb.append(toUpperCaseWithLocale(tsb.toString(), l)); break; } ! case DateTime.DATE_TIME: { // 'c' (Sat Nov 04 12:02:33 EST 1999) char sep = ' '; ! print(sb, t, DateTime.NAME_OF_DAY_ABBREV, l).append(sep); ! print(sb, t, DateTime.NAME_OF_MONTH_ABBREV, l).append(sep); ! print(sb, t, DateTime.DAY_OF_MONTH_0, l).append(sep); ! print(sb, t, DateTime.TIME, l).append(sep); ! print(sb, t, DateTime.ZONE, l).append(sep); ! print(sb, t, DateTime.YEAR_4, l); break; } ! case DateTime.DATE: { // 'D' (mm/dd/yy) char sep = '/'; ! print(sb, t, DateTime.MONTH, l).append(sep); ! print(sb, t, DateTime.DAY_OF_MONTH_0, l).append(sep); ! print(sb, t, DateTime.YEAR_2, l); break; } ! case DateTime.ISO_STANDARD_DATE: { // 'F' (%Y-%m-%d) char sep = '-'; ! print(sb, t, DateTime.YEAR_4, l).append(sep); ! print(sb, t, DateTime.MONTH, l).append(sep); ! print(sb, t, DateTime.DAY_OF_MONTH_0, l); break; } default: assert false; - } - return sb; } ! private void print(TemporalAccessor t, char c, Locale l) throws IOException { ! StringBuilder sb = new StringBuilder(); ! print(sb, t, c, l); ! // justify based on width ! if (f.contains(Flags.UPPERCASE)) { ! appendJustified(a, toUpperCaseWithLocale(sb.toString(), l)); ! } else { ! appendJustified(a, sb); ! } } ! private Appendable print(StringBuilder sb, TemporalAccessor t, char c, ! Locale l) throws IOException { ! if (sb == null) ! sb = new StringBuilder(); ! try { ! switch (c) { ! case DateTime.HOUR_OF_DAY_0: { // 'H' (00 - 23) int i = t.get(ChronoField.HOUR_OF_DAY); sb.append(localizedMagnitude(null, i, Flags.ZERO_PAD, 2, l)); break; } ! case DateTime.HOUR_OF_DAY: { // 'k' (0 - 23) -- like H int i = t.get(ChronoField.HOUR_OF_DAY); sb.append(localizedMagnitude(null, i, Flags.NONE, 2, l)); break; } ! case DateTime.HOUR_0: { // 'I' (01 - 12) int i = t.get(ChronoField.CLOCK_HOUR_OF_AMPM); sb.append(localizedMagnitude(null, i, Flags.ZERO_PAD, 2, l)); break; } ! case DateTime.HOUR: { // 'l' (1 - 12) -- like I int i = t.get(ChronoField.CLOCK_HOUR_OF_AMPM); sb.append(localizedMagnitude(null, i, Flags.NONE, 2, l)); break; } ! case DateTime.MINUTE: { // 'M' (00 - 59) int i = t.get(ChronoField.MINUTE_OF_HOUR); ! Flags flags = Flags.ZERO_PAD; sb.append(localizedMagnitude(null, i, flags, 2, l)); break; } ! case DateTime.NANOSECOND: { // 'N' (000000000 - 999999999) int i; try { i = t.get(ChronoField.NANO_OF_SECOND); } catch (UnsupportedTemporalTypeException u) { i = t.get(ChronoField.MILLI_OF_SECOND) * 1000000; } ! Flags flags = Flags.ZERO_PAD; sb.append(localizedMagnitude(null, i, flags, 9, l)); break; } ! case DateTime.MILLISECOND: { // 'L' (000 - 999) int i = t.get(ChronoField.MILLI_OF_SECOND); ! Flags flags = Flags.ZERO_PAD; sb.append(localizedMagnitude(null, i, flags, 3, l)); break; } ! case DateTime.MILLISECOND_SINCE_EPOCH: { // 'Q' (0 - 99...?) long i = t.getLong(ChronoField.INSTANT_SECONDS) * 1000L + ! t.getLong(ChronoField.MILLI_OF_SECOND); ! Flags flags = Flags.NONE; ! sb.append(localizedMagnitude(null, i, flags, width, l)); break; } ! case DateTime.AM_PM: { // 'p' (am or pm) // Calendar.AM = 0, Calendar.PM = 1, LocaleElements defines upper String[] ampm = { "AM", "PM" }; if (l != null && l != Locale.US) { DateFormatSymbols dfs = DateFormatSymbols.getInstance(l); ampm = dfs.getAmPmStrings(); --- 2655,3943 ---- * If this formatter has been closed by invoking its {@link * #close()} method * * @return This formatter */ + @IntrinsicCandidate public Formatter format(Locale l, String format, Object ... args) { + List<FormatToken> fsa = parse(format); ensureOpen(); // index of last argument referenced int last = -1; // last ordinary index int lasto = -1; ! for (FormatToken ft : fsa) { try { + int index = ft.index(); switch (index) { ! case -2: // fixed string, "%n", or "%%" ! if (ft instanceof FixedString) { ! ((FixedString) ft).print(this); ! } else { ! print((FormatSpecifier) ft, (Object) null, l); ! } ! break; ! case -1: // relative index ! if (last < 0 || (args != null && last > args.length - 1)) ! throw new MissingFormatArgumentException(ft.toString()); ! print((FormatSpecifier) ft, (args == null ? null : args[last]), l); ! break; ! case 0: // ordinary index ! lasto++; ! last = lasto; ! if (args != null && lasto > args.length - 1) ! throw new MissingFormatArgumentException(ft.toString()); ! print((FormatSpecifier) ft, (args == null ? null : args[lasto]), l); ! break; ! default: // explicit index ! last = index - 1; ! if (args != null && last > args.length - 1) ! throw new MissingFormatArgumentException(ft.toString()); ! print((FormatSpecifier) ft, (args == null ? null : args[last]), l); ! break; } } catch (IOException x) { lastException = x; } } return this; } ! private Formatter print(FormatSpecifier spec, Object arg, Locale l) throws IOException { ! switch (spec.conversion()) { ! case DECIMAL_INTEGER: ! case OCTAL_INTEGER: ! case HEXADECIMAL_INTEGER: ! printInteger(spec, arg, l); break; ! case DATE_TIME: ! printDateTime(spec, arg, l); break; ! case SCIENTIFIC: ! case GENERAL: ! case DECIMAL_FLOAT: ! case HEXADECIMAL_FLOAT: ! printFloat(spec, arg, l); break; ! case CHARACTER: ! case CHARACTER_UPPER: ! printCharacter(spec, arg, l); break; ! case BOOLEAN: ! printBoolean(spec, arg, l); break; ! case STRING: ! printString(spec, arg, l); break; ! case HASHCODE: ! printHashCode(spec, arg, l); break; ! case LINE_SEPARATOR: ! out().append(System.lineSeparator()); break; ! case PERCENT_SIGN: ! print(spec, "%", l); break; default: assert false; } + return this; + } ! private void printInteger(FormatSpecifier spec, Object arg, Locale l) throws IOException { ! if (arg == null) ! print(spec, "null", l); ! else if (arg instanceof Byte) ! print(spec, ((Byte) arg).byteValue(), l); ! else if (arg instanceof Short) ! print(spec, ((Short) arg).shortValue(), l); ! else if (arg instanceof Integer) ! print(spec, ((Integer) arg).intValue(), l); ! else if (arg instanceof Long) ! print(spec, ((Long) arg).longValue(), l); ! else if (arg instanceof BigInteger) ! print(spec, (BigInteger) arg, l); ! else ! spec.conversion().fail(arg); ! } ! private void printFloat(FormatSpecifier spec, Object arg, Locale l) throws IOException { ! if (arg == null) ! print(spec, "null", l); ! else if (arg instanceof Float) ! print(spec, ((Float) arg).floatValue(), l); ! else if (arg instanceof Double) ! print(spec, ((Double) arg).doubleValue(), l); ! else if (arg instanceof BigDecimal) ! print(spec, (BigDecimal) arg, l); ! else ! spec.conversion().fail(arg); ! } ! private void printDateTime(FormatSpecifier spec, Object arg, Locale l) throws IOException { ! if (arg == null) { ! print(spec, "null", l); ! return; } + Calendar cal = null; ! // Instead of Calendar.setLenient(true), perhaps we should ! // wrap the IllegalArgumentException that might be thrown? ! if (arg instanceof Long) { ! // Note that the following method uses an instance of the ! // default time zone (TimeZone.getDefaultRef(). ! cal = Calendar.getInstance(l == null ? Locale.US : l); ! cal.setTimeInMillis((Long)arg); ! } else if (arg instanceof Date) { ! // Note that the following method uses an instance of the ! // default time zone (TimeZone.getDefaultRef(). ! cal = Calendar.getInstance(l == null ? Locale.US : l); ! cal.setTime((Date)arg); ! } else if (arg instanceof Calendar) { ! cal = (Calendar) ((Calendar) arg).clone(); ! cal.setLenient(true); ! } else if (arg instanceof TemporalAccessor) { ! print(spec, (TemporalAccessor) arg, spec.dateTime(), l); ! return; ! } else { ! spec.conversion().fail(arg); } + // Use the provided locale so that invocations of + // localizedMagnitude() use optimizations for null. + print(spec, cal, spec.dateTime(), l); + } ! private void printCharacter(FormatSpecifier spec, Object arg, Locale l) throws IOException { ! if (arg == null) { ! print(spec, "null", l); ! return; ! } ! String s = null; ! if (arg instanceof Character) { ! s = ((Character)arg).toString(); ! } else if (arg instanceof Byte) { ! byte i = (Byte) arg; ! if (Character.isValidCodePoint(i)) ! s = new String(Character.toChars(i)); else ! throw new IllegalFormatCodePointException(i); ! } else if (arg instanceof Short) { ! short i = (Short) arg; ! if (Character.isValidCodePoint(i)) ! s = new String(Character.toChars(i)); ! else ! throw new IllegalFormatCodePointException(i); ! } else if (arg instanceof Integer) { ! int i = (Integer) arg; ! if (Character.isValidCodePoint(i)) ! s = new String(Character.toChars(i)); ! else ! throw new IllegalFormatCodePointException(i); ! } else { ! spec.conversion().fail(arg); } + print(spec, s, l); + } ! private void printString(FormatSpecifier spec, Object arg, Locale l) throws IOException { ! if (arg instanceof Formattable) { ! Formatter fmt = this; ! if (fmt.locale() != l) ! fmt = new Formatter(fmt.out(), l); ! ((Formattable)arg).formatTo(fmt, spec.flags(), spec.width(), spec.precision()); ! } else { ! if (Flags.contains(spec.flags(), Flags.ALTERNATE)) ! failMismatch(Flags.ALTERNATE, 's'); ! if (arg == null) ! print(spec, "null", l); ! else ! print(spec, arg.toString(), l); } + } ! private void printBoolean(FormatSpecifier spec, Object arg, Locale l) throws IOException { ! String s; ! if (arg != null) ! s = ((arg instanceof Boolean) ! ? ((Boolean)arg).toString() ! : Boolean.toString(true)); ! else ! s = Boolean.toString(false); ! print(spec, s, l); ! } ! private Formatter printHashCode(FormatSpecifier spec, Object arg, Locale l) throws IOException { ! String s = (arg == null ! ? "null" ! : Integer.toHexString(arg.hashCode())); ! print(spec, s, l); ! return this; ! } ! private Formatter print(FormatSpecifier spec, String s, Locale l) throws IOException { ! if (spec.precision() != -1 && spec.precision() < s.length()) ! s = s.substring(0, spec.precision()); ! if (Flags.contains(spec.flags(), Flags.UPPERCASE)) ! s = toUpperCaseWithLocale(s, l); ! appendJustified(spec, a, s); ! return this; ! } ! private String toUpperCaseWithLocale(String s, Locale l) { ! return s.toUpperCase(Objects.requireNonNullElse(l, ! Locale.getDefault(Locale.Category.FORMAT))); ! } ! private Appendable appendJustified(FormatSpecifier spec, Appendable a, CharSequence cs) throws IOException { ! if (spec.width() == -1) { ! return a.append(cs); ! } ! boolean padRight = Flags.contains(spec.flags(), Flags.LEFT_JUSTIFY); ! int sp = spec.width() - cs.length(); ! if (padRight) { ! a.append(cs); } ! for (int i = 0; i < sp; i++) { ! a.append(' '); } + if (!padRight) { + a.append(cs); + } + return a; + } ! private Formatter print(FormatSpecifier spec, byte value, Locale l) throws IOException { ! long v = value; ! if (value < 0 ! && (spec.conversion() == Conversion.OCTAL_INTEGER ! || spec.conversion() == Conversion.HEXADECIMAL_INTEGER)) { ! v += (1L << 8); ! assert v >= 0 : v; } + return print(spec, v, l); + } ! private Formatter print(FormatSpecifier spec, short value, Locale l) throws IOException { ! long v = value; ! if (value < 0 ! && (spec.conversion() == Conversion.OCTAL_INTEGER ! || spec.conversion() == Conversion.HEXADECIMAL_INTEGER)) { ! v += (1L << 16); ! assert v >= 0 : v; } + return print(spec, v, l); + } ! private Formatter print(FormatSpecifier spec, int value, Locale l) throws IOException { ! long v = value; ! if (value < 0 ! && (spec.conversion() == Conversion.OCTAL_INTEGER ! || spec.conversion() == Conversion.HEXADECIMAL_INTEGER)) { ! v += (1L << 32); ! assert v >= 0 : v; } + return print(spec, v, l); + } ! private Formatter print(FormatSpecifier spec, long value, Locale l) throws IOException { ! StringBuilder sb = new StringBuilder(); ! if (spec.conversion() == Conversion.DECIMAL_INTEGER) { ! boolean neg = value < 0; ! String valueStr = Long.toString(value, 10); ! // leading sign indicator ! leadingSign(spec, sb, neg); ! // the value ! localizedMagnitude(sb, valueStr, neg ? 1 : 0, spec.flags(), adjustWidth(spec.width(), spec.flags(), neg), l); ! // trailing sign indicator ! trailingSign(spec, sb, neg); ! } else if (spec.conversion() == Conversion.OCTAL_INTEGER) { ! spec.checkBadFlags(Flags.PARENTHESES, Flags.LEADING_SPACE, Flags.PLUS); ! String s = Long.toOctalString(value); ! int len = (Flags.contains(spec.flags(), Flags.ALTERNATE) ! ? s.length() + 1 ! : s.length()); ! // apply ALTERNATE (radix indicator for octal) before ZERO_PAD ! if (Flags.contains(spec.flags(), Flags.ALTERNATE)) ! sb.append('0'); ! if (Flags.contains(spec.flags(), Flags.ZERO_PAD)) { ! trailingZeros(sb, spec.width() - len); } ! sb.append(s); ! } else if (spec.conversion() == Conversion.HEXADECIMAL_INTEGER) { ! spec.checkBadFlags(Flags.PARENTHESES, Flags.LEADING_SPACE, ! Flags.PLUS); ! String s = Long.toHexString(value); ! int len = (Flags.contains(spec.flags(), Flags.ALTERNATE) ! ? s.length() + 2 ! : s.length()); ! ! // apply ALTERNATE (radix indicator for hex) before ZERO_PAD ! if (Flags.contains(spec.flags(), Flags.ALTERNATE)) ! sb.append(Flags.contains(spec.flags(), Flags.UPPERCASE) ? "0X" : "0x"); ! if (Flags.contains(spec.flags(), Flags.ZERO_PAD)) { ! trailingZeros(sb, spec.width() - len); ! } ! if (Flags.contains(spec.flags(), Flags.UPPERCASE)) ! s = toUpperCaseWithLocale(s, l); ! sb.append(s); } ! // justify based on width ! appendJustified(spec, a, sb); ! return this; ! } ! ! // neg := val < 0 ! private void leadingSign(FormatSpecifier spec, StringBuilder sb, boolean neg) { ! if (!neg) { ! if (Flags.contains(spec.flags(), Flags.PLUS)) { ! sb.append('+'); ! } else if (Flags.contains(spec.flags(), Flags.LEADING_SPACE)) { ! sb.append(' '); } ! } else { ! if (Flags.contains(spec.flags(), Flags.PARENTHESES)) ! sb.append('('); ! else ! sb.append('-'); } + } + + // neg := val < 0 + private void trailingSign(FormatSpecifier spec, StringBuilder sb, boolean neg) { + if (neg && Flags.contains(spec.flags(), Flags.PARENTHESES)) + sb.append(')'); + } ! private void print(FormatSpecifier spec, BigInteger value, Locale l) throws IOException { ! StringBuilder sb = new StringBuilder(); ! boolean neg = value.signum() == -1; ! BigInteger v = value.abs(); ! ! // leading sign indicator ! leadingSign(spec, sb, neg); ! ! // the value ! if (spec.conversion() == Conversion.DECIMAL_INTEGER) { ! localizedMagnitude(sb, v.toString(), 0, spec.flags(), adjustWidth(spec.width(), spec.flags(), neg), l); ! } else if (spec.conversion() == Conversion.OCTAL_INTEGER) { ! String s = v.toString(8); ! ! int len = s.length() + sb.length(); ! if (neg && Flags.contains(spec.flags(), Flags.PARENTHESES)) ! len++; ! ! // apply ALTERNATE (radix indicator for octal) before ZERO_PAD ! if (Flags.contains(spec.flags(), Flags.ALTERNATE)) { ! len++; ! sb.append('0'); ! } ! if (Flags.contains(spec.flags(), Flags.ZERO_PAD)) { ! trailingZeros(sb, spec.width() - len); ! } ! sb.append(s); ! } else if (spec.conversion() == Conversion.HEXADECIMAL_INTEGER) { ! String s = v.toString(16); ! ! int len = s.length() + sb.length(); ! if (neg && Flags.contains(spec.flags(), Flags.PARENTHESES)) ! len++; ! ! // apply ALTERNATE (radix indicator for hex) before ZERO_PAD ! if (Flags.contains(spec.flags(), Flags.ALTERNATE)) { ! len += 2; ! sb.append(Flags.contains(spec.flags(), Flags.UPPERCASE) ? "0X" : "0x"); } ! if (Flags.contains(spec.flags(), Flags.ZERO_PAD)) { ! trailingZeros(sb, spec.width() - len); ! } ! if (Flags.contains(spec.flags(), Flags.UPPERCASE)) ! s = toUpperCaseWithLocale(s, l); ! sb.append(s); } ! // trailing sign indicator ! trailingSign(spec, sb, (value.signum() == -1)); ! // justify based on width ! appendJustified(spec, a, sb); ! } ! private Formatter print(FormatSpecifier spec, float value, Locale l) throws IOException { ! return print(spec, (double) value, l); ! } ! private Formatter print(FormatSpecifier spec, double value, Locale l) throws IOException { ! StringBuilder sb = new StringBuilder(); ! boolean neg = Double.compare(value, 0.0) == -1; ! if (!Double.isNaN(value)) { ! double v = Math.abs(value); // leading sign indicator ! leadingSign(spec, sb, neg); // the value ! if (!Double.isInfinite(v)) ! print(spec, sb, v, l, spec.flags(), spec.conversion(), spec.precision(), neg); ! else ! sb.append(Flags.contains(spec.flags(), Flags.UPPERCASE) ! ? "INFINITY" : "Infinity"); // trailing sign indicator ! trailingSign(spec, sb, neg); ! } else { ! sb.append(Flags.contains(spec.flags(), Flags.UPPERCASE) ? "NAN" : "NaN"); } ! // justify based on width ! appendJustified(spec, a, sb); ! return this; ! } ! // !Double.isInfinite(value) && !Double.isNaN(value) ! private void print(FormatSpecifier spec, StringBuilder sb, double value, Locale l, ! int f, Conversion conversion, int precision, boolean neg) throws IOException ! { ! if (conversion == Conversion.SCIENTIFIC) { ! // Create a new FormattedFloatingDecimal with the desired ! // precision. ! int prec = (precision == -1 ? 6 : precision); ! FormattedFloatingDecimal fd ! = FormattedFloatingDecimal.valueOf(value, prec, ! FormattedFloatingDecimal.Form.SCIENTIFIC); ! StringBuilder mant = new StringBuilder().append(fd.getMantissa()); ! addZeros(mant, prec); ! // If the precision is zero and the '#' flag is set, add the ! // requested decimal point. ! if (Flags.contains(f, Flags.ALTERNATE) && (prec == 0)) { ! mant.append('.'); ! } ! char[] exp = (value == 0.0) ? new char[] {'+','0','0'} : fd.getExponent(); ! int width = spec.width(); ! if (width != -1) { ! width = adjustWidth(width - exp.length - 1, f, neg); ! } ! localizedMagnitude(sb, mant, 0, f, width, l); ! sb.append(Flags.contains(f, Flags.UPPERCASE) ? 'E' : 'e'); ! char sign = exp[0]; ! assert(sign == '+' || sign == '-'); ! sb.append(sign); ! localizedMagnitudeExp(spec, sb, exp, 1, l); ! } else if (conversion == Conversion.DECIMAL_FLOAT) { ! // Create a new FormattedFloatingDecimal with the desired ! // precision. ! int prec = (precision == -1 ? 6 : precision); ! FormattedFloatingDecimal fd ! = FormattedFloatingDecimal.valueOf(value, prec, ! FormattedFloatingDecimal.Form.DECIMAL_FLOAT); ! StringBuilder mant = new StringBuilder().append(fd.getMantissa()); ! addZeros(mant, prec); ! // If the precision is zero and the '#' flag is set, add the ! // requested decimal point. ! if (Flags.contains(f, Flags.ALTERNATE) && (prec == 0)) ! mant.append('.'); ! int width = spec.width(); ! if (width != -1) ! width = adjustWidth(width, f, neg); ! localizedMagnitude(sb, mant, 0, f, width, l); ! } else if (conversion == Conversion.GENERAL) { ! int prec = precision; ! if (precision == -1) ! prec = 6; ! else if (precision == 0) ! prec = 1; ! ! char[] exp; ! StringBuilder mant = new StringBuilder(); ! int expRounded; ! if (value == 0.0) { ! exp = null; ! mant.append('0'); ! expRounded = 0; ! } else { ! FormattedFloatingDecimal fd = FormattedFloatingDecimal.valueOf(value, prec, ! FormattedFloatingDecimal.Form.GENERAL); ! exp = fd.getExponent(); ! mant.append(fd.getMantissa()); ! expRounded = fd.getExponentRounded(); ! } ! if (exp != null) { ! prec -= 1; ! } else { ! prec -= expRounded + 1; ! } ! addZeros(mant, prec); ! // If the precision is zero and the '#' flag is set, add the ! // requested decimal point. ! if (Flags.contains(f, Flags.ALTERNATE) && (prec == 0)) { ! mant.append('.'); ! } ! int width = spec.width(); ! if (width != -1) { ! if (exp != null) ! width = adjustWidth(width - exp.length - 1, f, neg); ! else ! width = adjustWidth(width, f, neg); ! } ! localizedMagnitude(sb, mant, 0, f, width, l); ! if (exp != null) { ! sb.append(Flags.contains(f, Flags.UPPERCASE) ? 'E' : 'e'); ! char sign = exp[0]; ! assert(sign == '+' || sign == '-'); ! sb.append(sign); ! localizedMagnitudeExp(spec, sb, exp, 1, l); } ! } else if (conversion == Conversion.HEXADECIMAL_FLOAT) { ! int prec = precision; ! if (precision == -1) ! // assume that we want all of the digits ! prec = 0; ! else if (precision == 0) ! prec = 1; ! ! String s = hexDouble(value, prec); ! ! StringBuilder va = new StringBuilder(); ! boolean upper = Flags.contains(f, Flags.UPPERCASE); ! sb.append(upper ? "0X" : "0x"); ! ! if (Flags.contains(f, Flags.ZERO_PAD)) { ! trailingZeros(sb, spec.width() - s.length() - 2); ! } ! ! int idx = s.indexOf('p'); ! if (upper) { ! String tmp = s.substring(0, idx); ! // don't localize hex ! tmp = tmp.toUpperCase(Locale.ROOT); ! va.append(tmp); ! } else { ! va.append(s, 0, idx); } ! if (prec != 0) { ! addZeros(va, prec); } + sb.append(va); + sb.append(upper ? 'P' : 'p'); + sb.append(s, idx+1, s.length()); + } + } ! // Add zeros to the requested precision. ! private void addZeros(StringBuilder sb, int prec) { ! // Look for the dot. If we don't find one, the we'll need to add ! // it before we add the zeros. ! int len = sb.length(); ! int i; ! for (i = 0; i < len; i++) { ! if (sb.charAt(i) == '.') { ! break; } + } + boolean needDot = i == len; ! // Determine existing precision. ! int outPrec = len - i - (needDot ? 0 : 1); ! assert (outPrec <= prec); ! if (outPrec == prec) { ! return; ! } ! // Add dot if previously determined to be necessary. ! if (needDot) { ! sb.append('.'); } ! // Add zeros. ! trailingZeros(sb, prec - outPrec); ! } ! ! // Method assumes that d > 0. ! private String hexDouble(double d, int prec) { ! // Let Double.toHexString handle simple cases ! if (!Double.isFinite(d) || d == 0.0 || prec == 0 || prec >= 13) { ! // remove "0x" ! return Double.toHexString(d).substring(2); ! } else { ! assert(prec >= 1 && prec <= 12); ! int exponent = Math.getExponent(d); ! boolean subnormal = (exponent == Double.MIN_EXPONENT - 1); ! // If this is subnormal input so normalize (could be faster to ! // do as integer operation). ! if (subnormal) { ! d *= SCALEUP; ! // Calculate the exponent. This is not just exponent + 54 ! // since the former is not the normalized exponent. ! exponent = Math.getExponent(d); ! assert exponent >= Double.MIN_EXPONENT && exponent <= Double.MAX_EXPONENT: exponent; ! } ! int precision = 1 + prec*4; ! int shiftDistance = DoubleConsts.SIGNIFICAND_WIDTH - precision; ! assert(shiftDistance >= 1 && shiftDistance < DoubleConsts.SIGNIFICAND_WIDTH); ! long doppel = Double.doubleToLongBits(d); ! // Deterime the number of bits to keep. ! long newSignif = (doppel & (DoubleConsts.EXP_BIT_MASK ! | DoubleConsts.SIGNIF_BIT_MASK)) ! >> shiftDistance; ! // Bits to round away. ! long roundingBits = doppel & ~(~0L << shiftDistance); ! ! // To decide how to round, look at the low-order bit of the ! // working significand, the highest order discarded bit (the ! // round bit) and whether any of the lower order discarded bits ! // are nonzero (the sticky bit). ! boolean leastZero = (newSignif & 0x1L) == 0L; ! boolean round = ((1L << (shiftDistance - 1) ) & roundingBits) != 0L; ! boolean sticky = shiftDistance > 1 && (~(1L<< (shiftDistance - 1)) & roundingBits) != 0; ! if((leastZero && round && sticky) || (!leastZero && round)) { ! newSignif++; ! } ! long signBit = doppel & DoubleConsts.SIGN_BIT_MASK; ! newSignif = signBit | (newSignif << shiftDistance); ! double result = Double.longBitsToDouble(newSignif); ! if (Double.isInfinite(result) ) { ! // Infinite result generated by rounding ! return "1.0p1024"; ! } else { ! String res = Double.toHexString(result).substring(2); ! if (!subnormal) ! return res; ! else { ! // Create a normalized subnormal string. ! int idx = res.indexOf('p'); ! if (idx == -1) { ! // No 'p' character in hex string. ! assert false; ! return null; ! } else { ! // Get exponent and append at the end. ! String exp = res.substring(idx + 1); ! int iexp = Integer.parseInt(exp) -54; ! return res.substring(0, idx) + "p" + Integer.toString(iexp); } } } } + } ! private void print(FormatSpecifier spec, BigDecimal value, Locale l) throws IOException { ! if (spec.conversion() == Conversion.HEXADECIMAL_FLOAT) ! spec.conversion().fail(value); ! StringBuilder sb = new StringBuilder(); ! boolean neg = value.signum() == -1; ! BigDecimal v = value.abs(); ! // leading sign indicator ! leadingSign(spec, sb, neg); ! // the value ! print(sb, v, l, spec.flags(), spec.conversion(), spec.width(), spec.precision(), neg); ! // trailing sign indicator ! trailingSign(spec, sb, neg); ! // justify based on width ! appendJustified(spec, a, sb); ! } ! // value > 0 ! private void print(StringBuilder sb, BigDecimal value, Locale l, ! int f, Conversion conversion, int width, int precision, boolean neg) throws IOException ! { ! if (conversion == Conversion.SCIENTIFIC) { ! // Create a new BigDecimal with the desired precision. ! int prec = (precision == -1 ? 6 : precision); ! int scale = value.scale(); ! int origPrec = value.precision(); ! int nzeros = 0; ! int compPrec; ! ! if (prec > origPrec - 1) { ! compPrec = origPrec; ! nzeros = prec - (origPrec - 1); ! } else { ! compPrec = prec + 1; ! } ! MathContext mc = new MathContext(compPrec); ! BigDecimal v = new BigDecimal(value.unscaledValue(), scale, mc); ! BigDecimalLayout bdl = new BigDecimalLayout(v.unscaledValue(), v.scale(), ! BigDecimalLayoutForm.SCIENTIFIC); ! StringBuilder mant = bdl.mantissa(); ! // Add a decimal point if necessary. The mantissa may not ! // contain a decimal point if the scale is zero (the internal ! // representation has no fractional part) or the original ! // precision is one. Append a decimal point if '#' is set or if ! // we require zero padding to get to the requested precision. ! if ((origPrec == 1 || !bdl.hasDot()) ! && (nzeros > 0 || (Flags.contains(f, Flags.ALTERNATE)))) { ! mant.append('.'); ! } ! // Add trailing zeros in the case precision is greater than ! // the number of available digits after the decimal separator. ! trailingZeros(mant, nzeros); ! StringBuilder exp = bdl.exponent(); ! int newW = width; ! if (newW != -1) { ! newW = adjustWidth(newW - exp.length() - 1, f, neg); ! } ! localizedMagnitude(sb, mant, 0, f, newW, l); ! sb.append(Flags.contains(f, Flags.UPPERCASE) ? 'E' : 'e'); ! int flags = Flags.remove(f, Flags.GROUP); ! char sign = exp.charAt(0); ! assert(sign == '+' || sign == '-'); ! sb.append(sign); ! ! sb.append(localizedMagnitude(null, exp, 1, flags, -1, l)); ! } else if (conversion == Conversion.DECIMAL_FLOAT) { ! // Create a new BigDecimal with the desired precision. ! int prec = (precision == -1 ? 6 : precision); ! int scale = value.scale(); ! ! if (scale > prec) { ! // more "scale" digits than the requested "precision" ! int compPrec = value.precision(); ! if (compPrec <= scale) { ! // case of 0.xxxxxx ! value = value.setScale(prec, RoundingMode.HALF_UP); ! } else { ! compPrec -= (scale - prec); ! value = new BigDecimal(value.unscaledValue(), ! scale, ! new MathContext(compPrec)); ! } ! } ! BigDecimalLayout bdl = new BigDecimalLayout( ! value.unscaledValue(), ! value.scale(), ! BigDecimalLayoutForm.DECIMAL_FLOAT); ! ! StringBuilder mant = bdl.mantissa(); ! int nzeros = (bdl.scale() < prec ? prec - bdl.scale() : 0); ! ! // Add a decimal point if necessary. The mantissa may not ! // contain a decimal point if the scale is zero (the internal ! // representation has no fractional part). Append a decimal ! // point if '#' is set or we require zero padding to get to the ! // requested precision. ! if (bdl.scale() == 0 && (Flags.contains(f, Flags.ALTERNATE) ! || nzeros > 0)) { ! mant.append('.'); ! } ! ! // Add trailing zeros if the precision is greater than the ! // number of available digits after the decimal separator. ! trailingZeros(mant, nzeros); ! ! localizedMagnitude(sb, mant, 0, f, adjustWidth(width, f, neg), l); ! } else if (conversion == Conversion.GENERAL) { ! int prec = precision; ! if (precision == -1) ! prec = 6; ! else if (precision == 0) ! prec = 1; ! ! BigDecimal tenToTheNegFour = BigDecimal.valueOf(1, 4); ! BigDecimal tenToThePrec = BigDecimal.valueOf(1, -prec); ! if ((value.equals(BigDecimal.ZERO)) || ((value.compareTo(tenToTheNegFour) != -1) ! && (value.compareTo(tenToThePrec) == -1))) { ! int e = - value.scale() + (value.unscaledValue().toString().length() - 1); ! // xxx.yyy ! // g precision (# sig digits) = #x + #y ! // f precision = #y ! // exponent = #x - 1 ! // => f precision = g precision - exponent - 1 ! // 0.000zzz ! // g precision (# sig digits) = #z ! // f precision = #0 (after '.') + #z ! // exponent = - #0 (after '.') - 1 ! // => f precision = g precision - exponent - 1 ! prec = prec - e - 1; ! print(sb, value, l, f, Conversion.DECIMAL_FLOAT, width, prec, neg); ! } else { ! print(sb, value, l, f, Conversion.SCIENTIFIC, width, prec - 1, neg); } + } else if (conversion == Conversion.HEXADECIMAL_FLOAT) { + // This conversion isn't supported. The error should be + // reported earlier. + assert false; + } + } + + private class BigDecimalLayout { + private StringBuilder mant; + private StringBuilder exp; + private boolean dot = false; + private int scale; + + public BigDecimalLayout(BigInteger intVal, int scale, BigDecimalLayoutForm form) { + layout(intVal, scale, form); } ! public boolean hasDot() { ! return dot; ! } ! ! public int scale() { ! return scale; ! } ! ! public StringBuilder mantissa() { ! return mant; ! } ! ! // The exponent will be formatted as a sign ('+' or '-') followed ! // by the exponent zero-padded to include at least two digits. ! public StringBuilder exponent() { ! return exp; ! } ! ! private void layout(BigInteger intVal, int scale, BigDecimalLayoutForm form) { ! String coeff = intVal.toString(); ! this.scale = scale; ! ! // Construct a buffer, with sufficient capacity for all cases. ! // If E-notation is needed, length will be: +1 if negative, +1 ! // if '.' needed, +2 for "E+", + up to 10 for adjusted ! // exponent. Otherwise it could have +1 if negative, plus ! // leading "0.00000" ! int len = coeff.length(); ! mant = new StringBuilder(len + 14); ! ! if (scale == 0) { ! if (len > 1) { ! mant.append(coeff.charAt(0)); ! if (form == BigDecimalLayoutForm.SCIENTIFIC) { ! mant.append('.'); ! dot = true; ! mant.append(coeff, 1, len); ! exp = new StringBuilder("+"); ! if (len < 10) { ! exp.append('0').append(len - 1); } else { ! exp.append(len - 1); } } else { ! mant.append(coeff, 1, len); } ! } else { ! mant.append(coeff); ! if (form == BigDecimalLayoutForm.SCIENTIFIC) { ! exp = new StringBuilder("+00"); } + } + } else if (form == BigDecimalLayoutForm.DECIMAL_FLOAT) { + // count of padding zeros + + if (scale >= len) { + // 0.xxx form + mant.append("0."); + dot = true; + trailingZeros(mant, scale - len); + mant.append(coeff); } else { ! if (scale > 0) { ! // xx.xx form ! int pad = len - scale; ! mant.append(coeff, 0, pad); mant.append('.'); dot = true; ! mant.append(coeff, pad, len); ! } else { // scale < 0 ! // xx form ! mant.append(coeff, 0, len); ! if (intVal.signum() != 0) { ! trailingZeros(mant, -scale); } ! this.scale = 0; } } + } else { + // x.xxx form + mant.append(coeff.charAt(0)); + if (len > 1) { + mant.append('.'); + dot = true; + mant.append(coeff, 1, len); + } + exp = new StringBuilder(); + long adjusted = -(long) scale + (len - 1); + if (adjusted != 0) { + long abs = Math.abs(adjusted); + // require sign + exp.append(adjusted < 0 ? '-' : '+'); + if (abs < 10) { + exp.append('0'); + } + exp.append(abs); + } else { + exp.append("+00"); + } } } + } ! private int adjustWidth(int width, int f, boolean neg) { ! int newW = width; ! if (newW != -1 && neg && Flags.contains(f, Flags.PARENTHESES)) ! newW--; ! return newW; ! } ! // Add trailing zeros ! private void trailingZeros(StringBuilder sb, int nzeros) { ! for (int i = 0; i < nzeros; i++) { ! sb.append('0'); } + } ! private void print(FormatSpecifier spec, Calendar t, DateTime dt, Locale l) throws IOException { ! StringBuilder sb = new StringBuilder(); ! print(spec, sb, t, dt, l); ! ! // justify based on width ! if (Flags.contains(spec.flags(), Flags.UPPERCASE)) { ! appendJustified(spec, a, toUpperCaseWithLocale(sb.toString(), l)); ! } else { ! appendJustified(spec, a, sb); } + } ! private Appendable print(FormatSpecifier spec, StringBuilder sb, Calendar t, DateTime dt, Locale l) ! throws IOException { ! if (sb == null) ! sb = new StringBuilder(); ! switch (dt) { ! case HOUR_OF_DAY_0: // 'H' (00 - 23) ! case HOUR_0: // 'I' (01 - 12) ! case HOUR_OF_DAY: // 'k' (0 - 23) -- like H ! case HOUR: { // 'l' (1 - 12) -- like I int i = t.get(Calendar.HOUR_OF_DAY); ! if (dt == DateTime.HOUR_0 || dt == DateTime.HOUR) i = (i == 0 || i == 12 ? 12 : i % 12); ! int flags = (dt == DateTime.HOUR_OF_DAY_0 ! || dt == DateTime.HOUR_0 ! ? Flags.ZERO_PAD ! : Flags.NONE); sb.append(localizedMagnitude(null, i, flags, 2, l)); break; } ! case MINUTE: { // 'M' (00 - 59) int i = t.get(Calendar.MINUTE); ! int flags = Flags.ZERO_PAD; sb.append(localizedMagnitude(null, i, flags, 2, l)); break; } ! case NANOSECOND: { // 'N' (000000000 - 999999999) int i = t.get(Calendar.MILLISECOND) * 1000000; ! int flags = Flags.ZERO_PAD; sb.append(localizedMagnitude(null, i, flags, 9, l)); break; } ! case MILLISECOND: { // 'L' (000 - 999) int i = t.get(Calendar.MILLISECOND); ! int flags = Flags.ZERO_PAD; sb.append(localizedMagnitude(null, i, flags, 3, l)); break; } ! case MILLISECOND_SINCE_EPOCH: { // 'Q' (0 - 99...?) long i = t.getTimeInMillis(); ! int flags = Flags.NONE; ! sb.append(localizedMagnitude(null, i, flags, spec.width(), l)); break; } ! case AM_PM: { // 'p' (am or pm) // Calendar.AM = 0, Calendar.PM = 1, LocaleElements defines upper String[] ampm = { "AM", "PM" }; if (l != null && l != Locale.US) { DateFormatSymbols dfs = DateFormatSymbols.getInstance(l); ampm = dfs.getAmPmStrings(); } String s = ampm[t.get(Calendar.AM_PM)]; sb.append(s.toLowerCase(Objects.requireNonNullElse(l, ! Locale.getDefault(Locale.Category.FORMAT)))); break; } ! case SECONDS_SINCE_EPOCH: { // 's' (0 - 99...?) long i = t.getTimeInMillis() / 1000; ! int flags = Flags.NONE; ! sb.append(localizedMagnitude(null, i, flags, spec.width(), l)); break; } ! case SECOND: { // 'S' (00 - 60 - leap second) int i = t.get(Calendar.SECOND); ! int flags = Flags.ZERO_PAD; sb.append(localizedMagnitude(null, i, flags, 2, l)); break; } ! case ZONE_NUMERIC: { // 'z' ({-|+}####) - ls minus? int i = t.get(Calendar.ZONE_OFFSET) + t.get(Calendar.DST_OFFSET); boolean neg = i < 0; sb.append(neg ? '-' : '+'); if (neg) i = -i; int min = i / 60000; // combine minute and hour into a single integer int offset = (min / 60) * 100 + (min % 60); ! int flags = Flags.ZERO_PAD; sb.append(localizedMagnitude(null, offset, flags, 4, l)); break; } ! case ZONE: { // 'Z' (symbol) TimeZone tz = t.getTimeZone(); sb.append(tz.getDisplayName((t.get(Calendar.DST_OFFSET) != 0), ! TimeZone.SHORT, ! Objects.requireNonNullElse(l, Locale.US))); break; } // Date ! case NAME_OF_DAY_ABBREV: // 'a' ! case NAME_OF_DAY: { // 'A' int i = t.get(Calendar.DAY_OF_WEEK); Locale lt = Objects.requireNonNullElse(l, Locale.US); DateFormatSymbols dfs = DateFormatSymbols.getInstance(lt); ! if (dt == DateTime.NAME_OF_DAY) sb.append(dfs.getWeekdays()[i]); else sb.append(dfs.getShortWeekdays()[i]); break; } ! case NAME_OF_MONTH_ABBREV: // 'b' ! case NAME_OF_MONTH_ABBREV_X: // 'h' -- same b ! case NAME_OF_MONTH: { // 'B' int i = t.get(Calendar.MONTH); Locale lt = Objects.requireNonNullElse(l, Locale.US); DateFormatSymbols dfs = DateFormatSymbols.getInstance(lt); ! if (dt == DateTime.NAME_OF_MONTH) sb.append(dfs.getMonths()[i]); else sb.append(dfs.getShortMonths()[i]); break; } ! case CENTURY: // 'C' (00 - 99) ! case YEAR_2: // 'y' (00 - 99) ! case YEAR_4: { // 'Y' (0000 - 9999) int i = t.get(Calendar.YEAR); int size = 2; ! switch (dt) { ! case CENTURY: ! i /= 100; ! break; ! case YEAR_2: ! i %= 100; ! break; ! case YEAR_4: ! size = 4; ! break; } ! int flags = Flags.ZERO_PAD; sb.append(localizedMagnitude(null, i, flags, size, l)); break; } ! case DAY_OF_MONTH_0: // 'd' (01 - 31) ! case DAY_OF_MONTH: { // 'e' (1 - 31) -- like d int i = t.get(Calendar.DATE); ! int flags = (dt == DateTime.DAY_OF_MONTH_0 ! ? Flags.ZERO_PAD ! : Flags.NONE); sb.append(localizedMagnitude(null, i, flags, 2, l)); break; } ! case DAY_OF_YEAR: { // 'j' (001 - 366) int i = t.get(Calendar.DAY_OF_YEAR); ! int flags = Flags.ZERO_PAD; sb.append(localizedMagnitude(null, i, flags, 3, l)); break; } ! case MONTH: { // 'm' (01 - 12) int i = t.get(Calendar.MONTH) + 1; ! int flags = Flags.ZERO_PAD; sb.append(localizedMagnitude(null, i, flags, 2, l)); break; } // Composites ! case TIME: // 'T' (24 hour hh:mm:ss - %tH:%tM:%tS) ! case TIME_24_HOUR: { // 'R' (hh:mm same as %H:%M) char sep = ':'; ! print(spec, sb, t, DateTime.HOUR_OF_DAY_0, l).append(sep); ! print(spec, sb, t, DateTime.MINUTE, l); ! if (dt == DateTime.TIME) { sb.append(sep); ! print(spec, sb, t, DateTime.SECOND, l); } break; } ! case TIME_12_HOUR: { // 'r' (hh:mm:ss [AP]M) char sep = ':'; ! print(spec, sb, t, DateTime.HOUR_0, l).append(sep); ! print(spec, sb, t, DateTime.MINUTE, l).append(sep); ! print(spec, sb, t, DateTime.SECOND, l).append(' '); // this may be in wrong place for some locales StringBuilder tsb = new StringBuilder(); ! print(spec, tsb, t, DateTime.AM_PM, l); sb.append(toUpperCaseWithLocale(tsb.toString(), l)); break; } ! case DATE_TIME: { // 'c' (Sat Nov 04 12:02:33 EST 1999) char sep = ' '; ! print(spec, sb, t, DateTime.NAME_OF_DAY_ABBREV, l).append(sep); ! print(spec, sb, t, DateTime.NAME_OF_MONTH_ABBREV, l).append(sep); ! print(spec, sb, t, DateTime.DAY_OF_MONTH_0, l).append(sep); ! print(spec, sb, t, DateTime.TIME, l).append(sep); ! print(spec, sb, t, DateTime.ZONE, l).append(sep); ! print(spec, sb, t, DateTime.YEAR_4, l); break; } ! case DATE: { // 'D' (mm/dd/yy) char sep = '/'; ! print(spec, sb, t, DateTime.MONTH, l).append(sep); ! print(spec, sb, t, DateTime.DAY_OF_MONTH_0, l).append(sep); ! print(spec, sb, t, DateTime.YEAR_2, l); break; } ! case ISO_STANDARD_DATE: { // 'F' (%Y-%m-%d) char sep = '-'; ! print(spec, sb, t, DateTime.YEAR_4, l).append(sep); ! print(spec, sb, t, DateTime.MONTH, l).append(sep); ! print(spec, sb, t, DateTime.DAY_OF_MONTH_0, l); break; } default: assert false; } + return sb; + } ! private void print(FormatSpecifier spec, TemporalAccessor t, DateTime dt, Locale l) ! throws IOException { ! StringBuilder sb = new StringBuilder(); ! print(spec, sb, t, dt, l); ! // justify based on width ! if (Flags.contains(spec.flags(), Flags.UPPERCASE)) { ! appendJustified(spec, a, toUpperCaseWithLocale(sb.toString(), l)); ! } else { ! appendJustified(spec, a, sb); } + } ! private Appendable print(FormatSpecifier spec, StringBuilder sb, TemporalAccessor t, ! DateTime dt, Locale l) throws IOException { ! if (sb == null) ! sb = new StringBuilder(); ! try { ! switch (dt) { ! case HOUR_OF_DAY_0: { // 'H' (00 - 23) int i = t.get(ChronoField.HOUR_OF_DAY); sb.append(localizedMagnitude(null, i, Flags.ZERO_PAD, 2, l)); break; } ! case HOUR_OF_DAY: { // 'k' (0 - 23) -- like H int i = t.get(ChronoField.HOUR_OF_DAY); sb.append(localizedMagnitude(null, i, Flags.NONE, 2, l)); break; } ! case HOUR_0: { // 'I' (01 - 12) int i = t.get(ChronoField.CLOCK_HOUR_OF_AMPM); sb.append(localizedMagnitude(null, i, Flags.ZERO_PAD, 2, l)); break; } ! case HOUR: { // 'l' (1 - 12) -- like I int i = t.get(ChronoField.CLOCK_HOUR_OF_AMPM); sb.append(localizedMagnitude(null, i, Flags.NONE, 2, l)); break; } ! case MINUTE: { // 'M' (00 - 59) int i = t.get(ChronoField.MINUTE_OF_HOUR); ! int flags = Flags.ZERO_PAD; sb.append(localizedMagnitude(null, i, flags, 2, l)); break; } ! case NANOSECOND: { // 'N' (000000000 - 999999999) int i; try { i = t.get(ChronoField.NANO_OF_SECOND); } catch (UnsupportedTemporalTypeException u) { i = t.get(ChronoField.MILLI_OF_SECOND) * 1000000; } ! int flags = Flags.ZERO_PAD; sb.append(localizedMagnitude(null, i, flags, 9, l)); break; } ! case MILLISECOND: { // 'L' (000 - 999) int i = t.get(ChronoField.MILLI_OF_SECOND); ! int flags = Flags.ZERO_PAD; sb.append(localizedMagnitude(null, i, flags, 3, l)); break; } ! case MILLISECOND_SINCE_EPOCH: { // 'Q' (0 - 99...?) long i = t.getLong(ChronoField.INSTANT_SECONDS) * 1000L + ! t.getLong(ChronoField.MILLI_OF_SECOND); ! int flags = Flags.NONE; ! sb.append(localizedMagnitude(null, i, flags, spec.width(), l)); break; } ! case AM_PM: { // 'p' (am or pm) // Calendar.AM = 0, Calendar.PM = 1, LocaleElements defines upper String[] ampm = { "AM", "PM" }; if (l != null && l != Locale.US) { DateFormatSymbols dfs = DateFormatSymbols.getInstance(l); ampm = dfs.getAmPmStrings();
*** 4249,4860 **** String s = ampm[t.get(ChronoField.AMPM_OF_DAY)]; sb.append(s.toLowerCase(Objects.requireNonNullElse(l, Locale.getDefault(Locale.Category.FORMAT)))); break; } ! case DateTime.SECONDS_SINCE_EPOCH: { // 's' (0 - 99...?) long i = t.getLong(ChronoField.INSTANT_SECONDS); ! Flags flags = Flags.NONE; ! sb.append(localizedMagnitude(null, i, flags, width, l)); break; } ! case DateTime.SECOND: { // 'S' (00 - 60 - leap second) int i = t.get(ChronoField.SECOND_OF_MINUTE); ! Flags flags = Flags.ZERO_PAD; sb.append(localizedMagnitude(null, i, flags, 2, l)); break; } ! case DateTime.ZONE_NUMERIC: { // 'z' ({-|+}####) - ls minus? int i = t.get(ChronoField.OFFSET_SECONDS); boolean neg = i < 0; sb.append(neg ? '-' : '+'); if (neg) i = -i; int min = i / 60; // combine minute and hour into a single integer int offset = (min / 60) * 100 + (min % 60); ! Flags flags = Flags.ZERO_PAD; sb.append(localizedMagnitude(null, offset, flags, 4, l)); break; } ! case DateTime.ZONE: { // 'Z' (symbol) ZoneId zid = t.query(TemporalQueries.zone()); if (zid == null) { ! throw new IllegalFormatConversionException(c, t.getClass()); } if (!(zid instanceof ZoneOffset) && ! t.isSupported(ChronoField.INSTANT_SECONDS)) { Instant instant = Instant.from(t); sb.append(TimeZone.getTimeZone(zid.getId()) ! .getDisplayName(zid.getRules().isDaylightSavings(instant), ! TimeZone.SHORT, ! Objects.requireNonNullElse(l, Locale.US))); break; } sb.append(zid.getId()); break; } // Date ! case DateTime.NAME_OF_DAY_ABBREV: // 'a' ! case DateTime.NAME_OF_DAY: { // 'A' int i = t.get(ChronoField.DAY_OF_WEEK) % 7 + 1; Locale lt = Objects.requireNonNullElse(l, Locale.US); DateFormatSymbols dfs = DateFormatSymbols.getInstance(lt); ! if (c == DateTime.NAME_OF_DAY) sb.append(dfs.getWeekdays()[i]); else sb.append(dfs.getShortWeekdays()[i]); break; } ! case DateTime.NAME_OF_MONTH_ABBREV: // 'b' ! case DateTime.NAME_OF_MONTH_ABBREV_X: // 'h' -- same b ! case DateTime.NAME_OF_MONTH: { // 'B' int i = t.get(ChronoField.MONTH_OF_YEAR) - 1; Locale lt = Objects.requireNonNullElse(l, Locale.US); DateFormatSymbols dfs = DateFormatSymbols.getInstance(lt); ! if (c == DateTime.NAME_OF_MONTH) sb.append(dfs.getMonths()[i]); else sb.append(dfs.getShortMonths()[i]); break; } ! case DateTime.CENTURY: // 'C' (00 - 99) ! case DateTime.YEAR_2: // 'y' (00 - 99) ! case DateTime.YEAR_4: { // 'Y' (0000 - 9999) int i = t.get(ChronoField.YEAR_OF_ERA); int size = 2; ! switch (c) { ! case DateTime.CENTURY: ! i /= 100; ! break; ! case DateTime.YEAR_2: ! i %= 100; ! break; ! case DateTime.YEAR_4: ! size = 4; ! break; } ! Flags flags = Flags.ZERO_PAD; sb.append(localizedMagnitude(null, i, flags, size, l)); break; } ! case DateTime.DAY_OF_MONTH_0: // 'd' (01 - 31) ! case DateTime.DAY_OF_MONTH: { // 'e' (1 - 31) -- like d int i = t.get(ChronoField.DAY_OF_MONTH); ! Flags flags = (c == DateTime.DAY_OF_MONTH_0 ! ? Flags.ZERO_PAD ! : Flags.NONE); sb.append(localizedMagnitude(null, i, flags, 2, l)); break; } ! case DateTime.DAY_OF_YEAR: { // 'j' (001 - 366) int i = t.get(ChronoField.DAY_OF_YEAR); ! Flags flags = Flags.ZERO_PAD; sb.append(localizedMagnitude(null, i, flags, 3, l)); break; } ! case DateTime.MONTH: { // 'm' (01 - 12) int i = t.get(ChronoField.MONTH_OF_YEAR); ! Flags flags = Flags.ZERO_PAD; sb.append(localizedMagnitude(null, i, flags, 2, l)); break; } // Composites ! case DateTime.TIME: // 'T' (24 hour hh:mm:ss - %tH:%tM:%tS) ! case DateTime.TIME_24_HOUR: { // 'R' (hh:mm same as %H:%M) char sep = ':'; ! print(sb, t, DateTime.HOUR_OF_DAY_0, l).append(sep); ! print(sb, t, DateTime.MINUTE, l); ! if (c == DateTime.TIME) { sb.append(sep); ! print(sb, t, DateTime.SECOND, l); } break; } ! case DateTime.TIME_12_HOUR: { // 'r' (hh:mm:ss [AP]M) char sep = ':'; ! print(sb, t, DateTime.HOUR_0, l).append(sep); ! print(sb, t, DateTime.MINUTE, l).append(sep); ! print(sb, t, DateTime.SECOND, l).append(' '); // this may be in wrong place for some locales StringBuilder tsb = new StringBuilder(); ! print(tsb, t, DateTime.AM_PM, l); sb.append(toUpperCaseWithLocale(tsb.toString(), l)); break; } ! case DateTime.DATE_TIME: { // 'c' (Sat Nov 04 12:02:33 EST 1999) char sep = ' '; ! print(sb, t, DateTime.NAME_OF_DAY_ABBREV, l).append(sep); ! print(sb, t, DateTime.NAME_OF_MONTH_ABBREV, l).append(sep); ! print(sb, t, DateTime.DAY_OF_MONTH_0, l).append(sep); ! print(sb, t, DateTime.TIME, l).append(sep); ! print(sb, t, DateTime.ZONE, l).append(sep); ! print(sb, t, DateTime.YEAR_4, l); break; } ! case DateTime.DATE: { // 'D' (mm/dd/yy) char sep = '/'; ! print(sb, t, DateTime.MONTH, l).append(sep); ! print(sb, t, DateTime.DAY_OF_MONTH_0, l).append(sep); ! print(sb, t, DateTime.YEAR_2, l); break; } ! case DateTime.ISO_STANDARD_DATE: { // 'F' (%Y-%m-%d) char sep = '-'; ! print(sb, t, DateTime.YEAR_4, l).append(sep); ! print(sb, t, DateTime.MONTH, l).append(sep); ! print(sb, t, DateTime.DAY_OF_MONTH_0, l); break; } default: assert false; - } - } catch (DateTimeException x) { - throw new IllegalFormatConversionException(c, t.getClass()); } ! return sb; } ! // -- Methods to support throwing exceptions -- ! private void failMismatch(Flags f, char c) { ! String fs = f.toString(); ! throw new FormatFlagsConversionMismatchException(fs, c); } ! private void failConversion(char c, Object arg) { ! throw new IllegalFormatConversionException(c, arg.getClass()); } ! private char getZero(Locale l) { ! if ((l != null) && !l.equals(locale())) { DecimalFormatSymbols dfs = DecimalFormatSymbols.getInstance(l); ! return dfs.getZeroDigit(); } - return zero; } ! private StringBuilder localizedMagnitude(StringBuilder sb, ! long value, Flags f, int width, Locale l) { ! return localizedMagnitude(sb, Long.toString(value, 10), 0, f, width, l); } ! private StringBuilder localizedMagnitude(StringBuilder sb, ! CharSequence value, final int offset, Flags f, int width, ! Locale l) { ! if (sb == null) { ! sb = new StringBuilder(); } - int begin = sb.length(); ! char zero = getZero(l); ! // determine localized grouping separator and size ! char grpSep = '\0'; ! int grpSize = -1; ! char decSep = '\0'; ! int len = value.length(); ! int dot = len; ! for (int j = offset; j < len; j++) { ! if (value.charAt(j) == '.') { ! dot = j; ! break; } } ! if (dot < len) { ! if (l == null || l.equals(Locale.US)) { ! decSep = '.'; ! } else { ! DecimalFormatSymbols dfs = DecimalFormatSymbols.getInstance(l); ! decSep = dfs.getDecimalSeparator(); ! } } ! if (f.contains(Flags.GROUP)) { ! if (l == null || l.equals(Locale.US)) { ! grpSep = ','; ! grpSize = 3; ! } else { ! DecimalFormatSymbols dfs = DecimalFormatSymbols.getInstance(l); ! grpSep = dfs.getGroupingSeparator(); ! DecimalFormat df = null; ! NumberFormat nf = NumberFormat.getNumberInstance(l); ! if (nf instanceof DecimalFormat) { ! df = (DecimalFormat) nf; ! } else { ! // Use DecimalFormat constructor to obtain the instance, ! // in case NumberFormat.getNumberInstance(l) ! // returns instance other than DecimalFormat ! LocaleProviderAdapter adapter = LocaleProviderAdapter ! .getAdapter(NumberFormatProvider.class, l); ! if (!(adapter instanceof ResourceBundleBasedAdapter)) { ! adapter = LocaleProviderAdapter.getResourceBundleBased(); ! } ! String[] all = adapter.getLocaleResources(l) ! .getNumberPatterns(); ! df = new DecimalFormat(all[0], dfs); ! } ! grpSize = df.getGroupingSize(); ! // Some locales do not use grouping (the number ! // pattern for these locales does not contain group, e.g. ! // ("#0.###")), but specify a grouping separator. ! // To avoid unnecessary identification of the position of ! // grouping separator, reset its value with null character ! if (!df.isGroupingUsed() || grpSize == 0) { ! grpSep = '\0'; ! } ! } } ! // localize the digits inserting group separators as necessary ! for (int j = offset; j < len; j++) { ! if (j == dot) { ! sb.append(decSep); ! // no more group separators after the decimal separator ! grpSep = '\0'; ! continue; } ! char c = value.charAt(j); ! sb.append((char) ((c - '0') + zero)); ! if (grpSep != '\0' && j != dot - 1 && ((dot - j) % grpSize == 1)) { ! sb.append(grpSep); } } ! // apply zero padding ! if (width != -1 && f.contains(Flags.ZERO_PAD)) { ! for (int k = sb.length(); k < width; k++) { ! sb.insert(begin, zero); } } ! return sb; } ! // Specialized localization of exponents, where the source value can only ! // contain characters '0' through '9', starting at index offset, and no ! // group separators is added for any locale. ! private void localizedMagnitudeExp(StringBuilder sb, char[] value, ! final int offset, Locale l) { ! char zero = getZero(l); - int len = value.length; - for (int j = offset; j < len; j++) { - char c = value[j]; - sb.append((char) ((c - '0') + zero)); } } - } ! private static class Flags { ! private int flags; ! static final Flags NONE = new Flags(0); // '' ! // duplicate declarations from Formattable.java ! static final Flags LEFT_JUSTIFY = new Flags(1<<0); // '-' ! static final Flags UPPERCASE = new Flags(1<<1); // '^' ! static final Flags ALTERNATE = new Flags(1<<2); // '#' ! // numerics ! static final Flags PLUS = new Flags(1<<3); // '+' ! static final Flags LEADING_SPACE = new Flags(1<<4); // ' ' ! static final Flags ZERO_PAD = new Flags(1<<5); // '0' ! static final Flags GROUP = new Flags(1<<6); // ',' ! static final Flags PARENTHESES = new Flags(1<<7); // '(' ! // indexing ! static final Flags PREVIOUS = new Flags(1<<8); // '<' ! private Flags(int f) { ! flags = f; } ! public int valueOf() { ! return flags; } ! public boolean contains(Flags f) { ! return (flags & f.valueOf()) == f.valueOf(); } ! public Flags dup() { ! return new Flags(flags); } ! private Flags add(Flags f) { ! flags |= f.valueOf(); ! return this; } ! public Flags remove(Flags f) { ! flags &= ~f.valueOf(); ! return this; } ! public static Flags parse(String s, int start, int end) { ! Flags f = new Flags(0); for (int i = start; i < end; i++) { char c = s.charAt(i); ! Flags v = parse(c); ! if (f.contains(v)) ! throw new DuplicateFormatFlagsException(v.toString()); ! f.add(v); } return f; } // parse those flags which may be provided by users ! private static Flags parse(char c) { switch (c) { ! case '-': return LEFT_JUSTIFY; ! case '#': return ALTERNATE; ! case '+': return PLUS; ! case ' ': return LEADING_SPACE; ! case '0': return ZERO_PAD; ! case ',': return GROUP; ! case '(': return PARENTHESES; ! case '<': return PREVIOUS; ! default: ! throw new UnknownFormatFlagsException(String.valueOf(c)); } } ! // Returns a string representation of the current {@code Flags}. ! public static String toString(Flags f) { ! return f.toString(); ! } ! ! public String toString() { StringBuilder sb = new StringBuilder(); ! if (contains(LEFT_JUSTIFY)) sb.append('-'); ! if (contains(UPPERCASE)) sb.append('^'); ! if (contains(ALTERNATE)) sb.append('#'); ! if (contains(PLUS)) sb.append('+'); ! if (contains(LEADING_SPACE)) sb.append(' '); ! if (contains(ZERO_PAD)) sb.append('0'); ! if (contains(GROUP)) sb.append(','); ! if (contains(PARENTHESES)) sb.append('('); ! if (contains(PREVIOUS)) sb.append('<'); return sb.toString(); } } ! private static class Conversion { // Byte, Short, Integer, Long, BigInteger // (and associated primitives due to autoboxing) ! static final char DECIMAL_INTEGER = 'd'; ! static final char OCTAL_INTEGER = 'o'; ! static final char HEXADECIMAL_INTEGER = 'x'; ! static final char HEXADECIMAL_INTEGER_UPPER = 'X'; // Float, Double, BigDecimal // (and associated primitives due to autoboxing) ! static final char SCIENTIFIC = 'e'; ! static final char SCIENTIFIC_UPPER = 'E'; ! static final char GENERAL = 'g'; ! static final char GENERAL_UPPER = 'G'; ! static final char DECIMAL_FLOAT = 'f'; ! static final char HEXADECIMAL_FLOAT = 'a'; ! static final char HEXADECIMAL_FLOAT_UPPER = 'A'; // Character, Byte, Short, Integer // (and associated primitives due to autoboxing) ! static final char CHARACTER = 'c'; ! static final char CHARACTER_UPPER = 'C'; // java.util.Date, java.util.Calendar, long ! static final char DATE_TIME = 't'; ! static final char DATE_TIME_UPPER = 'T'; // if (arg.TYPE != boolean) return boolean // if (arg != null) return true; else return false; ! static final char BOOLEAN = 'b'; ! static final char BOOLEAN_UPPER = 'B'; // if (arg instanceof Formattable) arg.formatTo() // else arg.toString(); ! static final char STRING = 's'; ! static final char STRING_UPPER = 'S'; // arg.hashCode() ! static final char HASHCODE = 'h'; ! static final char HASHCODE_UPPER = 'H'; ! static final char LINE_SEPARATOR = 'n'; ! static final char PERCENT_SIGN = '%'; ! static boolean isValid(char c) { ! return (isGeneral(c) || isInteger(c) || isFloat(c) || isText(c) ! || c == 't' || isCharacter(c)); } ! // Returns true iff the Conversion is applicable to all objects. ! static boolean isGeneral(char c) { switch (c) { ! case BOOLEAN: ! case BOOLEAN_UPPER: ! case STRING: ! case STRING_UPPER: ! case HASHCODE: ! case HASHCODE_UPPER: ! return true; ! default: ! return false; } } ! // Returns true iff the Conversion is applicable to character. ! static boolean isCharacter(char c) { switch (c) { ! case CHARACTER: ! case CHARACTER_UPPER: ! return true; ! default: return false; } } ! // Returns true iff the Conversion is an integer type. ! static boolean isInteger(char c) { ! switch (c) { case DECIMAL_INTEGER: ! case OCTAL_INTEGER: case HEXADECIMAL_INTEGER: ! case HEXADECIMAL_INTEGER_UPPER: ! return true; default: ! return false; ! } } ! // Returns true iff the Conversion is a floating-point type. ! static boolean isFloat(char c) { ! switch (c) { ! case SCIENTIFIC: ! case SCIENTIFIC_UPPER: ! case GENERAL: ! case GENERAL_UPPER: ! case DECIMAL_FLOAT: ! case HEXADECIMAL_FLOAT: ! case HEXADECIMAL_FLOAT_UPPER: ! return true; ! default: ! return false; } } ! // Returns true iff the Conversion does not require an argument ! static boolean isText(char c) { ! switch (c) { case LINE_SEPARATOR: case PERCENT_SIGN: - return true; - default: return false; ! } } } ! private static class DateTime { ! static final char HOUR_OF_DAY_0 = 'H'; // (00 - 23) ! static final char HOUR_0 = 'I'; // (01 - 12) ! static final char HOUR_OF_DAY = 'k'; // (0 - 23) -- like H ! static final char HOUR = 'l'; // (1 - 12) -- like I ! static final char MINUTE = 'M'; // (00 - 59) ! static final char NANOSECOND = 'N'; // (000000000 - 999999999) ! static final char MILLISECOND = 'L'; // jdk, not in gnu (000 - 999) ! static final char MILLISECOND_SINCE_EPOCH = 'Q'; // (0 - 99...?) ! static final char AM_PM = 'p'; // (am or pm) ! static final char SECONDS_SINCE_EPOCH = 's'; // (0 - 99...?) ! static final char SECOND = 'S'; // (00 - 60 - leap second) ! static final char TIME = 'T'; // (24 hour hh:mm:ss) ! static final char ZONE_NUMERIC = 'z'; // (-1200 - +1200) - ls minus? ! static final char ZONE = 'Z'; // (symbol) ! // Date ! static final char NAME_OF_DAY_ABBREV = 'a'; // 'a' ! static final char NAME_OF_DAY = 'A'; // 'A' ! static final char NAME_OF_MONTH_ABBREV = 'b'; // 'b' ! static final char NAME_OF_MONTH = 'B'; // 'B' ! static final char CENTURY = 'C'; // (00 - 99) ! static final char DAY_OF_MONTH_0 = 'd'; // (01 - 31) ! static final char DAY_OF_MONTH = 'e'; // (1 - 31) -- like d ! // * static final char ISO_WEEK_OF_YEAR_2 = 'g'; // cross %y %V ! // * static final char ISO_WEEK_OF_YEAR_4 = 'G'; // cross %Y %V ! static final char NAME_OF_MONTH_ABBREV_X = 'h'; // -- same b ! static final char DAY_OF_YEAR = 'j'; // (001 - 366) ! static final char MONTH = 'm'; // (01 - 12) ! // * static final char DAY_OF_WEEK_1 = 'u'; // (1 - 7) Monday ! // * static final char WEEK_OF_YEAR_SUNDAY = 'U'; // (0 - 53) Sunday+ ! // * static final char WEEK_OF_YEAR_MONDAY_01 = 'V'; // (01 - 53) Monday+ ! // * static final char DAY_OF_WEEK_0 = 'w'; // (0 - 6) Sunday ! // * static final char WEEK_OF_YEAR_MONDAY = 'W'; // (00 - 53) Monday ! static final char YEAR_2 = 'y'; // (00 - 99) ! static final char YEAR_4 = 'Y'; // (0000 - 9999) ! // Composites ! static final char TIME_12_HOUR = 'r'; // (hh:mm:ss [AP]M) ! static final char TIME_24_HOUR = 'R'; // (hh:mm same as %H:%M) ! // * static final char LOCALE_TIME = 'X'; // (%H:%M:%S) - parse format? ! static final char DATE_TIME = 'c'; ! // (Sat Nov 04 12:02:33 EST 1999) ! static final char DATE = 'D'; // (mm/dd/yy) ! static final char ISO_STANDARD_DATE = 'F'; // (%Y-%m-%d) ! // * static final char LOCALE_DATE = 'x'; // (mm/dd/yy) ! static boolean isValid(char c) { ! switch (c) { ! case HOUR_OF_DAY_0: ! case HOUR_0: ! case HOUR_OF_DAY: ! case HOUR: ! case MINUTE: ! case NANOSECOND: ! case MILLISECOND: ! case MILLISECOND_SINCE_EPOCH: ! case AM_PM: ! case SECONDS_SINCE_EPOCH: ! case SECOND: ! case TIME: ! case ZONE_NUMERIC: ! case ZONE: ! // Date ! case NAME_OF_DAY_ABBREV: ! case NAME_OF_DAY: ! case NAME_OF_MONTH_ABBREV: ! case NAME_OF_MONTH: ! case CENTURY: ! case DAY_OF_MONTH_0: ! case DAY_OF_MONTH: ! // * case ISO_WEEK_OF_YEAR_2: ! // * case ISO_WEEK_OF_YEAR_4: ! case NAME_OF_MONTH_ABBREV_X: ! case DAY_OF_YEAR: ! case MONTH: ! // * case DAY_OF_WEEK_1: ! // * case WEEK_OF_YEAR_SUNDAY: ! // * case WEEK_OF_YEAR_MONDAY_01: ! // * case DAY_OF_WEEK_0: ! // * case WEEK_OF_YEAR_MONDAY: ! case YEAR_2: ! case YEAR_4: ! // Composites ! case TIME_12_HOUR: ! case TIME_24_HOUR: ! // * case LOCALE_TIME: ! case DATE_TIME: ! case DATE: ! case ISO_STANDARD_DATE: ! // * case LOCALE_DATE: ! return true; ! default: ! return false; ! } } } } --- 3945,5632 ---- String s = ampm[t.get(ChronoField.AMPM_OF_DAY)]; sb.append(s.toLowerCase(Objects.requireNonNullElse(l, Locale.getDefault(Locale.Category.FORMAT)))); break; } ! case SECONDS_SINCE_EPOCH: { // 's' (0 - 99...?) long i = t.getLong(ChronoField.INSTANT_SECONDS); ! int flags = Flags.NONE; ! sb.append(localizedMagnitude(null, i, flags, spec.width(), l)); break; } ! case SECOND: { // 'S' (00 - 60 - leap second) int i = t.get(ChronoField.SECOND_OF_MINUTE); ! int flags = Flags.ZERO_PAD; sb.append(localizedMagnitude(null, i, flags, 2, l)); break; } ! case ZONE_NUMERIC: { // 'z' ({-|+}####) - ls minus? int i = t.get(ChronoField.OFFSET_SECONDS); boolean neg = i < 0; sb.append(neg ? '-' : '+'); if (neg) i = -i; int min = i / 60; // combine minute and hour into a single integer int offset = (min / 60) * 100 + (min % 60); ! int flags = Flags.ZERO_PAD; sb.append(localizedMagnitude(null, offset, flags, 4, l)); break; } ! case ZONE: { // 'Z' (symbol) ZoneId zid = t.query(TemporalQueries.zone()); if (zid == null) { ! dt.fail(t); } if (!(zid instanceof ZoneOffset) && ! t.isSupported(ChronoField.INSTANT_SECONDS)) { Instant instant = Instant.from(t); sb.append(TimeZone.getTimeZone(zid.getId()) ! .getDisplayName(zid.getRules().isDaylightSavings(instant), ! TimeZone.SHORT, ! Objects.requireNonNullElse(l, Locale.US))); break; } sb.append(zid.getId()); break; } // Date ! case NAME_OF_DAY_ABBREV: // 'a' ! case NAME_OF_DAY: { // 'A' int i = t.get(ChronoField.DAY_OF_WEEK) % 7 + 1; Locale lt = Objects.requireNonNullElse(l, Locale.US); DateFormatSymbols dfs = DateFormatSymbols.getInstance(lt); ! if (dt == DateTime.NAME_OF_DAY) sb.append(dfs.getWeekdays()[i]); else sb.append(dfs.getShortWeekdays()[i]); break; } ! case NAME_OF_MONTH_ABBREV: // 'b' ! case NAME_OF_MONTH_ABBREV_X: // 'h' -- same b ! case NAME_OF_MONTH: { // 'B' int i = t.get(ChronoField.MONTH_OF_YEAR) - 1; Locale lt = Objects.requireNonNullElse(l, Locale.US); DateFormatSymbols dfs = DateFormatSymbols.getInstance(lt); ! if (dt == DateTime.NAME_OF_MONTH) sb.append(dfs.getMonths()[i]); else sb.append(dfs.getShortMonths()[i]); break; } ! case CENTURY: // 'C' (00 - 99) ! case YEAR_2: // 'y' (00 - 99) ! case YEAR_4: { // 'Y' (0000 - 9999) int i = t.get(ChronoField.YEAR_OF_ERA); int size = 2; ! switch (dt) { ! case CENTURY: ! i /= 100; ! break; ! case YEAR_2: ! i %= 100; ! break; ! case YEAR_4: ! size = 4; ! break; } ! int flags = Flags.ZERO_PAD; sb.append(localizedMagnitude(null, i, flags, size, l)); break; } ! case DAY_OF_MONTH_0: // 'd' (01 - 31) ! case DAY_OF_MONTH: { // 'e' (1 - 31) -- like d int i = t.get(ChronoField.DAY_OF_MONTH); ! int flags = (dt == DateTime.DAY_OF_MONTH_0 ! ? Flags.ZERO_PAD ! : Flags.NONE); sb.append(localizedMagnitude(null, i, flags, 2, l)); break; } ! case DAY_OF_YEAR: { // 'j' (001 - 366) int i = t.get(ChronoField.DAY_OF_YEAR); ! int flags = Flags.ZERO_PAD; sb.append(localizedMagnitude(null, i, flags, 3, l)); break; } ! case MONTH: { // 'm' (01 - 12) int i = t.get(ChronoField.MONTH_OF_YEAR); ! int flags = Flags.ZERO_PAD; sb.append(localizedMagnitude(null, i, flags, 2, l)); break; } // Composites ! case TIME: // 'T' (24 hour hh:mm:ss - %tH:%tM:%tS) ! case TIME_24_HOUR: { // 'R' (hh:mm same as %H:%M) char sep = ':'; ! print(spec, sb, t, DateTime.HOUR_OF_DAY_0, l).append(sep); ! print(spec, sb, t, DateTime.MINUTE, l); ! if (dt == DateTime.TIME) { sb.append(sep); ! print(spec, sb, t, DateTime.SECOND, l); } break; } ! case TIME_12_HOUR: { // 'r' (hh:mm:ss [AP]M) char sep = ':'; ! print(spec, sb, t, DateTime.HOUR_0, l).append(sep); ! print(spec, sb, t, DateTime.MINUTE, l).append(sep); ! print(spec, sb, t, DateTime.SECOND, l).append(' '); // this may be in wrong place for some locales StringBuilder tsb = new StringBuilder(); ! print(spec, tsb, t, DateTime.AM_PM, l); sb.append(toUpperCaseWithLocale(tsb.toString(), l)); break; } ! case DATE_TIME: { // 'c' (Sat Nov 04 12:02:33 EST 1999) char sep = ' '; ! print(spec, sb, t, DateTime.NAME_OF_DAY_ABBREV, l).append(sep); ! print(spec, sb, t, DateTime.NAME_OF_MONTH_ABBREV, l).append(sep); ! print(spec, sb, t, DateTime.DAY_OF_MONTH_0, l).append(sep); ! print(spec, sb, t, DateTime.TIME, l).append(sep); ! print(spec, sb, t, DateTime.ZONE, l).append(sep); ! print(spec, sb, t, DateTime.YEAR_4, l); break; } ! case DATE: { // 'D' (mm/dd/yy) char sep = '/'; ! print(spec, sb, t, DateTime.MONTH, l).append(sep); ! print(spec, sb, t, DateTime.DAY_OF_MONTH_0, l).append(sep); ! print(spec, sb, t, DateTime.YEAR_2, l); break; } ! case ISO_STANDARD_DATE: { // 'F' (%Y-%m-%d) char sep = '-'; ! print(spec, sb, t, DateTime.YEAR_4, l).append(sep); ! print(spec, sb, t, DateTime.MONTH, l).append(sep); ! print(spec, sb, t, DateTime.DAY_OF_MONTH_0, l); break; } default: assert false; } ! } catch (DateTimeException x) { ! spec.conversion().fail(t); } + return sb; + } ! // -- Methods to support throwing exceptions -- ! private void failMismatch(int flags, char c) { ! String fs = Flags.toString(flags); ! throw new FormatFlagsConversionMismatchException(fs, c); ! } ! ! private static char getZero(Formatter fmt, Locale l) { ! if (l != null && !l.equals(fmt.locale())) { ! DecimalFormatSymbols dfs = DecimalFormatSymbols.getInstance(l); ! return dfs.getZeroDigit(); } + return fmt.zero; + } ! private StringBuilder localizedMagnitude(StringBuilder sb, long value, int flags, int width, Locale l) { ! return localizedMagnitude(sb, Long.toString(value, 10), 0, flags, width, l); ! } ! ! StringBuilder localizedMagnitude(StringBuilder sb, ! CharSequence value, final int offset, int flags, ! int width, Locale l) { ! if (sb == null) { ! sb = new StringBuilder(); ! } ! int begin = sb.length(); ! ! char zero = getZero(this, l); ! ! // determine localized grouping separator and size ! char grpSep = '\0'; ! int grpSize = -1; ! char decSep = '\0'; ! ! int len = value.length(); ! int dot = len; ! for (int j = offset; j < len; j++) { ! if (value.charAt(j) == '.') { ! dot = j; ! break; ! } } ! if (dot < len) { ! if (l == null || l.equals(Locale.US)) { ! decSep = '.'; ! } else { DecimalFormatSymbols dfs = DecimalFormatSymbols.getInstance(l); ! decSep = dfs.getDecimalSeparator(); } } ! if (Flags.contains(flags, Flags.GROUP)) { ! if (l == null || l.equals(Locale.US)) { ! grpSep = ','; ! grpSize = 3; ! } else { ! DecimalFormatSymbols dfs = DecimalFormatSymbols.getInstance(l); ! grpSep = dfs.getGroupingSeparator(); ! DecimalFormat df = null; ! NumberFormat nf = NumberFormat.getNumberInstance(l); ! if (nf instanceof DecimalFormat) { ! df = (DecimalFormat) nf; ! } else { ! ! // Use DecimalFormat constructor to obtain the instance, ! // in case NumberFormat.getNumberInstance(l) ! // returns instance other than DecimalFormat ! LocaleProviderAdapter adapter = LocaleProviderAdapter ! .getAdapter(NumberFormatProvider.class, l); ! if (!(adapter instanceof ResourceBundleBasedAdapter)) { ! adapter = LocaleProviderAdapter.getResourceBundleBased(); ! } ! String[] all = adapter.getLocaleResources(l) ! .getNumberPatterns(); ! df = new DecimalFormat(all[0], dfs); ! } ! grpSize = df.getGroupingSize(); ! // Some locales do not use grouping (the number ! // pattern for these locales does not contain group, e.g. ! // ("#0.###")), but specify a grouping separator. ! // To avoid unnecessary identification of the position of ! // grouping separator, reset its value with null character ! if (!df.isGroupingUsed() || grpSize == 0) { ! grpSep = '\0'; ! } ! } } ! // localize the digits inserting group separators as necessary ! for (int j = offset; j < len; j++) { ! if (j == dot) { ! sb.append(decSep); ! // no more group separators after the decimal separator ! grpSep = '\0'; ! continue; } ! char c = value.charAt(j); ! sb.append((char) ((c - '0') + zero)); ! if (grpSep != '\0' && j != dot - 1 && ((dot - j) % grpSize == 1)) { ! sb.append(grpSep); ! } ! } ! // apply zero padding ! if (width != -1 && Flags.contains(flags, Flags.ZERO_PAD)) { ! for (int k = sb.length(); k < width; k++) { ! sb.insert(begin, zero); ! } ! } ! return sb; ! } ! ! // Specialized localization of exponents, where the source value can only ! // contain characters '0' through '9', starting at index offset, and no ! // group separators is added for any locale. ! private void localizedMagnitudeExp(FormatSpecifier spec, StringBuilder sb, char[] value, ! final int offset, Locale l) { ! char zero = getZero(this, l); ! ! int len = value.length; ! for (int j = offset; j < len; j++) { ! char c = value[j]; ! sb.append((char) ((c - '0') + zero)); ! } ! } ! ! ! /** ! * Enum for {@code BigDecimal} formatting. ! */ ! public enum BigDecimalLayoutForm { ! /** ! * Format the {@code BigDecimal} in computerized scientific notation. ! */ ! SCIENTIFIC, ! ! /** ! * Format the {@code BigDecimal} as a decimal number. ! */ ! DECIMAL_FLOAT ! }; ! ! // refactoring to provide additional tools to the Formatter BSM ! // %[argument_index$][flags][width][.precision][t]conversion ! private static final String formatSpecifier ! = "%(\\d+\\$)?([-#+ 0,(\\<]*)?(\\d+)?(\\.\\d+)?([tT])?([a-zA-Z%])"; ! ! private static Pattern fsPattern = Pattern.compile(formatSpecifier); ! ! /** ! * Creates a parsed format list. ! * @param format to parse ! */ ! private static List<FormatToken> parse(String format) { ! ArrayList<FormatToken> al = new ArrayList<>(); ! Matcher m = fsPattern.matcher(format); ! for (int i = 0, len = format.length(); i < len; ) { ! if (m.find(i)) { ! // Anything between the start of the string and the beginning ! // of the format specifier is either fixed text or contains ! // an invalid format string. ! if (m.start() != i) { ! // Make sure we didn't miss any invalid format specifiers ! checkText(format, i, m.start()); ! // Assume previous characters were fixed text ! al.add(new FixedString(format, i, m.start())); } + + al.add(new FormatSpecifier(format, m)); + i = m.end(); + } else { + // No more valid format specifiers. Check for possible invalid + // format specifiers. + checkText(format, i, len); + // The rest of the string is fixed text + al.add(new FixedString(format, i, format.length())); + break; } + } + return al; + } ! private static void checkText(String s, int start, int end) { ! for (int i = start; i < end; i++) { ! // Any '%' found in the region starts an invalid format specifier. ! if (s.charAt(i) == '%') { ! char c = (i == end - 1) ? '%' : s.charAt(i + 1); ! throw new UnknownFormatConversionException(String.valueOf(c)); } + } + } ! /** ! * Interface for Formatter specifiers. ! */ ! private interface FormatToken { ! /** ! * Return the specifier index. ! * @return the index ! */ ! int index(); ! ! } ! ! private static class FixedString implements FormatToken { ! private String s; ! private int start; ! private int end; ! ! FixedString(String s, int start, int end) { ! this.s = s; ! this.start = start; ! this.end = end; ! } ! ! public int index() { ! return -2; ! } ! ! public Formatter print(Formatter formatter) throws IOException { ! formatter.out().append(s, start, end); ! return formatter; ! } ! ! public String toString() { ! return s.substring(start, end); ! } ! } ! ! private static class FormatSpecifier implements FormatToken { ! private int index = -1; ! private int f = Flags.NONE; ! private int width; ! private int precision; ! private Conversion conversion; ! private DateTime dateTime; ! ! FormatSpecifier(String s, Matcher m) { ! index(s, m.start(1), m.end(1)); ! flags(s, m.start(2), m.end(2)); ! width(s, m.start(3), m.end(3)); ! precision(s, m.start(4), m.end(4)); ! ! int tTStart = m.start(5); ! if (tTStart >= 0) { ! conversion(s.charAt(tTStart)); ! dateTime = DateTime.lookup(s.charAt(m.start(6))); ! } else { ! conversion(s.charAt(m.start(6))); } + checkConversion(); + } ! private void index(String s, int start, int end) { ! if (start >= 0) { ! try { ! // skip the trailing '$' ! index = Integer.parseInt(s, start, end - 1, 10); ! } catch (NumberFormatException x) { ! assert (false); } + } else { + index = 0; + } + } + + private void flags(String s, int start, int end) { + f = Flags.parse(s, start, end); + if (Flags.contains(f, Flags.PREVIOUS)) + index = -1; + } ! private void width(String s, int start, int end) { ! width = -1; ! if (start >= 0) { ! try { ! width = Integer.parseInt(s, start, end, 10); ! if (width < 0) ! throw new IllegalFormatWidthException(width); ! } catch (NumberFormatException x) { ! assert (false); } } + } ! private void precision(String s, int start, int end) { ! precision = -1; ! if (start >= 0) { ! try { ! // skip the leading '.' ! precision = Integer.parseInt(s, start + 1, end, 10); ! if (precision < 0) ! throw new IllegalFormatPrecisionException(precision); ! } catch (NumberFormatException x) { ! assert (false); } } + } + + private void conversion(char conv) { + conversion = Conversion.lookup(conv); + if (Character.isUpperCase(conv)) { + f = Flags.add(f, Flags.UPPERCASE); + conversion = Conversion.lookup(Character.toLowerCase(conv)); + } + if (Conversion.isText(conversion)) { + index = -2; + } + } + + public int index() { + return index; + } + + public String value() { + return toString(); + } + + public Conversion conversion() { + return conversion; + } + + public DateTime dateTime() { + return dateTime; + } + + public int flags() { + return f; + } + + public int width() { + return width; + } ! public int precision() { ! return precision; } ! public String toString() { ! StringBuilder sb = new StringBuilder("%"); ! // Flags.UPPERCASE is set internally for legal conversions. ! sb.append(Flags.toString(Flags.remove(f, Flags.UPPERCASE))); ! if (index > 0) ! sb.append(index).append('$'); ! if (width != -1) ! sb.append(width); ! if (precision != -1) ! sb.append('.').append(precision); ! sb.append(Flags.contains(f, Flags.UPPERCASE) ! ? Character.toUpperCase(conversion.c) : conversion.c); ! if (dateTime != null) ! sb.append(dateTime.c); ! return sb.toString(); ! } ! ! private void checkConversion() { ! switch (conversion) { ! ! // Conversions applicable to all objects. ! case BOOLEAN: ! case BOOLEAN_UPPER: ! case STRING: ! case STRING_UPPER: ! case HASHCODE: ! case HASHCODE_UPPER: ! checkGeneral(); ! break; ! ! // Conversions applicable to date objects. ! case DATE_TIME: ! case DATE_TIME_UPPER: ! checkDateTime(); ! break; ! ! // Conversions applicable to character. ! case CHARACTER: ! case CHARACTER_UPPER: ! checkCharacter(); ! break; ! ! // Conversions applicable to integer types. ! case DECIMAL_INTEGER: ! case OCTAL_INTEGER: ! case HEXADECIMAL_INTEGER: ! case HEXADECIMAL_INTEGER_UPPER: ! checkInteger(); ! break; ! ! // Conversions applicable to floating-point types. ! case SCIENTIFIC: ! case SCIENTIFIC_UPPER: ! case GENERAL: ! case GENERAL_UPPER: ! case DECIMAL_FLOAT: ! case HEXADECIMAL_FLOAT: ! case HEXADECIMAL_FLOAT_UPPER: ! checkFloat(); ! break; ! ! // Conversions that do not require an argument ! case LINE_SEPARATOR: ! case PERCENT_SIGN: ! checkText(); ! break; } } ! private void checkGeneral() { ! if ((conversion == Conversion.BOOLEAN || conversion == Conversion.HASHCODE) ! && Flags.contains(f, Flags.ALTERNATE)) ! failMismatch(Flags.ALTERNATE, conversion); ! // '-' requires a width ! if (width == -1 && Flags.contains(f, Flags.LEFT_JUSTIFY)) ! throw new MissingFormatWidthException(toString()); ! checkBadFlags(Flags.PLUS, Flags.LEADING_SPACE, Flags.ZERO_PAD, ! Flags.GROUP, Flags.PARENTHESES); ! } ! private void checkDateTime() { ! if (precision != -1) ! throw new IllegalFormatPrecisionException(precision); ! if (dateTime == null) ! throw new UnknownFormatConversionException(String.valueOf(conversion.c)); ! checkBadFlags(Flags.ALTERNATE, Flags.PLUS, Flags.LEADING_SPACE, ! Flags.ZERO_PAD, Flags.GROUP, Flags.PARENTHESES); ! // '-' requires a width ! if (width == -1 && Flags.contains(f, Flags.LEFT_JUSTIFY)) ! throw new MissingFormatWidthException(toString()); ! } ! private void checkCharacter() { ! if (precision != -1) ! throw new IllegalFormatPrecisionException(precision); ! checkBadFlags(Flags.ALTERNATE, Flags.PLUS, Flags.LEADING_SPACE, ! Flags.ZERO_PAD, Flags.GROUP, Flags.PARENTHESES); ! // '-' requires a width ! if (width == -1 && Flags.contains(f, Flags.LEFT_JUSTIFY)) ! throw new MissingFormatWidthException(toString()); ! } ! private void checkInteger() { ! checkNumeric(); ! if (precision != -1) ! throw new IllegalFormatPrecisionException(precision); ! if (conversion == Conversion.DECIMAL_INTEGER) ! checkBadFlags(Flags.ALTERNATE); ! else if (conversion == Conversion.OCTAL_INTEGER) ! checkBadFlags(Flags.GROUP); ! else ! checkBadFlags(Flags.GROUP); ! } ! public void checkBadFlags(int... badFlags) { ! for (int badFlag : badFlags) ! if (Flags.contains(f, badFlag)) ! failMismatch(badFlag, conversion); } ! private void checkFloat() { ! checkNumeric(); ! if (conversion == Conversion.DECIMAL_FLOAT) { ! // no check ! } else if (conversion == Conversion.HEXADECIMAL_FLOAT) { ! checkBadFlags(Flags.PARENTHESES, Flags.GROUP); ! } else if (conversion == Conversion.SCIENTIFIC) { ! checkBadFlags(Flags.GROUP); ! } else if (conversion == Conversion.GENERAL) { ! checkBadFlags(Flags.ALTERNATE); ! } } ! private void checkNumeric() { ! if (width != -1 && width < 0) ! throw new IllegalFormatWidthException(width); ! ! if (precision != -1 && precision < 0) ! throw new IllegalFormatPrecisionException(precision); ! ! // '-' and '0' require a width ! if (width == -1 ! && (Flags.contains(f, Flags.LEFT_JUSTIFY) || Flags.contains(f, Flags.ZERO_PAD))) ! throw new MissingFormatWidthException(toString()); ! ! // bad combination ! if ((Flags.contains(f, Flags.PLUS) && Flags.contains(f, Flags.LEADING_SPACE)) ! || (Flags.contains(f, Flags.LEFT_JUSTIFY) && Flags.contains(f, Flags.ZERO_PAD))) ! throw new IllegalFormatFlagsException(Flags.toString(f)); } ! private void checkText() { ! if (precision != -1) ! throw new IllegalFormatPrecisionException(precision); ! switch (conversion) { ! case PERCENT_SIGN: ! if (f != Flags.LEFT_JUSTIFY ! && f != Flags.NONE) ! throw new IllegalFormatFlagsException(Flags.toString(f)); ! // '-' requires a width ! if (width == -1 && Flags.contains(f, Flags.LEFT_JUSTIFY)) ! throw new MissingFormatWidthException(toString()); ! break; ! case LINE_SEPARATOR: ! if (width != -1) ! throw new IllegalFormatWidthException(width); ! if (f != Flags.NONE) ! throw new IllegalFormatFlagsException(Flags.toString(f)); ! break; ! default: ! assert false; ! } } ! // -- Methods to support throwing exceptions -- ! ! public static void failMismatch(int flags, Conversion conersion) { ! String fs = Flags.toString(flags); ! throw new FormatFlagsConversionMismatchException(fs, conersion.c); } ! } ! ! private static class Flags { ! public static final int NONE = 0; // '' ! ! // duplicate declarations from Formattable.java ! public static final int LEFT_JUSTIFY = 1 << 0; // '-' ! public static final int UPPERCASE = 1 << 1; // '^' ! public static final int ALTERNATE = 1 << 2; // '#' ! ! // numerics ! public static final int PLUS = 1 << 3; // '+' ! public static final int LEADING_SPACE = 1 << 4; // ' ' ! public static final int ZERO_PAD = 1 << 5; // '0' ! public static final int GROUP = 1 << 6; // ',' ! public static final int PARENTHESES = 1 << 7; // '(' ! ! // indexing ! public static final int PREVIOUS = 1 << 8; // '<' ! ! private Flags() {} ! ! public static boolean contains(int flags, int f) { ! return (flags & f) == f; ! } ! ! public static int remove(int flags, int f) { ! return flags & ~f; } ! private static int add(int flags, int f) { ! return flags | f; ! } ! ! private static int parse(String s, int start, int end) { ! int f = NONE; for (int i = start; i < end; i++) { char c = s.charAt(i); ! int v = parse(c); ! if (contains(f, v)) ! throw new DuplicateFormatFlagsException(toString(v)); ! f = add(f, v); } return f; } // parse those flags which may be provided by users ! private static int parse(char c) { switch (c) { ! case '-': return LEFT_JUSTIFY; ! case '#': return ALTERNATE; ! case '+': return PLUS; ! case ' ': return LEADING_SPACE; ! case '0': return ZERO_PAD; ! case ',': return GROUP; ! case '(': return PARENTHESES; ! case '<': return PREVIOUS; ! default: ! throw new UnknownFormatFlagsException(String.valueOf(c)); } } ! // Returns a string representation of the current {@code flags}. ! public static String toString(int f) { StringBuilder sb = new StringBuilder(); ! if (contains(f, LEFT_JUSTIFY)) sb.append('-'); ! if (contains(f, UPPERCASE)) sb.append('^'); ! if (contains(f, ALTERNATE)) sb.append('#'); ! if (contains(f, PLUS)) sb.append('+'); ! if (contains(f, LEADING_SPACE)) sb.append(' '); ! if (contains(f, ZERO_PAD)) sb.append('0'); ! if (contains(f, GROUP)) sb.append(','); ! if (contains(f, PARENTHESES)) sb.append('('); ! if (contains(f, PREVIOUS)) sb.append('<'); return sb.toString(); } } ! private enum Conversion { ! NONE('\0'), // Byte, Short, Integer, Long, BigInteger // (and associated primitives due to autoboxing) ! DECIMAL_INTEGER('d'), // 'd' ! OCTAL_INTEGER('o'), // 'o' ! HEXADECIMAL_INTEGER('x'), // 'x' ! HEXADECIMAL_INTEGER_UPPER('X'), // 'X' // Float, Double, BigDecimal // (and associated primitives due to autoboxing) ! SCIENTIFIC('e'), // 'e'; ! SCIENTIFIC_UPPER('E'), // 'E'; ! GENERAL('g'), // 'g'; ! GENERAL_UPPER('G'), // 'G'; ! DECIMAL_FLOAT('f'), // 'f'; ! HEXADECIMAL_FLOAT('a'), // 'a'; ! HEXADECIMAL_FLOAT_UPPER('A'), // 'A'; // Character, Byte, Short, Integer // (and associated primitives due to autoboxing) ! CHARACTER('c'), // 'c'; ! CHARACTER_UPPER('C'), // 'C'; // java.util.Date, java.util.Calendar, long ! DATE_TIME('t'), // 't'; ! DATE_TIME_UPPER('T'), // 'T'; // if (arg.TYPE != boolean) return boolean // if (arg != null) return true; else return false; ! BOOLEAN('b'), // 'b'; ! BOOLEAN_UPPER('B'), // 'B'; // if (arg instanceof Formattable) arg.formatTo() // else arg.toString(); ! STRING('s'), // 's'; ! STRING_UPPER('S'), // 'S'; // arg.hashCode() ! HASHCODE('h'), // 'h'; ! HASHCODE_UPPER('H'), // 'H'; ! LINE_SEPARATOR('n'), // 'n'; ! PERCENT_SIGN('%'); // '%'; ! private final char c; ! ! Conversion (char c) { ! this.c = c; } ! static Conversion lookup(char c) { switch (c) { ! case 'd': return DECIMAL_INTEGER; ! case 'o': return OCTAL_INTEGER; ! case 'x': return HEXADECIMAL_INTEGER; ! case 'X': return HEXADECIMAL_INTEGER_UPPER; ! case 'e': return SCIENTIFIC; ! case 'E': return SCIENTIFIC_UPPER; ! case 'g': return GENERAL; ! case 'G': return GENERAL_UPPER; ! case 'f': return DECIMAL_FLOAT; ! case 'a': return HEXADECIMAL_FLOAT; ! case 'A': return HEXADECIMAL_FLOAT_UPPER; ! case 'c': return CHARACTER; ! case 'C': return CHARACTER_UPPER; ! case 't': return DATE_TIME; ! case 'T': return DATE_TIME_UPPER; ! case 'b': return BOOLEAN; ! case 'B': return BOOLEAN_UPPER; ! case 's': return STRING; ! case 'S': return STRING_UPPER; ! case 'h': return HASHCODE; ! case 'H': return HASHCODE_UPPER; ! case 'n': return LINE_SEPARATOR; ! case '%': return PERCENT_SIGN; ! default: ! throw new UnknownFormatConversionException(String.valueOf(c)); ! } ! } ! ! public void fail(Object arg) { ! throw new IllegalFormatConversionException(c, arg.getClass()); ! } ! ! static boolean isText(Conversion conv) { ! switch (conv) { ! case LINE_SEPARATOR: ! case PERCENT_SIGN: ! return true; ! default: ! return false; } } + } ! private enum DateTime { ! HOUR_OF_DAY_0('H'), // 'H' (00 - 23) ! HOUR_0('I'), // 'I' (01 - 12) ! HOUR_OF_DAY('k'), // 'k' (0 - 23) -- like H ! HOUR('l'), // 'l' (1 - 12) -- like I ! MINUTE('M'), // 'M' (00 - 59) ! NANOSECOND('N'), // 'N' (000000000 - 999999999) ! MILLISECOND('L'), // 'L' jdk, not in gnu (000 - 999) ! MILLISECOND_SINCE_EPOCH('Q'), // 'Q' (0 - 99...?) ! AM_PM('p'), // 'p' (am or pm) ! SECONDS_SINCE_EPOCH('s'), // 's' (0 - 99...?) ! SECOND('S'), // 'S' (00 - 60 - leap second) ! TIME('T'), // 'T' (24 hour hh:mm:ss) ! ZONE_NUMERIC('z'), // 'z' (-1200 - +1200) - ls minus? ! ZONE('Z'), // 'Z' (symbol) ! ! // Date ! NAME_OF_DAY_ABBREV('a'), // 'a' 'a' ! NAME_OF_DAY('A'), // 'A' 'A' ! NAME_OF_MONTH_ABBREV('b'), // 'b' 'b' ! NAME_OF_MONTH('B'), // 'B' 'B' ! CENTURY('C'), // 'C' (00 - 99) ! DAY_OF_MONTH_0('d'), // 'd' (01 - 31) ! DAY_OF_MONTH('e'), // 'e' (1 - 31) -- like d ! // * ISO_WEEK_OF_YEAR_2('g'), // 'g' cross %y %V ! // * ISO_WEEK_OF_YEAR_4('G'), // 'G' cross %Y %V ! NAME_OF_MONTH_ABBREV_X('h'), // 'h' -- same b ! DAY_OF_YEAR('j'), // 'j' (001 - 366) ! MONTH('m'), // 'm' (01 - 12) ! // * DAY_OF_WEEK_1('u'), // 'u' (1 - 7) Monday ! // * WEEK_OF_YEAR_SUNDAY('U'), // 'U' (0 - 53) Sunday+ ! // * WEEK_OF_YEAR_MONDAY_01('V'), // 'V' (01 - 53) Monday+ ! // * DAY_OF_WEEK_0('w'), // 'w' (0 - 6) Sunday ! // * WEEK_OF_YEAR_MONDAY('W'), // 'W' (00 - 53) Monday ! YEAR_2('y'), // 'y' (00 - 99) ! YEAR_4('Y'), // 'Y' (0000 - 9999) ! ! // Composites ! TIME_12_HOUR('r'), // 'r' (hh:mm:ss [AP]M) ! TIME_24_HOUR('R'), // 'R' (hh:mm same as %H:%M) ! // * LOCALE_TIME('X'), // 'X' (%H:%M:%S) - parse format? ! DATE_TIME('c'), // 'c' (Sat Nov 04 12:02:33 EST 1999) ! DATE('D'), // 'D' (mm/dd/yy) ! ISO_STANDARD_DATE('F'); // 'F' (%Y-%m-%d) ! // * LOCALE_DATE('x') // 'x' (mm/dd/yy) ! ! static DateTime lookup(char c) { switch (c) { ! case 'H': return HOUR_OF_DAY_0; ! case 'I': return HOUR_0; ! case 'k': return HOUR_OF_DAY; ! case 'l': return HOUR; ! case 'M': return MINUTE; ! case 'N': return NANOSECOND; ! case 'L': return MILLISECOND; ! case 'Q': return MILLISECOND_SINCE_EPOCH; ! case 'p': return AM_PM; ! case 's': return SECONDS_SINCE_EPOCH; ! case 'S': return SECOND; ! case 'T': return TIME; ! case 'z': return ZONE_NUMERIC; ! case 'Z': return ZONE; ! ! // Date ! case 'a': return NAME_OF_DAY_ABBREV; ! case 'A': return NAME_OF_DAY; ! case 'b': return NAME_OF_MONTH_ABBREV; ! case 'B': return NAME_OF_MONTH; ! case 'C': return CENTURY; ! case 'd': return DAY_OF_MONTH_0; ! case 'e': return DAY_OF_MONTH; ! // * case 'g': return ISO_WEEK_OF_YEAR_2; ! // * case 'G': return ISO_WEEK_OF_YEAR_4; ! case 'h': return NAME_OF_MONTH_ABBREV_X; ! case 'j': return DAY_OF_YEAR; ! case 'm': return MONTH; ! // * case 'u': return DAY_OF_WEEK_1; ! // * case 'U': return WEEK_OF_YEAR_SUNDAY; ! // * case 'V': return WEEK_OF_YEAR_MONDAY_01; ! // * case 'w': return DAY_OF_WEEK_0; ! // * case 'W': return WEEK_OF_YEAR_MONDAY; ! case 'y': return YEAR_2; ! case 'Y': return YEAR_4; ! ! // Composites ! case 'r': return TIME_12_HOUR; ! case 'R': return TIME_24_HOUR; ! // * case 'X': return LOCALE_TIME; ! case 'c': return DATE_TIME; ! case 'D': return DATE; ! case 'F': return ISO_STANDARD_DATE; ! // * case 'x': return LOCALE_DATE; ! default: ! throw new UnknownFormatConversionException("t" + c); ! } ! } ! ! private final char c; ! ! DateTime(char c) { ! this.c = c; ! } ! ! public void fail(Object arg) { ! throw new IllegalFormatConversionException(c, arg.getClass()); ! } ! } ! ! // Formatter BSM ! ! private static final MethodHandles.Lookup LOOKUP = MethodHandles.lookup(); ! ! private static final MethodHandle SPECIFIER_PRINT = ! findVirtualMethodHandle(Formatter.class, "print", ! methodType(Formatter.class, FormatSpecifier.class, Object.class, Locale.class)); ! private static final MethodHandle SPECIFIER_PRINT_STRING = ! findVirtualMethodHandle(Formatter.class, "print", ! methodType(Formatter.class, FormatSpecifier.class, String.class, Locale.class)); ! private static final MethodHandle SPECIFIER_PRINT_INT = ! findVirtualMethodHandle(Formatter.class, "print", ! methodType(Formatter.class, FormatSpecifier.class, int.class, Locale.class)); ! private static final MethodHandle SPECIFIER_PRINT_LONG = ! findVirtualMethodHandle(Formatter.class, "print", ! methodType(Formatter.class, FormatSpecifier.class, long.class, Locale.class)); ! private static final MethodHandle SPECIFIER_PRINT_BYTE = ! findVirtualMethodHandle(Formatter.class, "print", ! methodType(Formatter.class, FormatSpecifier.class, byte.class, Locale.class)); ! private static final MethodHandle SPECIFIER_PRINT_SHORT = ! findVirtualMethodHandle(Formatter.class, "print", ! methodType(Formatter.class, FormatSpecifier.class, short.class, Locale.class)); ! private static final MethodHandle SPECIFIER_PRINT_FLOAT = ! findVirtualMethodHandle(Formatter.class, "print", ! methodType(Formatter.class, FormatSpecifier.class, float.class, Locale.class)); ! private static final MethodHandle SPECIFIER_PRINT_DOUBLE = ! findVirtualMethodHandle(Formatter.class, "print", ! methodType(Formatter.class, FormatSpecifier.class, double.class, Locale.class)); ! private static final MethodHandle SPECIFIER_PRINT_HASHCODE = ! findVirtualMethodHandle(Formatter.class, "printHashCode", ! methodType(Formatter.class, FormatSpecifier.class, Object.class, Locale.class)); ! private static final MethodHandle FIXED_STRING_PRINT = ! findVirtualMethodHandle(FixedString.class, "print", ! methodType(Formatter.class, Formatter.class)); ! ! private static final MethodHandle CONSTRUCT_FORMATTER = ! findConstructorMethodHandle(Formatter.class, methodType(void.class)) ! .asType(methodType(Formatter.class)); ! private static final MethodHandle CONSTRUCT_FORMATTER_APPENDABLE = ! findConstructorMethodHandle(Formatter.class, methodType(void.class, Appendable.class)) ! .asType(methodType(Formatter.class, Appendable.class)); ! ! private static final MethodHandle CONSTRUCT_MISSING_FORMAT_ARGUMENT_EXCEPTION = ! findConstructorMethodHandle(MissingFormatArgumentException.class, methodType(void.class, String.class)); ! private static final MethodHandle APPENDABLE_TO_STRING = ! findVirtualMethodHandle(Appendable.class, "toString", methodType(String.class)); ! private static final MethodHandle FORMATTER_OUT = ! findVirtualMethodHandle(Formatter.class, "out", methodType(Appendable.class)); ! private static final MethodHandle LOCALE_GETDEFAULT = ! insertArguments(findStaticMethodHandle(Locale.class, "getDefault", ! methodType(Locale.class, Locale.Category.class)),0, Locale.Category.FORMAT); ! private static final MethodHandle FORMATTER_LOCALE = ! findVirtualMethodHandle(Formatter.class, "locale", methodType(Locale.class)); ! private static final MethodHandle ILLEGAL_FORMAT_EXCEPTION_CLONE = ! findVirtualMethodHandle(IllegalFormatException.class, "clone", methodType(IllegalFormatException.class)); ! ! private static final MethodHandle INT_TO_STRING = ! findStaticMethodHandle(Integer.class, "toString", methodType(String.class, int.class)); ! private static final MethodHandle BOOLEAN_TO_STRING = ! findStaticMethodHandle(Boolean.class, "toString", methodType(String.class, boolean.class)); ! private static final MethodHandle OBJECT_HASHCODE = ! findVirtualMethodHandle(Object.class, "hashCode", methodType(int.class)); ! private static final MethodHandle INTEGER_TO_HEX_STRING = ! findStaticMethodHandle(Integer.class, "toHexString", methodType(String.class, int.class)); ! private static final MethodHandle INTEGER_TO_OCTAL_STRING = ! findStaticMethodHandle(Integer.class, "toOctalString", methodType(String.class, int.class)); ! private static final MethodHandle LONG_TO_STRING = ! findStaticMethodHandle(Long.class, "toString", methodType(String.class, long.class)); ! private static final MethodHandle LONG_TO_HEX_STRING = ! findStaticMethodHandle(Long.class, "toHexString", methodType(String.class, long.class)); ! private static final MethodHandle LONG_TO_OCTAL_STRING = ! findStaticMethodHandle(Long.class, "toOctalString", methodType(String.class, long.class)); ! private static final MethodHandle STRING_TO_UPPER_CASE = ! findVirtualMethodHandle(String.class, "toUpperCase", methodType(String.class)); ! ! private static final MethodHandle LOCALE_GUARD = findStaticMethodHandle(Formatter.class, "localeGuard", ! methodType(boolean.class, Locale.class, Locale.class)); ! private static final MethodHandle BOOLEAN_OBJECT_FILTER = findStaticMethodHandle(Formatter.class, "booleanObjectFilter", ! methodType(boolean.class, Object.class)); ! private static final MethodHandle NOT_NULL_TEST = findStaticMethodHandle(Formatter.class, "notNullTest", ! methodType(boolean.class, Object.class)); ! ! private static final int MISSING_ARGUMENT_INDEX = Integer.MIN_VALUE; ! ! /** ! * formatterFormatBootstrap bootstrap. ! * @param lookup MethodHandles lookup ! * @param name Name of method ! * @param methodType Method signature ! * @param format Formatter format string ! * @param isStringMethod Method's owner is String or not ! * @param hasLocale has Locale ! * @throws NoSuchMethodException no such method ! * @throws IllegalAccessException illegal access ! * @throws StringConcatException string concat error ! * @return Callsite for intrinsic method ! */ ! public static CallSite formatterBootstrap(MethodHandles.Lookup lookup, ! String name, ! MethodType methodType, ! String format, ! int isStringMethod, ! int hasLocale) ! throws NoSuchMethodException, IllegalAccessException, StringConcatException { ! boolean isString = isStringMethod == 1; ! boolean hasLocaleArg = hasLocale == 1; ! boolean isVarArgs = isVarArgsType(methodType, isString, hasLocaleArg); ! if (isVarArgs) { ! return new ConstantCallSite(fallbackMethodHandle( ! lookup, name, methodType, format, ! isString, hasLocaleArg, isVarArgs)); ! } ! ! List<FormatToken> specs; ! ! try { ! specs = parse(format); ! } catch (IllegalFormatException illegalFormatException) { ! return new ConstantCallSite(illegalFormatThrower(illegalFormatException, methodType)); ! } ! ! if (specs.isEmpty()) { ! return new ConstantCallSite(isString ? ! constant(String.class, "").asType(methodType) : ! identity(methodType.parameterType(0)).asType(methodType)); ! } ! // Array of formatter args excluding target and locale ! Class<?>[] argTypes = methodType.dropParameterTypes(0, firstFormatterArg(isString, hasLocaleArg)).parameterArray(); ! // index array is needed for argument analysis ! int[] argIndexes = calculateArgumentIndexes(specs, argTypes.length); ! return isString && mayNotNeedFormatter(specs, argTypes, argIndexes) ? ! new ConstantCallSite(new StringConcatHandleBuilder(specs, argTypes, argIndexes, hasLocaleArg).getHandle(lookup, methodType)) : ! new ConstantCallSite(fallbackMethodHandle(lookup, name, methodType, format, isString, hasLocaleArg, isVarArgs)); ! } ! ! private static int[] calculateArgumentIndexes(List<FormatToken> specs, int argCount) { ! int[] argIndexes = new int[specs.size()]; ! int last = -1; ! int lasto = -1; ! ! // Calculate indices and throw exceptions for missing arguments ! for (int i = 0; i < specs.size(); i++) { ! FormatToken ft = specs.get(i); ! ! int index = ft.index(); ! switch (index) { ! case -2: // fixed string, "%n", or "%%" ! argIndexes[i] = -1; ! break; ! case -1: // relative index ! argIndexes[i] = (last < 0 || last >= argCount) ? MISSING_ARGUMENT_INDEX : last; ! break; ! case 0: // ordinary index ! lasto++; ! last = lasto; ! argIndexes[i] = (last < 0 || last >= argCount) ? MISSING_ARGUMENT_INDEX : last; ! break; ! default: // explicit index ! last = index - 1; ! argIndexes[i] = (last < 0 || last >= argCount) ? MISSING_ARGUMENT_INDEX : last; ! break; ! } ! } ! ! return argIndexes; ! } ! ! private static boolean mayNotNeedFormatter(List<FormatToken> specs, Class<?>[] argTypes, int[] argIndexes) { ! for (int i = 0; i < specs.size(); i++) { ! if (argIndexes[i] >= 0 ! && !canUseDirectConcat((FormatSpecifier) specs.get(i), argTypes[argIndexes[i]]) ! && !canUseSpecialConverter((FormatSpecifier) specs.get(i), argTypes[argIndexes[i]])) { return false; } } ! return true; ! } ! ! private static int firstFormatterArg(boolean isStringMethod, boolean hasLocaleArg) { ! int index = isStringMethod ? 0 : 1; ! return hasLocaleArg ? index + 1 : index; ! } ! ! private static MethodHandle findVirtualMethodHandle(Class<?> type, String name, MethodType methodType) { ! try { ! return LOOKUP.findVirtual(type, name, methodType); ! } catch (NoSuchMethodException | IllegalAccessException e) { ! throw new RuntimeException(e); ! } ! } ! ! private static MethodHandle findStaticMethodHandle(Class<?> type, String name, MethodType methodType) { ! try { ! return LOOKUP.findStatic(type, name, methodType); ! } catch (NoSuchMethodException | IllegalAccessException e) { ! throw new RuntimeException(e); ! } ! } ! ! private static MethodHandle findConstructorMethodHandle(Class<?> type, MethodType methodType) { ! try { ! return LOOKUP.findConstructor(type, methodType); ! } catch (NoSuchMethodException | IllegalAccessException e) { ! throw new RuntimeException(e); ! } ! } ! ! ! static abstract class FormatHandleBuilder { ! final List<FormatToken> specs; ! final Class<?>[] argTypes; ! final int[] argIndexes; ! final boolean hasLocaleArg; ! ! FormatHandleBuilder(List<FormatToken> specs, Class<?>[] argTypes, int[] argIndexes, boolean hasLocaleArg) { ! this.specs = specs; ! this.argTypes = argTypes; ! this.argIndexes = argIndexes; ! this.hasLocaleArg = hasLocaleArg; ! } ! ! void buildHandles() { ! for (int i = 0; i < specs.size(); i++) { ! if (argIndexes[i] == -1) { ! addConstantMethodHandle(argTypes, specs.get(i)); ! } else if (argIndexes[i] == MISSING_ARGUMENT_INDEX) { ! addMissingArgumentMethodHandle(argTypes, (FormatSpecifier) specs.get(i)); ! } else { ! addArgumentMethodHandle(argTypes, (FormatSpecifier) specs.get(i), argIndexes[i]); ! } ! } ! } ! ! abstract void addArgumentMethodHandle(Class<?>[] argTypes, FormatSpecifier spec, int argIndex); ! abstract void addConstantMethodHandle(Class<?>[] argTypes, FormatToken spec); ! abstract void addMissingArgumentMethodHandle(Class<?>[] argTypes, FormatSpecifier spec); ! abstract MethodHandle getHandle(MethodHandles.Lookup lookup, MethodType methodType); ! } ! ! ! static class FormatterFormatHandleBuilder extends FormatHandleBuilder { ! ! private MethodHandle handle = null; ! final boolean isFormatterMethod; ! final boolean isStringMethod; ! ! FormatterFormatHandleBuilder(List<FormatToken> specs, Class<?>[] argTypes, int[] argIndexes, ! boolean hasLocaleArg, boolean isFormatterMethod, boolean isStringMethod) { ! super(specs, argTypes, argIndexes, hasLocaleArg); ! this.isFormatterMethod = isFormatterMethod; ! this.isStringMethod = isStringMethod; ! } ! ! @Override ! public void addArgumentMethodHandle(Class<?>[] argTypes, FormatSpecifier spec, int argIndex) { ! MethodHandle appender; ! ! if (canUseSpecialConverter(spec, argTypes[argIndex])) { ! MethodHandle conversionFilter = getSpecializedConverter(spec, argTypes[argIndex]); ! appender = filterArguments(SPECIFIER_PRINT_STRING, 2, conversionFilter); ! appender = insertArguments(appender, 1, spec); ! } else { ! appender = getPrintHandle(argTypes[argIndex], spec); ! appender = insertArguments(appender, 1, spec); ! } ! ! appender = appender.asType(appender.type().changeParameterType(1, argTypes[argIndex])); ! ! if (argIndex > 0) { ! appender = dropArguments(appender, 1, Arrays.copyOfRange(argTypes, 0, argIndex)); ! } ! if (argIndex < argTypes.length - 1) { ! appender = dropArguments(appender, argIndex + 2, Arrays.copyOfRange(argTypes, argIndex + 1, argTypes.length)); ! } ! ! if (handle == null) { ! handle = appender; ! } else { ! handle = foldArguments(appender, handle.asType(handle.type().changeReturnType(void.class))); ! } ! } ! ! @Override ! public void addConstantMethodHandle(Class<?>[] argTypes, FormatToken spec) { ! MethodHandle appender; ! if (spec instanceof FixedString) { ! appender = dropArguments(insertArguments(FIXED_STRING_PRINT, 0, spec), 1, Locale.class); ! } else { ! appender = insertArguments(SPECIFIER_PRINT, 1, spec); ! appender = insertArguments(appender, 1, (Object) null); ! } ! appender = dropArguments(appender, 1, Arrays.copyOfRange(argTypes, 0, argTypes.length)); ! ! if (handle == null) { ! handle = appender; ! } else { ! handle = foldArguments(appender, handle.asType(handle.type().changeReturnType(void.class))); ! } ! } ! ! @Override ! public void addMissingArgumentMethodHandle(Class<?>[] argTypes, FormatSpecifier spec) { ! MethodHandle thrower = missingFormatArgumentThrower(spec.toString(), Formatter.class); ! thrower = dropArguments(thrower, 0, Formatter.class); ! thrower = dropArguments(thrower, 1, argTypes); ! thrower = dropArguments(thrower, thrower.type().parameterCount(), Locale.class); ! ! if (handle == null) { ! handle = thrower; ! } else { ! handle = foldArguments(thrower, handle.asType(handle.type().changeReturnType(void.class))); ! } ! } ! ! @Override ! public MethodHandle getHandle(MethodHandles.Lookup lookup, MethodType methodType) { ! ! buildHandles(); ! ! MethodHandle wrapper; ! ! if (isFormatterMethod) { ! wrapper = handle; ! } else { ! if (isStringMethod) { ! wrapper = foldArguments(handle, 0, CONSTRUCT_FORMATTER); ! } else { ! wrapper = filterArguments(handle, 0, CONSTRUCT_FORMATTER_APPENDABLE); ! } ! wrapper = filterReturnValue(wrapper, FORMATTER_OUT); ! } ! ! if (hasLocaleArg) { ! int[] argmap = new int[methodType.parameterCount()]; ! if (!isStringMethod) { ! argmap[0] = 0; ! argmap[argmap.length - 1] = 1; ! for (int i = 1; i < argmap.length - 1; i++) { ! argmap[i] = i + 1; ! } ! } else { ! argmap[argmap.length - 1] = 0; ! for (int i = 0; i < argmap.length - 1; i++) { ! argmap[i] = i + 1; ! } ! } ! MethodType newType = methodType.changeReturnType(wrapper.type().returnType()); ! if (!isStringMethod) { ! newType = newType.changeParameterType(0, wrapper.type().parameterType(0)); ! } ! wrapper = MethodHandles.permuteArguments(wrapper, newType, argmap); ! } else { ! if (isFormatterMethod) { ! wrapper = foldLocaleFromFormatter(wrapper, methodType.parameterCount()); ! } else { ! wrapper = foldArguments(wrapper, methodType.parameterCount(), LOCALE_GETDEFAULT); ! } ! } ! if (isStringMethod) { ! wrapper = filterReturnValue(wrapper, APPENDABLE_TO_STRING); ! } ! return wrapper.asType(methodType); ! } ! } ! ! static class StringConcatHandleBuilder extends FormatHandleBuilder { ! ! MethodType concatType = methodType(String.class); ! StringBuilder recipe = new StringBuilder(); ! ! List<Object> constants = new ArrayList<>(); ! List<Integer> reorder = new ArrayList<>(); ! List<MethodHandle> argumentFormatters = new ArrayList<>(); ! ! boolean needsLocaleGuard = false; ! ! StringConcatHandleBuilder(List<FormatToken> specs, Class<?>[] argTypes, int[] argIndexes, boolean hasLocaleArg) { ! super(specs, argTypes, argIndexes, hasLocaleArg); ! } ! ! @Override ! public void addArgumentMethodHandle(Class<?>[] argTypes, FormatSpecifier spec, int argIndex) { ! ! // Add argument token to recipe ! recipe.append('\1'); ! ! Class<?> argType = argTypes[argIndex]; ! boolean useDirectConcat = canUseDirectConcat(spec, argType); ! concatType = concatType.appendParameterTypes(useDirectConcat ? argType : String.class); ! reorder.add(argIndex); ! ! if (useDirectConcat) { ! if (spec.conversion() == Conversion.DECIMAL_INTEGER) { ! // Direct string concat, but we need to guard against locales requiring Unicode decimal symbols ! needsLocaleGuard = true; ! } ! argumentFormatters.add(null); ! } else { ! // Direct handle requiring no formatter or localization ! assert canUseSpecialConverter(spec, argType); ! MethodHandle conversionFilter = getSpecializedConverter(spec, argType); ! argumentFormatters.add(conversionFilter); ! ! } ! } ! ! @Override ! public void addConstantMethodHandle(Class<?>[] argTypes, FormatToken spec) { ! String value = getConstantSpecValue(spec); ! // '\1' and '\2' denote argument or constant to StringConcatFactory ! if (value.indexOf('\1') == -1 && value.indexOf('\2') == -1) { ! recipe.append(value); ! } else { ! recipe.append('\2'); ! constants.add(value); ! } ! } ! ! @Override ! public void addMissingArgumentMethodHandle(Class<?>[] argTypes, FormatSpecifier spec) { ! MethodHandle thrower = throwException(void.class, MissingFormatArgumentException.class); ! thrower = foldArguments(thrower, insertArguments(CONSTRUCT_MISSING_FORMAT_ARGUMENT_EXCEPTION, 0, spec.toString())); ! argumentFormatters.add(thrower); ! } ! ! @Override ! public MethodHandle getHandle(MethodHandles.Lookup lookup, MethodType methodType) { ! ! buildHandles(); ! ! CallSite cs; ! ! try { ! cs = StringConcatFactory.makeConcatWithConstants(lookup, "formatterBootstrap", concatType, recipe.toString(), constants.toArray()); ! } catch (StringConcatException sce) { ! throw new RuntimeException(sce); ! } ! ! MethodHandle handle = dropArguments(cs.getTarget(), concatType.parameterCount(), Locale.class); ! ! int paramIndex = 0; ! ! for (MethodHandle formatter : argumentFormatters) { ! ! if (formatter != null) { ! int paramCount = formatter.type().parameterCount(); ! if (paramCount == 0) { ! handle = foldArguments(handle, 0, formatter); ! } else { ! assert paramCount == 1; ! handle = filterArguments(handle, paramIndex, formatter); ! } ! } ! ! paramIndex++; ! } ! ! // move Locale argument from last to first position ! handle = moveArgToFront(handle, handle.type().parameterCount() - 1); ! ! if (needsLocaleGuard) { ! // We have a decimal int without formatter - this doesn't work for ! // locales using unicode decimal symbols, so add a guard and fallback handle for that case ! Locale safeDefaultLocale = getSafeDefaultLocale(); ! MethodType mt = hasLocaleArg ? methodType : methodType.insertParameterTypes(0, Locale.class); ! handle = MethodHandles.guardWithTest( ! insertArguments(LOCALE_GUARD, 0, safeDefaultLocale), ! handle, ! new FormatterFormatHandleBuilder(specs, argTypes, argIndexes, true, false, true) ! .getHandle(lookup, mt)); ! } ! ! if (!hasLocaleArg) { ! handle = foldArguments(handle, 0, LOCALE_GETDEFAULT); ! } ! ! int[] reorderArray = hasLocaleArg ? ! // Leading Locale arg - add initial element to keep it in place and increase other values by 1 ! IntStream.concat(IntStream.of(0), reorder.stream().mapToInt(i -> i + 1)).toArray() : ! reorder.stream().mapToInt(i -> i).toArray(); ! ! return MethodHandles.permuteArguments(handle, methodType, reorderArray); ! } ! } ! ! private static Locale getSafeDefaultLocale() { ! Locale defaultLocale = Locale.getDefault(Locale.Category.FORMAT); ! if (defaultLocale == null || DecimalFormatSymbols.getInstance(defaultLocale).getZeroDigit() != '0') { ! defaultLocale = Locale.US; ! } ! return defaultLocale; ! } ! ! private static MethodHandle moveArgToFront(MethodHandle handle, int argIndex) { ! Class<?>[] paramTypes = handle.type().parameterArray(); ! ! MethodType methodType = methodType(handle.type().returnType(), paramTypes[argIndex]); ! int[] reorder = new int[paramTypes.length]; ! reorder[argIndex] = 0; ! ! for (int i = 0, j = 1; i < paramTypes.length; i++) { ! if (i != argIndex) { ! methodType = methodType.appendParameterTypes(paramTypes[i]); ! reorder[i] = j++; ! } ! } ! return permuteArguments(handle, methodType, reorder); ! } ! ! private static MethodHandle foldLocaleFromFormatter(MethodHandle handle, int localeArgIndex) { ! return foldArguments(moveArgToFront(handle, localeArgIndex), FORMATTER_LOCALE); ! } ! ! private static String getConstantSpecValue(Object o) { ! if (o instanceof FormatSpecifier) { ! FormatSpecifier spec = (FormatSpecifier) o; ! assert spec.index() == -2; ! if (spec.conversion() == Conversion.LINE_SEPARATOR) { ! return System.lineSeparator(); ! } else if (spec.conversion() == Conversion.PERCENT_SIGN) { ! return String.format(spec.toString()); ! } ! } ! return o.toString(); ! } ! ! private static MethodHandle missingFormatArgumentThrower(String message, Class<?> returnType) { ! MethodHandle thrower = throwException(returnType, MissingFormatArgumentException.class); ! return foldArguments(thrower, insertArguments(CONSTRUCT_MISSING_FORMAT_ARGUMENT_EXCEPTION, 0, message)); ! } ! ! private static MethodHandle illegalFormatThrower(IllegalFormatException illegalFormat, MethodType methodType) { ! MethodHandle thrower = throwException(methodType.returnType(), IllegalFormatException.class); ! thrower = foldArguments(thrower, 0, insertArguments(ILLEGAL_FORMAT_EXCEPTION_CLONE, 0, illegalFormat)); ! return dropArguments(thrower, 0, methodType.parameterArray()); ! } ! ! private static boolean localeGuard(Locale locale1, Locale locale2) { ! return locale1 == locale2; ! } ! ! private static boolean booleanObjectFilter(Object arg) { ! return arg != null && (! (arg instanceof Boolean) || ((Boolean) arg)); ! } ! ! private static boolean notNullTest(Object arg) { ! return arg != null; ! } ! ! ! private static MethodHandle getSpecializedConverter(FormatSpecifier spec, Class<?> argType) { ! MethodHandle conversionFilter; ! ! switch (spec.conversion()) { ! case HASHCODE: ! conversionFilter = filterArguments(INTEGER_TO_HEX_STRING, 0, OBJECT_HASHCODE); ! break; case DECIMAL_INTEGER: ! conversionFilter = argType == long.class ? LONG_TO_STRING : INT_TO_STRING; ! break; case HEXADECIMAL_INTEGER: ! conversionFilter = argType == long.class ? LONG_TO_HEX_STRING : INTEGER_TO_HEX_STRING; ! break; ! case OCTAL_INTEGER: ! conversionFilter = argType == long.class ? LONG_TO_OCTAL_STRING : INTEGER_TO_OCTAL_STRING; ! break; ! case BOOLEAN: ! conversionFilter = BOOLEAN_TO_STRING; ! break; default: ! throw new IllegalStateException("Unexpected conversion: " + spec.conversion()); } ! if (conversionFilter.type().parameterType(0) != argType) { ! if (spec.conversion() == Conversion.BOOLEAN) ! conversionFilter = filterArguments(conversionFilter, 0, BOOLEAN_OBJECT_FILTER); ! else if (! argType.isPrimitive()) ! conversionFilter = guardWithTest(NOT_NULL_TEST, ! conversionFilter.asType(methodType(String.class, Object.class)), ! dropArguments(constant(String.class, "null"), 0, Object.class)); ! conversionFilter = conversionFilter.asType(conversionFilter.type().changeParameterType(0, argType)); ! } ! ! if (spec.flags() == Flags.UPPERCASE) { ! conversionFilter = filterArguments(STRING_TO_UPPER_CASE,0, conversionFilter); ! } ! ! return conversionFilter; ! } ! ! private static boolean canUseSpecialConverter(FormatSpecifier spec, Class<?> argType) { ! return (spec.flags() == Flags.NONE || spec.flags() == Flags.UPPERCASE) ! && spec.width() == -1 ! && !requiresLocalization(spec) ! && isSafeArgumentType(spec.conversion(), argType); ! } ! ! private static boolean canUseDirectConcat(FormatSpecifier spec, Class<?> argType) { ! if (spec.flags() == Flags.NONE ! && spec.width() == -1 ! && isSafeArgumentType(spec.conversion(), argType)) { ! switch (spec.conversion()) { ! case STRING: ! case BOOLEAN: ! case CHARACTER: ! case DECIMAL_INTEGER: ! case LINE_SEPARATOR: ! case PERCENT_SIGN: ! return true; } } + return false; + } ! private static boolean isSafeArgumentType(Conversion conversion, Class<?> type) { ! if (conversion == Conversion.BOOLEAN) { ! return type == boolean.class || type == Boolean.class; ! } ! if (conversion == Conversion.CHARACTER) { ! return type == char.class || type == Character.class; ! } ! if (conversion == Conversion.DECIMAL_INTEGER ! || conversion == Conversion.HEXADECIMAL_INTEGER ! || conversion == Conversion.OCTAL_INTEGER) { ! return type == int.class || type == long.class || type == Integer.class; ! } ! if (conversion == Conversion.HASHCODE) { ! return true; ! } ! // Limit to String to prevent us from doing toString() on a java.util.Formattable ! return conversion == Conversion.STRING && type == String.class; ! } ! ! private static boolean requiresLocalization(FormatSpecifier spec) { ! switch (spec.conversion()) { ! case BOOLEAN: ! case HEXADECIMAL_INTEGER: ! case OCTAL_INTEGER: ! case HASHCODE: case LINE_SEPARATOR: case PERCENT_SIGN: return false; ! default: ! return true; } } ! private static MethodHandle getPrintHandle(Class<?> argType, FormatSpecifier spec) { ! if (spec.conversion() == Conversion.HASHCODE) { ! return SPECIFIER_PRINT_HASHCODE; ! } else if (spec.conversion() == Conversion.DECIMAL_INTEGER && argType == int.class) { ! return SPECIFIER_PRINT_INT; ! } else if (spec.conversion() == Conversion.DECIMAL_INTEGER && argType == long.class) { ! return SPECIFIER_PRINT_LONG; ! } else if (spec.conversion() == Conversion.DECIMAL_INTEGER && argType == byte.class) { ! return SPECIFIER_PRINT_BYTE; ! } else if (spec.conversion() == Conversion.DECIMAL_INTEGER && argType == short.class) { ! return SPECIFIER_PRINT_SHORT; ! } else if (spec.conversion() == Conversion.DECIMAL_FLOAT && argType == float.class) { ! return SPECIFIER_PRINT_FLOAT; ! } else if (spec.conversion() == Conversion.DECIMAL_FLOAT && argType == double.class) { ! return SPECIFIER_PRINT_DOUBLE; ! } else { ! return SPECIFIER_PRINT; ! } ! } ! private static MethodHandle fallbackMethodHandle(MethodHandles.Lookup lookup, String name, ! MethodType methodType, String format, boolean isStringMethod, ! boolean hasLocaleArg, boolean isVarArgs) { ! if (isStringMethod) { ! MethodHandle handle = findStaticMethodHandle(lookup, String.class, name, ! hasLocaleArg ? methodType(String.class, Locale.class, String.class, Object[].class) ! : methodType(String.class, String.class, Object[].class)); ! return wrapHandle(handle, hasLocaleArg ? 1 : 0, format, methodType, isVarArgs); ! } ! Class<?> type = methodType.parameterType(0); ! MethodHandle handle = findVirtualMethodHandle(lookup, type, name, ! hasLocaleArg ? methodType(type, Locale.class, String.class, Object[].class) ! : methodType(type, String.class, Object[].class)); ! return wrapHandle(handle, hasLocaleArg ? 2 : 1, format, methodType, isVarArgs); ! } ! private static MethodHandle wrapHandle(MethodHandle handle, int formatArgIndex, String format, MethodType methodType, boolean isVarArg) { ! MethodHandle h = MethodHandles.insertArguments(handle, formatArgIndex, format); ! if (!isVarArg) { ! h = h.asCollector(Object[].class, methodType.parameterCount() - formatArgIndex); ! } ! return h.asType(methodType); ! } ! private static boolean isVarArgsType(MethodType methodType, boolean isStringMethod, boolean hasLocaleArg) { ! int expectedArrayArgument = (isStringMethod ? 0 : 1) + (hasLocaleArg ? 1 : 0); ! return methodType.parameterCount() == expectedArrayArgument + 1 ! && methodType.parameterType(expectedArrayArgument) == Object[].class; ! } ! private static MethodHandle findVirtualMethodHandle(MethodHandles.Lookup lookup, Class<?> type, String name, MethodType methodType) { ! try { ! return lookup.findVirtual(type, name, methodType); ! } catch (NoSuchMethodException | IllegalAccessException e) { ! throw new RuntimeException(e); ! } ! } ! private static MethodHandle findStaticMethodHandle(MethodHandles.Lookup lookup, Class<?> type, String name, MethodType methodType) { ! try { ! return lookup.findStatic(type, name, methodType); ! } catch (NoSuchMethodException | IllegalAccessException e) { ! throw new RuntimeException(e); } } }
< prev index next >