TryCatchRedo
From APIDesign
JaroslavTulach (Talk | contribs)
(New page: Often when thinking about exceptions, they are thought to be somewhat special, not really full featured classes. At least many coding practices advice to n...)
Next diff →
Revision as of 16:49, 1 February 2009
Often when thinking about exceptions, they are thought to be somewhat special, not really full featured classes. At least many coding practices advice to not use them in such and such situations. As a result developers often do not think about using inheritance or other object oriented techniques when dealing with exceptions. Sometimes this is justified, sometimes it is too restricting approach. In Java exceptions are just like any regular classes and this can sometimes be used to achieve surprising results.
All developers think about exceptions as something that can be thrown and caught. Some know that it is useful to call exceptions' getters to get more info about the failure represented by the exception. However only few explore design where exceptions also contain setters or other mutable methods. Not that it would make sense to modify state of exception, as that is short living object, however the exception can serve as bridge to some internal longer living object and can server as an interface that changes its state.
This imperative can be use to implement Try/Catch/Redo pattern. Imagine that some code deep inside of set of I/O operations cannot proceed without asking a question. For example the NetBeans version control modules may need to ask the user whether file shall be locked before allowing its content to be changed. For that purpose they may implement a special extended IOException:
Code from UserQuestionException.java:
See the whole file./** Specialized I/O exception to request some kind of user confirmation. * A code that needs to ask user shall not attempt to open a dialog itself, * rather it shall emit this exception and let its callers show the dialog * at appropriate time. * * @author Jaroslav Tulach * @since 2.0 */ public abstract class UserQuestionException extends IOException { /** Description of the dialog to show to the user. Whoever catches * this exception shall use * {@link #getQuestionPane()}. * {@link JOptionPane#createDialog(java.lang.String)} * to construct and display the dialog. * * @return the pane to display to user */ public abstract JOptionPane getQuestionPane(); /** When the user confirms (or rejects) message presented by the * {@link #getQuestionPane()} dialog, the exception shall be notified * by calling this method with {@link JOptionPane#getValue()} option. * * @param option the option selected by the user */ public abstract void confirm(Object option); }
This is just another IOException, so for clueless API users nothing changes. One can continue to write classical saving code:
Code from SaveAction.java:
See the whole file.try { OutputStream os = where.openConnection().getOutputStream(); os.write(what.toString().getBytes()); os.close(); } catch (IOException ex) { JOptionPane.showMessageDialog(null, ex); }
However in case one needs to provide support for safe queries, one can extend the code to recognize the special exception and communicate with it:
Code from SaveActionWithQuery.java:
See the whole file.for (;;) { try { OutputStream os = where.openConnection().getOutputStream(); os.write(what.toString().getBytes()); os.close(); } catch (UserQuestionException ex) { JOptionPane p = ex.getQuestionPane(); JDialog d = p.createDialog(ex.getLocalizedMessage()); setVisible(d, p); ex.confirm(p.getValue()); if ( !p.getValue().equals(JOptionPane.CANCEL_OPTION) && !p.getValue().equals(JOptionPane.CLOSED_OPTION) ) { continue; } } catch (IOException ex) { JOptionPane.showMessageDialog(null, ex); } break; }
The confirm method is the callback to the internals of the URLConnection provider, like the NetBeans version control modules or this simple stream that can ask questions:
Code from QueryStream.java:
See the whole file.public final class QueryStream extends OutputStream { private ByteArrayOutputStream arr = new ByteArrayOutputStream(); /** this field can be manipulated by the QueryException */ Boolean reverse; @Override public synchronized void write(byte[] b, int off, int len) throws IOException { if (reverse == null) { throw new QueryException(); } arr.write(b, off, len); } @Override public synchronized void write(int b) throws IOException { if (reverse == null) { throw new QueryException(); } arr.write(b); } @Override public String toString() { if (reverse == null) { return "Reverse question was not answered yet!"; } if (reverse) { StringBuilder sb = new StringBuilder(); sb.append(arr.toString()); sb.reverse(); return sb.toString(); } return arr.toString(); } private class QueryException extends UserQuestionException { @Override public JOptionPane getQuestionPane() { JOptionPane p = new JOptionPane("Store in reverse way?"); p.setOptionType(JOptionPane.YES_NO_CANCEL_OPTION); return p; } @Override public void confirm(Object option) { if (option.equals(JOptionPane.YES_OPTION)) { reverse = Boolean.TRUE; return; } if (option.equals(JOptionPane.NO_OPTION)) { reverse = Boolean.FALSE; return; } } } }