'. '

TryCatchRedo

From APIDesign

(Difference between revisions)
Jump to: navigation, search
(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...)
Line 1: Line 1:
-
Often when thinking about [[APIDesignPatterns:Exceptions|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 [[ExceptionExtensibility|inheritance]] or other object oriented techniques when dealing with [[APIDesignPatterns:Exceptions|exceptions]]. Sometimes this is justified, sometimes it is too restricting approach. In [[Java]] [[APIDesignPatterns:Exceptions|exceptions]] are just like any regular classes and this can sometimes be used to achieve surprising results.
+
Often when reasoning about [[APIDesignPatterns:Exceptions|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 [[ExceptionExtensibility|inheritance]] or other object oriented aspects when dealing with [[APIDesignPatterns:Exceptions|exceptions]]. Sometimes this is justified, sometimes it can be too restricting. In [[Java]] [[APIDesignPatterns:Exceptions|exceptions]] are just like any regular classes and this can be sometimes 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' [[GettersAndSetters|getters]] to get more info about the failure represented by the exception. However only few explore design where exceptions also contain [[GettersAndSetters|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.
+
All developers think about exceptions as something that can be thrown and caught. Some know that it is useful to call exceptions' [[GettersAndSetters|getters]] to get more info about the failure represented by the exception. However only few explore design style where exceptions also contain [[GettersAndSetters|setters]] or other mutable methods. Not that it would make sense to modify state of the exception itself, as that is short living object, however the exception can serve as a bridge to some internal longer living object and can be its interface allowing changes to its state.
-
This imperative can be use to implement '''Try/Catch/Redo''' [[APIDesignPatterns|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 [[ExceptionExtensibility|extended]] '''IOException''':
+
This imperative can be used to implement '''Try/Catch/Redo''' [[APIDesignPatterns|pattern]]. Imagine that some code deep inside of a 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 [[ExceptionExtensibility|extended]] '''IOException''':
<source lang="java" snippet="trycatchredo.UserQuestionException"/>
<source lang="java" snippet="trycatchredo.UserQuestionException"/>
Line 15: Line 15:
<source lang="java" snippet="trycatchredo.SaveActionWithQuery"/>
<source lang="java" snippet="trycatchredo.SaveActionWithQuery"/>
-
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:
+
The '''confirm''' method is the callback to the internals of the '''URLConnection''' provider, like the [[NetBeans]] version control modules. The '''confirm''' method can change the internal state, just like in simple ''stream'' example:
<source lang="java" snippet="trycatchredo.stream"/>
<source lang="java" snippet="trycatchredo.stream"/>
 +
After ''confirming'' the desire to really ''lock'' the file, one can '''redo''' the whole operation. The stream's internal state is now changed and the whole operation successfully proceeds.
[[Category:APIDesignPatterns]]
[[Category:APIDesignPatterns]]
[[Category:APIDesignPatterns:Exceptions]]
[[Category:APIDesignPatterns:Exceptions]]

Revision as of 17:42, 1 February 2009

Often when reasoning 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 aspects when dealing with exceptions. Sometimes this is justified, sometimes it can be too restricting. In Java exceptions are just like any regular classes and this can be sometimes 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 style where exceptions also contain setters or other mutable methods. Not that it would make sense to modify state of the exception itself, as that is short living object, however the exception can serve as a bridge to some internal longer living object and can be its interface allowing changes to its state.

This imperative can be used to implement Try/Catch/Redo pattern. Imagine that some code deep inside of a 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. The confirm method can change the internal state, just like in simple stream example:

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;
            }
        }
    }
}
 

After confirming the desire to really lock the file, one can redo the whole operation. The stream's internal state is now changed and the whole operation successfully proceeds.

Personal tools
buy