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}