All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.nextbreakpoint.Try Maven / Gradle / Ivy

The newest version!
/*
 * This file is part of Try
 * 
 * Copyright (c) 2016, Andrea Medeghini
 * All rights reserved.
 */
package com.nextbreakpoint;

import java.util.NoSuchElementException;
import java.util.Objects;
import java.util.Optional;
import java.util.concurrent.Callable;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.function.Predicate;
import java.util.function.Supplier;

/**
 * Try implements a monad for handling checked and unchecked exceptions.
 * 
 * @author Andrea Medeghini
 *
 * @param  the type of returned value
 * @param  the type of captured exception
 */
public abstract class Try {
    /**
     * The function to transform an exception into expected exception.
     */
    protected final Function mapper;

	/**
	 * The filter to apply when getting or mapping value.
	 */
	protected final Predicate filter;

	/**
	 * The handler to call when result is success.
	 */
	protected final Consumer> onSuccess;

	/**
	 * The handler to call when result is failure.
	 */
	protected final Consumer onFailure;

	/**
	 * Returns true if exception occurred.
	 * @return true when exception if present 
	 */
	public abstract boolean isFailure();

	/**
	 * Returns true if no exception occurred.
	 * @return true when exception if not present
	 */
	public abstract boolean isSuccess();

	/**
	 * Returns true if value is present.
	 * @return true when value is present
	 */
	public abstract boolean isPresent();

	/**
	 * Invokes consumer if exception occurred.
	 * @param consumer the consumer
	 */
	public abstract void ifFailure(Consumer consumer);

	/**
	 * Invokes consumer if no exception occurred.
	 * @param consumer the consumer
	 */
	public abstract void ifSuccess(Consumer> consumer);

	/**
	 * Invokes consumer if value is present.
	 * @param consumer the consumer
	 */
	public abstract void ifPresent(Consumer consumer);

	/**
	 * Invokes consumer if value is present or throws exception if failure.
	 * @param consumer the consumer
	 * @throws E the exception
	 */
	public abstract void ifPresentOrThrow(Consumer consumer) throws E;

	/**
	 * Returns the value.
	 * @return the value
	 * @throws NoSuchElementException if value not present.
	 */
	public abstract V get();

	/**
	 * Returns the value if present or returns the default value.
	 * @param value the default value
	 * @return the value
	 */
	public abstract V orElse(V value);

	/**
	 * Returns the value if present or invokes the supplier to get the value.
	 * @param supplier the supplier
	 * @return the value
	 */
	public abstract V orElseGet(Supplier supplier);

	/**
	 * Returns the value if present or throws exception if failure.
	 * @return the value
	 * @throws E the exception
	 * @throws NoSuchElementException if value not present.
	 */
	public abstract V orThrow() throws E;

	/**
	 * Returns the value if present or throws exception if failure or returns the default value.
	 * @param value the default value
	 * @return the value
	 * @throws E the exception
	 */
	public abstract V orThrow(V value) throws E;

	/**
	 * Returns optional of value.
	 * @return new optional
	 */
	public abstract Optional value();

	/**
	 * Throws exception if failure.
	 * @throws E the exception
	 */
	public abstract void throwIfFailure() throws E;

	/**
	 * Creates new instance mapper given mapping function.
	 * @param func the function
	 * @param  the result's value type
	 * @return new instance
	 * @throws NullPointerException if func is null.
	 */
	public abstract  Try map(Function func);

	/**
	 * Creates new instance mapper given mapping function.
	 * @param func the function
	 * @param  the result's value type
	 * @return new instance
	 * @throws NullPointerException if func is null.
	 */
	public abstract  Try flatMap(Function> func);

	/**
	 * Creates new instance which executes alternative callable if failure.
	 * @param callable the callable
	 * @return new instance
	 * @throws NullPointerException if callable is null.
	 */
	public abstract Try or(Callable callable);

	/**
	 * Creates new instance which executes consecutive callable if success.
	 * @param callable the callable
	 * @return new instance
	 * @throws NullPointerException if callable is null.
	 */
	public abstract Try and(Callable callable);

	/**
	 * Creates new instance with given consumer of success event.
	 * @param consumer the consumer
	 * @return new instance
	 */
	public abstract Try onSuccess(Consumer> consumer);

	/**
	 * Creates new instance with given consumer of failure event.
	 * @param consumer the consumer
	 * @return new instance
	 */
	public abstract Try onFailure(Consumer consumer);

