A method pattern we have suggested to use along with @Yield annotation brought funny questions like: "why should I mark my method with @Yield annotation at all?"
@Yield
Well, in many cases you may live with ArrayList populated with data, and then to perform iteration. But in some cases this approach is not practical either due to amount of data or due to the time required to get first item.
ArrayList
In later case you usually want to build an iterator that calculates items on demand. The @Yield annotation is designed as a marker of such methods. They are refactored into state machines at compilation time, where each addition to a result list is transformed into a new item yielded by the iterator.
So, if you have decided to use @Yield annotation then at some point you will ask yourself what happens with resources acquired during iteration. Will resources be released if iteration is interrupted in the middle due to exception or a break statement?
To address the problem yield iterator implements Closeable interface.
Closeable
This way when you call close() before iteration reached the end, the state machine works as if break statement of the method body is injected after the yield point. Thus all finally blocks of the original method are executed and resources are released.
close()
Consider an example of data iterator:
@Yield public Iterable<Data> getData(final Connection connection) throws Exception { ArrayList<Data> result = new ArrayList<Data>(); PreparedStatement statement = connection.prepareStatement("select key, value from table"); try { ResultSet resultSet = statement.executeQuery(); try { while(resultSet.next()) { Data data = new Data(); data.key = resultSet.getInt(1); data.value = resultSet.getString(2); result.add(data); // yield point } } finally { resultSet.close(); } } finally { statement.close(); } return result; } private static void close(Object value) throws IOException { if (value instanceof Closeable) { Closeable closeable = (Closeable)value; closeable.close(); } } public void daoAction(Connection connection) throws Exception { Iterable<Data> items = getData(connection); try { for(Data data: items) { // do something that potentially throws exception. } } finally { close(items); } }
getData() iterates over sql data. During the lifecycle it creates and releases PreparedStatement and ResultSet.
getData()
PreparedStatement
ResultSet
daoAction() iterates over results provided by getData() and performs some actions that potentially throw an exception. The goal of close() is to release opened sql resources in case of such an exception.
daoAction()
Here you can inspect how state machine is implemented for such a method:
@Yield() public static Iterable<Data> getData(final Connection connection) throws Exception { assert (java.util.ArrayList<Data>)(ArrayList<Data>)null == null; class $state implements java.lang.Iterable<Data>, java.util.Iterator<Data>, java.io.Closeable { public java.util.Iterator<Data> iterator() { if ($state$id == 0) { $state$id = 1; return this; } else return new $state(); } public boolean hasNext() { if (!$state$nextDefined) { $state$hasNext = $state$next(); $state$nextDefined = true; } return $state$hasNext; } public Data next() { if (!hasNext()) throw new java.util.NoSuchElementException(); $state$nextDefined = false; return $state$next; } public void remove() { throw new java.lang.UnsupportedOperationException(); } public void close() { do switch ($state$id) { case 3: $state$id2 = 8; $state$id = 5; continue; default: $state$id = 8; continue; } while ($state$next()); } private boolean $state$next() { java.lang.Throwable $state$exception; while (true) { try { switch ($state$id) { case 0: $state$id = 1; case 1: statement = connection.prepareStatement("select key, value from table"); $state$exception1 = null; $state$id1 = 8; $state$id = 2; case 2: resultSet = statement.executeQuery(); $state$exception2 = null; $state$id2 = 6; $state$id = 3; case 3: if (!resultSet.next()) { $state$id = 4; continue; } data = new Data(); data.key = resultSet.getInt(1); data.value = resultSet.getString(2); $state$next = data; $state$id = 3; return true; case 4: $state$id = 5; case 5: { resultSet.close(); } if ($state$exception2 != null) { $state$exception = $state$exception2; break; } if ($state$id2 > 7) { $state$id1 = $state$id2; $state$id = 7; } else $state$id = $state$id2; continue; case 6: $state$id = 7; case 7: { statement.close(); } if ($state$exception1 != null) { $state$exception = $state$exception1; break; } $state$id = $state$id1; continue; case 8: default: return false; } } catch (java.lang.Throwable e) { $state$exception = e; } switch ($state$id) { case 3: case 4: $state$exception2 = $state$exception; $state$id = 5; continue; case 2: case 5: case 6: $state$exception1 = $state$exception; $state$id = 7; continue; default: $state$id = 8; java.util.ConcurrentModificationException ce = new java.util.ConcurrentModificationException(); ce.initCause($state$exception); throw ce; } } } private PreparedStatement statement; private ResultSet resultSet; private Data data; private int $state$id; private boolean $state$hasNext; private boolean $state$nextDefined; private Data $state$next; private java.lang.Throwable $state$exception1; private int $state$id1; private java.lang.Throwable $state$exception2; private int $state$id2; } return new $state(); }
Now, you can estimate for what it worth to write an algorithm as a sound state machine comparing to the conventional implementation.
Yield annotation processor can be downloaded from Yield.zip or Yield.jar
See also Yield return feature in java.
Remember Me
a@href@title, b, blockquote@cite, em, i, strike, strong, sub, super, u