001/*
002 * Copyright (c) 2023. JEFF Media GbR / mfnalex et al.
003 *
004 * This program is free software: you can redistribute it and/or modify
005 * it under the terms of the GNU General Public License as published by
006 * the Free Software Foundation, either version 3 of the License, or
007 * (at your option) any later version.
008 *
009 * This program is distributed in the hope that it will be useful,
010 * but WITHOUT ANY WARRANTY; without even the implied warranty of
011 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
012 * GNU General Public License for more details.
013 *
014 * You should have received a copy of the GNU General Public License
015 * along with this program.  If not, see <https://www.gnu.org/licenses/>.
016 */
017
018package com.jeff_media.jefflib;
019
020import java.util.Arrays;
021import java.util.Collection;
022import java.util.Collections;
023import java.util.EnumMap;
024import java.util.EnumSet;
025import java.util.HashMap;
026import java.util.List;
027import java.util.Locale;
028import java.util.Map;
029import java.util.Objects;
030import java.util.Optional;
031import java.util.Set;
032import java.util.regex.Pattern;
033import java.util.stream.Collector;
034import java.util.stream.Collectors;
035import lombok.experimental.UtilityClass;
036
037/**
038 * Enum related methods
039 */
040@UtilityClass
041public class EnumUtils {
042
043    private static final Map<Class<? extends Enum<?>>, Set<String>> ENUM_CACHE = new HashMap<>();
044    private static final Map<Class<? extends Enum<?>>, List<? extends Enum<?>>> ENUM_ARRAY_CACHE = new HashMap<>();
045    private static final Map<Class<? extends Enum<?>>, EnumMap<?, ?>> NEXT_ENUMS = new HashMap<>();
046
047    /**
048     * Gets an EnumSet of the given Enum constants by their names. Enum constants that aren't found will print a warning.
049     * Case is ignored for Bukkit enums.
050     */
051    public static <E extends Enum<E>> EnumSet<E> getEnumsFromListAsEnumSet(final Class<E> enumClazz, final List<String> list) {
052        return getEnumsFromList(enumClazz, list, Collectors.toCollection(() -> EnumSet.noneOf(enumClazz)));
053    }
054
055    /**
056     * Gets a Set of the given Enum constants by their names. Enum constants that aren't found will print a warning.
057     * Case is ignored for Bukkit enums.
058     */
059    public static <E extends Enum<E>> Set<E> getEnumsFromListAsSet(final Class<E> enumClazz, final List<String> list) {
060        return getEnumsFromList(enumClazz, list, Collectors.toSet());
061    }
062
063    /**
064     * Gets a Collection of the given Enum constants by their names. Enums constants that aren't found will print a warning.
065     * Case is ignored for Bukkit enums.
066     */
067    public static <E extends Enum<E>, C extends Collection<E>> C getEnumsFromList(final Class<E> enumClazz, final List<String> list, final Collector<? super E, ?, C> collector) {
068        return list.stream().map(entry -> {
069            final Optional<E> result = getIfPresent(enumClazz, enumClazz.getName().startsWith("org.bukkit") ? entry.toUpperCase(Locale.ROOT) : entry);
070            if (!result.isPresent()) {
071                JeffLib.getPlugin().getLogger().severe("Could not find " + enumClazz.getSimpleName() + ": '" + entry + "'");
072                return null;
073            }
074            return result.get();
075        }).filter(Objects::nonNull).collect(collector);
076    }
077
078    /**
079     * Gets an {@link Optional} of a given Enum by its name
080     */
081    public static <E extends Enum<E>> Optional<E> getIfPresent(final Class<E> enumClazz, final String value) {
082        final Set<String> enumSet = ENUM_CACHE.computeIfAbsent(enumClazz, EnumUtils::toStringSet);
083        return Optional.ofNullable(enumSet.contains(value) ? Enum.valueOf(enumClazz, value) : null);
084    }
085
086    private static Set<String> toStringSet(final Class<? extends Enum<?>> enumClazz) {
087        return Arrays.stream(enumClazz.getEnumConstants()).map(Enum::toString).collect(Collectors.toSet());
088    }
089
090    /**
091     * Gets an EnumSet of the given Enum constants by a list of regex patterns. Example:
092     * <pre>
093     * materials:
094     * - "^((.+)_)*CHEST$" # matches CHEST, TRAPPED_CHEST, etc
095     * - "^((.+)_)*SHULKER_BOX$" # matches SHULKER_BOX, RED_SHULKER_BOX, etc
096     * - "^BARREL$" # matches only BARREL
097     * </pre>
098     */
099    public static <E extends Enum<E>> EnumSet<E> getEnumsFromRegexList(final Class<E> enumClazz, final List<String> list) {
100        final EnumSet<E> result = EnumSet.noneOf(enumClazz);
101        for (final String regex : list) {
102            final Pattern pattern = Pattern.compile(regex);
103            for (final E e : enumClazz.getEnumConstants()) {
104                if (result.contains(e)) continue;
105                final String name = e.name();
106                if (pattern.matcher(name).matches()) {
107                    result.add(e);
108                }
109            }
110        }
111        return result;
112    }
113
114    /**
115     * Gets a random value of the given Enum class. Values are cached, so it doesn't have to call values() all the time.
116     */
117    public static <E extends Enum<E>> E getRandomElement(final Class<E> enumClazz) {
118        final List<E> values = getValues(enumClazz);
119        return values.get(JeffLib.getThreadLocalRandom().nextInt(values.size()));
120    }
121
122    /**
123     * Returns all elements of the given enum class. Unlike calling values() on an element instance,
124     * or calling getEnumConstants() on an enum class, this will cache the delivered array and
125     * doesn't have to create a new one everytime.
126     * The returned list is unmodifiable.
127     */
128    @SuppressWarnings("unchecked")
129    public static <E extends Enum<E>> List<E> getValues(final Class<E> enumClazz) {
130        List<E> values = (List<E>) ENUM_ARRAY_CACHE.get(enumClazz);
131        if (values == null) {
132            values = Collections.unmodifiableList(Arrays.asList(enumClazz.getEnumConstants()));
133            ENUM_ARRAY_CACHE.put(enumClazz, values);
134        }
135        return values;
136    }
137
138    /**
139     * Gets the next element of the given enum class by its ordinal.
140     * For example, if your enum class has three declared values A, B and C, then calling this method with A will return B,
141     * calling it with B will return C, and calling it with C will return A.
142     * The next element of each element is cached and does not require to call values() all the time.
143     */
144    @SuppressWarnings("unchecked")
145    public static <E extends Enum<E>> E getNextElement(final E e) {
146        final Class<E> enumClazz = (Class<E>) e.getClass();
147        final EnumMap<E, E> nextEnums = (EnumMap<E, E>) NEXT_ENUMS.computeIfAbsent(enumClazz, __ -> new EnumMap<E, E>(enumClazz));
148        E next = nextEnums.get(e);
149        if (next == null) {
150            final int ordinal = e.ordinal();
151            final List<E> values = getValues(enumClazz);
152            next = values.get((ordinal + 1) % values.size());
153            nextEnums.put(e, next);
154        }
155        return next;
156    }
157
158
159}
160