mirror of
https://github.com/konsoletyper/teavm.git
synced 2024-11-27 01:30:35 +08:00
Implement java.util.Formatter for subset of available specifiers
This commit is contained in:
parent
25011ee7a6
commit
5109691a8d
@ -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));
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
@ -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;
|
||||
}
|
@ -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';
|
||||
}
|
||||
}
|
||||
}
|
@ -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() {
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
@ -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());
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user