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 ninja.leaping.configurate.util; 018 019import com.google.common.cache.CacheBuilder; 020import com.google.common.cache.CacheLoader; 021import com.google.common.cache.LoadingCache; 022import com.google.common.collect.ImmutableMap; 023import org.checkerframework.checker.nullness.qual.NonNull; 024 025import java.util.HashMap; 026import java.util.Map; 027import java.util.Optional; 028import java.util.concurrent.ExecutionException; 029 030import static java.util.Objects.requireNonNull; 031 032/** 033 * Utility class to cache more flexible enum lookup. 034 * 035 * <p>While normally case and punctuation have to match exactly, this method performs lookup that:</p> 036 * 037 * <ul> 038 * <li>is case-insensitive</li> 039 * <li>ignores underscores</li> 040 * <li>caches mappings</li> 041 * </ul> 042 * 043 * <p>If the enum has two fields that are equal except for case and underscores, an exact match 044 * will return the appropriate value, and any fuzzy matches will map to the first value in the enum 045 * that is applicable.</p> 046 */ 047public final class EnumLookup { 048 private EnumLookup() {} 049 050 private static final LoadingCache<Class<? extends Enum<?>>, Map<String, Enum<?>>> ENUM_FIELD_CACHE = CacheBuilder 051 .newBuilder() 052 .weakKeys() 053 .maximumSize(512) 054 .build(new CacheLoader<Class<? extends Enum<?>>, Map<String, Enum<?>>>() { 055 @Override 056 public Map<String, Enum<?>> load(@NonNull Class<? extends Enum<?>> key) { 057 Map<String, Enum<?>> ret = new HashMap<>(); 058 for (Enum<?> field : key.getEnumConstants()) { 059 ret.put(field.name(), field); 060 ret.putIfAbsent(processKey(field.name()), field); 061 } 062 return ImmutableMap.copyOf(ret); 063 } 064 }); 065 066 @NonNull 067 private static String processKey(@NonNull String key) { 068 // stick a flower at the front so processed keys are different from literal keys 069 return "🌸" + key.toLowerCase().replace("_", ""); 070 } 071 072 @SuppressWarnings("unchecked") 073 @NonNull 074 public static <T extends Enum<T>> Optional<T> lookupEnum(@NonNull Class<T> clazz, @NonNull String key) { 075 try { 076 Map<String, Enum<?>> vals = ENUM_FIELD_CACHE.get(requireNonNull(clazz, "clazz")); 077 Enum<?> possibleRet = vals.get(requireNonNull(key, "key")); 078 if (possibleRet != null) { 079 return Optional.of((T) possibleRet); 080 } 081 return Optional.ofNullable((T) vals.get(processKey(key))); 082 } catch (ExecutionException e) { 083 return Optional.empty(); 084 } 085 } 086}