Simple Resource Clean-up

Neal Gafter neal at gafter.com
Mon Mar 2 23:23:41 PST 2009


You say "The object being created must implement the Closeable
interface."  But java.sql.Statement, to pick one example, cannot be
made to implement that interface because the exception signatures are
not compatible.

I'll let others comment about issues with exception handling in the translation.

-Neal

On Mon, Mar 2, 2009 at 9:02 PM, Roger Hernandez <rhvarona at gmail.com> wrote:
> I saw how the previous Automatic Resource Management proposal got torn to
> pieces but I feel strongly enough about this issue to post a similar
> proposal.
>
> Some points:
>
> I am not a programming language designer.  I have very little experience
> with byte code and with design and implementation of compilers.  What I do
> have is many years of experience in writing code for large business
> applications, both in house-custom programs and shrink-wrapped products sold
> to customers.
>
> About 1/3 of the Java code I write contributes almost nothing to the
> functionality or flexibility of the program.  It is composed of very simple
> and very repetitive resource clean-up code.  JDBC code is an especially bad
> case.  I know we should be using JDO or JPA, but: 1) most of the Java
> database code in existence is using JDBC to some extent or another, 2) for
> the kind of processing our software does with large datasets having hundreds
> of megabytes of row data and millions of rows per account per month, custom
> JDBC code still beats the other solutions in memory utilization and
> throughput.
>
> In most business code we don't do complex exception processing.  If we get a
> exception we rollback the transaction and unroll the stack until we reach
> some program level error handler that logs the error for some administrator
> to review at a later date.  So if this proposal is not applicable to complex
> error handling scenarios, that is fine.  Taking care of the simple scenarios
> will still get rid of most of that 1/3 of the code I write, allowing me to
> concentrate on the actual program logic, not the resource clean-up noise.
>
> I also program quite a bit in C++ and C# and when I work in Java I sorely
> miss RAII (Resource Acquisition Is Initialization) and the "using" statement
> respectively.
>
> At the end of the day, what I would like is a solution to minimize all the
> resource clean-up boiler plate.
>
> -----------------------------------------------------------------------------------------
> PROJECT COIN SMALL LANGUAGE CHANGE PROPOSAL FORM v1.0
>
>   AUTHOR: Roger Hernandez, rogerh at velocityconsultinginc.com
>
> OVERVIEW
>
>   FEATURE SUMMARY: Syntactic sugar for simple cases of the common
> new/try/finally/close language idiom.  The object being created must
> implement the Closeable interface.
>
>   MAJOR ADVANTAGE: Significantly reduces lines of code when working with
> objects that encapsulate external resources that should be closed as soon as
> possible.  Examples are Stream, Reader, Writer classes in java.io, and
> Connection, Statement, PreparedStatement, ResultSet in java.sql.*.
>
>   MAJOR BENEFIT: It allows writing code that uses these kinds of object to
> more clearly express the both the lifetime of the utilization of each
> resource, and allows the logic flow of the code to be more visible.
>
>   MAJOR DISADVANTAGE: Either a new keyword, or an additional overloaded
> meaning on an existing keyword.
>
>   ALTERNATIVES: You can always use the standard idiom: SomeType val = new
> val(...); try { ... } finally { val.close(); }
>
> EXAMPLES
>
>   SIMPLE EXAMPLE:  A simple Java version of the command line utility "tee".
>      //This is the existing way of doing it.
>      //Lines of code: 19
>      package com.vci.projectcoin.using;
>
>      import java.io.*;
>
>      public class SimpleExample {
>
>         public static void main(String[] args) throws IOException {
>            byte []buffer = new byte[1024];
>            FileOutputStream out = new FileOutputStream(args[0]);
>            try {
>               for (int count; (count = System.in.read(buffer)) != -1;) {
>                  out.write(buffer, 0, count);
>                  System.out.write(buffer, 0, count);
>               }
>            } finally {
>               out.close();
>            }
>         }
>      }
>
>      //This is the proposed way of doing it, the compiler converts the
> syntactic sugar into the same byte codes
>      //I am adding a new use to the the "try" keyword to avoid adding more
> to the language, but it would work
>      //just a well with a "using" keyword.
>      //Lines of code: 16
>      package com.vci.projectcoin.using;
>
>      import java.io.*;
>
>      public class SimpleExample {
>
>         public static void main(String[] args) throws IOException {
>            byte []buffer = new byte[1024];
>            try (FileOutputStream out = new FileOutputStream(args[0])) {
>               for (int count; (count = System.in.read(buffer)) != -1;) {
>                  out.write(buffer, 0, count);
>                  System.out.write(buffer, 0, count);
>               }
>            }
>         }
>      }
>
>   ADVANCED EXAMPLE: A simple utility to execute a query and write values as
> a comma delimited file.
>
>      //This is the existing way of doing it
>      //Lines of code: 55
>      package com.vci.projectcoin.using;
>
>      import java.sql.*;
>      import java.io.*;
>
>      public class AdvancedExample {
>         final static String EOL = System.getProperty("line.separator");
>
>         //Command Line: <JDBC url> <query sql> [<output file>]
>         static public void main(String []args) throws SQLException,
> FileNotFoundException {
>            String url = args[0];
>            String sql = args[1];
>            PrintWriter out = new PrintWriter(args.length > 2 ? new
> FileOutputStream(args[2]) : System.out);
>            try {
>               Connection conn = DriverManager.getConnection(url);
>               try {
>                  Statement query =
> conn.createStatement(ResultSet.TYPE_FORWARD_ONLY,
> ResultSet.CONCUR_READ_ONLY);
>                  try {
>                     ResultSet results = query.executeQuery(sql);
>                     try {
>                        ResultSetMetaData meta = results.getMetaData();
>                        int colCount = meta.getColumnCount();
>                        while (results.next()) {
>                           for (int index = 1; index <= colCount; index++) {
>                              int colType = meta.getColumnType(index);
>                              boolean quoted = colType == Types.CHAR  ||
> colType == Types.LONGNVARCHAR || colType == Types.LONGVARCHAR ||
>                                               colType == Types.NCHAR ||
> colType == Types.NVARCHAR     || colType == Types.VARCHAR;
>                              if (quoted) {
>                                 System.out.append('"');
>                              }
>                              System.out.append(results.getString(index));
>                              if (quoted) {
>                                 System.out.append('"');
>                              }
>                              if (index < colCount) {
>                                 System.out.print(',');
>                              } else {
>                                 System.out.print(EOL);
>                              }
>                           }
>                        }
>                     } finally {
>                        results.close();
>                     }
>                  } finally {
>                     query.close();
>                  }
>               } finally {
>                  conn.close();
>               }
>            } finally {
>               out.close();
>            }
>         }
>      }
>
>      //This is the proposed way of doing it
>      //This proposal gets rid of the finally clean up per object.  It lets
> one write robust resource clean-up code without a lot of effort.
>      //Lines of code: 43
>      package com.vci.projectcoin.using;
>
>      import java.sql.*;
>      import java.io.*;
>
>      public class AdvancedExample {
>         final static String EOL = System.getProperty("line.separator");
>
>         //Command Line: <JDBC url> <query sql> [<output file>]
>         static public void main(String []args) throws SQLException,
> FileNotFoundException {
>            String url = args[0];
>            String sql = args[1];
>            try (PrintWriter out = new PrintWriter(args.length > 2 ? new
> FileOutputStream(args[2]) : System.out)) {
>               try (Connection conn = DriverManager.getConnection(url)) {
>                  try (Statement query =
> conn.createStatement(ResultSet.TYPE_FORWARD_ONLY,
> ResultSet.CONCUR_READ_ONLY)) {
>                     try (ResultSet results = query.executeQuery(sql)) {
>                        ResultSetMetaData meta = results.getMetaData();
>                        int colCount = meta.getColumnCount();
>                        while (results.next()) {
>                           for (int index = 1; index <= colCount; index++) {
>                              int colType = meta.getColumnType(index);
>                              boolean quoted = colType == Types.CHAR ||
> colType == Types.LONGNVARCHAR || colType == Types.LONGVARCHAR || colType ==
> Types.NCHAR || colType == Types.NVARCHAR || colType == Types.VARCHAR;
>                              if (quoted) {
>                                 System.out.append('"');
>                              }
>                              System.out.append(results.getString(index));
>                              if (quoted) {
>                                 System.out.append('"');
>                              }
>                              if (index < colCount) {
>                                 System.out.print(',');
>                              } else {
>                                 System.out.print(EOL);
>                              }
>                           }
>                           System.out.println();
>                        }
>                     }
>                  }
>               }
>            }
>         }
>      }
>
>      //This is an additional syntactic sugar proposal, allowing multiple
> objects to be allocated inside one try block.  The compiler converts all
> three programs into the same bytecode
>      //This proposal gets rid of the additional indentation level and
> closing brace per object.  It further minimize the clean-up boiler-plate,
> allowing the point of the program logic to be clearer.
>      //Lines of code: 38
>      package com.vci.projectcoin.using;
>
>      import java.sql.*;
>      import java.io.*;
>
>      public class AdvancedExample {
>         final static String EOL = System.getProperty("line.separator");
>
>         //Command Line: <JDBC url> <query sql> [<output file>]
>         static public void main(String []args) throws SQLException,
> FileNotFoundException {
>            String url = args[0];
>            String sql = args[1];
>            try (PrintWriter out = new PrintWriter(args.length > 2 ? new
> FileOutputStream(args[2]) : System.out),
>                 Statement query =
> conn.createStatement(ResultSet.TYPE_FORWARD_ONLY,
> ResultSet.CONCUR_READ_ONLY),
>                 ResultSet results = query.executeQuery(sql)) {
>               ResultSetMetaData meta = results.getMetaData();
>               int colCount = meta.getColumnCount();
>               while (results.next()) {
>               for (int index = 1; index <= colCount; index++) {
>                  int colType = meta.getColumnType(index);
>                  boolean quoted = colType == Types.CHAR  || colType ==
> Types.LONGNVARCHAR || colType == Types.LONGVARCHAR || colType == Types.NCHAR
> || colType == Types.NVARCHAR     || colType == Types.VARCHAR;
>                  if (quoted) {
>                     System.out.append('"');
>                  }
>                  System.out.append(results.getString(index));
>                  if (quoted) {
>                     System.out.append('"');
>                  }
>                  if (index < colCount) {
>                     System.out.print(',');
>                  } else {
>                     System.out.print(EOL);
>                  }
>               }
>               System.out.println();
>               }
>            }
>         }
>      }
>
> DETAILS
>   The specification requires that the object in the try () block have a
> "close()" method.  Wether the method throws any or no exception, or if it
> returns a value or no value does not matter.  The proposal is not trying to
> introduce any new intelligence into the try finally clause, it is just
> syntactic sugar to minimize simple resource clean-up code.
>
>   SPECIFICATION:
>      The "try" keyword will have an overloaded meaning
>
>      CASE 1
>      ------
>      try (ClassWithCloseMethod value = new ClassWithCloseMethod(...)) {
>         //work gets done here
>      }
>
>      Will be syntactic sugar for:
>      ClassWithCloseMethod value = new ClassWithCloseMethod(...);
>      try {
>         //work gets done here
>      } finally {
>         value.close();
>      }
>
>      CASE 2
>      ------
>      try (ClassWithCloseMethod value = new ClassWithCloseMethod(...)) {
>         //work gets done here
>      } finally {
>         //additional clean-up code
>      }
>
>      Will be syntactic sugar for:
>      ClassWithCloseMethod value = new ClassWithCloseMethod(...);
>      try {
>         //work gets done here
>      } finally {
>         value.close();
>         //additional clean-up code
>      }
>
>      CASE 3
>      ------
>      try (ClassWithCloseMethod value = new ClassWithCloseMethod(...)) {
>         //work gets done here
>      } catch (Exception ex) {
>         //exception handling code
>      } finally {
>         //additional clean-up code
>      }
>
>      Will be syntactic sugar for:
>      ClassWithCloseMethod value = new ClassWithCloseMethod(...);
>      try {
>         //work gets done here
>      } catch (Exception ex) {
>         //exception handling code
>      } finally {
>         value.close();
>         //additional clean-up code
>      }
>
>      CASE 4
>      ------
>      try (Class1WithCloseMethod value1 = new Class1WithCloseMethod(...),
>           Class2WithCloseMethod value2 = new Class2WithCloseMethod(...),
>           Class3WithCloseMethod value3 = new Class3WithCloseMethod(...)) {
>         //work gets done here
>      }
>
>      Will be syntactic sugar for:
>      Class1WithCloseMethod value1 = new Class1WithCloseMethod(...);
>      try {
>         Class2WithCloseMethod value2 = new Class2WithCloseMethod(...);
>         try {
>            Class3WithCloseMethod value3 = new Class3WithCloseMethod(...);
>            try {
>               //work gets done here
>            } finally {
>               value3.close();
>            }
>         } finally {
>            value2.close();
>         }
>      } finally {
>         value1.close();
>      }
>
>      CASE 5
>      ------
>      try (Class1WithCloseMethod value1 = new Class1WithCloseMethod(...),
>           Class2WithCloseMethod value2 = new Class2WithCloseMethod(...),
>           Class3WithCloseMethod value3 = new Class3WithCloseMethod(...)) {
>         //work gets done here
>      } finally {
>         //additional clean-up code
>      }
>
>      Will be syntactic sugar for:
>      Class1WithCloseMethod value1 = new Class1WithCloseMethod(...);
>      try {
>         Class2WithCloseMethod value2 = new Class2WithCloseMethod(...);
>         try {
>            Class3WithCloseMethod value3 = new Class3WithCloseMethod(...);
>            try {
>               //work gets done here
>            } finally {
>               value3.close();
>            }
>         } finally {
>            value2.close();
>         }
>      } finally {
>         value1.close();
>         //additional clean-up code
>      }
>
>      CASE 6
>      ------
>      try (Class1WithCloseMethod value1 = new Class1WithCloseMethod(...),
>           Class2WithCloseMethod value2 = new Class2WithCloseMethod(...),
>           Class3WithCloseMethod value3 = new Class3WithCloseMethod(...)) {
>         //work gets done here
>      } catch (Exception ex) {
>         //exception handling code
>      } finally {
>         //additional clean-up code
>      }
>
>      Will be syntactic sugar for:
>      Class1WithCloseMethod value1 = new Class1WithCloseMethod(...);
>      try {
>         Class2WithCloseMethod value2 = new Class2WithCloseMethod(...);
>         try {
>            Class3WithCloseMethod value3 = new Class3WithCloseMethod(...);
>            try {
>               //work gets done here
>            } finally {
>               value3.close();
>            }
>         } finally {
>            value2.close();
>         }
>      } catch (Exception ex) {
>         //exception handling code
>      } finally {
>         value1.close();
>         //additional clean-up code
>      }
>
>   COMPILATION: The SPECIFICATION section above shows the desugaring for
> each case.  Byte code would be identical to the desugared constructs.
>
>   TESTING: Byte code comparison of common code constructs.  If the byte
> code is not identical to the desugared version, test fails.
>
>   LIBRARY SUPPORT: No.
>
>   REFLECTIVE APIS: No.
>
>   OTHER CHANGES: No.
>
>   MIGRATION: For each case in the SPECIFICATION section, convert the
> existing code to the syntactic sugar proposal.
>
>
> COMPATIBILITY
>
>   BREAKING CHANGES: None.
>
>   EXISTING PROGRAMS: Compile accepts both existing and new forms of the
> "try" statement.  Byte code does not change.
>
> REFERENCES
>
>   EXISTING BUGS: None
>
>
> --
> Roger Hernandez
>
>



More information about the coin-dev mailing list