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.ThrowingFunction;
028import java.util.HashMap;
029import java.util.Map;
030
031import static io.blt.util.Obj.newInstanceOf;
032
033/**
034 * Static utility methods for operating on implementations of {@code Collection} and {@code Map} i.e. Containers.
035 * <p>
036 * For methods that return a modification of a passed container, the result will be of the same type if possible.
037 * This is accomplished using {@link Obj#newInstanceOf(Object)} and its limitations apply.
038 * </p>
039 */
040public final class Ctr {
041
042    private Ctr() {
043        throw new IllegalAccessError("Utility class should be accessed statically and never constructed");
044    }
045
046    /**
047     * Returns a new {@link Map} containing the entries of {@code source} with {@code transform} applied to the values.
048     * <p>
049     * If possible, the result is of the same type as the passed {@code source} map.
050     * </p>
051     * e.g.
052     * <pre>{@code
053     * var scores = Map.of("Louis", 95, "Greg", 92, "Mike", 71, "Phil", 86);
054     * var grades = Ctr.transformValues(scores, score -> {
055     *     if (score >= 90) {
056     *         return "A";
057     *     } else if (score >= 80) {
058     *         return "B";
059     *     } else if (score >= 70) {
060     *         return "C";
061     *     } else if (score >= 60) {
062     *         return "D";
063     *     } else {
064     *         return "F";
065     *     }
066     * });
067     * // grades = Map.of("Louis", "A", "Greg", "A", "Mike", "C", "Phil", "B")
068     * }</pre>
069     *
070     * @param source    {@link Map} whose values should be transformed
071     * @param transform value transformation function
072     * @param <K>       {@code map} key type
073     * @param <V>       {@code map} value type
074     * @param <R>       returned map value type
075     * @param <E>       type of {@code transform} throwable
076     * @return a new {@link Map} containing the entries of {@code source} with {@code transform} applied to the values
077     * @see Obj#newInstanceOf(Object)
078     */
079    @SuppressWarnings("unchecked")
080    public static <K, V, R, E extends Throwable> Map<K, R> transformValues(
081            Map<K, V> source, ThrowingFunction<? super V, R, E> transform) throws E {
082        var result = newInstanceOf((Map<K, R>) source).orElse(new DefaultMap<>());
083
084        for (var entry : source.entrySet()) {
085            result.put(entry.getKey(), transform.apply(entry.getValue()));
086        }
087
088        return result instanceof DefaultMap ? Map.copyOf(result) : result;
089    }
090
091    private static final class DefaultMap<K, V> extends HashMap<K, V> {}
092
093}