mirror of
https://github.com/corda/corda.git
synced 2025-01-19 11:16:54 +00:00
Merge pull request #427 from bgould/master
Added java.util.Formatter implementation. Basic/common formats work,
This commit is contained in:
commit
4bacddb20a
992
classpath/avian/FormatString.java
Normal file
992
classpath/avian/FormatString.java
Normal file
@ -0,0 +1,992 @@
|
||||
/* Copyright (c) 2008-2015, Avian Contributors
|
||||
|
||||
Permission to use, copy, modify, and/or distribute this software
|
||||
for any purpose with or without fee is hereby granted, provided
|
||||
that the above copyright notice and this permission notice appear
|
||||
in all copies.
|
||||
|
||||
There is NO WARRANTY for this software. See license.txt for
|
||||
details. */
|
||||
|
||||
package avian;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.IllegalFormatException;
|
||||
import java.util.List;
|
||||
|
||||
// ------------------------------------------------------------------------- //
|
||||
// things that must be done in order to call this semi-complete:
|
||||
// ------------------------------------------------------------------------- //
|
||||
// * get the date formatter working for individual fields at a minimum
|
||||
// ------------------------------------------------------------------------- //
|
||||
|
||||
/**
|
||||
* A Java flavored printf for classpath-challenged JVMs.
|
||||
*
|
||||
* Each instance of this class is a threadsafe pre-parsed format pattern. Refer
|
||||
* to the very detailed class description of java.util.Formatter in the OpenJDK
|
||||
* API documentation for an explanation of the supported formats.
|
||||
*
|
||||
* Should be easily portable to other Java runtimes that do not include the
|
||||
* printf functionality that was introduced in Java 5.
|
||||
*
|
||||
* Aims to be lightweight and reasonably fast, and provide reasonably complete
|
||||
* API compatibility with the OpenJDK implementation. To clarify what
|
||||
* "reasonably complete" means in this context, this implementation should
|
||||
* accept any valid format string that the OpenJDK version accepts. However,
|
||||
* it should not be relied upon to throw an Exception for every format pattern
|
||||
* that the OpenJDK implementation does.
|
||||
*
|
||||
* If your program's behavior relies on the side effects from an Exception
|
||||
* being thrown for an invalid format string, this might not be for you.
|
||||
*
|
||||
* Perhaps more troubling is the fact that the correct localization of numbers
|
||||
* and temporal values is barely even attempted, even though the parser accepts
|
||||
* the flags without even a warning. However, now you have been warned.
|
||||
*
|
||||
* @author bcg
|
||||
*/
|
||||
public final class FormatString {
|
||||
|
||||
/** Parses a format string and returns a compiled representation of it. */
|
||||
public static final FormatString compile(String fmt) {
|
||||
return new FormatString(fmt);
|
||||
}
|
||||
|
||||
/** The original string value that was parsed */
|
||||
public String source() {
|
||||
return _source;
|
||||
}
|
||||
|
||||
/** Processes the supplied arguments through the compiled format string
|
||||
and returns the result as a String. */
|
||||
public final String format(Object... args) {
|
||||
final StringBuilder bldr = new StringBuilder();
|
||||
try {
|
||||
format(bldr, args);
|
||||
return bldr.toString();
|
||||
} catch (IOException e) {
|
||||
throw new IllegalStateException(
|
||||
"Should not get IOException when writing to StringBuilder", e
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/** Processes the supplied arguments through the compiled format string
|
||||
and writes the result of each component directly to an Appendable */
|
||||
public final void format(final Appendable a, Object... fmt_args)
|
||||
throws IOException {
|
||||
final Object[] args = fmt_args != null ? fmt_args : new Object[0];
|
||||
int cntr = 0;
|
||||
for (final FmtCmpnt cmp : _components) {
|
||||
if (cmp._conversion == CONV_LITRL) {
|
||||
a.append(cmp._source);
|
||||
continue;
|
||||
}
|
||||
final Object arg;
|
||||
if (!acceptsArgument(cmp._conversion)) {
|
||||
arg = null;
|
||||
} else {
|
||||
final int index = cmp._argument_index;
|
||||
switch (index) {
|
||||
case AIDX_NONE:
|
||||
if ((cntr) >= args.length) {
|
||||
throw new IllegalFormatException(
|
||||
"Format specified at least " + (cntr+1) +
|
||||
" arguments, but " + cntr + " were supplied."
|
||||
);
|
||||
}
|
||||
arg = args[cntr++];
|
||||
break;
|
||||
case AIDX_PREV:
|
||||
arg = args[cntr];
|
||||
break;
|
||||
default:
|
||||
if (index < 1) {
|
||||
throw new IllegalArgumentException();
|
||||
} else if (index > args.length) {
|
||||
throw new IllegalArgumentException();
|
||||
} else {
|
||||
arg = args[index - 1];
|
||||
}
|
||||
}
|
||||
}
|
||||
convert(a, arg, cmp._conversion, cmp._flags, cmp._width, cmp._precision);
|
||||
}
|
||||
}
|
||||
|
||||
//- conversions
|
||||
static final byte CONV_LITRL = 0x0;
|
||||
static final byte CONV_NLINE = 0x1;
|
||||
static final byte CONV_PRCNT = 0x2;
|
||||
static final byte CONV_BOOLN = 0x3;
|
||||
static final byte CONV_DTIME = 0x4;
|
||||
static final byte CONV_STRNG = 0x5;
|
||||
static final byte CONV_HCODE = 0x6;
|
||||
static final byte CONV_CHRCT = 0x7;
|
||||
static final byte CONV_DECML = 0x8;
|
||||
static final byte CONV_OCTAL = 0x9;
|
||||
static final byte CONV_HXDEC = 0xA;
|
||||
static final byte CONV_CPSCI = 0xB;
|
||||
static final byte CONV_GNSCI = 0xC;
|
||||
static final byte CONV_FLOAT = 0xD;
|
||||
static final byte CONV_HXEXP = 0xE;
|
||||
|
||||
//- format component flags
|
||||
static final byte FLAG_FORCE_UPPER_CASE = (byte)(1<<7);
|
||||
static final byte FLAG_NEGATIVES_IN_PARENS = (byte)(1<<6); // ('(')
|
||||
static final byte FLAG_GROUPING_SEPARATORS = (byte)(1<<5); // (',')
|
||||
static final byte FLAG_LEADING_ZERO_PADDED = (byte)(1<<4); // ('0')
|
||||
static final byte FLAG_LEADING_SPACE_PADDED = (byte)(1<<3); // (' ')
|
||||
static final byte FLAG_ALWAYS_INCLUDES_SIGN = (byte)(1<<2); // ('+')
|
||||
static final byte FLAG_ALTERNATE_FORM = (byte)(1<<1); // ('#')
|
||||
static final byte FLAG_LEFT_JUSTIFIED = (byte)(1<<0); // ('-')
|
||||
|
||||
//- conversion capability flags
|
||||
static final byte CFLG_WDTH_SUPPRT = CONV_PRCNT;
|
||||
static final byte CFLG_ACCEPTS_ARG = CONV_BOOLN;
|
||||
static final byte CFLG_NUMERIC_VAL = CONV_DECML;
|
||||
static final byte CFLG_PREC_SUPPRT = CONV_STRNG;
|
||||
|
||||
//- special argument indices
|
||||
static final int AIDX_PREV = -1;
|
||||
static final int AIDX_NONE = 0;
|
||||
|
||||
/** the original serialized format string */
|
||||
private final String _source;
|
||||
|
||||
/** array of components parsed from the source string */
|
||||
private final FmtCmpnt[] _components;
|
||||
|
||||
/*/ keeping this private for now to encourage access through the static
|
||||
compile method, which might allow caching format string instances if it
|
||||
turns out there is an advantage to that. /*/
|
||||
/** Constructor */
|
||||
private FormatString(final String fmt) {
|
||||
this._source = fmt;
|
||||
final List<FmtCmpnt> cmps = new ArrayList<FmtCmpnt>();
|
||||
for ( int i = 0; (i = next(fmt, cmps, i)) > -1; );
|
||||
this._components = cmps.toArray(new FmtCmpnt[cmps.size()]);
|
||||
}
|
||||
|
||||
/** Iterates over the tokens in an input string to extract the components */
|
||||
private static final int next(
|
||||
final String fmt, final List<FmtCmpnt> cmps, final int startIndex) {
|
||||
final int strln = fmt.length();
|
||||
if (startIndex >= strln) {
|
||||
return -1;
|
||||
}
|
||||
final char c = fmt.charAt(startIndex);
|
||||
if (c == '%') {
|
||||
// this is the start of a specifier
|
||||
final FmtSpecBldr bldr = new FmtSpecBldr();
|
||||
for (int i = startIndex + 1; i < strln; i++) {
|
||||
final char ch = fmt.charAt(i);
|
||||
final FmtCmpnt cmp = bldr.append(ch);
|
||||
if (cmp != null) {
|
||||
cmps.add(cmp);
|
||||
return (i+1);
|
||||
}
|
||||
}
|
||||
throw new IllegalFormatException("Incomplete specifier at end of fmt");
|
||||
} else {
|
||||
// this is the start of a literal
|
||||
final StringBuilder literal = new StringBuilder();
|
||||
literal.append(c);
|
||||
for (int i = startIndex + 1; i < strln; i++) {
|
||||
final char ch = fmt.charAt(i);
|
||||
// write the current buffer if the next character starts a specifier
|
||||
if (ch == '%') {
|
||||
final FmtCmpnt cmp = new FmtCmpnt(literal.toString());
|
||||
cmps.add(cmp);
|
||||
return i;
|
||||
}
|
||||
literal.append(ch);
|
||||
}
|
||||
// write the current buffer if the end of the format has been reached
|
||||
final FmtCmpnt cmp = new FmtCmpnt(literal.toString());
|
||||
cmps.add(cmp);
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
/** Checks a flag byte to see if a given flag is set. Only FLAG_* constants
|
||||
from the enclosing class should be passed in for toCheck... otherwise the
|
||||
behavior is undefined. */
|
||||
static final boolean checkFlag(final byte flags, final byte toCheck) {
|
||||
return (flags & toCheck) != 0;
|
||||
}
|
||||
|
||||
/** Checks if a given conversion accepts(requires) an argument. Only the CONV_
|
||||
flags from the enclosing class should be passed in, otherwise the result
|
||||
of this method is undefined as should not be used. */
|
||||
static final boolean acceptsArgument(final byte conversion) {
|
||||
return conversion >= CFLG_ACCEPTS_ARG;
|
||||
}
|
||||
|
||||
/** Checks if a given conversion allows specifying a precision. Only the CONV_
|
||||
flags from the enclosing class should be passed in, otherwise the result
|
||||
of this method is undefined as should not be used. */
|
||||
static final boolean precisionSupported(final byte conversion) {
|
||||
return conversion >= CFLG_PREC_SUPPRT;
|
||||
}
|
||||
|
||||
/** Checks if a given conversion allows specifying a width. Only the CONV_
|
||||
flags from the enclosing class should be passed in, otherwise the result
|
||||
of this method is undefined and should not be trusted. */
|
||||
static final boolean widthSupported(final byte conversion) {
|
||||
return conversion >= CFLG_WDTH_SUPPRT;
|
||||
}
|
||||
|
||||
/** Checks if a given conversion expects a numeric value. Only the CONV_
|
||||
flags from the enclosing class should be passed in, otherwise the result
|
||||
of this method is undefined and should not be trusted. */
|
||||
static final boolean isNumeric(final byte conversion) {
|
||||
return conversion >= CFLG_NUMERIC_VAL;
|
||||
}
|
||||
|
||||
/** The newline character for the current platform. */
|
||||
static final String NEWLINE = System.getProperty("line.separator");
|
||||
|
||||
/** Performs conversion on the supplied argument */
|
||||
static final void convert(
|
||||
final Appendable appendable,
|
||||
final Object arg,
|
||||
final byte conversion,
|
||||
final byte flags,
|
||||
final int width,
|
||||
final int precision) throws IOException {
|
||||
int radix = 0;
|
||||
switch (conversion) {
|
||||
case CONV_LITRL:
|
||||
throw new IllegalArgumentException("cannot convert a literal");
|
||||
case CONV_NLINE:
|
||||
appendable.append(NEWLINE);
|
||||
return;
|
||||
case CONV_PRCNT:
|
||||
convertPercent(appendable, arg, flags, width, precision);
|
||||
return;
|
||||
case CONV_BOOLN:
|
||||
convertBoolean(appendable, arg, flags, width, precision);
|
||||
return;
|
||||
case CONV_DTIME:
|
||||
convertDate(appendable, arg, flags, width, precision);
|
||||
return;
|
||||
case CONV_STRNG:
|
||||
convertString(appendable, arg, flags, width, precision);
|
||||
return;
|
||||
case CONV_HCODE:
|
||||
convertHashcode(appendable, arg, flags, width, precision);
|
||||
return;
|
||||
case CONV_CHRCT:
|
||||
convertChar(appendable, arg, flags, width, precision);
|
||||
return;
|
||||
case CONV_DECML: if (radix == 0) { radix = 10; };
|
||||
case CONV_OCTAL: if (radix == 0) { radix = 8; };
|
||||
case CONV_HXDEC: if (radix == 0) { radix = 16; };
|
||||
if (arg instanceof Long) {
|
||||
convertLong(appendable, (Long) arg, flags, width, precision, radix);
|
||||
} else {
|
||||
convertInteger(appendable, arg, flags, width, precision, radix);
|
||||
}
|
||||
return;
|
||||
case CONV_CPSCI:
|
||||
case CONV_GNSCI:
|
||||
case CONV_FLOAT:
|
||||
case CONV_HXEXP:
|
||||
convertFloat(appendable, arg, flags, width, precision, 10);
|
||||
return;
|
||||
}
|
||||
throw new IllegalStateException("not implemented: " + conversion);
|
||||
}
|
||||
|
||||
static void convertPercent(
|
||||
final Appendable a,
|
||||
final Object arg,
|
||||
final byte flags,
|
||||
final int width,
|
||||
final int precision) throws IOException {
|
||||
final String val = "%";
|
||||
appendify(a, val, flags, width, precision);
|
||||
}
|
||||
|
||||
static void convertDate(
|
||||
final Appendable a,
|
||||
final Object arg,
|
||||
final byte flags,
|
||||
final int width,
|
||||
final int precision) throws IOException {
|
||||
final String val = (arg == null) ? "null" : arg.toString();
|
||||
appendify(a, val, flags, width, precision);
|
||||
}
|
||||
|
||||
static void convertString(
|
||||
final Appendable a,
|
||||
final Object arg,
|
||||
final byte flags,
|
||||
final int width,
|
||||
final int precision) throws IOException {
|
||||
final String val = (arg == null) ? "null" : arg.toString();
|
||||
appendify(a, val, flags, width, precision);
|
||||
}
|
||||
|
||||
static void convertHashcode(
|
||||
final Appendable a,
|
||||
final Object arg,
|
||||
final byte flags,
|
||||
final int width,
|
||||
final int precision) throws IOException {
|
||||
final String val = (arg == null)
|
||||
? "null"
|
||||
: Integer.toHexString(arg.hashCode());
|
||||
appendify(a, val, flags, width, precision);
|
||||
}
|
||||
|
||||
static void convertBoolean(
|
||||
final Appendable a,
|
||||
final Object arg,
|
||||
final byte flags,
|
||||
final int width,
|
||||
final int precision) throws IOException {
|
||||
final String val;
|
||||
if (arg == null) {
|
||||
val = "false";
|
||||
} else if (arg instanceof Boolean) {
|
||||
val = String.valueOf(arg);
|
||||
} else {
|
||||
val = "true";
|
||||
}
|
||||
appendify(a, val, flags, width, precision);
|
||||
}
|
||||
|
||||
static void convertChar(
|
||||
final Appendable a,
|
||||
final Object arg,
|
||||
final byte flags,
|
||||
final int width,
|
||||
final int precision) throws IOException {
|
||||
final String val;
|
||||
if (arg instanceof Character) {
|
||||
val = ((Character) arg).toString();
|
||||
} else if ( arg instanceof Byte ||
|
||||
arg instanceof Short ||
|
||||
arg instanceof Integer ){
|
||||
final int codePoint = ((Number) arg).intValue();
|
||||
if (codePoint >= 0 && codePoint <= 0x10FFFF) { //<-- isValidCodePoint()?
|
||||
val = new String(Character.toChars(codePoint));
|
||||
} else {
|
||||
throw new IllegalFormatException("Invalid code point: " + arg);
|
||||
}
|
||||
} else {
|
||||
throw new IllegalFormatException("Cannot do char conversion: " + arg);
|
||||
}
|
||||
appendify(a, val, flags, width, precision);
|
||||
}
|
||||
|
||||
// FIXME: this is broken for octal formats with negative values
|
||||
static void convertLong(
|
||||
final Appendable a,
|
||||
final Long arg,
|
||||
final byte flags,
|
||||
final int width,
|
||||
final int precision,
|
||||
final int radix) throws IOException {
|
||||
final String val;
|
||||
final Long n = arg;
|
||||
final long longValue = n.longValue();
|
||||
if (radix == 10 || longValue > -1) {
|
||||
val = Long.toString(longValue, radix);
|
||||
} else {
|
||||
final long upper = 0xFFFFFFFFL&(longValue>>31);
|
||||
final long lower = 0xFFFFFFFFL&(longValue);
|
||||
val = Long.toString(upper, radix) + Long.toString(lower, radix);
|
||||
}
|
||||
appendify(a, val, flags, width, precision);
|
||||
}
|
||||
|
||||
static void convertInteger(
|
||||
final Appendable a,
|
||||
final Object arg,
|
||||
final byte flags,
|
||||
final int width,
|
||||
final int precision,
|
||||
final int radix) throws IOException {
|
||||
final String val;
|
||||
final Number n = (Number) arg;
|
||||
final long longValue = n.longValue();
|
||||
final long modifier;
|
||||
if (arg instanceof Integer) modifier = 0xFFFFFFFFL+1; else
|
||||
if (arg instanceof Short) modifier = 0xFFFFL+1; else
|
||||
if (arg instanceof Byte) modifier = 0xFFL+1;
|
||||
else throw new IllegalFormatException(
|
||||
"not an integer number: " + (arg != null ? arg.getClass() : null)
|
||||
);
|
||||
if (radix != 10 && longValue < 0) {
|
||||
val = Long.toString(longValue + modifier, radix);
|
||||
} else {
|
||||
val = Long.toString(longValue, radix);
|
||||
}
|
||||
appendify(a, val, flags, width, precision);
|
||||
}
|
||||
|
||||
// FIXME: I'm lazy, so hexidecimal exponential isn't implemented, sorry - bcg
|
||||
static void convertFloat(
|
||||
final Appendable a,
|
||||
final Object arg,
|
||||
final byte flags,
|
||||
final int width,
|
||||
final int precision,
|
||||
final int radix) throws IOException {
|
||||
final String val;
|
||||
final Number n = (Number) arg;
|
||||
if (arg instanceof Float) {
|
||||
val = Float.toString(n.floatValue());
|
||||
} else if (arg instanceof Double) {
|
||||
val = Double.toString(n.doubleValue());
|
||||
} else {
|
||||
throw new IllegalFormatException(
|
||||
"not a floating point number: " + (arg != null ? arg.getClass() : null)
|
||||
);
|
||||
}
|
||||
appendify(a, val, flags, width, precision);
|
||||
}
|
||||
|
||||
static void appendify(
|
||||
final Appendable a,
|
||||
final String val,
|
||||
final byte flags,
|
||||
final int width,
|
||||
final int precision) throws IOException {
|
||||
String result = val;
|
||||
if (checkFlag(flags, FLAG_FORCE_UPPER_CASE)) {
|
||||
result = result.toUpperCase();
|
||||
}
|
||||
// TODO: implement other flags
|
||||
// (+) always include sign
|
||||
// (,) grouping separators
|
||||
// (() negatives in parentheses
|
||||
if (precision > 0) {
|
||||
// FIXME: this behavior should be different for floating point numbers
|
||||
final int difference = result.length() - precision;
|
||||
if (difference > 0) {
|
||||
result = result.substring(0, precision);
|
||||
a.append(result);
|
||||
return;
|
||||
}
|
||||
}
|
||||
if (width > 0) {
|
||||
final int difference = width - result.length();
|
||||
final boolean leftJustified = checkFlag(flags, FLAG_LEFT_JUSTIFIED);
|
||||
if (!leftJustified && difference > 0) {
|
||||
char fill = checkFlag(flags, FLAG_LEADING_ZERO_PADDED) ? '0' : ' ';
|
||||
fill(a, difference, fill);
|
||||
}
|
||||
a.append(result);
|
||||
if (leftJustified && difference > 0) {
|
||||
fill(a, difference, ' ');
|
||||
}
|
||||
return;
|
||||
}
|
||||
a.append(result);
|
||||
}
|
||||
|
||||
private static void fill(Appendable a, int num, char c) throws IOException {
|
||||
while (num > 0) {
|
||||
a.append(c);
|
||||
num--;
|
||||
}
|
||||
}
|
||||
|
||||
/** Represents a single chunk of the format string, either a literal or one of
|
||||
the specifiers documented in OpenJDK's javadoc for java.util.Formatter.
|
||||
This struct is immutable so can be safely shared across threads. */
|
||||
private static final class FmtCmpnt {
|
||||
|
||||
private final String _source;
|
||||
private final byte _conversion;
|
||||
private final int _argument_index;
|
||||
private final int _width;
|
||||
private final int _precision;
|
||||
private final byte _flags;
|
||||
|
||||
private FmtCmpnt(final String literal) {
|
||||
this(literal, CONV_LITRL, 0, 0, 0, (byte)0);
|
||||
}
|
||||
private FmtCmpnt(
|
||||
final String src,
|
||||
final byte conversion,
|
||||
final int argumentIndex,
|
||||
final int width,
|
||||
final int precision,
|
||||
final byte flags) {
|
||||
this._source = src;
|
||||
this._conversion = conversion;
|
||||
this._argument_index = argumentIndex;
|
||||
this._width = width;
|
||||
this._precision = precision;
|
||||
this._flags = flags;
|
||||
}
|
||||
@Override
|
||||
public String toString() {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
sb.append("{ ")
|
||||
.append("source = '").append(_source).append("', ")
|
||||
.append("conversion = ").append(_conversion).append(", ")
|
||||
.append("flags = ").append(Byte.toString(_flags, 2)).append(", ")
|
||||
.append("arg_index = ").append(_argument_index).append(", ")
|
||||
.append("width = ").append(_width).append(", ")
|
||||
.append("precision = ").append(_precision).append(", ")
|
||||
.append("}");
|
||||
return sb.toString();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper class for parsing a stream of characters into FmtCmpnt objects.
|
||||
*/
|
||||
private static final class FmtSpecBldr {
|
||||
|
||||
private StringBuilder _source;
|
||||
private byte _conversion;
|
||||
private int _argument_index;
|
||||
private int _width;
|
||||
private int _precision;
|
||||
private byte _flags;
|
||||
|
||||
private FmtSpecBldr() {
|
||||
this.init();
|
||||
}
|
||||
|
||||
private final void init() {
|
||||
_argument_index =
|
||||
_width =
|
||||
_precision =
|
||||
_conversion =
|
||||
_flags = 0;
|
||||
_source = null;
|
||||
}
|
||||
|
||||
private final FmtCmpnt build() {
|
||||
final FmtCmpnt result = new FmtCmpnt(
|
||||
_source.toString(),
|
||||
_conversion,
|
||||
_argument_index,
|
||||
_width,
|
||||
_precision,
|
||||
_flags
|
||||
);
|
||||
init();
|
||||
return result;
|
||||
}
|
||||
|
||||
private final FmtCmpnt append(char c) {
|
||||
|
||||
if (_source == null) {
|
||||
_source = new StringBuilder();
|
||||
}
|
||||
_source.append(c);
|
||||
|
||||
// FIXME: none of these date formats are implemented, because lazy
|
||||
// if a datetime is specified, after the conversion character a time
|
||||
// format specifier is expected. This is the only case where a character
|
||||
// is allowed after the conversion character.
|
||||
if (_conversion == CONV_DTIME) switch (c) {
|
||||
|
||||
// Hour of the day for the 24-hour clock, formatted as two digits with a
|
||||
// leading zero as necessary i.e. 00 - 23.
|
||||
case 'H':
|
||||
|
||||
// Hour for the 12-hour clock, formatted as two digits with a leading
|
||||
// zero as necessary, i.e. 01 - 12.
|
||||
case 'I':
|
||||
|
||||
// Hour of the day for the 24-hour clock, i.e. 0 - 23.
|
||||
case 'k':
|
||||
|
||||
// Hour for the 12-hour clock, i.e. 1 - 12.
|
||||
case 'l':
|
||||
|
||||
// Minute within the hour formatted as two digits with a leading zero
|
||||
// as necessary, i.e. 00 - 59.
|
||||
case 'M':
|
||||
|
||||
// Seconds within the minute, formatted as two digits with a leading
|
||||
// zero as necessary, i.e. 00 - 60 ("60" is a special value required to
|
||||
// support leap seconds).
|
||||
case 'S':
|
||||
|
||||
// Millisecond within the second formatted as three digits with leading
|
||||
// zeros as necessary, i.e. 000 - 999.
|
||||
case 'L':
|
||||
|
||||
// Nanosecond within the second, formatted as nine digits with leading
|
||||
// zeros as necessary, i.e. 000000000 - 999999999.
|
||||
case 'N':
|
||||
|
||||
// Locale-specific morning or afternoon marker in lower case,
|
||||
// e.g."am" or "pm". Use of the conversion prefix 'T' forces this
|
||||
// output to upper case.
|
||||
case 'p':
|
||||
|
||||
// RFC 822 style numeric time zone offset from GMT, e.g. -0800.
|
||||
case 'z':
|
||||
|
||||
// A string representing the abbreviation for the time zone. The
|
||||
// Formatter's locale will supersede the locale of the argument
|
||||
// (if any).
|
||||
case 'Z':
|
||||
|
||||
// Seconds since the beginning of the epoch
|
||||
// starting at 1 January 1970 00:00:00 UTC,
|
||||
// i.e. Long.MIN_VALUE/1000 to Long.MAX_VALUE/1000.
|
||||
case 's':
|
||||
|
||||
// Milliseconds since the beginning of the epoch
|
||||
// starting at 1 January 1970 00:00:00 UTC,
|
||||
// i.e. Long.MIN_VALUE to Long.MAX_VALUE.
|
||||
case 'Q':
|
||||
|
||||
// ------------------------------------------------------------------
|
||||
// The following conversion characters are used for formatting dates:
|
||||
// ------------------------------------------------------------------
|
||||
|
||||
// Locale-specific full month name, e.g. "January", "February".
|
||||
case 'B':
|
||||
|
||||
// Locale-specific abbreviated month name, e.g. "Jan", "Feb".
|
||||
case 'b':
|
||||
|
||||
// Same as 'b'.
|
||||
case 'h':
|
||||
|
||||
// Locale-specific full name of the day of the week, e.g. "Sunday"
|
||||
case 'A':
|
||||
|
||||
// Locale-specific short name of the day of the week, e.g. "Sun"
|
||||
case 'a':
|
||||
|
||||
// Four-digit year divided by 100, formatted as two digits with leading
|
||||
// zero as necessary, i.e. 00 - 99
|
||||
case 'C':
|
||||
|
||||
// Year, formatted as at least four digits with leading zeros
|
||||
// as necessary, e.g. 0092 equals 92 CE for the Gregorian calendar.
|
||||
case 'Y':
|
||||
|
||||
// Last two digits of the year, formatted with leading zeros
|
||||
// as necessary, i.e. 00 - 99.
|
||||
case 'y':
|
||||
|
||||
// Day of year, formatted as three digits with leading zeros
|
||||
// as necessary, e.g. 001 - 366 for the Gregorian calendar.
|
||||
case 'j':
|
||||
|
||||
// Month, formatted as two digits with leading zeros as necessary,
|
||||
// i.e. 01 - 13.
|
||||
case 'm':
|
||||
|
||||
// Day of month, formatted as two digits with leading zeros as
|
||||
// necessary, i.e. 01 - 31
|
||||
case 'd':
|
||||
|
||||
// Day of month, formatted as two digits, i.e. 1 - 31.
|
||||
case 'e':
|
||||
|
||||
// -------------------------------------------------------------------
|
||||
// The following conversion characters are used for formatting common
|
||||
// date/time compositions.
|
||||
// -------------------------------------------------------------------
|
||||
|
||||
// Time formatted for the 24-hour clock as "%tH:%tM"
|
||||
case 'R':
|
||||
|
||||
// Time formatted for the 24-hour clock as "%tH:%tM:%tS".
|
||||
case 'T':
|
||||
|
||||
// Time formatted for the 12-hour clock as "%tI:%tM:%tS %Tp". The location
|
||||
// of the morning or afternoon marker ('%Tp') may be locale-dependent.
|
||||
case 'r':
|
||||
|
||||
// Date formatted as "%tm/%td/%ty".
|
||||
case 'D':
|
||||
|
||||
// ISO 8601 complete date formatted as "%tY-%tm-%td".
|
||||
case 'F':
|
||||
|
||||
// Date and time formatted as "%ta %tb %td %tT %tZ %tY", e.g.
|
||||
// "Sun Jul 20 16:17:00 EDT 1969".
|
||||
case 'c':
|
||||
return seal(CONV_DTIME);
|
||||
|
||||
default:
|
||||
throw new IllegalFormatException("Illegal date/time modifier: " + c);
|
||||
}
|
||||
|
||||
// -- Flags and Switches -----------------------------------------------
|
||||
// these are the possible characters for flags, width, etc. if the input
|
||||
// is not one of these characters, it needs to be a valid conversion char.
|
||||
// because the possible flags can vary based on the type of conversion,
|
||||
// it is easiest to just buffer the flags, argument index, etc. until a
|
||||
// conversion character has been reached. The seal() method will then
|
||||
// work out if the specified flags are valid for the given conversion.
|
||||
switch (c) {
|
||||
case '$':
|
||||
case '<':
|
||||
case '0':
|
||||
case '1':
|
||||
case '2':
|
||||
case '3':
|
||||
case '4':
|
||||
case '5':
|
||||
case '6':
|
||||
case '7':
|
||||
case '8':
|
||||
case '9':
|
||||
case '-':
|
||||
case '+':
|
||||
case '\'':
|
||||
case ' ':
|
||||
case '#':
|
||||
case ',':
|
||||
case '.':
|
||||
case '(':
|
||||
return null;
|
||||
// -- End of Flags and Switches ----------------------------------------
|
||||
|
||||
// -- Conversion characters --------------------------------------------
|
||||
// If this point is reached, then the current character must be a valid
|
||||
// conversion character. If it is not, it should fall through the rest
|
||||
// of the switch statement below and throw an IllegalFormatException
|
||||
|
||||
// string conversion
|
||||
case 'S':
|
||||
setFlagTrue(FLAG_FORCE_UPPER_CASE);
|
||||
case 's':
|
||||
return seal(CONV_STRNG);
|
||||
|
||||
// newline conversion
|
||||
case 'n':
|
||||
return seal(CONV_NLINE);
|
||||
|
||||
// percent conversion
|
||||
case '%':
|
||||
return seal(CONV_PRCNT);
|
||||
|
||||
// decimal numeric conversion
|
||||
case 'd':
|
||||
return seal(CONV_DECML);
|
||||
|
||||
// hexidecimal numeric conversion
|
||||
case 'X':
|
||||
setFlagTrue(FLAG_FORCE_UPPER_CASE);
|
||||
case 'x':
|
||||
return seal(CONV_HXDEC);
|
||||
|
||||
// datetime conversion
|
||||
case 'T':
|
||||
setFlagTrue(FLAG_FORCE_UPPER_CASE);
|
||||
case 't':
|
||||
_conversion = CONV_DTIME;
|
||||
return null;
|
||||
|
||||
// boolean conversion
|
||||
case 'B':
|
||||
setFlagTrue(FLAG_FORCE_UPPER_CASE);
|
||||
case 'b':
|
||||
return seal(CONV_BOOLN);
|
||||
|
||||
// hashcode conversion
|
||||
case 'H':
|
||||
setFlagTrue(FLAG_FORCE_UPPER_CASE);
|
||||
case 'h':
|
||||
return seal(CONV_HCODE);
|
||||
|
||||
// character conversion
|
||||
case 'C':
|
||||
setFlagTrue(FLAG_FORCE_UPPER_CASE);
|
||||
case 'c':
|
||||
return seal(CONV_CHRCT);
|
||||
|
||||
// octal numeric conversion
|
||||
case 'o':
|
||||
return seal(CONV_OCTAL);
|
||||
|
||||
// computerized scientific conversion
|
||||
case 'E':
|
||||
setFlagTrue(FLAG_FORCE_UPPER_CASE);
|
||||
case 'e':
|
||||
return seal(CONV_CPSCI);
|
||||
|
||||
// floating point conversion
|
||||
case 'f':
|
||||
return seal(CONV_FLOAT);
|
||||
|
||||
// general scientific floating point conversion
|
||||
case 'G':
|
||||
setFlagTrue(FLAG_FORCE_UPPER_CASE);
|
||||
case 'g':
|
||||
return seal(CONV_GNSCI);
|
||||
|
||||
// hexidecimal exponential floating point conversion
|
||||
case 'A':
|
||||
setFlagTrue(FLAG_FORCE_UPPER_CASE);
|
||||
case 'a':
|
||||
return seal(CONV_HXEXP);
|
||||
// -- End of Conversion characters --------------------------------------
|
||||
|
||||
default:
|
||||
throw new IllegalFormatException(
|
||||
"Invalid character encountered while parsing specifier: " + c);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
sb.append("{ ")
|
||||
.append("source = '").append(_source).append("', ")
|
||||
.append("conversion = ").append(_conversion).append(", ")
|
||||
.append("flags = ").append(Byte.toString(_flags, 2)).append(", ")
|
||||
.append("arg_index = ").append(_argument_index).append(", ")
|
||||
.append("width = ").append(_width).append(", ")
|
||||
.append("precision = ").append(_precision).append(", ")
|
||||
.append("}");
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
private final FmtCmpnt seal(final byte conversion) {
|
||||
|
||||
// throwing IllegalStateException instead of IllegalFormatException
|
||||
// because this should only occur if there is a bug in the append()
|
||||
// method. In that case I'd prefer to fail fast even if the user is
|
||||
// explicitly trying to catch IllegalFormatException.
|
||||
if (conversion < 0 || conversion > 0xE) {
|
||||
throw new IllegalArgumentException();
|
||||
}
|
||||
|
||||
this._conversion = conversion;
|
||||
|
||||
// if the length is less than 2, it must mean that only the conversion
|
||||
// character was specified. Since a conversion character by itself is a
|
||||
// valid pattern, just build and return
|
||||
if (_source.length() < 2) {
|
||||
return build();
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------
|
||||
// spec format: [argument_index$][flags][width][.precision]
|
||||
// ---------------------------------------------------------------------
|
||||
// the last character of the spec is the conversion character which has
|
||||
// already been translated the appropriate byte by the append() method,
|
||||
// so the last character gets chopped off before processing
|
||||
final String spec = _source.substring(0, _source.length() - 1);
|
||||
|
||||
// if argument index is supported, it should be followed by a '$' and be
|
||||
// comprised only of digit characters, or it should be a single '<' char
|
||||
final int dollarIndex = spec.indexOf('$');
|
||||
if (dollarIndex > -1) {
|
||||
if (acceptsArgument(conversion)) {
|
||||
if (spec.charAt(dollarIndex - 1) == '<') {
|
||||
_argument_index = AIDX_PREV;
|
||||
} else {
|
||||
_argument_index = Integer.valueOf(spec.substring(0, dollarIndex));
|
||||
}
|
||||
} else {
|
||||
throw new IllegalFormatException(
|
||||
"Formats that do not accept arguments cannot specify an index."
|
||||
);
|
||||
}
|
||||
}
|
||||
if (dollarIndex == (spec.length() - 1)) {
|
||||
return build();
|
||||
}
|
||||
|
||||
// if precision is supported, look for the first period and assume that
|
||||
// everything before is the width and everything after is the precision
|
||||
final int dotIndex = spec.indexOf('.');
|
||||
if (dotIndex > -1) {
|
||||
if (precisionSupported(conversion)) {
|
||||
_precision = Integer.valueOf(spec.substring(dotIndex + 1));
|
||||
} else {
|
||||
throw new IllegalFormatException(
|
||||
"Precision is not supported for " + conversion
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// Now loop over the remaining characters to get the width as well as any
|
||||
// applicable flags. Note: 0 is a valid flag so must be handled carefully
|
||||
final String remaining = spec.substring(
|
||||
Math.max(dollarIndex, 0), dotIndex > -1 ? dotIndex : spec.length()
|
||||
);
|
||||
int flagsEnd = -1;
|
||||
for (int i = 0, n = remaining.length(); i < n && (flagsEnd == -1); i++) {
|
||||
final char c = remaining.charAt(i);
|
||||
switch (c) {
|
||||
case '-':
|
||||
ensureLeftJustifySupported();
|
||||
setFlagTrue(FLAG_LEFT_JUSTIFIED);
|
||||
break;
|
||||
case '#':
|
||||
ensureNumeric(c);
|
||||
setFlagTrue(FLAG_ALTERNATE_FORM);
|
||||
break;
|
||||
case '+':
|
||||
ensureNumeric(c);
|
||||
setFlagTrue(FLAG_ALWAYS_INCLUDES_SIGN);
|
||||
break;
|
||||
case ' ':
|
||||
ensureNumeric(c);
|
||||
setFlagTrue(FLAG_LEADING_SPACE_PADDED);
|
||||
break;
|
||||
case ',':
|
||||
ensureNumeric(c);
|
||||
setFlagTrue(FLAG_GROUPING_SEPARATORS);
|
||||
break;
|
||||
case '(':
|
||||
ensureNumeric(c);
|
||||
setFlagTrue(FLAG_NEGATIVES_IN_PARENS);
|
||||
break;
|
||||
case '0':
|
||||
ensureNumeric(c);
|
||||
setFlagTrue(FLAG_LEADING_ZERO_PADDED);
|
||||
break;
|
||||
case '1':
|
||||
case '2':
|
||||
case '3':
|
||||
case '4':
|
||||
case '5':
|
||||
case '6':
|
||||
case '7':
|
||||
case '8':
|
||||
case '9':
|
||||
flagsEnd = i;
|
||||
_width = Integer.valueOf(remaining.substring(flagsEnd));
|
||||
return build();
|
||||
}
|
||||
}
|
||||
throw new IllegalStateException();
|
||||
}
|
||||
private final void ensureLeftJustifySupported() {
|
||||
if (!widthSupported(_conversion)) {
|
||||
throw new IllegalFormatException(
|
||||
"Conversion must support width if specifying left justified."
|
||||
);
|
||||
}
|
||||
}
|
||||
private final void ensureNumeric(final char c) {
|
||||
if (!isNumeric(_conversion)) {
|
||||
throw new IllegalFormatException(
|
||||
"flag " + c + " only supported on numeric specifiers."
|
||||
);
|
||||
}
|
||||
}
|
||||
private final void setFlagTrue(final byte flag) {
|
||||
_flags |= flag;
|
||||
}/*
|
||||
final void setFlagFalse(final byte flag) {
|
||||
_flags &= ~flag;
|
||||
}*/
|
||||
}
|
||||
}
|
@ -78,6 +78,25 @@ public class PrintStream extends OutputStream {
|
||||
print(String.valueOf(s));
|
||||
}
|
||||
|
||||
public synchronized void printf(java.util.Locale locale, String format, Object... args) {
|
||||
// should this be cached in an instance variable??
|
||||
final java.util.Formatter formatter = new java.util.Formatter(this);
|
||||
formatter.format(locale, format, args);
|
||||
}
|
||||
|
||||
public synchronized void printf(String format, Object... args) {
|
||||
final java.util.Formatter formatter = new java.util.Formatter(this);
|
||||
formatter.format(format, args);
|
||||
}
|
||||
|
||||
public void format(String format, Object... args) {
|
||||
printf(format, args);
|
||||
}
|
||||
|
||||
public void format(java.util.Locale locale, String format, Object... args) {
|
||||
printf(locale, format, args);
|
||||
}
|
||||
|
||||
public synchronized void println(String s) {
|
||||
try {
|
||||
out.write(s.getBytes());
|
||||
|
@ -10,7 +10,7 @@
|
||||
|
||||
package java.io;
|
||||
|
||||
public abstract class Writer implements Closeable, Flushable {
|
||||
public abstract class Writer implements Closeable, Flushable, Appendable {
|
||||
public void write(int c) throws IOException {
|
||||
char[] buffer = new char[] { (char) c };
|
||||
write(buffer);
|
||||
@ -33,6 +33,30 @@ public abstract class Writer implements Closeable, Flushable {
|
||||
public abstract void write(char[] buffer, int offset, int length)
|
||||
throws IOException;
|
||||
|
||||
public Appendable append(final char c) throws IOException {
|
||||
write((int)c);
|
||||
return this;
|
||||
}
|
||||
|
||||
public Appendable append(final CharSequence sequence) throws IOException {
|
||||
return append(sequence, 0, sequence.length());
|
||||
}
|
||||
|
||||
public Appendable append(CharSequence sequence, int start, int end)
|
||||
throws IOException {
|
||||
final int length = end - start;
|
||||
if (sequence instanceof String) {
|
||||
write((String)sequence, start, length);
|
||||
} else {
|
||||
final char[] charArray = new char[length];
|
||||
for (int i = start; i < end; i++) {
|
||||
charArray[i] = sequence.charAt(i);
|
||||
}
|
||||
write(charArray, 0, length);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
public abstract void flush() throws IOException;
|
||||
|
||||
public abstract void close() throws IOException;
|
||||
|
@ -13,6 +13,7 @@ package java.lang;
|
||||
import java.io.Serializable;
|
||||
import java.io.UnsupportedEncodingException;
|
||||
import java.util.Comparator;
|
||||
import java.util.Formatter;
|
||||
import java.util.Locale;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
@ -574,6 +575,20 @@ public final class String
|
||||
|
||||
public native String intern();
|
||||
|
||||
public static String format(String fmt, Object... args) {
|
||||
final Formatter formatter = new Formatter();
|
||||
final String result = formatter.format(fmt, args).toString();
|
||||
formatter.close();
|
||||
return result;
|
||||
}
|
||||
|
||||
public static String format(Locale l, String fmt, Object... args) {
|
||||
final Formatter formatter = new Formatter();
|
||||
final String result = formatter.format(l, fmt, args).toString();
|
||||
formatter.close();
|
||||
return result;
|
||||
}
|
||||
|
||||
public static String valueOf(Object s) {
|
||||
return s == null ? "null" : s.toString();
|
||||
}
|
||||
|
158
classpath/java/util/Formatter.java
Normal file
158
classpath/java/util/Formatter.java
Normal file
@ -0,0 +1,158 @@
|
||||
/* Copyright (c) 2008-2015, Avian Contributors
|
||||
|
||||
Permission to use, copy, modify, and/or distribute this software
|
||||
for any purpose with or without fee is hereby granted, provided
|
||||
that the above copyright notice and this permission notice appear
|
||||
in all copies.
|
||||
|
||||
There is NO WARRANTY for this software. See license.txt for
|
||||
details. */
|
||||
|
||||
package java.util;
|
||||
|
||||
import java.io.Closeable;
|
||||
import java.io.File;
|
||||
import java.io.FileWriter;
|
||||
import java.io.Flushable;
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStream;
|
||||
import java.io.OutputStreamWriter;
|
||||
import java.io.PrintStream;
|
||||
import java.io.Writer;
|
||||
|
||||
import avian.FormatString;
|
||||
|
||||
public class Formatter implements Closeable, Flushable, AutoCloseable {
|
||||
|
||||
private final Appendable _out;
|
||||
|
||||
private final Locale _locale;
|
||||
|
||||
private IOException lastException;
|
||||
|
||||
private boolean _closed;
|
||||
|
||||
private void ensureNotClosed() {
|
||||
if (this._closed) {
|
||||
throw new IllegalStateException();
|
||||
}
|
||||
}
|
||||
|
||||
public Formatter() {
|
||||
this((Appendable) null, (Locale) null);
|
||||
}
|
||||
|
||||
public Formatter(final Appendable a) {
|
||||
this(a, null);
|
||||
}
|
||||
|
||||
public Formatter(final Appendable a, final Locale l) {
|
||||
if (a == null) {
|
||||
this._out = new StringBuilder();
|
||||
} else {
|
||||
this._out = a;
|
||||
}
|
||||
if (l == null) {
|
||||
this._locale = Locale.getDefault();
|
||||
} else {
|
||||
this._locale = l;
|
||||
}
|
||||
}
|
||||
|
||||
public Formatter(File file) throws IOException {
|
||||
this(file, null);
|
||||
}
|
||||
|
||||
public Formatter(File file, Locale l) throws IOException {
|
||||
this(new FileWriter(file), l);
|
||||
}
|
||||
|
||||
public Formatter(Locale l) {
|
||||
this((Appendable) null, l);
|
||||
}
|
||||
|
||||
public Formatter(OutputStream os) {
|
||||
this(os, null);
|
||||
}
|
||||
|
||||
public Formatter(OutputStream os, Locale l) {
|
||||
this(new OutputStreamWriter(os), l);
|
||||
}
|
||||
|
||||
public Formatter(PrintStream ps) {
|
||||
this(new OutputStreamWriter(ps));
|
||||
}
|
||||
|
||||
public Formatter(String fileName) throws IOException {
|
||||
this(fileName, (Locale) null);
|
||||
}
|
||||
|
||||
public Formatter(String fileName, Locale l) throws IOException {
|
||||
this(new File(fileName), l);
|
||||
}
|
||||
|
||||
public void close() {
|
||||
if (this._closed) {
|
||||
return;
|
||||
}
|
||||
final Appendable out = out();
|
||||
if (out instanceof Closeable) {
|
||||
final Closeable closeable = (Closeable) out;
|
||||
try {
|
||||
closeable.close();
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException("An error occurred while closing.", e);
|
||||
}
|
||||
this._closed = true;
|
||||
}
|
||||
}
|
||||
|
||||
public void flush() {
|
||||
ensureNotClosed();
|
||||
final Appendable out = out();
|
||||
if (out instanceof Flushable) {
|
||||
final Flushable flushable = (Flushable) out;
|
||||
try {
|
||||
flushable.flush();
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException("An error occurred while flushing.", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public Formatter format(Locale l, final String format, final Object...args) {
|
||||
ensureNotClosed();
|
||||
try {
|
||||
final FormatString formatString = FormatString.compile(format);
|
||||
this._out.append(formatString.format(args));
|
||||
} catch (IOException e) {
|
||||
this.lastException = e;
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
public Formatter format(String format, Object... args) {
|
||||
ensureNotClosed();
|
||||
return this.format(null, format, args);
|
||||
}
|
||||
|
||||
public IOException ioException() {
|
||||
return lastException;
|
||||
}
|
||||
|
||||
public Locale locale() {
|
||||
ensureNotClosed();
|
||||
return this._locale;
|
||||
}
|
||||
|
||||
public Appendable out() {
|
||||
ensureNotClosed();
|
||||
return this._out;
|
||||
}
|
||||
|
||||
public String toString() {
|
||||
ensureNotClosed();
|
||||
return this._out.toString();
|
||||
}
|
||||
|
||||
}
|
31
classpath/java/util/IllegalFormatException.java
Normal file
31
classpath/java/util/IllegalFormatException.java
Normal file
@ -0,0 +1,31 @@
|
||||
/* Copyright (c) 2008-2015, Avian Contributors
|
||||
|
||||
Permission to use, copy, modify, and/or distribute this software
|
||||
for any purpose with or without fee is hereby granted, provided
|
||||
that the above copyright notice and this permission notice appear
|
||||
in all copies.
|
||||
|
||||
There is NO WARRANTY for this software. See license.txt for
|
||||
details. */
|
||||
|
||||
package java.util;
|
||||
|
||||
public class IllegalFormatException extends IllegalArgumentException {
|
||||
|
||||
public IllegalFormatException() {
|
||||
super();
|
||||
}
|
||||
|
||||
public IllegalFormatException(String message, Throwable cause) {
|
||||
super(message, cause);
|
||||
}
|
||||
|
||||
public IllegalFormatException(String message) {
|
||||
super(message);
|
||||
}
|
||||
|
||||
public IllegalFormatException(Throwable cause) {
|
||||
super(cause);
|
||||
}
|
||||
|
||||
}
|
296
test/FormatStrings.java
Normal file
296
test/FormatStrings.java
Normal file
@ -0,0 +1,296 @@
|
||||
/* Copyright (c) 2008-2015, Avian Contributors
|
||||
|
||||
Permission to use, copy, modify, and/or distribute this software
|
||||
for any purpose with or without fee is hereby granted, provided
|
||||
that the above copyright notice and this permission notice appear
|
||||
in all copies.
|
||||
|
||||
There is NO WARRANTY for this software. See license.txt for
|
||||
details. */
|
||||
|
||||
/*
|
||||
* @author bcg
|
||||
*/
|
||||
public class FormatStrings {
|
||||
|
||||
public static void main(String... args) throws Exception {
|
||||
FormatStrings test = new FormatStrings();
|
||||
test.testLiteral();
|
||||
test.testString();
|
||||
test.testNewline();
|
||||
test.testPercent();
|
||||
test.testBoolean();
|
||||
test.testCharacter();
|
||||
test.testHashCode();
|
||||
test.testIntegers();
|
||||
test.testWidths();
|
||||
test.testPrecisions();
|
||||
}
|
||||
|
||||
private void _testFormat(String expected, String format, Object... args) {
|
||||
String actual = String.format(format, args);
|
||||
ensureEquals(expected, actual);
|
||||
System.err.println("Expected: " + expected + ", Actual: " + actual);
|
||||
}
|
||||
|
||||
private static void ensureEquals(String expected, String actual) {
|
||||
if (expected != actual) {
|
||||
if ((expected == null || actual == null) || !(expected.equals(actual))) {
|
||||
throw new IllegalArgumentException(
|
||||
"Expected `" + expected + "` but was actually `" + actual + "`.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void testLiteral() {
|
||||
_testFormat("test Literal 1", "test Literal 1");
|
||||
_testFormat("test Literal 2", "test Literal 2", (Object[]) null);
|
||||
_testFormat("test Literal 3", "test Literal 3", new Object[0]);
|
||||
}
|
||||
|
||||
public void testString() {
|
||||
_testFormat("test String 1", "test %s", "String 1");
|
||||
_testFormat("test String null", "test String %s", new Object[]{null} );
|
||||
_testFormat("test String 2", "test %2$s", "String 1", "String 2");
|
||||
_testFormat("String `string`", "String `%s`", new String("string"));
|
||||
_testFormat("String `STRING`", "String `%S`", new String("string"));
|
||||
_testFormat("String `another string`", "String `%s`", new String("another string"));
|
||||
_testFormat("String `ANOTHER STRING`", "String `%S`", new String("another string"));
|
||||
_testFormat("String `null`", "String `%s`", (String)null);
|
||||
_testFormat("String `NULL`", "String `%S`", (String)null);
|
||||
_testFormat("String `true`", "String `%s`", new Boolean("true"));
|
||||
_testFormat("String `TRUE`", "String `%S`", new Boolean("true"));
|
||||
_testFormat("String `false`", "String `%s`", new Boolean("false"));
|
||||
_testFormat("String `FALSE`", "String `%S`", new Boolean("false"));
|
||||
}
|
||||
|
||||
public void testNewline() {
|
||||
final String newline = System.getProperty("line.separator");
|
||||
_testFormat(
|
||||
"<<<" + newline + " test newlines" + newline + ">>>",
|
||||
"<<<%n test newlines%n>>>"
|
||||
);
|
||||
}
|
||||
|
||||
public void testBoolean() {
|
||||
_testFormat("test boolean true", "test boolean %b", true);
|
||||
_testFormat("test Boolean true", "test Boolean %b", Boolean.TRUE);
|
||||
_testFormat("test boolean false", "test boolean %b", false);
|
||||
_testFormat("test Boolean false", "test Boolean %b", Boolean.FALSE);
|
||||
_testFormat("test null Boolean (false)", "test null Boolean (%b)", new Object[]{(Boolean)null});
|
||||
_testFormat("test non-null Boolean (true)", "test non-null Boolean (%b)", new Object());
|
||||
_testFormat("test boolean string (true)", "test boolean string (%b)", "false");
|
||||
_testFormat("Boolean `true`", "Boolean `%b`", new Boolean("true"));
|
||||
_testFormat("Boolean `TRUE`", "Boolean `%B`", new Boolean("true"));
|
||||
_testFormat("Boolean `false`", "Boolean `%b`", new Boolean("false"));
|
||||
_testFormat("Boolean `FALSE`", "Boolean `%B`", new Boolean("false"));
|
||||
_testFormat("Boolean `false`", "Boolean `%b`", (String)null);
|
||||
_testFormat("Boolean `FALSE`", "Boolean `%B`", (String)null);
|
||||
_testFormat("Boolean `true`", "Boolean `%b`", new String(""));
|
||||
_testFormat("Boolean `TRUE`", "Boolean `%B`", new String(""));
|
||||
_testFormat("Boolean `true`", "Boolean `%b`", new String("true"));
|
||||
_testFormat("Boolean `TRUE`", "Boolean `%B`", new String("true"));
|
||||
_testFormat("Boolean `true`", "Boolean `%b`", new String("false"));
|
||||
_testFormat("Boolean `TRUE`", "Boolean `%B`", new String("false"));
|
||||
}
|
||||
|
||||
public void testPercent() {
|
||||
_testFormat("Percents work 100%", "Percents work 100%%");
|
||||
}
|
||||
|
||||
public void testCharacter() {
|
||||
_testFormat("test character such as a", "test character such as %c", 'a');
|
||||
_testFormat("test character such as b", "test character such as %c", (int) 98);
|
||||
_testFormat("test character such as c", "test character such as %c", (byte) 99);
|
||||
_testFormat("test character such as d", "test character such as %c", (short) 100);
|
||||
}
|
||||
|
||||
public void testHashCode() {
|
||||
final Object obj1 = new Object();
|
||||
final Object obj2 = new Object();
|
||||
final String hc1 = Integer.toHexString(obj1.hashCode());
|
||||
final String hc2 = Integer.toHexString(obj2.hashCode());
|
||||
_testFormat("test hashcode 1 (" + hc1 + ")" , "test hashcode 1 (%h)", obj1, obj2);
|
||||
_testFormat("test hashcode 2 (" + hc2 + ")" , "test hashcode 2 (%2$h)", obj1, obj2);
|
||||
_testFormat("test hashcode null", "test hashcode %h", (String) null);
|
||||
}
|
||||
|
||||
public void testIntegers() {
|
||||
|
||||
_testFormat("Long 1", "Long %d", new Long(1));
|
||||
_testFormat("Long 2", "Long %2$d", new Long(1), new Long(2));
|
||||
_testFormat("Integer 1", "Integer %d", new Integer(1));
|
||||
_testFormat("Integer 2", "Integer %2$d", new Integer(1), new Integer(2));
|
||||
_testFormat("Short 1", "Short %d", new Short((short)1));
|
||||
_testFormat("Short 2", "Short %2$d", new Short((short)1), new Short((short)2));
|
||||
_testFormat("Byte 1", "Byte %d", new Byte((byte)1));
|
||||
_testFormat("Byte 2", "Byte %2$d", new Byte((byte)1), new Byte((byte)2));
|
||||
|
||||
_testFormat("Long 144", "Long %o", new Long(100));
|
||||
_testFormat("Long 310", "Long %2$o", new Long(100), new Long(200));
|
||||
_testFormat("Integer 144", "Integer %o", new Integer(100));
|
||||
_testFormat("Integer 310", "Integer %2$o", new Integer(100), new Integer(200));
|
||||
_testFormat("Short 144", "Short %o", new Short((short)100));
|
||||
_testFormat("Short 310", "Short %2$o", new Short((short)100), new Short((short)200));
|
||||
_testFormat("Byte 144", "Byte %o", new Byte((byte)100));
|
||||
_testFormat("Byte 310", "Byte %2$o", new Byte((byte)100), new Byte((byte)200));
|
||||
|
||||
_testFormat("Long 64", "Long %x", new Long(100));
|
||||
_testFormat("Long c8", "Long %2$x", new Long(100), new Long(200));
|
||||
_testFormat("Long C8", "Long %2$X", new Long(100), new Long(200));
|
||||
_testFormat("Integer 64", "Integer %x", new Integer(100));
|
||||
_testFormat("Integer c8", "Integer %2$x", new Integer(100), new Integer(200));
|
||||
_testFormat("Short 64", "Short %x", new Short((short)100));
|
||||
_testFormat("Short C8", "Short %2$X", new Short((short)100), new Short((short)200));
|
||||
_testFormat("Byte 64", "Byte %x", new Byte((byte)100));
|
||||
_testFormat("Byte c8", "Byte %2$x", new Byte((byte)100), new Byte((byte)200));
|
||||
|
||||
_testFormat("Decimal `1`", "Decimal `%d`", new Integer((int)1));
|
||||
_testFormat("Decimal `0`", "Decimal `%d`", new Integer((int)0));
|
||||
_testFormat("Decimal `100`", "Decimal `%d`", new Integer((int)100));
|
||||
_testFormat("Decimal `100000`", "Decimal `%d`", new Integer((int)100000));
|
||||
_testFormat("Decimal `63`", "Decimal `%d`", new Integer((int)63));
|
||||
_testFormat("Decimal `64`", "Decimal `%d`", new Integer((int)64));
|
||||
_testFormat("Decimal `-1`", "Decimal `%d`", new Integer((int)-1));
|
||||
_testFormat("Decimal `-100`", "Decimal `%d`", new Integer((int)-100));
|
||||
_testFormat("Decimal `-100000`", "Decimal `%d`", new Integer((int)-100000));
|
||||
_testFormat("Decimal `1`", "Decimal `%d`", new Byte((byte)1));
|
||||
_testFormat("Decimal `0`", "Decimal `%d`", new Byte((byte)0));
|
||||
_testFormat("Decimal `100`", "Decimal `%d`", new Byte((byte)100));
|
||||
_testFormat("Decimal `63`", "Decimal `%d`", new Byte((byte)63));
|
||||
_testFormat("Decimal `64`", "Decimal `%d`", new Byte((byte)64));
|
||||
_testFormat("Decimal `-1`", "Decimal `%d`", new Byte((byte)-1));
|
||||
_testFormat("Decimal `-100`", "Decimal `%d`", new Byte((byte)-100));
|
||||
_testFormat("Decimal `1`", "Decimal `%d`", new Long((long)1));
|
||||
_testFormat("Decimal `0`", "Decimal `%d`", new Long((long)0));
|
||||
_testFormat("Decimal `100`", "Decimal `%d`", new Long((long)100));
|
||||
_testFormat("Decimal `100000`", "Decimal `%d`", new Long((long)100000));
|
||||
_testFormat("Decimal `63`", "Decimal `%d`", new Long((long)63));
|
||||
_testFormat("Decimal `64`", "Decimal `%d`", new Long((long)64));
|
||||
_testFormat("Decimal `-1`", "Decimal `%d`", new Long((long)-1));
|
||||
_testFormat("Decimal `-100`", "Decimal `%d`", new Long((long)-100));
|
||||
_testFormat("Decimal `-100000`", "Decimal `%d`", new Long((long)-100000));
|
||||
_testFormat("Decimal `1`", "Decimal `%d`", new Short((short)1));
|
||||
_testFormat("Decimal `0`", "Decimal `%d`", new Short((short)0));
|
||||
_testFormat("Decimal `100`", "Decimal `%d`", new Short((short)100));
|
||||
_testFormat("Decimal `63`", "Decimal `%d`", new Short((short)63));
|
||||
_testFormat("Decimal `64`", "Decimal `%d`", new Short((short)64));
|
||||
_testFormat("Decimal `-1`", "Decimal `%d`", new Short((short)-1));
|
||||
_testFormat("Decimal `-100`", "Decimal `%d`", new Short((short)-100));
|
||||
|
||||
_testFormat("Octal `1`", "Octal `%o`", new Integer((int)1));
|
||||
_testFormat("Octal `0`", "Octal `%o`", new Integer((int)0));
|
||||
_testFormat("Octal `144`", "Octal `%o`", new Integer((int)100));
|
||||
_testFormat("Octal `303240`", "Octal `%o`", new Integer((int)100000));
|
||||
_testFormat("Octal `77`", "Octal `%o`", new Integer((int)63));
|
||||
_testFormat("Octal `100`", "Octal `%o`", new Integer((int)64));
|
||||
_testFormat("Octal `37777777777`", "Octal `%o`", new Integer((int)-1));
|
||||
_testFormat("Octal `37777777634`", "Octal `%o`", new Integer((int)-100));
|
||||
_testFormat("Octal `37777474540`", "Octal `%o`", new Integer((int)-100000));
|
||||
_testFormat("Octal `1`", "Octal `%o`", new Byte((byte)1));
|
||||
_testFormat("Octal `0`", "Octal `%o`", new Byte((byte)0));
|
||||
_testFormat("Octal `144`", "Octal `%o`", new Byte((byte)100));
|
||||
_testFormat("Octal `77`", "Octal `%o`", new Byte((byte)63));
|
||||
_testFormat("Octal `100`", "Octal `%o`", new Byte((byte)64));
|
||||
_testFormat("Octal `377`", "Octal `%o`", new Byte((byte)-1));
|
||||
_testFormat("Octal `234`", "Octal `%o`", new Byte((byte)-100));
|
||||
_testFormat("Octal `1`", "Octal `%o`", new Long((long)1));
|
||||
_testFormat("Octal `0`", "Octal `%o`", new Long((long)0));
|
||||
_testFormat("Octal `144`", "Octal `%o`", new Long((long)100));
|
||||
_testFormat("Octal `303240`", "Octal `%o`", new Long((long)100000));
|
||||
_testFormat("Octal `77`", "Octal `%o`", new Long((long)63));
|
||||
_testFormat("Octal `100`", "Octal `%o`", new Long((long)64));
|
||||
_testFormat("Octal `1`", "Octal `%o`", new Short((short)1));
|
||||
_testFormat("Octal `0`", "Octal `%o`", new Short((short)0));
|
||||
_testFormat("Octal `144`", "Octal `%o`", new Short((short)100));
|
||||
_testFormat("Octal `77`", "Octal `%o`", new Short((short)63));
|
||||
_testFormat("Octal `100`", "Octal `%o`", new Short((short)64));
|
||||
_testFormat("Octal `177777`", "Octal `%o`", new Short((short)-1));
|
||||
_testFormat("Octal `177634`", "Octal `%o`", new Short((short)-100));
|
||||
|
||||
_testFormat("HexDec `1`", "HexDec `%x`", new Integer((int)1));
|
||||
_testFormat("HexDec `1`", "HexDec `%X`", new Integer((int)1));
|
||||
_testFormat("HexDec `0`", "HexDec `%x`", new Integer((int)0));
|
||||
_testFormat("HexDec `0`", "HexDec `%X`", new Integer((int)0));
|
||||
_testFormat("HexDec `64`", "HexDec `%x`", new Integer((int)100));
|
||||
_testFormat("HexDec `64`", "HexDec `%X`", new Integer((int)100));
|
||||
_testFormat("HexDec `186a0`", "HexDec `%x`", new Integer((int)100000));
|
||||
_testFormat("HexDec `186A0`", "HexDec `%X`", new Integer((int)100000));
|
||||
_testFormat("HexDec `3f`", "HexDec `%x`", new Integer((int)63));
|
||||
_testFormat("HexDec `3F`", "HexDec `%X`", new Integer((int)63));
|
||||
_testFormat("HexDec `40`", "HexDec `%x`", new Integer((int)64));
|
||||
_testFormat("HexDec `40`", "HexDec `%X`", new Integer((int)64));
|
||||
_testFormat("HexDec `ffffffff`", "HexDec `%x`", new Integer((int)-1));
|
||||
_testFormat("HexDec `FFFFFFFF`", "HexDec `%X`", new Integer((int)-1));
|
||||
_testFormat("HexDec `ffffff9c`", "HexDec `%x`", new Integer((int)-100));
|
||||
_testFormat("HexDec `FFFFFF9C`", "HexDec `%X`", new Integer((int)-100));
|
||||
_testFormat("HexDec `fffe7960`", "HexDec `%x`", new Integer((int)-100000));
|
||||
_testFormat("HexDec `FFFE7960`", "HexDec `%X`", new Integer((int)-100000));
|
||||
_testFormat("HexDec `1`", "HexDec `%x`", new Byte((byte)1));
|
||||
_testFormat("HexDec `1`", "HexDec `%X`", new Byte((byte)1));
|
||||
_testFormat("HexDec `0`", "HexDec `%x`", new Byte((byte)0));
|
||||
_testFormat("HexDec `0`", "HexDec `%X`", new Byte((byte)0));
|
||||
_testFormat("HexDec `64`", "HexDec `%x`", new Byte((byte)100));
|
||||
_testFormat("HexDec `64`", "HexDec `%X`", new Byte((byte)100));
|
||||
_testFormat("HexDec `3f`", "HexDec `%x`", new Byte((byte)63));
|
||||
_testFormat("HexDec `3F`", "HexDec `%X`", new Byte((byte)63));
|
||||
_testFormat("HexDec `40`", "HexDec `%x`", new Byte((byte)64));
|
||||
_testFormat("HexDec `40`", "HexDec `%X`", new Byte((byte)64));
|
||||
_testFormat("HexDec `ff`", "HexDec `%x`", new Byte((byte)-1));
|
||||
_testFormat("HexDec `FF`", "HexDec `%X`", new Byte((byte)-1));
|
||||
_testFormat("HexDec `9c`", "HexDec `%x`", new Byte((byte)-100));
|
||||
_testFormat("HexDec `9C`", "HexDec `%X`", new Byte((byte)-100));
|
||||
_testFormat("HexDec `1`", "HexDec `%x`", new Long((long)1));
|
||||
_testFormat("HexDec `1`", "HexDec `%X`", new Long((long)1));
|
||||
_testFormat("HexDec `0`", "HexDec `%x`", new Long((long)0));
|
||||
_testFormat("HexDec `0`", "HexDec `%X`", new Long((long)0));
|
||||
_testFormat("HexDec `64`", "HexDec `%x`", new Long((long)100));
|
||||
_testFormat("HexDec `64`", "HexDec `%X`", new Long((long)100));
|
||||
_testFormat("HexDec `186a0`", "HexDec `%x`", new Long((long)100000));
|
||||
_testFormat("HexDec `186A0`", "HexDec `%X`", new Long((long)100000));
|
||||
_testFormat("HexDec `3f`", "HexDec `%x`", new Long((long)63));
|
||||
_testFormat("HexDec `3F`", "HexDec `%X`", new Long((long)63));
|
||||
_testFormat("HexDec `40`", "HexDec `%x`", new Long((long)64));
|
||||
_testFormat("HexDec `40`", "HexDec `%X`", new Long((long)64));
|
||||
_testFormat("HexDec `ffffffffffffffff`", "HexDec `%x`", new Long((long)-1));
|
||||
_testFormat("HexDec `FFFFFFFFFFFFFFFF`", "HexDec `%X`", new Long((long)-1));
|
||||
_testFormat("HexDec `ffffffffffffff9c`", "HexDec `%x`", new Long((long)-100));
|
||||
_testFormat("HexDec `FFFFFFFFFFFFFF9C`", "HexDec `%X`", new Long((long)-100));
|
||||
_testFormat("HexDec `fffffffffffe7960`", "HexDec `%x`", new Long((long)-100000));
|
||||
_testFormat("HexDec `FFFFFFFFFFFE7960`", "HexDec `%X`", new Long((long)-100000));
|
||||
_testFormat("HexDec `1`", "HexDec `%x`", new Short((short)1));
|
||||
_testFormat("HexDec `1`", "HexDec `%X`", new Short((short)1));
|
||||
_testFormat("HexDec `0`", "HexDec `%x`", new Short((short)0));
|
||||
_testFormat("HexDec `0`", "HexDec `%X`", new Short((short)0));
|
||||
_testFormat("HexDec `64`", "HexDec `%x`", new Short((short)100));
|
||||
_testFormat("HexDec `64`", "HexDec `%X`", new Short((short)100));
|
||||
_testFormat("HexDec `3f`", "HexDec `%x`", new Short((short)63));
|
||||
_testFormat("HexDec `3F`", "HexDec `%X`", new Short((short)63));
|
||||
_testFormat("HexDec `40`", "HexDec `%x`", new Short((short)64));
|
||||
_testFormat("HexDec `40`", "HexDec `%X`", new Short((short)64));
|
||||
_testFormat("HexDec `ffff`", "HexDec `%x`", new Short((short)-1));
|
||||
_testFormat("HexDec `FFFF`", "HexDec `%X`", new Short((short)-1));
|
||||
_testFormat("HexDec `ff9c`", "HexDec `%x`", new Short((short)-100));
|
||||
_testFormat("HexDec `FF9C`", "HexDec `%X`", new Short((short)-100));
|
||||
}
|
||||
|
||||
public void testWidths() {
|
||||
_testFormat("0001", "%04d", 1);
|
||||
_testFormat(" 1", "%4d", 1);
|
||||
_testFormat(" 11", "%4x", 17);
|
||||
_testFormat("0011", "%04x", 17);
|
||||
_testFormat(" a", "%2x", 10);
|
||||
_testFormat(" A", "%2X", 10);
|
||||
_testFormat("a ", "%-2x", 10);
|
||||
_testFormat("A ", "%-2X", 10);
|
||||
_testFormat("10000", "%4d", 10000);
|
||||
_testFormat("Hello World ", "%-15s", "Hello World");
|
||||
_testFormat(" Hello World", "%15s", "Hello World");
|
||||
}
|
||||
|
||||
public void testPrecisions() {
|
||||
_testFormat("Hello", "%-1.5s", "Hello World");
|
||||
_testFormat("Hello", "%1.5s", "Hello World");
|
||||
}
|
||||
|
||||
}
|
Loading…
Reference in New Issue
Block a user