Tuesday, May 19, 2015

Java 8: Getting rid of checked Exceptions

The scenario

  • you have some DAO that can enrich a DTO with different pieces of information
  • the call site can specify which information options should be added to the DTO
  • there are many different types of options that could potentially be added
  • you hate switch/case but you love java 8 streams
  • you want to play with java 8

The problem

  • we want to collect enrichment-methods in a Map and execute them dynamically
  • our enrichment methods throw SQLException and (for some crazy reason) cannot be changed
  • we still want (for some other crazy reason) to propagate any SQLException up to the call site

The solution

  • we create a @FunctionalInterface representing something that consumes a T and throws a SQLException
  • we allow that interface to transform itself into a Function that takes a T and returns an Optional<SQLException>.
Thus we can remove the checked Exception that would otherwise have stopped us from using method references in options.stream().

import java.sql.SQLException;  
import java.util.Map;  
import java.util.Optional;  
import java.util.Set;  
import java.util.function.Function;

import static java.util.Arrays.asList;

public class SomeDao<T> {  
    private final Map<With, OptionHandler<T>> optionHandlers;

    public SomeDao() {
        this.optionHandlers = ImmutableMap.of(
                With.THIS, this::enrichWithThis,
                With.THAT, this::enrichWithThat
        );
    }

    public void enrich(final T someDto, final With... options)
            throws SQLException {
        final Optional<SQLException> e = asList(options).stream()
                .map(optionHandlers::get)
                .map(OptionHandler::toFunction)
                .map(function -> function.apply(someDto))
                .filter(Optional::isPresent)
                .map(Optional::get)
                .findAny(); // this will short circuit execution
                            // in case a SQLException occurs

        if (e.isPresent()) {
            throw e.get();
        }
    }

    @FunctionalInterface
    private interface OptionHandler<T> {
        void accept(final T t) throws SQLException;

        default Function<T, Optional<SQLException>> toFunction() {
            return argument -> {
                try {
                    accept(argument);
                    return Optional.empty();
                } catch (SQLException e) {
                    return Optional.of(e);
                }
            };
        }
    }

    public enum With {
        THIS, THAT
    }

    private void enrichWithThis(final T dto) throws SQLException {
        // something
    }

    private void enrichWithThat(final T dto) throws SQLException {
        // something
    }
}

The call site would typically look something like this:

 
  private final SomeDao<MyDto> someDao;
  ...

  private void prepareMyDto(final MyDto myDto) {
    someDao.enrich(myDto, With.THIS, With.THAT);
  }
}