From 96b2d4e7d67ba159e3bcdb3040907e84563066f3 Mon Sep 17 00:00:00 2001 From: Alexey Andreev Date: Sun, 28 Jan 2018 19:19:45 +0300 Subject: [PATCH] Support o and x format specifiers in String.format --- .../org/teavm/classlib/impl/IntegerUtil.java | 24 +++++- .../teavm/classlib/java/lang/TInteger.java | 6 +- .../org/teavm/classlib/java/lang/TLong.java | 22 +----- .../teavm/classlib/java/util/TFormatter.java | 79 ++++++++++++++++--- .../classlib/java/util/FormatterTest.java | 60 ++++++++++++++ 5 files changed, 154 insertions(+), 37 deletions(-) diff --git a/classlib/src/main/java/org/teavm/classlib/impl/IntegerUtil.java b/classlib/src/main/java/org/teavm/classlib/impl/IntegerUtil.java index ad4329e2f..c5e2271f1 100644 --- a/classlib/src/main/java/org/teavm/classlib/impl/IntegerUtil.java +++ b/classlib/src/main/java/org/teavm/classlib/impl/IntegerUtil.java @@ -21,14 +21,14 @@ public final class IntegerUtil { private IntegerUtil() { } - public static String toUnsignedLogRadixString(int value, int radixLog2, int size) { + public static String toUnsignedLogRadixString(int value, int radixLog2) { if (value == 0) { return "0"; } int radix = 1 << radixLog2; int mask = radix - 1; - int sz = (size - Integer.numberOfLeadingZeros(value) + radixLog2 - 1) / radixLog2; + int sz = (Integer.SIZE - Integer.numberOfLeadingZeros(value) + radixLog2 - 1) / radixLog2; char[] chars = new char[sz]; int pos = (sz - 1) * radixLog2; @@ -40,4 +40,24 @@ public final class IntegerUtil { return new String(chars); } + + public static String toUnsignedLogRadixString(long value, int radixLog2) { + if (value == 0) { + return "0"; + } + + int radix = 1 << radixLog2; + int mask = radix - 1; + int sz = (Long.SIZE - Long.numberOfLeadingZeros(value) + radixLog2 - 1) / radixLog2; + char[] chars = new char[sz]; + + long pos = (sz - 1) * radixLog2; + int target = 0; + while (pos >= 0) { + chars[target++] = TCharacter.forDigit((int) (value >>> pos) & mask, radix); + pos -= radixLog2; + } + + return new String(chars); + } } diff --git a/classlib/src/main/java/org/teavm/classlib/java/lang/TInteger.java b/classlib/src/main/java/org/teavm/classlib/java/lang/TInteger.java index ab8f82b1f..aa4cc04ce 100644 --- a/classlib/src/main/java/org/teavm/classlib/java/lang/TInteger.java +++ b/classlib/src/main/java/org/teavm/classlib/java/lang/TInteger.java @@ -41,15 +41,15 @@ public class TInteger extends TNumber implements TComparable { } public static String toHexString(int i) { - return toUnsignedLogRadixString(i, 4, SIZE); + return toUnsignedLogRadixString(i, 4); } public static String toOctalString(int i) { - return toUnsignedLogRadixString(i, 3, SIZE); + return toUnsignedLogRadixString(i, 3); } public static String toBinaryString(int i) { - return toUnsignedLogRadixString(i, 1, SIZE); + return toUnsignedLogRadixString(i, 1); } public static String toString(int i) { diff --git a/classlib/src/main/java/org/teavm/classlib/java/lang/TLong.java b/classlib/src/main/java/org/teavm/classlib/java/lang/TLong.java index 72225116c..3ba61b952 100644 --- a/classlib/src/main/java/org/teavm/classlib/java/lang/TLong.java +++ b/classlib/src/main/java/org/teavm/classlib/java/lang/TLong.java @@ -15,6 +15,8 @@ */ package org.teavm.classlib.java.lang; +import static org.teavm.classlib.impl.IntegerUtil.toUnsignedLogRadixString; + public class TLong extends TNumber implements TComparable { public static final long MIN_VALUE = -0x8000000000000000L; public static final long MAX_VALUE = 0x7FFFFFFFFFFFFFFFL; @@ -183,26 +185,6 @@ public class TLong extends TNumber implements TComparable { return toUnsignedLogRadixString(i, 1); } - private static String toUnsignedLogRadixString(long value, int radixLog2) { - if (value == 0) { - return "0"; - } - - int radix = 1 << radixLog2; - int mask = radix - 1; - int sz = (SIZE - numberOfLeadingZeros(value) + radixLog2 - 1) / radixLog2; - char[] chars = new char[sz]; - - long pos = (sz - 1) * radixLog2; - int target = 0; - while (pos >= 0) { - chars[target++] = TCharacter.forDigit((int) (value >>> pos) & mask, radix); - pos -= radixLog2; - } - - return new String(chars); - } - public static String toString(long value) { return new TStringBuilder().append(value).toString(); } diff --git a/classlib/src/main/java/org/teavm/classlib/java/util/TFormatter.java b/classlib/src/main/java/org/teavm/classlib/java/util/TFormatter.java index 8cda19575..01fb15661 100644 --- a/classlib/src/main/java/org/teavm/classlib/java/util/TFormatter.java +++ b/classlib/src/main/java/org/teavm/classlib/java/util/TFormatter.java @@ -30,6 +30,7 @@ import java.util.FormatFlagsConversionMismatchException; import java.util.IllegalFormatConversionException; import java.util.Locale; import java.util.UnknownFormatConversionException; +import org.teavm.classlib.impl.IntegerUtil; public final class TFormatter implements Closeable, Flushable { private Locale locale; @@ -149,6 +150,8 @@ public final class TFormatter implements Closeable, Flushable { | TFormattableFlags.LEADING_SPACE | TFormattableFlags.ZERO_PADDED | TFormattableFlags.PARENTHESIZED_NEGATIVE | TFormattableFlags.SIGNED | TFormattableFlags.GROUPING_SEPARATOR; + private static final int MASK_FOR_INT_RADIX_FORMAT = MASK_FOR_GENERAL_FORMAT + | TFormattableFlags.ZERO_PADDED | TFormattableFlags.PARENTHESIZED_NEGATIVE; private TFormatter formatter; Appendable out; Locale locale; @@ -223,6 +226,20 @@ public final class TFormatter implements Closeable, Flushable { formatDecimalInt(specifier, true); break; + case 'o': + formatRadixInt(specifier, 3, false); + break; + case 'O': + formatRadixInt(specifier, 3, true); + break; + + case 'x': + formatRadixInt(specifier, 4, false); + break; + case 'X': + formatRadixInt(specifier, 4, true); + break; + default: throw new UnknownFormatConversionException(String.valueOf(specifier)); } @@ -288,18 +305,7 @@ public final class TFormatter implements Closeable, Flushable { private void formatDecimalInt(char specifier, boolean upperCase) throws IOException { verifyFlags(specifier, MASK_FOR_INT_DECIMAL_FORMAT); - if ((flags & TFormattableFlags.SIGNED) != 0 && (flags & TFormattableFlags.LEADING_SPACE) != 0) { - throw new TIllegalFormatFlagsException("+ "); - } - if ((flags & TFormattableFlags.ZERO_PADDED) != 0 && (flags & TFormattableFlags.LEFT_JUSTIFY) != 0) { - throw new TIllegalFormatFlagsException("0-"); - } - if (precision >= 0) { - throw new TIllegalFormatPrecisionException(precision); - } - if ((flags & TFormattableFlags.LEFT_JUSTIFY) != 0 && width < 0) { - throw new TMissingFormatWidthException(format.substring(formatSpecifierStart, index)); - } + verifyIntFlags(); String str; Object arg = args[argumentIndex]; @@ -371,6 +377,55 @@ public final class TFormatter implements Closeable, Flushable { formatGivenString(upperCase, sb.toString()); } + private void formatRadixInt(char specifier, int radixLog2, boolean upperCase) throws IOException { + verifyFlags(specifier, MASK_FOR_INT_RADIX_FORMAT); + verifyIntFlags(); + + String str; + Object arg = args[argumentIndex]; + if (arg instanceof Long) { + str = IntegerUtil.toUnsignedLogRadixString((Long) arg, radixLog2); + } else if (arg instanceof Integer) { + str = IntegerUtil.toUnsignedLogRadixString((Integer) arg, radixLog2); + } else if (arg instanceof Short) { + str = IntegerUtil.toUnsignedLogRadixString((Short) arg & 0xFFFF, radixLog2); + } else if (arg instanceof Byte) { + str = IntegerUtil.toUnsignedLogRadixString((Byte) arg & 0xFF, radixLog2); + } else { + throw new IllegalFormatConversionException(specifier, arg != null ? arg.getClass() : null); + } + + StringBuilder sb = new StringBuilder(); + if ((flags & TFormattableFlags.ALTERNATE) != 0) { + String prefix = radixLog2 == 4 ? "0x" : "0"; + str = prefix + str; + } + + if ((flags & TFormattableFlags.ZERO_PADDED) != 0) { + for (int i = str.length(); i < width; ++i) { + sb.append(Character.forDigit(0, 10)); + } + } + sb.append(str); + + formatGivenString(upperCase, sb.toString()); + } + + private void verifyIntFlags() { + if ((flags & TFormattableFlags.SIGNED) != 0 && (flags & TFormattableFlags.LEADING_SPACE) != 0) { + throw new TIllegalFormatFlagsException("+ "); + } + if ((flags & TFormattableFlags.ZERO_PADDED) != 0 && (flags & TFormattableFlags.LEFT_JUSTIFY) != 0) { + throw new TIllegalFormatFlagsException("0-"); + } + if (precision >= 0) { + throw new TIllegalFormatPrecisionException(precision); + } + if ((flags & TFormattableFlags.LEFT_JUSTIFY) != 0 && width < 0) { + throw new TMissingFormatWidthException(format.substring(formatSpecifierStart, index)); + } + } + private void formatGivenString(boolean upperCase, String str) throws IOException { if (precision > 0) { str = str.substring(0, precision); diff --git a/tests/src/test/java/org/teavm/classlib/java/util/FormatterTest.java b/tests/src/test/java/org/teavm/classlib/java/util/FormatterTest.java index c95dfb308..a88dafba4 100644 --- a/tests/src/test/java/org/teavm/classlib/java/util/FormatterTest.java +++ b/tests/src/test/java/org/teavm/classlib/java/util/FormatterTest.java @@ -190,4 +190,64 @@ public class FormatterTest { assertEquals(2, e.getPrecision()); } } + + @Test + public void formatsOctalInteger() { + assertEquals("1 2 3 4", new Formatter().format("%o %o %o %o", (byte) 1, (short) 2, 3, 4L).toString()); + + assertEquals("00027", new Formatter().format("%05o", 23).toString()); + assertEquals("0173", new Formatter().format("%#o", 123).toString()); + + try { + new Formatter().format("%-01o", 23); + fail("Should have thrown exception 1"); + } catch (IllegalFormatFlagsException e) { + assertTrue(e.getFlags().contains("-")); + assertTrue(e.getFlags().contains("0")); + } + + try { + new Formatter().format("%-o", 23); + fail("Should have thrown exception 2"); + } catch (MissingFormatWidthException e) { + assertTrue(e.getFormatSpecifier().contains("o")); + } + + try { + new Formatter().format("%1.2o", 23); + fail("Should have thrown exception 3"); + } catch (IllegalFormatPrecisionException e) { + assertEquals(2, e.getPrecision()); + } + } + + @Test + public void formatsHexInteger() { + assertEquals("1 2 3 4", new Formatter().format("%x %x %x %x", (byte) 1, (short) 2, 3, 4L).toString()); + + assertEquals("00017", new Formatter().format("%05x", 23).toString()); + assertEquals("0x7b", new Formatter().format("%#x", 123).toString()); + + try { + new Formatter().format("%-01x", 23); + fail("Should have thrown exception 1"); + } catch (IllegalFormatFlagsException e) { + assertTrue(e.getFlags().contains("-")); + assertTrue(e.getFlags().contains("0")); + } + + try { + new Formatter().format("%-x", 23); + fail("Should have thrown exception 2"); + } catch (MissingFormatWidthException e) { + assertTrue(e.getFormatSpecifier().contains("x")); + } + + try { + new Formatter().format("%1.2x", 23); + fail("Should have thrown exception 3"); + } catch (IllegalFormatPrecisionException e) { + assertEquals(2, e.getPrecision()); + } + } }