Neal Gafter's closures prototype http://www.javac.info/closures.tar.gz is now feature-complete and, among others, supports returning from inside an unrestricted closure to a target outside the closure. This was for me an excellent reason to revisit my database example and refactor its code.
My database example uses the sample database that comes with Netbeans. Start the database server (on a Unix based platforn using asadmin start-database in your ~/.netbeans-derby directory).
A good way to learn about Java closures is via Neal Gafter's JavaOne 2008 Closures Cookbook talk. You can download the slides here.
Before starting let's briefly try to convince the non-believers (are there still any out there?) about the usefulness of closures from a conceptual point of view. The example illustrates this usefulness from a practical point of view. Of course, this conceptual point of view is necessarily informal (this post is not about the mathematics behind it all).
Compositionality is a useful thing: it enables us to recursively build components from atomic components. When realized in an appropriate way, compositionality enables component reuse.
Parameterization is a useful thing: it enables us to recursively plug components into component templates. When realized in an appropriate way, parameterization enables component template reuse..
So, as far as functionality substitution is concerned, the Java realization is sub-optimal: we need an extra object layer to obtain methods. Of course, is technically possible to use anonymous instances of anonymous classes implementing a one-method interface (compare this with the need for a calculator to do a calculation, or the need for a manager to do management, or the need for a printer to do a print job) : what an overkill, not very elegant!. This is where closures come to the rescue: they are, rougly speaking, blocks of code that can be used both as objects and methods. Using closures it is not not only possible to reuse blocks of code, but also blocks of code templates in an elegant way.
When processing a result set that has been obtained via a data source by executing a query, the following coding standards could have been imposed company wide:
The allocated database resources are
Things that can go wrong at the database level are
Let's start with the second coding standard. This coding standard can be imposed in a general way using a DSL consisting of the closure processing methods wrapIf and unwrapAndExitIf:
public static <throws E> void wrapIf(
{ Exception => boolean } exceptionPredicate,
E wrappingException,
{ ==> void throws Exception } throwingBlock) throws E, Exception {
try {
throwingBlock.invoke();
} catch(Exception thrownException) {
if(exceptionPredicate.invoke(thrownException)) {
wrappingException.initCause(thrownException);
throw wrappingException;
} else {
throw thrownException;
}
}
}
public static void unwrapAndExitIf(
{ Exception => boolean } exceptionPredicate,
{ ==> void throws Exception } throwingBlock) throws Exception {
try {
throwingBlock.invoke();
} catch(Exception exception) {
if(exceptionPredicate.invoke(exception)) {
Logger logger = Logger.getLogger("");
logger.severe(
exception.getClass().getName() + " thrown\n" +
"cause: " + exception.getCause() + "\nexiting...");
System.exit(1);
} else {
throw exception;
}
}
}
Let's continue with the first coding standard. This coding standard can be imposed using a DSL consisting of the closure processing method withResultSet. Note that the code makes use of the previous closure processing methods for dealing with database related exceptions. It also makes use of a predicate isSQLException that is defined elsewhere.
private static { Exception => boolean } isSQLException
= SQLPredicates#isSQLException(Exception);
public static <Y, throws E> Y withResultSet(
DataSource dataSource, String query,
{ ResultSet ==> Y throws SQLException } resultSetProcessor) throws E,
GetConnectionException, CreateStatementException,
ExecuteQueryException, ProcessResultSetException,
CloseResultSetException, CloseStatementException, CloseConnectionException {
Connection connection = null;
wrapIf(isSQLException, new GetConnectionException()) {
connection = dataSource.getConnection();
}
Statement statement = null;
wrapIf(isSQLException, new CreateStatementException()) {
statement = connection.createStatement();
}
ResultSet resultSet = null;
wrapIf(isSQLException, new ExecuteQueryException()) {
resultSet = statement.executeQuery(query);
}
try {
wrapIf(isSQLException, new ProcessResultSetException()) {
return resultSetProcessor.invoke(resultSet);
}
} finally {
if(resultSet != null) {
wrapIf(isSQLException, new CloseResultSetException()) {
statement.close();
}
}
if(statement != null) {
wrapIf(isSQLException, new CloseStatementException()) {
statement.close();
}
}
if(connection != null) {
wrapIf(isSQLException, new CloseConnectionException()) {
connection.close();
}
}
}
// still here?
return null;
}
We are ready now for using our closure processing methods in an application. It makes use of a predicate iswrappedSQLException that is defined elsewhere. Read the code as with the result set obtained from a data source by executing a query.
private void showProductInfo(DataSource dataSource) throws
GetConnectionException, CreateStatementException,
ExecuteQueryException, ProcessResultSetException,
CloseResultSetException, CloseStatementException, CloseConnectionException {
withResultSet(ResultSet resultSet: dataSource, "select * from PRODUCT") {
while(resultSet.next()) {
System.out.println(
new Product(
resultSet.getInt("PRODUCT_ID"), resultSet.getString("DESCRIPTION")));
}
}
}
private List<Product> getProductInfo(DataSource dataSource) throws
GetConnectionException, CreateStatementException,
ExecuteQueryException, ProcessResultSetException,
CloseResultSetException, CloseStatementException, CloseConnectionException {
List<Product> products = new LinkedList<Product>();
withResultSet(dataSource, "select * from PRODUCT",
{ ResultSet resultSet ==>
while(resultSet.next()) {
products.add(
new Product(
resultSet.getInt("PRODUCT_ID"), resultSet.getString("DESCRIPTION")));
}
// returning from inside an unrestricted closure to a target outside the closure
return products;
});
}
private { Exception => boolean } isWrappedSQLException
= SQLPredicates#isWrappedSQLException(Exception);
// ...
unwrapAndExitIf(isWrappedSQLException) {
showProductInfo(dataSource);
}
unwrapAndExitIf(isWrappedSQLException) {
System.out.println(getProductInfo(dataSource));
}
// ...
It is instructive to throw other exceptions as well. For example, try something like:
// ...
while(resultSet.next()) {
if(Math.random()<0.05) throw new RuntimeException("RuntimeException");
System.out.println(
new Product(
resultSet.getInt("PRODUCT_ID"), resultSet.getString("DESCRIPTION")));
}
// ...
If you are interested in the code: luc duponcheel at gmail com

0 comments:
Post a Comment