001/* 002 * MIT License 003 * 004 * Copyright (c) 2023 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.ThrowingConsumer; 028import io.blt.util.functional.ThrowingSupplier; 029import java.lang.reflect.InvocationTargetException; 030import java.util.Optional; 031import java.util.function.Predicate; 032import java.util.function.Supplier; 033 034import static java.util.Objects.nonNull; 035 036/** 037 * Static utility methods for operating on {@code Object}. 038 */ 039public final class Obj { 040 041 private Obj() { 042 throw new IllegalAccessError("Utility class should be accessed statically and never constructed"); 043 } 044 045 /** 046 * Passes the {@code instance} to the {@code consumer}, then returns the {@code instance}. 047 * e.g. 048 * <pre>{@code 049 * var user = Obj.poke(new User(), u -> { 050 * u.setName("Greg"); 051 * u.setAge(15); 052 * }); 053 * }</pre> 054 * <p> 055 * Optionally, the {@code consumer} may throw which will bubble up. 056 * </p> 057 * 058 * @param instance instance to consume and return 059 * @param consumer operation to perform on {@code instance} 060 * @param <T> type of {@code instance} 061 * @param <E> type of {@code consumer} throwable 062 * @return {@code instance} after accepting side effects via {@code consumer}. 063 */ 064 public static <T, E extends Throwable> T poke(T instance, ThrowingConsumer<T, E> consumer) throws E { 065 consumer.accept(instance); 066 return instance; 067 } 068 069 /** 070 * Calls the {@code supplier} to retrieve an instance which is mutated by the {@code consumer} then returned. 071 * e.g. 072 * <pre>{@code 073 * var user = Obj.tap(User::new, u -> { 074 * u.setName("Greg"); 075 * u.setAge(15); 076 * }); 077 * }</pre> 078 * <p> 079 * Optionally, the {@code consumer} may throw which will bubble up. 080 * </p> 081 * 082 * @param supplier supplies an instance to consume and return 083 * @param consumer operation to perform on supplied instance 084 * @param <T> type of instance 085 * @param <E> type of {@code consumer} throwable 086 * @return Supplied instance after applying side effects via {@code consumer}. 087 */ 088 public static <T, E extends Throwable> T tap(Supplier<T> supplier, ThrowingConsumer<T, E> consumer) throws E { 089 return poke(supplier.get(), consumer); 090 } 091 092 /** 093 * Returns {@code value} if non-null, else invokes and returns the result of {@code supplier}. 094 * <p> 095 * Optionally, the {@code supplier} may throw which will bubble up. 096 * </p> 097 * e.g. 098 * <pre>{@code 099 * private URL homepageOrDefault(URL homepage) throws MalformedURLException { 100 * return Obj.orElseGet(homepage, () -> new URL("https://google.com")); 101 * } 102 * }</pre> 103 * 104 * @param value returned if non-null 105 * @param supplier called and returned if {@code value} is null 106 * @param <T> type of the returned value 107 * @param <E> type of {@code supplier} throwable 108 * @return {@code value} if non-null, else the result of {@code supplier} 109 * @throws E {@code Throwable} that may be thrown if the {@code supplier} is invoked 110 */ 111 public static <T, E extends Throwable> T orElseGet(T value, ThrowingSupplier<T, E> supplier) throws E { 112 return nonNull(value) ? value : supplier.get(); 113 } 114 115 /** 116 * Invokes and returns the result of {@code supplier} if no exception is thrown, else returns {@code defaultValue}. 117 * e.g. 118 * <pre>{@code 119 * private InputStream openFileOrResource(String name) { 120 * return Obj.orElseOnException( 121 * () -> new FileInputStream(name), 122 * getClass().getResourceAsStream(name)); 123 * } 124 * }</pre> 125 * 126 * @param supplier called and returned if no exception is thrown 127 * @param defaultValue returned if an exception is thrown when calling {@code supplier} 128 * @param <T> type of the returned value 129 * @param <E> type of {@code supplier} throwable 130 * @return result of {@code supplier} if no exception is thrown, else {@code defaultValue} 131 */ 132 public static <T, E extends Throwable> T orElseOnException(ThrowingSupplier<T, E> supplier, T defaultValue) { 133 return supplier.orOnException(defaultValue); 134 } 135 136 /** 137 * Throws the specified {@code throwable} if the given {@code value} satisfies the provided {@code predicate}. 138 * For convenience, {@code value} is returned. 139 * e.g. 140 * <pre>{@code 141 * public Map<String, String> loadProperties() { 142 * return throwIf(Properties.loadFromJson(FILENAME), Map::isEmpty, 143 * () -> new IllegalStateException("Properties must not be empty")); 144 * } 145 * }</pre> 146 * 147 * @param value the value to be checked 148 * @param predicate the predicate to be evaluated 149 * @param throwable the supplier for the throwable to be thrown 150 * @param <T> the type of the value 151 * @param <E> the type of the throwable 152 * @return {@code value} 153 * @throws E if the given {@code value} satisfies the provided {@code predicate} 154 * @see Obj#throwUnless(Object, Predicate, Supplier) 155 */ 156 public static <T, E extends Throwable> T throwIf( 157 T value, Predicate<? super T> predicate, Supplier<? extends E> throwable) throws E { 158 if (predicate.test(value)) { 159 throw throwable.get(); 160 } 161 return value; 162 } 163 164 /** 165 * Throws the specified {@code throwable} if the given {@code value} does not satisfy the provided {@code predicate}. 166 * For convenience, {@code value} is returned. 167 * e.g. 168 * <pre>{@code 169 * throwUnless(properties, p -> p.containsKey("host"), 170 * () -> new IllegalStateException("Properties must contain a host")); 171 * }</pre> 172 * 173 * @param value the value to be checked 174 * @param predicate the predicate to be evaluated 175 * @param throwable the supplier for the throwable to be thrown 176 * @param <T> the type of the value 177 * @param <E> the type of the throwable 178 * @return {@code value} 179 * @throws E if the given {@code value} does not satisfy the provided {@code predicate} 180 * @see Obj#throwIf(Object, Predicate, Supplier) 181 */ 182 public static <T, E extends Throwable> T throwUnless( 183 T value, Predicate<? super T> predicate, Supplier<? extends E> throwable) throws E { 184 return throwIf(value, predicate.negate(), throwable); 185 } 186 187 /** 188 * Returns a new instance of the same type as the input object if possible; otherwise, returns empty. 189 * Supports only instances of concrete types that have a public zero argument constructor. 190 * e.g. 191 * <pre>{@code 192 * public <K, V> Map<K, V> mapOfSameTypeOrHashMap(Map<K, V> map) { 193 * return Obj.newInstanceOf(map).orElse(new HashMap<>()); 194 * } 195 * }</pre> 196 * 197 * @param obj object to try and create a new instance of 198 * @param <T> type of {@code obj} 199 * @return a new instance of the same type as {@code obj} or empty 200 */ 201 @SuppressWarnings("unchecked") 202 public static <T> Optional<T> newInstanceOf(T obj) { 203 try { 204 return Optional.of((T) obj.getClass().getConstructor().newInstance()); 205 } catch (InstantiationException 206 | IllegalAccessException 207 | InvocationTargetException 208 | NoSuchMethodException e) { 209 return Optional.empty(); 210 } 211 } 212 213}