/* * esodata - data structures and other things, of varying utility * Copyright 2022, Ben Culkin * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ package bjc.functypes; import java.util.*; import java.util.function.*; import bjc.typeclasses.Isomorphism; /** * An instance of {@link Function} that can throw an exception. * * @author Ben Culkin * * @param The input to the function. * @param The output to the function. * @param The type of exception thrown. */ public interface ThrowFunction { /** * Does the possibly throwing computation embodied by this function. * * @param arg The input to the function. * * @return The result of the function. * * @throws ExType If something went wrong with the function. */ public ReturnType apply(InputType arg) throws ExType; /** * Converts this into a {@link Function} by handling any thrown exceptions, * then mapping the return type to get a consistent return type. * * @param The new return type. * * @param clasz The class of the handled exception. This needs to be provided * because you can't catch a generic exception, and we want to * make sure that we aren't catching any types of exception that * we aren't supposed to. * @param mapper The function which maps the normal return. * @param handler The handler to use. * * @return A function which will either return its proper value, or the result * of invoking the handler. */ @SuppressWarnings("unchecked") default Function swallowMap( Class clasz, Function mapper, Function handler) { return (inp) -> { try { return mapper.apply(this.apply(inp)); } catch (Throwable ex) { if (clasz.isInstance(ex)) { // Swallow this return handler.apply((ExType) ex); } String msg = "Exception of incorrect type to be handled, only " + clasz.getName() + " are handled"; throw new RuntimeException(msg, ex); } }; } /** * Converts this into a {@link Function} by handling any thrown exceptions. * * @param clasz The class of the handled exception. This needs to be provided * because you can't catch a generic exception, and we want to * make sure that we aren't catching any types of exception that * we aren't supposed to. * * @param handler The handler to use. * * @return A function which will either return its proper value, or the result * of invoking the handler. */ default Function swallow( Class clasz, Function handler) { return swallowMap(clasz, (arg) -> arg, handler); } /** * Convert this to a function which will attempt to recover from the thrown exception. * * @param clasz The class of the exception. Needed for type-safety reasons. * @param rescue The function to use to convert an exception into a safe input value. * * @return A function which attempts to recover from a exception. */ @SuppressWarnings("unchecked") default Function recover( Class clasz, BiFunction rescue) { return Fixpoints.fix((arg, self) -> { try { return this.apply(arg); } catch (Throwable ex) { if (clasz.isInstance(ex)) { // Swallow this return self.apply(rescue.apply(arg, (ExType) ex)); } String msg = "Exception of incorrect type to be handled, only " + clasz.getName() + " are handled"; throw new RuntimeException(msg, ex); } }); } /** * Create a {@link Thrower} which will yield the result of calling this * function with the given argument. * * @param arg The argument to use. * * @return A thrower which will call this function with the given value. */ default Thrower suspend(InputType arg) { return () -> this.apply(arg); } /** * Compose two throwing functions together. * * @param The newly output type. * * @param func The throwing function to compose this with. * * @return A throwing function that composes the two. */ default ThrowFunction compose( ThrowFunction func) { return (arg) -> func.apply(this.apply(arg)); } /** Convert this function into one which will return an empty optional if an * exception is thrown, returning an optional containing the value otherwise. * * Note that if this function returns a null value by itself, that will also * yield an empty nullable. * * @param clasz The class of the exception. Needed because of type erasure, * to ensure that we are catching the proper class. * * @return A function which returns an optional instead. */ default Function> makeTotal(Class clasz) { return swallowMap(clasz, Optional::ofNullable, (ignored) -> Optional.empty()); } /** * ThrowFunctions and functions which return a {@link Thrower} are isomorphic. * * @param The function input type. * @param The function output type. * @param The exception type. * * @return The isomorphism between them. */ static Isomorphism, Function>> getIso() { // @FIXME Nov 23, 2020 Ben Culkin :EquivISO // Fix this to strip wrappers when appropriate, so that going // backwards and forwards leaves you where you started, not under // two levels of indirection. return Isomorphism.from((throwFun) -> { return (arg) -> throwFun.suspend(arg); }, (thrower) -> { return (arg) -> thrower.apply(arg).extract(); }); } }