Sunday 1 August 2010

Java: a fluent I/O API (3/4)

This is the third post about my experiments with a fluent I/O API. This post covers how the API enhances exception handling.

Eliminating unnecessary catch blocks

This code compresses a string into an array of bytes:

  private static byte[] gzip(String data) {
    ByteArrayOutputStream buffer = new ByteArrayOutputStream();
    try {
      OutputStream gzip = new GZIPOutputStream(buffer);
      Closeable resource = gzip;
      try {
        Writer writer = new OutputStreamWriter(gzip, forName("UTF-8"));
        resource = writer;
        writer.write(data);
      finally {
        resource.close();
      }
    catch (IOException e) {
      throw new IllegalStateException(e);
    }
    return buffer.toByteArray();
  }

The try/finally block is necessary. This ensures that the general contract for stream handling is honoured. If the GZIPOutputStream implementation acquired native resources, this would ensure that they are released.

However, it is difficult to conceive of a reasonable situation in which an IOException would occur in this code.

This code eliminates the IOException handling by using the operate method:

  private static byte[] fluentGzip(String data) {
    ByteArrayOutputStream buffer = new ByteArrayOutputStream();
    UncheckedCloser closer = uncheckedCloser();
    try {
      IO.out(closer, buffer)
        .operate(GZIP_OUT)
        .utf8()
        .printer()
        .die()
        .write(data);
    finally {
      closer.close();
    }
    return buffer.toByteArray();
  }

Note that this code does not swallow exceptions. If an IOException were thrown, it would be wrapped in the API's RuntimeIOException.

Runtime exception handling

The problem with checked exceptions is in interacting with methods that don't declare them. Consider this code to write an exception trace to a file:

  private static void writeTraceToFile(File file, Throwable t)
      throws IOException {
    OutputStream out = new FileOutputStream(file, true);
    Closeable resource = out;
    try {
      Writer writer = new OutputStreamWriter(out, forName("UTF-8"));
      resource = writer;
      PrintWriter pw = new PrintWriter(writer);
      resource = pw;
      t.printStackTrace(pw);
      if (pw.checkError()) {
        throw new IOException("Some I/O error happened. "
            "I have no idea which one.");
      }
    finally {
      resource.close();
    }
  }

The PrintWriter will swallow any exceptions thrown by the underlying streams. The boolean isn't a great error handling mechanism: it doesn't expose any useful information; if you pass the PrintWriter off to something else, you have to wait until when it passes it back to your code to check it.

By having an underlying stream that catches the IOException and throws it as a runtime exception, we can get and throw the underlying error:

  private static void writeTraceToFileHandled(File file, Throwable t)
      throws IOException {
    Closer closer = Closers.closer();
    try {
      PrintWriter pw = IO.open(closer, file, true)
                         .utf8()
                         .unchecked()
                         .printer()
                         .die();
      t.printStackTrace(pw);
    catch (RuntimeIOException e) {
      throw e.getIOException();
    finally {
      closer.close();
    }
  }

I/O and checked exception handling is likely to become more of an issue with Java 7 and lambda support.

Related posts

Comments & criticism are welcome.

No comments:

Post a Comment

All comments are moderated