Implement java.util.Formatter for subset of available specifiers

This commit is contained in:
Alexey Andreev 2017-10-15 15:41:11 +03:00
parent 25011ee7a6
commit 5109691a8d
16 changed files with 1084 additions and 6 deletions

View File

@ -97,7 +97,7 @@ public abstract class TNumberFormat extends TFormat {
return TLocale.getAvailableLocales();
}
public final static TNumberFormat getIntegerInstance() {
public static TNumberFormat getIntegerInstance() {
return getIntegerInstance(TLocale.getDefault());
}
@ -111,7 +111,7 @@ public abstract class TNumberFormat extends TFormat {
return format;
}
public final static TNumberFormat getInstance() {
public static TNumberFormat getInstance() {
return getNumberInstance();
}
@ -135,7 +135,7 @@ public abstract class TNumberFormat extends TFormat {
return minimumIntegerDigits;
}
public final static TNumberFormat getNumberInstance() {
public static TNumberFormat getNumberInstance() {
return getNumberInstance(TLocale.getDefault());
}
@ -144,7 +144,7 @@ public abstract class TNumberFormat extends TFormat {
return new TDecimalFormat(pattern, new TDecimalFormatSymbols(locale));
}
public final static TNumberFormat getPercentInstance() {
public static TNumberFormat getPercentInstance() {
return getPercentInstance(TLocale.getDefault());
}
@ -153,11 +153,11 @@ public abstract class TNumberFormat extends TFormat {
return new TDecimalFormat(pattern, new TDecimalFormatSymbols(locale));
}
public final static TNumberFormat getCurrencyInstance() {
public static TNumberFormat getCurrencyInstance() {
return getCurrencyInstance(TLocale.getDefault());
}
public final static TNumberFormat getCurrencyInstance(TLocale locale) {
public static TNumberFormat getCurrencyInstance(TLocale locale) {
String pattern = CLDRHelper.resolveCurrencyFormat(locale.getLanguage(), locale.getCountry());
return new TDecimalFormat(pattern, new TDecimalFormatSymbols(locale));
}

View File

@ -0,0 +1,29 @@
/*
* Copyright 2017 Alexey Andreev.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.teavm.classlib.java.util;
public class TDuplicateFormatFlagsException extends TIllegalFormatException {
private String flags;
public TDuplicateFormatFlagsException(String flags) {
super("Duplicate format flags: " + flags);
this.flags = flags;
}
public String getFlags() {
return flags;
}
}

View File

@ -0,0 +1,35 @@
/*
* Copyright 2017 Alexey Andreev.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.teavm.classlib.java.util;
public class TFormatFlagsConversionMismatchException extends TIllegalFormatException {
private String flags;
private char conversion;
public TFormatFlagsConversionMismatchException(String flags, char conversion) {
super("Illegal format flags " + flags + " for conversion " + conversion);
this.flags = flags;
this.conversion = conversion;
}
public String getFlags() {
return flags;
}
public char getConversion() {
return conversion;
}
}

View File

@ -0,0 +1,20 @@
/*
* Copyright 2017 Alexey Andreev.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.teavm.classlib.java.util;
public interface TFormattable {
void formatTo(TFormatter formatter, int flags, int width, int precision);
}

View File

@ -0,0 +1,32 @@
/*
* Copyright 2017 Alexey Andreev.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.teavm.classlib.java.util;
public final class TFormattableFlags {
private TFormattableFlags() {
}
public static final int LEFT_JUSTIFY = 1;
public static final int UPPERCASE = 2;
public static final int ALTERNATE = 4;
static final int SIGNED = 8;
static final int LEADING_SPACE = 16;
static final int ZERO_PADDED = 32;
static final int GROUPING_SEPARATOR = 64;
static final int PARENTHESIZED_NEGATIVE = 128;
static final int PREVIOUS_ARGUMENT = 256;
}

View File

@ -0,0 +1,518 @@
/*
* Copyright 2017 Alexey Andreev.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.teavm.classlib.java.util;
import java.io.Closeable;
import java.io.Flushable;
import java.io.IOException;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.io.PrintStream;
import java.io.UnsupportedEncodingException;
import java.text.DecimalFormat;
import java.text.DecimalFormatSymbols;
import java.text.NumberFormat;
import java.util.DuplicateFormatFlagsException;
import java.util.FormatFlagsConversionMismatchException;
import java.util.IllegalFormatConversionException;
import java.util.Locale;
import java.util.UnknownFormatConversionException;
public final class TFormatter implements Closeable, Flushable {
private Locale locale;
private Appendable out;
private IOException ioException;
public TFormatter() {
this(Locale.getDefault());
}
public TFormatter(Appendable a) {
this(a, Locale.getDefault());
}
public TFormatter(Locale l) {
this(new StringBuilder(), l);
}
public TFormatter(Appendable a, Locale l) {
out = a;
locale = l;
}
public TFormatter(PrintStream ps) {
this(new OutputStreamWriter(ps));
}
public TFormatter(OutputStream os) {
this(new OutputStreamWriter(os));
}
public TFormatter(OutputStream os, String csn) throws UnsupportedEncodingException {
this(new OutputStreamWriter(os, csn));
}
public TFormatter(OutputStream os, String csn, Locale l) throws UnsupportedEncodingException {
this(new OutputStreamWriter(os, csn), l);
}
public Locale locale() {
requireOpen();
return locale;
}
public Appendable out() {
requireOpen();
return out;
}
private void requireOpen() {
if (out == null) {
throw new TFormatterClosedException();
}
}
@Override
public String toString() {
requireOpen();
return out.toString();
}
@Override
public void flush() {
requireOpen();
if (out instanceof Flushable) {
try {
((Flushable) out).flush();
} catch (IOException e) {
ioException = e;
}
}
}
@Override
public void close() {
requireOpen();
try {
if (out instanceof Closeable) {
((Closeable) out).close();
}
} catch (IOException e) {
ioException = e;
} finally {
out = null;
}
}
public IOException ioException() {
return ioException;
}
public TFormatter format(String format, Object... args) {
return format(locale, format, args);
}
public TFormatter format(Locale l, String format, Object... args) {
requireOpen();
try {
if (args == null) {
args = new Object[1];
}
new FormatWriter(this, out, l, format, args).write();
} catch (IOException e) {
ioException = e;
}
return this;
}
static class FormatWriter {
private static final String FORMAT_FLAGS = "--#+ 0,(<";
private static final int MASK_FOR_GENERAL_FORMAT =
TFormattableFlags.LEFT_JUSTIFY | TFormattableFlags.ALTERNATE
| TFormattableFlags.UPPERCASE | TFormattableFlags.PREVIOUS_ARGUMENT;
private static final int MASK_FOR_CHAR_FORMAT =
TFormattableFlags.LEFT_JUSTIFY | TFormattableFlags.UPPERCASE | TFormattableFlags.PREVIOUS_ARGUMENT;
private static final int MASK_FOR_INT_DECIMAL_FORMAT = MASK_FOR_GENERAL_FORMAT ^ TFormattableFlags.ALTERNATE
| TFormattableFlags.LEADING_SPACE | TFormattableFlags.ZERO_PADDED
| TFormattableFlags.PARENTHESIZED_NEGATIVE | TFormattableFlags.SIGNED
| TFormattableFlags.GROUPING_SEPARATOR;
private TFormatter formatter;
Appendable out;
Locale locale;
String format;
Object[] args;
int index;
int formatSpecifierStart;
int defaultArgumentIndex;
int argumentIndex;
int previousArgumentIndex;
int width;
int precision;
int flags;
FormatWriter(TFormatter formatter, Appendable out, Locale locale, String format, Object[] args) {
this.formatter = formatter;
this.out = out;
this.locale = locale;
this.format = format;
this.args = args;
}
void write() throws IOException {
while (true) {
int next = format.indexOf('%', index);
if (next < 0) {
out.append(format.substring(index));
break;
}
out.append(format.substring(index, next));
index = next + 1;
formatSpecifierStart = index;
char specifier = parseFormatSpecifier();
configureFormat();
formatValue(specifier);
}
}
private void formatValue(char specifier) throws IOException {
switch (specifier) {
case 'b':
formatBoolean(specifier, false);
break;
case 'B':
formatBoolean(specifier, true);
break;
case 'h':
formatHex(specifier, false);
break;
case 'H':
formatHex(specifier, true);
break;
case 's':
formatString(specifier, false);
break;
case 'S':
formatString(specifier, true);
break;
case 'c':
formatChar(specifier, false);
break;
case 'C':
formatChar(specifier, true);
break;
case 'd':
formatDecimalInt(specifier, false);
break;
case 'D':
formatDecimalInt(specifier, true);
break;
default:
throw new UnknownFormatConversionException(String.valueOf(specifier));
}
}
private void formatBoolean(char specifier, boolean upperCase) throws IOException {
verifyFlagsForGeneralFormat(specifier);
Object arg = args[argumentIndex];
String s = Boolean.toString(arg instanceof Boolean ? (Boolean) arg : arg != null);
formatGivenString(upperCase, s);
}
private void formatHex(char specifier, boolean upperCase) throws IOException {
verifyFlagsForGeneralFormat(specifier);
Object arg = args[argumentIndex];
String s = arg != null ? Integer.toHexString(arg.hashCode()) : "null";
formatGivenString(upperCase, s);
}
private void formatString(char specifier, boolean upperCase) throws IOException {
verifyFlagsForGeneralFormat(specifier);
Object arg = args[argumentIndex];
if (arg instanceof TFormattable) {
int flagsToPass = flags & 7;
if (upperCase) {
flagsToPass |= TFormattableFlags.UPPERCASE;
}
((TFormattable) arg).formatTo(formatter, flagsToPass, width, precision);
} else {
formatGivenString(upperCase, String.valueOf(arg));
}
}
private void formatChar(char specifier, boolean upperCase) throws IOException {
verifyFlags(specifier, MASK_FOR_CHAR_FORMAT);
Object arg = args[argumentIndex];
if (precision >= 0) {
throw new TIllegalFormatPrecisionException(precision);
}
int c;
if (arg instanceof Character) {
c = (Character) arg;
} else if (arg instanceof Byte) {
c = (char) (byte) arg;
} else if (arg instanceof Short) {
c = (char) (short) arg;
} else if (arg instanceof Integer) {
c = (int) arg;
if (!Character.isValidCodePoint(c)) {
throw new TIllegalFormatCodePointException(c);
}
} else if (arg == null) {
formatGivenString(upperCase, "null");
return;
} else {
throw new IllegalFormatConversionException(specifier, arg != null ? arg.getClass() : null);
}
formatGivenString(upperCase, new String(Character.toChars(c)));
}
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));
}
String str;
Object arg = args[argumentIndex];
boolean negative;
if (arg instanceof Long) {
long value = (Long) arg;
str = Long.toString(Math.abs(value));
negative = value < 0;
} else if (arg instanceof Integer || arg instanceof Byte || arg instanceof Short) {
int value = ((Number) arg).intValue();
str = Integer.toString(Math.abs(value));
negative = value < 0;
} else {
throw new IllegalFormatConversionException(specifier, arg != null ? arg.getClass() : null);
}
int additionalSymbols = 0;
StringBuilder sb = new StringBuilder();
if (negative) {
if ((flags & TFormattableFlags.PARENTHESIZED_NEGATIVE) != 0) {
sb.append('(');
additionalSymbols += 2;
} else {
sb.append('-');
additionalSymbols++;
}
} else {
if ((flags & TFormattableFlags.SIGNED) != 0) {
sb.append('+');
additionalSymbols++;
} else if ((flags & TFormattableFlags.LEADING_SPACE) != 0) {
sb.append(' ');
additionalSymbols++;
}
}
StringBuilder valueSb = new StringBuilder();
if ((flags & TFormattableFlags.GROUPING_SEPARATOR) != 0) {
char separator = new DecimalFormatSymbols(locale).getGroupingSeparator();
int size = ((DecimalFormat) NumberFormat.getNumberInstance(locale)).getGroupingSize();
int offset = str.length() % size;
if (offset == 0) {
offset = size;
}
int prev = 0;
for (int i = offset; i < str.length(); i += size) {
valueSb.append(str.substring(prev, i));
valueSb.append(separator);
prev = i;
}
valueSb.append(str.substring(prev));
} else {
valueSb.append(str);
}
if ((flags & TFormattableFlags.ZERO_PADDED) != 0) {
int actual = valueSb.length() + additionalSymbols;
for (int i = actual; i < width; ++i) {
sb.append(Character.forDigit(0, 10));
}
}
sb.append(valueSb);
if (negative && (flags & TFormattableFlags.PARENTHESIZED_NEGATIVE) != 0) {
sb.append(')');
}
formatGivenString(upperCase, sb.toString());
}
private void formatGivenString(boolean upperCase, String str) throws IOException {
if (precision > 0) {
str = str.substring(0, precision);
}
if (upperCase) {
str = str.toUpperCase();
}
if ((flags & TFormattableFlags.LEFT_JUSTIFY) != 0) {
out.append(str);
mayBeAppendSpaces(str);
} else {
mayBeAppendSpaces(str);
out.append(str);
}
}
private void verifyFlagsForGeneralFormat(char conversion) {
verifyFlags(conversion, MASK_FOR_GENERAL_FORMAT);
}
private void verifyFlags(char conversion, int mask) {
if ((flags | mask) != mask) {
throw new FormatFlagsConversionMismatchException(flagsToString(flags & ~mask), conversion);
}
}
private String flagsToString(int flags) {
int flagIndex = Integer.numberOfTrailingZeros(flags);
return String.valueOf(FORMAT_FLAGS.charAt(flagIndex));
}
private void mayBeAppendSpaces(String str) throws IOException {
if (width > str.length()) {
int diff = width - str.length();
StringBuilder sb = new StringBuilder(diff);
for (int i = 0; i < diff; ++i) {
sb.append(' ');
}
out.append(sb);
}
}
private void configureFormat() {
if ((flags & TFormattableFlags.PREVIOUS_ARGUMENT) != 0) {
argumentIndex = Math.max(0, previousArgumentIndex);
}
if (argumentIndex == -1) {
argumentIndex = defaultArgumentIndex++;
}
previousArgumentIndex = argumentIndex;
}
private char parseFormatSpecifier() {
flags = 0;
argumentIndex = -1;
width = -1;
precision = -1;
char c = format.charAt(index);
if (c != '0' && isDigit(c)) {
int n = readInt();
if (index < format.length() && format.charAt(index) == '$') {
index++;
argumentIndex = n - 1;
} else {
width = n;
}
}
parseFlags();
if (width < 0 && index < format.length() && isDigit(format.charAt(index))) {
width = readInt();
}
if (index < format.length() && format.charAt(index) == '.') {
index++;
if (index >= format.length() || !isDigit(format.charAt(index))) {
throw new UnknownFormatConversionException(String.valueOf(format.charAt(index - 1)));
}
precision = readInt();
}
if (index >= format.length()) {
throw new UnknownFormatConversionException(String.valueOf(format.charAt(format.length() - 1)));
}
return format.charAt(index++);
}
private void parseFlags() {
while (index < format.length()) {
char c = format.charAt(index);
int flag;
switch (c) {
case '-':
flag = TFormattableFlags.LEFT_JUSTIFY;
break;
case '#':
flag = TFormattableFlags.ALTERNATE;
break;
case '+':
flag = TFormattableFlags.SIGNED;
break;
case ' ':
flag = TFormattableFlags.LEADING_SPACE;
break;
case '0':
flag = TFormattableFlags.ZERO_PADDED;
break;
case ',':
flag = TFormattableFlags.GROUPING_SEPARATOR;
break;
case '(':
flag = TFormattableFlags.PARENTHESIZED_NEGATIVE;
break;
case '<':
flag = TFormattableFlags.PREVIOUS_ARGUMENT;
break;
default:
return;
}
if ((flags & flag) != 0) {
throw new DuplicateFormatFlagsException(String.valueOf(c));
}
flags |= flag;
index++;
}
}
private int readInt() {
int result = 0;
while (index < format.length() && isDigit(format.charAt(index))) {
result = result * 10 + (format.charAt(index++) - '0');
}
return result;
}
private static boolean isDigit(char c) {
return c >= '0' && c <= '9';
}
}
}

View File

@ -0,0 +1,21 @@
/*
* Copyright 2017 Alexey Andreev.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.teavm.classlib.java.util;
public class TFormatterClosedException extends IllegalStateException {
public TFormatterClosedException() {
}
}

View File

@ -0,0 +1,29 @@
/*
* Copyright 2017 Alexey Andreev.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.teavm.classlib.java.util;
public class TIllegalFormatCodePointException extends TIllegalFormatException {
private int codePoint;
public TIllegalFormatCodePointException(int codePoint) {
super("Can't convert code point " + codePoint + " to char");
this.codePoint = codePoint;
}
public int getCodePoint() {
return codePoint;
}
}

View File

@ -0,0 +1,35 @@
/*
* Copyright 2017 Alexey Andreev.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.teavm.classlib.java.util;
public class TIllegalFormatConversionException extends TIllegalFormatException {
private char conversion;
private Class<?> argumentClass;
public TIllegalFormatConversionException(char conversion, Class<?> argumentClass) {
super("Can't format argument of " + argumentClass + " using " + conversion + " conversion");
this.conversion = conversion;
this.argumentClass = argumentClass;
}
public char getConversion() {
return conversion;
}
public Class<?> getArgumentClass() {
return argumentClass;
}
}

View File

@ -0,0 +1,25 @@
/*
* Copyright 2017 Alexey Andreev.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.teavm.classlib.java.util;
public class TIllegalFormatException extends IllegalArgumentException {
TIllegalFormatException() {
}
TIllegalFormatException(String s) {
super(s);
}
}

View File

@ -0,0 +1,29 @@
/*
* Copyright 2017 Alexey Andreev.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.teavm.classlib.java.util;
public class TIllegalFormatFlagsException extends TIllegalFormatException {
private String flags;
public TIllegalFormatFlagsException(String flags) {
super("Illegal format flags: " + flags);
this.flags = flags;
}
public String getFlags() {
return flags;
}
}

View File

@ -0,0 +1,29 @@
/*
* Copyright 2017 Alexey Andreev.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.teavm.classlib.java.util;
public class TIllegalFormatPrecisionException extends TIllegalFormatException {
private int precision;
public TIllegalFormatPrecisionException(int precision) {
super("Illegal precision: " + precision);
this.precision = precision;
}
public int getPrecision() {
return precision;
}
}

View File

@ -0,0 +1,25 @@
/*
* Copyright 2017 Alexey Andreev.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.teavm.classlib.java.util;
public class TIllegalPrecisionException extends TIllegalFormatException {
private int precision;
public TIllegalPrecisionException(int precision) {
super("Illegal precision: " + precision);
this.precision = precision;
}
}

View File

@ -0,0 +1,29 @@
/*
* Copyright 2017 Alexey Andreev.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.teavm.classlib.java.util;
public class TMissingFormatWidthException extends TIllegalFormatException {
private String formatSpecifier;
public TMissingFormatWidthException(String formatSpecifier) {
super("Missing format with for specifier " + formatSpecifier);
this.formatSpecifier = formatSpecifier;
}
public String getFormatSpecifier() {
return formatSpecifier;
}
}

View File

@ -0,0 +1,29 @@
/*
* Copyright 2017 Alexey Andreev.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.teavm.classlib.java.util;
public class TUnknownFormatConversionException extends TIllegalFormatException {
private String conversion;
public TUnknownFormatConversionException(String conversion) {
super("Unknown format conversion: " + conversion);
this.conversion = conversion;
}
public String getConversion() {
return conversion;
}
}

View File

@ -0,0 +1,193 @@
/*
* Copyright 2017 Alexey Andreev.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.teavm.classlib.java.util;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
import java.util.DuplicateFormatFlagsException;
import java.util.FormatFlagsConversionMismatchException;
import java.util.Formattable;
import java.util.Formatter;
import java.util.IllegalFormatCodePointException;
import java.util.IllegalFormatConversionException;
import java.util.IllegalFormatFlagsException;
import java.util.IllegalFormatPrecisionException;
import java.util.Locale;
import java.util.MissingFormatWidthException;
import java.util.UnknownFormatConversionException;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.teavm.junit.TeaVMTestRunner;
@RunWith(TeaVMTestRunner.class)
public class FormatterTest {
@Test(expected = UnknownFormatConversionException.class)
public void unexpectedEndOfFormatString() {
new Formatter().format("%1", "foo");
}
@Test(expected = DuplicateFormatFlagsException.class)
public void duplicateFlag() {
new Formatter().format("%--s", "q");
}
@Test(expected = UnknownFormatConversionException.class)
public void noPrecisionAfterDot() {
new Formatter().format("%1.s", "q");
}
@Test
public void bothPreviousModifierAndArgumentIndexPresent() {
String result = new Formatter().format("%s %2$<s", "q", "w").toString();
assertEquals("q q", result);
}
@Test
public void formatsBoolean() {
assertEquals("true", new Formatter().format("%b", true).toString());
assertEquals("false", new Formatter().format("%b", false).toString());
assertEquals("true", new Formatter().format("%b", new Object()).toString());
assertEquals("false", new Formatter().format("%b", null).toString());
assertEquals(" true", new Formatter().format("%6b", true).toString());
assertEquals("true ", new Formatter().format("%-6b", true).toString());
assertEquals("true", new Formatter().format("%2b", true).toString());
assertEquals("tr", new Formatter().format("%2.2b", true).toString());
assertEquals(" tr", new Formatter().format("%4.2b", true).toString());
assertEquals("TRUE", new Formatter().format("%B", true).toString());
try {
new Formatter().format("%+b", true);
fail("Should have thrown exception");
} catch (FormatFlagsConversionMismatchException e) {
assertEquals("+", e.getFlags());
assertEquals('b', e.getConversion());
}
}
@Test
public void formatsString() {
assertEquals("23 foo", new Formatter().format("%s %s", 23, "foo").toString());
assertEquals("0:-1:-1", new Formatter().format("%s", new A()).toString());
assertEquals("0:2:-1", new Formatter().format("%2s", new A()).toString());
assertEquals("0:2:3", new Formatter().format("%2.3s", new A()).toString());
assertEquals("1:3:-1", new Formatter().format("%-3s", new A()).toString());
}
static class A implements Formattable {
@Override
public void formatTo(Formatter formatter, int flags, int width, int precision) {
formatter.format("%s", flags + ":" + width + ":" + precision);
}
}
@Test
public void formatsHashCode() {
assertEquals("18cc6 17C13", new Formatter().format("%h %H", "foo", "bar").toString());
}
@Test
public void respectsFormatArgumentOrder() {
String result = new Formatter().format("%s %s %<s %1$s %<s %s %1$s %s %<s", "a", "b", "c", "d").toString();
assertEquals("a b b a a c a d d", result);
}
@Test
public void formatsChar() {
assertEquals("x: Y:\uDBFF\uDFFF ", new Formatter().format("%c:%3C:%-3c", 'x', 'y', 0x10ffff).toString());
try {
new Formatter().format("%c", Integer.MAX_VALUE);
fail("IllegalFormatCodePointException expected");
} catch (IllegalFormatCodePointException e) {
assertEquals(Integer.MAX_VALUE, e.getCodePoint());
}
assertEquals("null", new Formatter().format("%c", new Object[] { null }).toString());
try {
new Formatter().format("%C", new A());
fail("IllegalFormatConversionException expected");
} catch (IllegalFormatConversionException e) {
assertEquals(A.class, e.getArgumentClass());
}
try {
new Formatter().format("%3.1c", 'X');
fail("IllegalFormatPrecisionException expected");
} catch (IllegalFormatPrecisionException e) {
assertEquals(1, e.getPrecision());
}
}
@Test
public void formatsDecimalInteger() {
assertEquals("1 2 3 4", new Formatter().format("%d %d %d %d", (byte) 1, (short) 2, 3, 4L).toString());
assertEquals("00023", new Formatter().format("%05d", 23).toString());
assertEquals("-0023", new Formatter().format("%05d", -23).toString());
assertEquals("00001,234", new Formatter(Locale.US).format("%0,9d", 1234).toString());
assertEquals("(001,234)", new Formatter(Locale.US).format("%0,(9d", -1234).toString());
assertEquals("1 12 123 1,234 12,345 123,456 1,234,567", new Formatter(Locale.US)
.format("%,d %,d %,d %,d %,d %,d %,d", 1, 12, 123, 1234, 12345, 123456, 1234567).toString());
assertEquals(" -123:-234 ", new Formatter().format("%6d:%-6d", -123, -234).toString());
assertEquals("+123 +0123 +0", new Formatter().format("%+d %+05d %+d", 123, 123, 0).toString());
assertEquals(": 123:-123:", new Formatter().format(":% d:% d:", 123, -123).toString());
try {
new Formatter().format("%#d", 23);
fail("Should have thrown exception 1");
} catch (FormatFlagsConversionMismatchException e) {
assertEquals("#", e.getFlags());
}
try {
new Formatter().format("% +d", 23);
fail("Should have thrown exception 2");
} catch (IllegalFormatFlagsException e) {
assertTrue(e.getFlags().contains("+"));
assertTrue(e.getFlags().contains(" "));
}
try {
new Formatter().format("%-01d", 23);
fail("Should have thrown exception 3");
} catch (IllegalFormatFlagsException e) {
assertTrue(e.getFlags().contains("-"));
assertTrue(e.getFlags().contains("0"));
}
try {
new Formatter().format("%-d", 23);
fail("Should have thrown exception 4");
} catch (MissingFormatWidthException e) {
assertTrue(e.getFormatSpecifier().contains("d"));
}
try {
new Formatter().format("%1.2d", 23);
fail("Should have thrown exception 5");
} catch (IllegalFormatPrecisionException e) {
assertEquals(2, e.getPrecision());
}
}
}