In the Logic layer, the file move function we saw previously could be written in the following way [i].

package org.qoolloop.sample.exceptionhandling;

import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.DirectoryNotEmptyException;
import static java.nio.file.StandardCopyOption.REPLACE_EXISTING;

public class FileManipulation {
        
    ・・・

    /**
     * Move a file.
     * 
     * @param i_sourceName path of the file to move
     * @param i_destinationName the new path for the file
     *  
     * @throws RecoveredException_NoSuchFile The source did not exist.
     * @throws RecoveredException_SourceNotFile The source was not a file.
     * @throws RecoveredException_DirectoryNotEmpty The destination was a non-empty directory.
     * @throws UnrecoveredException_IO There was an I/O exception, and storage may not have been recovered.
     */
        public static void move(String i_sourceName, String i_destinationName) 
	throws RecoveredException_NoSuchFile, RecoveredException_SourceNotFile, 
	       RecoveredException_DirectoryNotEmpty, UnrecoveredException_IO
    {        
	File sourceFile = new File(i_sourceName);
	File destinationFile = new File(i_destinationName);
                
	if (!sourceFile.exists()) {
	    throw new RecoveredException_NoSuchFile(null, sourceFile);
	}

	if (!sourceFile.isFile()) {
	    throw new RecoveredException_SourceNotFile(i_sourceName, null);
	}

	try {
	    Files.move(sourceFile.toPath(),destinationFile.toPath(), REPLACE_EXISTING);
	} catch (DirectoryNotEmptyException c_exception) {
	    throw new RecoveredException_DirectoryNotEmpty(c_exception, destinationFile);
	} catch (IOException c_exception) {
	    throw new UnrecoveredException_IO(c_exception, sourceFile, destinationFile);
	} catch (Throwable c_thrown) {
	    throw new FatalError("Unexpected Throwable", c_thrown);
	}
    }        
}

See how the exceptions thrown from Files.move() are translated to exceptions defined in our program. Here, RecoveredException_NoSuchFile, RecoveredException_SourceNotFile, and RecoveredException_DirectoryNotEmpty are subclasses of RecoveredException, since nothing permanent has happened till the point they are thrown.

FatalError has not been subclassed. If an error is not documented or not put in the throws clause, there is no point in defining it as a subclass of FatalError, since the caller will not be catching that specific class anyway. The subclassing of java.lang.Error can provide information useful for debugging. However, this can be done with messages and logs. These can include a lot more information than the name of the class.

As a rule, exceptions in a callee’s module should be converted to exceptions defined in the caller’s module unless they are system-wide exceptions. This is because of the information hiding principle. In addition to functions, exceptions also comprise the API of the module. If we expose the exceptions of the libraries that we are using in the API, we will lose the ability to change the implementation.

In reality, it is a little too time consuming to define exceptions for each package. As a compromise, we might define exceptions that are shared between a group of packages, such as those that share a common package higher in the hierarchy.


[i] In production code, this function above needs to be a little more complicated to accommodate the situations that Files.move() cannot handle. I will not be delving too much into the details here, since the purpose of this document is to explain the overall way of thinking, not the specific cases.

Contents

Background

Review of Exception Handling Basics

The Requirements

The Three-tiered Exception Handling Architecture

Summary