FULL PRODUCT VERSION :
java version "1.7.0_01"
Java(TM) SE Runtime Environment (build 1.7.0_01-b08)
Java HotSpot(TM) Client VM (build 21.1-b02, mixed mode, sharing)
ADDITIONAL OS VERSION INFORMATION :
Microsoft Windows XP [Version 5.1.2600]
A DESCRIPTION OF THE PROBLEM :
When formatting to an Appendable using a java.util.Formatter, IOExceptions thrown by the Appendable during formatting can usually be accessed using the ioException() method of the Formatter.
However, this does not work if (a) an Object implementing the Formattable interface is formatted and (b) a specific Locale which is different from the Formatters Locale is specified using the formatTo(Locale, String, Object...) method. In that case IOExceptions are ignored.
The reason can easily be seen in the source code of Formatter (lines 2826ff in the Oracle JDK 1.7.1_01 sources):
private void printString(Object arg, Locale l) throws IOException {
if (arg instanceof Formattable) {
Formatter fmt = Formatter.this;
if (fmt.locale() != l)
fmt = new Formatter(fmt.out(), l);
((Formattable)arg).formatTo(fmt, f.valueOf(), width, precision);
} else {
if (f.contains(Flags.ALTERNATE))
failMismatch(Flags.ALTERNATE, 's');
if (arg == null)
print("null");
else
print(arg.toString());
}
}
It's obvious that if conditions (a) and (b) both apply, formatTo(...) will not receive the original Formatter instance but a newly created one, using the same Appendable but the Locale specified in the method call instead of the Formatters Locale. Since after calling formatTo(...) there is no check if an IOException has been catched by this "temporary" Formatter. Therefore, if any IOException has been thrown, it will be lost and not accessible through ioException() in the original Formatter.
STEPS TO FOLLOW TO REPRODUCE THE PROBLEM :
I included some source code to demonstrate the problem. Just compile FormattableIOExceptionBug and execute it as a Java application.
EXPECTED VERSUS ACTUAL BEHAVIOR :
EXPECTED -
When executing FormattableIOExceptionBug, the following output would be expected:
(1) java.io.IOException: testing
(2) java.io.IOException: testing
(3) java.io.IOException: testing
(4) java.io.IOException: testing
(5) java.io.IOException: testing
ACTUAL -
The actual output of FormattableIOExceptionBug is:
(1) java.io.IOException: testing
(2) java.io.IOException: testing
(3) java.io.IOException: testing
(4) java.io.IOException: testing
(5) null
REPRODUCIBILITY :
This bug can be reproduced always.
---------- BEGIN SOURCE ----------
import java.io.IOException;
import java.util.Formattable;
import java.util.Formatter;
import java.util.Locale;
public class FormattableIOExceptionBug {
public static void main (String[] args) {
// an Appendable which always throws IOException:
Appendable apd = new ThrowingAppendable();
Formatter fmt = null;
// (1) no locale, not Formattable:
fmt = new Formatter(apd, Locale.US);
fmt.format("%s", "some-string");
System.out.println("(1) " + fmt.ioException());
// (2) no locale, Formattable:
fmt = new Formatter(apd, Locale.US);
fmt.format("%s", new MyFormattable());
System.out.println("(2) " + fmt.ioException());
// (3) with different locale, not Formattable:
fmt = new Formatter(apd, Locale.US);
fmt.format(Locale.GERMANY, "%s", "some-string");
System.out.println("(3) " + fmt.ioException());
// (4) with the Formatters locale, Formattable:
fmt = new Formatter(apd, Locale.US);
fmt.format(Locale.US, "%s", new MyFormattable());
System.out.println("(4) " + fmt.ioException());
// (5) BUG HERE - different locale, Formattable:
fmt = new Formatter(apd, Locale.US);
fmt.format(Locale.GERMANY, "%s", new MyFormattable());
System.out.println("(5) " + fmt.ioException());
}
private static final class ThrowingAppendable implements Appendable {
@Override
public Appendable append (CharSequence csq) throws IOException {
throw new IOException("testing");
}
@Override
public Appendable append (CharSequence csq, int start, int end) throws IOException {
throw new IOException("testing");
}
@Override
public Appendable append (char c) throws IOException {
throw new IOException("testing");
}
}
private static final class MyFormattable implements Formattable {
@Override
public void formatTo (Formatter formatter, int flags, int width, int precision) {
formatter.format("Simple demo implementation ignoring flags, width and precision.");
}
}
}
---------- END SOURCE ----------
CUSTOMER SUBMITTED WORKAROUND :
Sorry, no workaround. But it should be easy to fix.
java version "1.7.0_01"
Java(TM) SE Runtime Environment (build 1.7.0_01-b08)
Java HotSpot(TM) Client VM (build 21.1-b02, mixed mode, sharing)
ADDITIONAL OS VERSION INFORMATION :
Microsoft Windows XP [Version 5.1.2600]
A DESCRIPTION OF THE PROBLEM :
When formatting to an Appendable using a java.util.Formatter, IOExceptions thrown by the Appendable during formatting can usually be accessed using the ioException() method of the Formatter.
However, this does not work if (a) an Object implementing the Formattable interface is formatted and (b) a specific Locale which is different from the Formatters Locale is specified using the formatTo(Locale, String, Object...) method. In that case IOExceptions are ignored.
The reason can easily be seen in the source code of Formatter (lines 2826ff in the Oracle JDK 1.7.1_01 sources):
private void printString(Object arg, Locale l) throws IOException {
if (arg instanceof Formattable) {
Formatter fmt = Formatter.this;
if (fmt.locale() != l)
fmt = new Formatter(fmt.out(), l);
((Formattable)arg).formatTo(fmt, f.valueOf(), width, precision);
} else {
if (f.contains(Flags.ALTERNATE))
failMismatch(Flags.ALTERNATE, 's');
if (arg == null)
print("null");
else
print(arg.toString());
}
}
It's obvious that if conditions (a) and (b) both apply, formatTo(...) will not receive the original Formatter instance but a newly created one, using the same Appendable but the Locale specified in the method call instead of the Formatters Locale. Since after calling formatTo(...) there is no check if an IOException has been catched by this "temporary" Formatter. Therefore, if any IOException has been thrown, it will be lost and not accessible through ioException() in the original Formatter.
STEPS TO FOLLOW TO REPRODUCE THE PROBLEM :
I included some source code to demonstrate the problem. Just compile FormattableIOExceptionBug and execute it as a Java application.
EXPECTED VERSUS ACTUAL BEHAVIOR :
EXPECTED -
When executing FormattableIOExceptionBug, the following output would be expected:
(1) java.io.IOException: testing
(2) java.io.IOException: testing
(3) java.io.IOException: testing
(4) java.io.IOException: testing
(5) java.io.IOException: testing
ACTUAL -
The actual output of FormattableIOExceptionBug is:
(1) java.io.IOException: testing
(2) java.io.IOException: testing
(3) java.io.IOException: testing
(4) java.io.IOException: testing
(5) null
REPRODUCIBILITY :
This bug can be reproduced always.
---------- BEGIN SOURCE ----------
import java.io.IOException;
import java.util.Formattable;
import java.util.Formatter;
import java.util.Locale;
public class FormattableIOExceptionBug {
public static void main (String[] args) {
// an Appendable which always throws IOException:
Appendable apd = new ThrowingAppendable();
Formatter fmt = null;
// (1) no locale, not Formattable:
fmt = new Formatter(apd, Locale.US);
fmt.format("%s", "some-string");
System.out.println("(1) " + fmt.ioException());
// (2) no locale, Formattable:
fmt = new Formatter(apd, Locale.US);
fmt.format("%s", new MyFormattable());
System.out.println("(2) " + fmt.ioException());
// (3) with different locale, not Formattable:
fmt = new Formatter(apd, Locale.US);
fmt.format(Locale.GERMANY, "%s", "some-string");
System.out.println("(3) " + fmt.ioException());
// (4) with the Formatters locale, Formattable:
fmt = new Formatter(apd, Locale.US);
fmt.format(Locale.US, "%s", new MyFormattable());
System.out.println("(4) " + fmt.ioException());
// (5) BUG HERE - different locale, Formattable:
fmt = new Formatter(apd, Locale.US);
fmt.format(Locale.GERMANY, "%s", new MyFormattable());
System.out.println("(5) " + fmt.ioException());
}
private static final class ThrowingAppendable implements Appendable {
@Override
public Appendable append (CharSequence csq) throws IOException {
throw new IOException("testing");
}
@Override
public Appendable append (CharSequence csq, int start, int end) throws IOException {
throw new IOException("testing");
}
@Override
public Appendable append (char c) throws IOException {
throw new IOException("testing");
}
}
private static final class MyFormattable implements Formattable {
@Override
public void formatTo (Formatter formatter, int flags, int width, int precision) {
formatter.format("Simple demo implementation ignoring flags, width and precision.");
}
}
}
---------- END SOURCE ----------
CUSTOMER SUBMITTED WORKAROUND :
Sorry, no workaround. But it should be easy to fix.