001/* 002 * Configurate 003 * Copyright (C) zml and Configurate contributors 004 * 005 * Licensed under the Apache License, Version 2.0 (the "License"); 006 * you may not use this file except in compliance with the License. 007 * You may obtain a copy of the License at 008 * 009 * http://www.apache.org/licenses/LICENSE-2.0 010 * 011 * Unless required by applicable law or agreed to in writing, software 012 * distributed under the License is distributed on an "AS IS" BASIS, 013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 014 * See the License for the specific language governing permissions and 015 * limitations under the License. 016 */ 017package org.spongepowered.configurate.util; 018 019import static java.util.Objects.requireNonNull; 020 021import org.checkerframework.checker.nullness.qual.Nullable; 022 023import java.util.Collections; 024import java.util.HashMap; 025import java.util.Locale; 026import java.util.Map; 027import java.util.WeakHashMap; 028 029/** 030 * Utility class to cache more flexible enum lookup. 031 * 032 * <p>While normally case and punctuation have to match exactly, this method 033 * performs lookup that:</p> 034 * 035 * <ul> 036 * <li>is case-insensitive</li> 037 * <li>ignores underscores</li> 038 * <li>caches mappings</li> 039 * </ul> 040 * 041 * <p>If the enum has two fields that are equal except for case and underscores, 042 * an exact match will return the appropriate value, and any fuzzy matches will 043 * map to the first value in the enum that is applicable.</p> 044 * 045 * @since 4.0.0 046 */ 047public final class EnumLookup { 048 049 private EnumLookup() { 050 051 } 052 053 private static final Map<Class<? extends Enum<?>>, Map<String, Enum<?>>> ENUM_FIELD_CACHE = new WeakHashMap<>(); 054 055 private static String processKey(final String key) { 056 // stick a flower at the front so processed keys are different from literal keys 057 return "🌸" + key.toLowerCase(Locale.ROOT).replace("_", ""); 058 } 059 060 /** 061 * Perform a fuzzy lookup of {@code key} in enum {@code clazz}. 062 * 063 * @param clazz class to find key in 064 * @param key key to find 065 * @param <T> type of value 066 * @return optionally the enum 067 * @since 4.0.0 068 */ 069 @SuppressWarnings("unchecked") 070 public static <T extends Enum<T>> @Nullable T lookupEnum(final Class<T> clazz, final String key) { 071 final Map<String, Enum<?>> vals = ENUM_FIELD_CACHE.computeIfAbsent(requireNonNull(clazz), c2 -> { 072 final Map<String, Enum<?>> ret = new HashMap<>(); 073 for (Enum<?> field : c2.getEnumConstants()) { 074 ret.put(field.name(), field); 075 ret.putIfAbsent(processKey(field.name()), field); 076 } 077 return Collections.unmodifiableMap(ret); 078 }); 079 080 081 final Enum<?> possibleRet = vals.get(requireNonNull(key, "key")); 082 if (possibleRet != null) { 083 return (T) possibleRet; 084 } 085 return (T) vals.get(processKey(key)); 086 } 087 088}