    /**
     * Creates new instance with given exception mapper.
     * @param mapper the mapper
	 * @param  the exception type
     * @return new instance
	 * @throws NullPointerException if mapper is null.
     */
	public abstract  Try mapper(Function mapper);

	/**
	 * Creates new instance with given value filter.
	 * @param filter the filter
	 * @return new instance
	 * @throws NullPointerException if filter is null.
	 */
	public abstract Try filter(Predicate filter);

	/**
	 * Creates not lazy instance. Executes code if needed.
	 * @return new instance
	 */
	public abstract Try execute();

	/**
	 * Creates new instance with given callable.
	 * @param callable the callable
	 * @param  the result's value type
	 * @return new instance
	 */
    public static  Try of(Callable callable) {
		return new TryCallable<>(defaultMapper(), defaultFilter(), callable);
    }

	/**
     * Creates new instance with given exception.
     * @param exception the exception
	 * @param  the result's value type
     * @return new instance
     */
    public static  Try failure(Exception exception) {
		return new TryFailure<>(defaultMapper(), defaultFilter(), exception);
	}

    /**
     * Creates new instance with given value.
     * @param value the value
	 * @param  the result's value type
     * @return new instance
     */
    public static  Try success(R value) {
		return new TrySuccess<>(defaultMapper(), defaultFilter(), value);
	}

	private Try(Function mapper, Predicate filter, Consumer> onSuccess, Consumer onFailure) {
		this.mapper = Objects.requireNonNull(mapper);
		this.filter = Objects.requireNonNull(filter);
		this.onSuccess = onSuccess;
		this.onFailure = onFailure;
	}

	private static Function defaultMapper() {
		return x -> x;
	}

	private static Predicate defaultFilter() {
		return v -> v != null;
	}

	private static class TryFailure extends Try {
		private final E exception;

		public TryFailure(Function mapper, Predicate filter, Consumer> onSuccess, Consumer onFailure, E exception) {
			super(mapper, filter, onSuccess, onFailure);
			this.exception = Objects.requireNonNull(exception);
		}

		public TryFailure(Function mapper, Predicate filter, E exception) {
			this(mapper, filter, null, null, exception);
		}

		public boolean isFailure() {
			notifyEvent();
			return true;
		}

		public boolean isSuccess() {
			notifyEvent();
			return false;
		}

		public boolean isPresent() {
			notifyEvent();
			return false;
		}

		public void ifFailure(Consumer consumer) {
			notifyEvent();
			consumer.accept(exception);
		}

		public void ifSuccess(Consumer> consumer) {
			notifyEvent();
		}

		public void ifPresent(Consumer consumer) {
			notifyEvent();
		}

		public void ifPresentOrThrow(Consumer consumer) throws E {
			notifyEvent();
			throw exception;
		}

		public V get() {
			notifyEvent();
			throw new NoSuchElementException("Failure doesn't have any value");
		}

		public V orElse(V value) {
			notifyEvent();
			return value;
		}

		public V orElseGet(Supplier supplier) {
			notifyEvent();
			return supplier.get();
		}

		public V orThrow() throws E {
			notifyEvent();
			throw exception;
	    }

		public V orThrow(V value) throws E {
			notifyEvent();
			throw exception;
	    }
		
		public Optional value() {
			notifyEvent();
			return Optional.empty();
		}

		public void throwIfFailure() throws E {
			notifyEvent();
			throw exception;
		}

		public  Try map(Function func) {
			Objects.requireNonNull(func);
			return new TryFailure<>(mapper, defaultFilter(), onSuccess, onFailure, exception);
		}

		public  Try flatMap(Function> func) {
			Objects.requireNonNull(func);
			return new TryFailure<>(mapper, defaultFilter(), onSuccess, onFailure, exception);
		}

		public Try or(Callable callable) {
			Objects.requireNonNull(callable);
			return create(() -> orTry(onFailure, callable, mapper));
		}

		public Try and(Callable callable) {
			Objects.requireNonNull(callable);
			return new TryFailure<>(mapper, defaultFilter(), onSuccess, onFailure, exception);
		}

		public Try onSuccess(Consumer> consumer) {
			return new TryFailure<>(mapper, filter, consumer, onFailure, exception);
		}

		public Try onFailure(Consumer consumer) {
			return new TryFailure<>(mapper, filter, onSuccess, consumer, exception);
		}

		public  Try mapper(Function mapper) {
			return new TryFailure<>(mapper, filter, onSuccess, onFailure, mapper.apply(exception));
		}

