001/* 002 * MIT License 003 * 004 * Copyright (c) 2023-2024 Michael Cowan 005 * 006 * Permission is hereby granted, free of charge, to any person obtaining a copy 007 * of this software and associated documentation files (the "Software"), to deal 008 * in the Software without restriction, including without limitation the rights 009 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 010 * copies of the Software, and to permit persons to whom the Software is 011 * furnished to do so, subject to the following conditions: 012 * 013 * The above copyright notice and this permission notice shall be included in all 014 * copies or substantial portions of the Software. 015 * 016 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 017 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 018 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 019 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 020 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 021 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 022 * SOFTWARE. 023 */ 024 025package io.blt.util; 026 027import io.blt.util.functional.ThrowingRunnable; 028import io.blt.util.functional.ThrowingSupplier; 029import java.util.function.Function; 030import java.util.function.Predicate; 031import java.util.function.Supplier; 032 033/** 034 * Static utility methods centred around {@code Exception} and {@code Throwable}. 035 * <p>Includes raising and handling exceptions.</p> 036 */ 037public final class Ex { 038 039 private Ex() { 040 throw new IllegalAccessError("Utility class should be accessed statically and never constructed"); 041 } 042 043 /** 044 * Executes a supplier, transforming any thrown {@link Exception} using a specified function. 045 * A thrown {@link RuntimeException} will bubble up unaltered. 046 * 047 * <p>e.g., a custom high-level {@code XmlProcessingException} that we want to raise when parsing XML:</p> 048 * <pre>{@code 049 * public Document parseXml(String pathname) throws XmlProcessingException { 050 * return Ex.transformExceptions( 051 * () -> DocumentBuilderFactory 052 * .newInstance() 053 * .newDocumentBuilder() // throws ParserConfigurationException 054 * .parse(new File(pathname)), // throws SAXException, IOException 055 * XmlProcessingException::new); 056 * } 057 * }</pre> 058 * 059 * @param <R> The result type of the supplier 060 * @param <E> The type of transformed exception 061 * @param supplier The supplier that may throw an exception 062 * @param transformer Function to transform exceptions thrown by the supplier 063 * @return The result of the supplier if successful 064 * @throws E If the supplier throws an exception, transformed by the provided function 065 */ 066 public static <R, E extends Throwable> R transformExceptions( 067 ThrowingSupplier<R, ? extends Exception> supplier, Function<? super Exception, E> transformer) throws E { 068 try { 069 return supplier.get(); 070 } catch (RuntimeException e) { 071 throw e; 072 } catch (Exception e) { 073 throw transformer.apply(e); 074 } 075 } 076 077 /** 078 * Executes a runnable, transforming any thrown {@link Exception} using a specified function. 079 * A thrown {@link RuntimeException} will bubble up unaltered. 080 * 081 * <p>e.g.,</p> 082 * <pre>{@code 083 * public void appendFormattedDateToFile(String date, String fileName) throws LoggingException { 084 * Ex.transformExceptions( 085 * () -> { 086 * var formatted = new SimpleDateFormat() 087 * .parse(date) // ParseException 088 * .toString(); 089 * 090 * Files.write( // IOException 091 * Paths.get(fileName), 092 * formatted.getBytes(), 093 * StandardOpenOption.APPEND); 094 * }, LoggingException::new 095 * ); 096 * } 097 * }</pre> 098 * 099 * @param <E> The type of transformed exception 100 * @param runnable The runnable that may throw an exception 101 * @param transformer Function to transform exceptions thrown by the supplier 102 * @throws E If the supplier throws an exception, transformed by the provided function 103 */ 104 public static <E extends Throwable> void transformExceptions( 105 ThrowingRunnable<? extends Exception> runnable, Function<? super Exception, E> transformer) throws E { 106 transformExceptions(() -> { 107 runnable.run(); 108 return null; 109 }, transformer); 110 } 111 112 /** 113 * Throws the specified {@code throwable} if the given {@code value} satisfies the provided {@code predicate}. 114 * For convenience, {@code value} is returned. 115 * e.g., 116 * <pre>{@code 117 * public Map<String, String> loadProperties() { 118 * return throwIf(Properties.loadFromJson(FILENAME), Map::isEmpty, 119 * () -> new IllegalStateException("Properties must not be empty")); 120 * } 121 * }</pre> 122 * 123 * @param value the value to be checked 124 * @param predicate the predicate to be evaluated 125 * @param throwable the supplier for the throwable to be thrown 126 * @param <T> the type of the value 127 * @param <E> the type of the throwable 128 * @return {@code value} 129 * @throws E if the given {@code value} satisfies the provided {@code predicate} 130 * @see Ex#throwUnless(Object, Predicate, Supplier) 131 */ 132 public static <T, E extends Throwable> T throwIf( 133 T value, Predicate<? super T> predicate, Supplier<? extends E> throwable) throws E { 134 if (predicate.test(value)) { 135 throw throwable.get(); 136 } 137 return value; 138 } 139 140 /** 141 * Throws the specified {@code throwable} if the given {@code value} does not satisfy the provided {@code predicate}. 142 * For convenience, {@code value} is returned. 143 * e.g., 144 * <pre>{@code 145 * throwUnless(properties, p -> p.containsKey("host"), 146 * () -> new IllegalStateException("Properties must contain a host")); 147 * }</pre> 148 * 149 * @param value the value to be checked 150 * @param predicate the predicate to be evaluated 151 * @param throwable the supplier for the throwable to be thrown 152 * @param <T> the type of the value 153 * @param <E> the type of the throwable 154 * @return {@code value} 155 * @throws E if the given {@code value} does not satisfy the provided {@code predicate} 156 * @see Ex#throwIf(Object, Predicate, Supplier) 157 */ 158 public static <T, E extends Throwable> T throwUnless( 159 T value, Predicate<? super T> predicate, Supplier<? extends E> throwable) throws E { 160 return throwIf(value, predicate.negate(), throwable); 161 } 162 163}