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.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 * 039 * <p>Includes object construction, mutation, fallbacks and validation.</p> 040 */ 041public final class Obj { 042 043 private Obj() { 044 throw new IllegalAccessError("Utility class should be accessed statically and never constructed"); 045 } 046 047 /** 048 * Passes the {@code instance} to the {@code consumer}, then returns the {@code instance}. 049 * e.g., 050 * <pre>{@code 051 * var user = Obj.poke(new User(), u -> { 052 * u.setName("Greg"); 053 * u.setAge(15); 054 * }); 055 * }</pre> 056 * <p> 057 * Optionally, the {@code consumer} may throw which will bubble up. 058 * </p> 059 * 060 * @param instance instance to consume and return 061 * @param consumer operation to perform on {@code instance} 062 * @param <T> type of {@code instance} 063 * @param <E> type of {@code consumer} throwable 064 * @return {@code instance} after accepting side effects via {@code consumer} 065 */ 066 public static <T, E extends Throwable> T poke(T instance, ThrowingConsumer<T, E> consumer) throws E { 067 consumer.accept(instance); 068 return instance; 069 } 070 071 /** 072 * Calls the {@code supplier} to retrieve an instance which is mutated by the {@code consumer} then returned. 073 * e.g., 074 * <pre>{@code 075 * var user = Obj.tap(User::new, u -> { 076 * u.setName("Greg"); 077 * u.setAge(15); 078 * }); 079 * }</pre> 080 * <p> 081 * Optionally, the {@code consumer} may throw which will bubble up. 082 * </p> 083 * 084 * @param supplier supplies an instance to consume and return 085 * @param consumer operation to perform on supplied instance 086 * @param <T> type of instance 087 * @param <E> type of {@code consumer} throwable 088 * @return Supplied instance after applying side effects via {@code consumer} 089 */ 090 public static <T, E extends Throwable> T tap(Supplier<T> supplier, ThrowingConsumer<T, E> consumer) throws E { 091 return poke(supplier.get(), consumer); 092 } 093 094 /** 095 * Returns {@code value} if non-null, else invokes and returns the result of {@code supplier}. 096 * <p> 097 * Optionally, the {@code supplier} may throw which will bubble up. 098 * </p> 099 * e.g., 100 * <pre>{@code 101 * private URL homepageOrDefault(URL homepage) throws MalformedURLException { 102 * return Obj.orElseGet(homepage, () -> new URL("https://google.com")); 103 * } 104 * }</pre> 105 * 106 * @param value returned if non-null 107 * @param supplier called and returned if {@code value} is null 108 * @param <T> type of the returned value 109 * @param <E> type of {@code supplier} throwable 110 * @return {@code value} if non-null, else the result of {@code supplier} 111 * @throws E {@code Throwable} that may be thrown if the {@code supplier} is invoked 112 */ 113 public static <T, E extends Throwable> T orElseGet(T value, ThrowingSupplier<T, E> supplier) throws E { 114 return nonNull(value) ? value : supplier.get(); 115 } 116 117 /** 118 * Invokes and returns the result of {@code supplier} if no exception is thrown, else returns {@code defaultValue}. 119 * e.g., 120 * <pre>{@code 121 * private InputStream openFileOrResource(String name) { 122 * return Obj.orElseOnException( 123 * () -> new FileInputStream(name), 124 * getClass().getResourceAsStream(name)); 125 * } 126 * }</pre> 127 * 128 * @param supplier called and returned if no exception is thrown 129 * @param defaultValue returned if an exception is thrown when calling {@code supplier} 130 * @param <T> type of the returned value 131 * @param <E> type of {@code supplier} throwable 132 * @return result of {@code supplier} if no exception is thrown, else {@code defaultValue} 133 */ 134 public static <T, E extends Throwable> T orElseOnException(ThrowingSupplier<T, E> supplier, T defaultValue) { 135 try { 136 return supplier.get(); 137 } catch (Throwable ignore) { 138 return defaultValue; 139 } 140 } 141 142 /** 143 * @deprecated 144 * This has been moved to {@link Ex}. 145 * <p>Use {@link Ex#throwIf(Object, Predicate, Supplier)} instead.</p> 146 */ 147 @Deprecated(since = "1.0.7", forRemoval = true) 148 public static <T, E extends Throwable> T throwIf( 149 T value, Predicate<? super T> predicate, Supplier<? extends E> throwable) throws E { 150 return Ex.throwIf(value, predicate, throwable); 151 } 152 153 /** 154 * @deprecated 155 * This has been moved to {@link Ex}. 156 * <p>Use {@link Ex#throwUnless(Object, Predicate, Supplier)} instead.</p> 157 */ 158 @Deprecated(since = "1.0.7", forRemoval = true) 159 public static <T, E extends Throwable> T throwUnless( 160 T value, Predicate<? super T> predicate, Supplier<? extends E> throwable) throws E { 161 return Ex.throwUnless(value, predicate, throwable); 162 } 163 164 /** 165 * Invokes and returns the result of {@code supplier} if no exception is thrown; otherwise, returns empty. 166 * e.g., 167 * <pre>{@code 168 * private Optional<HttpResponse<InputStream>> fetchAsStream(HttpRequest request) { 169 * try (var client = HttpClient.newHttpClient()) { 170 * return Obj.orEmptyOnException(() -> client.send( 171 * request, HttpResponse.BodyHandlers.ofInputStream())); 172 * } 173 * } 174 * }</pre> 175 * 176 * @param supplier called and returned as an {@link Optional} if no exception is thrown 177 * @param <T> type of the returned value 178 * @param <E> type of {@code supplier} throwable 179 * @return result of {@code supplier} as an {@link Optional} if no exception is thrown, else empty 180 */ 181 public static <T, E extends Throwable> Optional<T> orEmptyOnException(ThrowingSupplier<T, E> supplier) { 182 return Optional.ofNullable(orElseOnException(supplier, null)); 183 } 184 185 /** 186 * Returns a new instance of the same type as the input object if possible; otherwise, returns empty. 187 * Supports only instances of concrete types that have a public zero argument constructor. 188 * e.g., 189 * <pre>{@code 190 * public <K, V> Map<K, V> mapOfSameTypeOrHashMap(Map<K, V> map) { 191 * return Obj.newInstanceOf(map).orElse(new HashMap<>()); 192 * } 193 * }</pre> 194 * 195 * @param obj object to try and create a new instance of 196 * @param <T> type of {@code obj} 197 * @return a new instance of the same type as {@code obj} or empty 198 */ 199 @SuppressWarnings("unchecked") 200 public static <T> Optional<T> newInstanceOf(T obj) { 201 try { 202 return Optional.of((T) obj.getClass().getConstructor().newInstance()); 203 } catch (InstantiationException 204 | IllegalAccessException 205 | InvocationTargetException 206 | NoSuchMethodException ignore) { 207 return Optional.empty(); 208 } 209 } 210 211}