		public Try filter(Predicate filter) {
			return new TryFailure<>(mapper, filter, onSuccess, onFailure, exception);
		}

		public Try execute() {
			return this;
		}

		private void notifyEvent() {
			Optional.ofNullable(onFailure).ifPresent(consumer -> consumer.accept(exception));
		}

		private  Try create(Callable callable) {
			return new TryCallable<>(mapper, defaultFilter(), onSuccess, onFailure, callable);
		}

		private V call(Callable callable) throws Exception {
			return callable.call();
		}

		private V orTry(Consumer onFailure, Callable callable, Function mapper) throws Exception {
			Optional.ofNullable(onFailure).ifPresent(consumer -> consumer.accept(mapper.apply(exception)));
			return call(callable);
		}
	}

	private static class TrySuccess extends Try {
		private final V value;

		public TrySuccess(Function mapper, Predicate filter, Consumer> onSuccess, Consumer onFailure, V value) {
			super(mapper, filter, onSuccess, onFailure);
			this.value = value;
		}

		public TrySuccess(Function mapper, Predicate filter, V value) {
			this(mapper, filter, null, null, value);
		}

		public boolean isFailure() {
			notifyEvent();
			return false;
		}

		public boolean isSuccess() {
			notifyEvent();
			return true;
		}

		public boolean isPresent() {
			return value().isPresent();
		}

		public void ifFailure(Consumer consumer) {
			notifyEvent();
		}

		public void ifSuccess(Consumer> consumer) {
			consumer.accept(value());
		}

		public void ifPresent(Consumer consumer) {
			value().ifPresent(consumer);
		}

		public void ifPresentOrThrow(Consumer consumer) {
			value().ifPresent(consumer);
		}

		public V get() {
			return value().get();
		}

		public V orElse(V value) {
			return value().orElse(value);
		}

		public V orElseGet(Supplier supplier) {
			return value().orElseGet(supplier);
		}

		public V orThrow() throws E {
	        return value().get();
	    }

		public V orThrow(V value) throws E {
	        return value().orElse(value);
	    }
		
		public Optional value() {
			notifyEvent();
			return evaluate();
		}

		public void throwIfFailure() throws E {
			notifyEvent();
		}

		public  Try map(Function func) {
			Objects.requireNonNull(func);
			return create(() -> evaluate().map(func).orElse(null));
		}

		public  Try flatMap(Function> func) {
			Objects.requireNonNull(func);
			return create(() -> evaluate().map(func).orElseGet(() -> empty()).orThrow(null));
		}

		public Try or(Callable callable) {
			Objects.requireNonNull(callable);
			return new TrySuccess<>(mapper, defaultFilter(), onSuccess, onFailure, value);
		}

		public Try and(Callable callable) {
			Objects.requireNonNull(callable);
			return create(() -> andTry(onSuccess, callable));
		}

		public Try onSuccess(Consumer> consumer) {
			return new TrySuccess<>(mapper, filter, consumer, onFailure, value);
		}

		public Try onFailure(Consumer consumer) {
			return new TrySuccess<>(mapper, filter, onSuccess, consumer, value);
		}

		public  Try mapper(Function mapper) {
			return new TrySuccess<>(mapper, filter, onSuccess, onFailure, value);
		}

		public Try filter(Predicate filter) {
			return new TrySuccess<>(mapper, filter, onSuccess, onFailure, value);
		}

		public Try execute() {
			return this;
		}

		private Optional evaluate() {
			return Optional.ofNullable(value).filter(filter);
		}

		private  Try empty() {
			return new TrySuccess<>(mapper, defaultFilter(), onSuccess, onFailure, null);
		}

		private  Try create(Callable callable) {
			return new TryCallable<>(mapper, defaultFilter(), onSuccess, onFailure, callable);
		}

		private void notifyEvent() {
			Optional.ofNullable(onSuccess).ifPresent(consumer -> consumer.accept(Optional.ofNullable(value)));
		}

		private V call(Callable callable) throws Exception {
			return callable.call();
		}

		private V andTry(Consumer> onSuccess, Callable callable) throws Exception {
			Optional value = evaluate();
			Optional.ofNullable(onSuccess).ifPresent(consumer -> consumer.accept(Optional.ofNullable(value.orElse(null))));
			return call(callable);
		}
	}

	private static class TryCallable extends Try {
		private final Callable callable;

