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}