		public TryCallable(Function mapper, Predicate filter, Consumer> onSuccess, Consumer onFailure, Callable callable) {
			super(mapper, filter, onSuccess, onFailure);
			this.callable = Objects.requireNonNull(callable);
		}

		public TryCallable(Function mapper, Predicate filter, Callable callable) {
			this(mapper, filter, null, null, callable);
		}

		public boolean isFailure() {
			return execute().isFailure();
		}

		public boolean isSuccess() {
			return execute().isSuccess();
		}

		public boolean isPresent() {
			return execute().isPresent();
		}

		public void ifFailure(Consumer consumer) {
			execute().ifFailure(consumer);
		}

		public void ifSuccess(Consumer> consumer) {
			execute().ifSuccess(consumer);
		}

		public void ifPresent(Consumer consumer) {
			execute().ifPresent(consumer);
		}

		public void ifPresentOrThrow(Consumer consumer) throws E {
			execute().ifPresentOrThrow(consumer);
		}

		public V get() {
			return execute().get();
		}

		public V orElse(V value) {
			return execute().orElse(value);
		}

		public V orElseGet(Supplier supplier) {
			return execute().orElseGet(supplier);
		}

		public V orThrow() throws E {
			return execute().orThrow();
		}

		public V orThrow(V value) throws E {
			return execute().orThrow(value);
		}

		public Optional value() {
			return execute().value();
		}

		public void throwIfFailure() throws E {
			execute().throwIfFailure();
		}

		public  Try map(Function func) {
			Objects.requireNonNull(func);
			return create(() -> evaluate().map(func).orElse(null));
		}

		public  Try flatMap(Function> func) {
			Objects.requireNonNull(func);
			return create(() -> evaluate().map(func).orElseGet(() -> empty()).orThrow(null));
		}

		public Try or(Callable callable) {
			Objects.requireNonNull(callable);
			return create(() -> orTry(onFailure, callable, mapper));
		}

		public Try and(Callable callable) {
			Objects.requireNonNull(callable);
			return create(() -> andTry(onSuccess, callable));
		}

		public Try onSuccess(Consumer> consumer) {
			return new TryCallable<>(mapper, filter, consumer, onFailure, callable);
		}

		public Try onFailure(Consumer consumer) {
			return new TryCallable<>(mapper, filter, onSuccess, consumer, callable);
		}

		public  Try mapper(Function mapper) {
			return new TryCallable<>(mapper, filter, onSuccess, onFailure, callable);
		}

		public Try filter(Predicate filter) {
			return new TryCallable<>(mapper, filter, onSuccess, onFailure, callable);
		}

		public Try execute() {
			try {
				V value = evaluate().orElse(null);
				Optional.ofNullable(onSuccess).ifPresent(consumer -> consumer.accept(Optional.ofNullable(value)));
				return createTerminal(value);
			} catch (Exception e) {
				E t = mapper.apply(e);
				Optional.ofNullable(onFailure).ifPresent(consumer -> consumer.accept(t));
				return createTerminal(t);
			}
		}

		private Optional evaluate() throws Exception {
			return evaluate(callable);
		}

		private Optional evaluate(Callable callable) throws Exception {
			return Optional.ofNullable(call(callable)).filter(filter);
		}

		private  Try empty() {
			return new TrySuccess<>(mapper, defaultFilter(), onSuccess, onFailure, null);
		}

		private  Try create(Callable callable) {
			return new TryCallable<>(mapper, defaultFilter(), onSuccess, onFailure, callable);
		}

		private  Try createTerminal(E exception) {
			return new TryFailure<>(mapper, defaultFilter(), null, null, exception);
		}

		private  Try createTerminal(R value) {
			return new TrySuccess<>(mapper, defaultFilter(), null, null, value);
		}

		private V call() throws Exception {
			return call(callable);
		}

		private V call(Callable callable) throws Exception {
			return callable.call();
		}

		private V orTry(Consumer onFailure, Callable callable, Function mapper) throws Exception {
			try {
				return call();
			} catch (Exception e) {
				Optional.ofNullable(onFailure).ifPresent(consumer -> consumer.accept(mapper.apply(e)));
				return call(callable);
			}
		}

		private V andTry(Consumer> onSuccess, Callable callable) throws Exception {
			Optional value = evaluate();
			Optional.ofNullable(onSuccess).ifPresent(consumer -> consumer.accept(Optional.ofNullable(value.orElse(null))));
			return call(callable);
		}
	}
}