Browse Source

enhance EnumConverter

Looly 5 years ago
parent
commit
c5e4d0ef34

+ 2 - 0
CHANGELOG.md

@@ -13,6 +13,8 @@
 * 【http  】     支持patch方法(issue#666@Github)
 * 【crypto】     BCUtil支持更加灵活的密钥类型,增加writePemObject方法
 * 【core  】     增加ServiceLoaderUtil
+* 【core  】     增加EnumUtil.getEnumAt方法
+* 【core  】     增强EnumConvert判断能力(issue#I17082@Gitee)
 
 ### Bug修复
 

+ 2 - 2
hutool-core/src/main/java/cn/hutool/core/convert/Convert.java

@@ -10,7 +10,7 @@ import java.util.*;
 import java.util.concurrent.TimeUnit;
 
 import cn.hutool.core.convert.impl.CollectionConverter;
-import cn.hutool.core.convert.impl.GenericEnumConverter;
+import cn.hutool.core.convert.impl.EnumConverter;
 import cn.hutool.core.convert.impl.MapConverter;
 import cn.hutool.core.lang.Assert;
 import cn.hutool.core.lang.TypeReference;
@@ -522,7 +522,7 @@ public class Convert {
 	 * @return Enum
 	 */
 	public static <E extends Enum<E>> E toEnum(Class<E> clazz, Object value, E defaultValue) {
-		return (new GenericEnumConverter<>(clazz)).convertQuietly(value, defaultValue);
+		return (E) (new EnumConverter(clazz)).convertQuietly(value, defaultValue);
 	}
 
 	/**

+ 78 - 5
hutool-core/src/main/java/cn/hutool/core/convert/impl/EnumConverter.java

@@ -1,22 +1,35 @@
 package cn.hutool.core.convert.impl;
 
 import cn.hutool.core.convert.AbstractConverter;
+import cn.hutool.core.map.MapUtil;
+import cn.hutool.core.util.ClassUtil;
+import cn.hutool.core.util.EnumUtil;
+import cn.hutool.core.util.ModifierUtil;
+import cn.hutool.core.util.ReflectUtil;
+
+import java.lang.reflect.Method;
+import java.util.Arrays;
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.stream.Collectors;
 
 /**
  * 无泛型检查的枚举转换器
- * 
+ *
  * @author Looly
  * @since 4.0.2
  */
-@SuppressWarnings({ "unchecked", "rawtypes" })
+@SuppressWarnings({"unchecked", "rawtypes"})
 public class EnumConverter extends AbstractConverter<Object> {
 	private static final long serialVersionUID = 1L;
 
+	private static final Map<Class<?>, Map<Class<?>, Method>> VALUE_OF_METHOD_CACHE = new ConcurrentHashMap<>();
+
 	private Class enumClass;
-	
+
 	/**
 	 * 构造
-	 * 
+	 *
 	 * @param enumClass 转换成的目标Enum类
 	 */
 	public EnumConverter(Class enumClass) {
@@ -25,11 +38,71 @@ public class EnumConverter extends AbstractConverter<Object> {
 
 	@Override
 	protected Object convertInternal(Object value) {
-		return Enum.valueOf(enumClass, convertToStr(value));
+		Enum enumValue = tryConvertEnum(value, this.enumClass);
+		if(null == enumValue && false == value instanceof String){
+			// 最后尝试valueOf转换
+			enumValue = Enum.valueOf(this.enumClass, convertToStr(value));
+		}
+		return enumValue;
 	}
 
 	@Override
 	public Class getTargetType() {
 		return this.enumClass;
 	}
+
+	/**
+	 * 尝试找到类似转换的静态方法调用实现转换
+	 *
+	 * @param value     被转换的值
+	 * @param enumClass enum类
+	 * @return 对应的枚举值
+	 */
+	protected static Enum tryConvertEnum(Object value, Class enumClass) {
+		Enum enumResult = null;
+		if (value instanceof Integer) {
+			enumResult = EnumUtil.getEnumAt(enumClass, (Integer)value);
+		} else if (value instanceof String) {
+			try {
+				enumResult = Enum.valueOf(enumClass, (String) value);
+			} catch (IllegalArgumentException e) {
+				//ignore
+			}
+		}
+
+		// 尝试查找其它用户自定义方法
+		if(null == enumResult){
+			final Map<Class<?>, Method> valueOfMethods = getValueOfMethods(enumClass);
+			if (MapUtil.isNotEmpty(valueOfMethods)) {
+				final Class<?> valueClass = value.getClass();
+				for (Map.Entry<Class<?>, Method> entry : valueOfMethods.entrySet()) {
+					if (ClassUtil.isAssignable(entry.getKey(), valueClass)) {
+						enumResult = ReflectUtil.invokeStatic(entry.getValue(), value);
+					}
+				}
+			}
+		}
+
+		return enumResult;
+	}
+
+	/**
+	 * 获取用于转换为enum的所有static方法
+	 *
+	 * @param enumClass 枚举类
+	 * @return 转换方法map
+	 */
+	private static Map<Class<?>, Method> getValueOfMethods(Class<?> enumClass) {
+		Map<Class<?>, Method> valueOfMethods = VALUE_OF_METHOD_CACHE.get(enumClass);
+		if (null == valueOfMethods) {
+			valueOfMethods = Arrays.stream(enumClass.getMethods())
+					.filter(ModifierUtil::isStatic)
+					.filter(m -> m.getReturnType() == enumClass)
+					.filter(m -> m.getParameterCount() == 1)
+					.filter(m -> false == "valueOf".equals(m.getName()))
+					.collect(Collectors.toMap(m -> m.getParameterTypes()[0], m -> m, (existing, replacement) -> existing));
+			VALUE_OF_METHOD_CACHE.put(enumClass, valueOfMethods);
+		}
+		return valueOfMethods;
+	}
 }

+ 9 - 1
hutool-core/src/main/java/cn/hutool/core/convert/impl/GenericEnumConverter.java

@@ -8,7 +8,9 @@ import cn.hutool.core.convert.AbstractConverter;
  * @param <E> 枚举类类型
  * @author Looly
  * @since 4.0.2
+ * @deprecated 请使用{@link EnumConverter}
  */
+@Deprecated
 public class GenericEnumConverter<E extends Enum<E>> extends AbstractConverter<E> {
 	private static final long serialVersionUID = 1L;
 
@@ -25,7 +27,13 @@ public class GenericEnumConverter<E extends Enum<E>> extends AbstractConverter<E
 
 	@Override
 	protected E convertInternal(Object value) {
-		return Enum.valueOf(enumClass, convertToStr(value));
+		//noinspection unchecked
+		E enumValue = (E) EnumConverter.tryConvertEnum(value, this.enumClass);
+		if(null == enumValue && false == value instanceof String){
+			// 最后尝试valueOf转换
+			enumValue = Enum.valueOf(this.enumClass, convertToStr(value));
+		}
+		return enumValue;
 	}
 
 	@Override

+ 16 - 2
hutool-core/src/main/java/cn/hutool/core/util/EnumUtil.java

@@ -55,6 +55,20 @@ public class EnumUtil {
 	 *
 	 * @param <E>       枚举类型泛型
 	 * @param enumClass 枚举类
+	 * @param index     枚举索引
+	 * @return 枚举值,null表示无此对应枚举
+	 * @since 5.1.6
+	 */
+	public static <E extends Enum<E>> E getEnumAt(Class<E> enumClass, int index) {
+		final E[] enumConstants = enumClass.getEnumConstants();
+		return index < enumConstants.length ? enumConstants[index] : null;
+	}
+
+	/**
+	 * 字符串转枚举,调用{@link Enum#valueOf(Class, String)}
+	 *
+	 * @param <E>       枚举类型泛型
+	 * @param enumClass 枚举类
 	 * @param value     值
 	 * @return 枚举值
 	 * @since 4.1.13
@@ -206,7 +220,7 @@ public class EnumUtil {
 	 * @since 4.0.2
 	 */
 	public static <E extends Enum<E>> LinkedHashMap<String, E> getEnumMap(final Class<E> enumClass) {
-		final LinkedHashMap<String, E> map = new LinkedHashMap<String, E>();
+		final LinkedHashMap<String, E> map = new LinkedHashMap<>();
 		for (final E e : enumClass.getEnumConstants()) {
 			map.put(e.name(), e);
 		}
@@ -248,7 +262,7 @@ public class EnumUtil {
 	/**
 	 * 判断某个值是不存在枚举中
 	 *
-	 * @param <E> 枚举类型
+	 * @param <E>       枚举类型
 	 * @param enumClass 枚举类
 	 * @param val       需要查找的值
 	 * @return 是否不存在

+ 56 - 0
hutool-core/src/test/java/cn/hutool/core/convert/EnumConvertTest.java

@@ -0,0 +1,56 @@
+package cn.hutool.core.convert;
+
+import org.junit.Assert;
+import org.junit.Test;
+
+/**
+ * Enum转换单元测试
+ */
+public class EnumConvertTest {
+
+	@Test
+	public void convertTest(){
+		TestEnum bbb = Convert.convert(TestEnum.class, "BBB");
+		Assert.assertEquals(TestEnum.B, bbb);
+
+		bbb = Convert.convert(TestEnum.class, 22);
+		Assert.assertEquals(TestEnum.B, bbb);
+	}
+
+	@Test
+	public void toEnumTest(){
+		TestEnum ccc = Convert.toEnum(TestEnum.class, "CCC");
+		Assert.assertEquals(TestEnum.C, ccc);
+
+		ccc = Convert.toEnum(TestEnum.class, 33);
+		Assert.assertEquals(TestEnum.C, ccc);
+	}
+
+	enum TestEnum {
+		A, B, C;
+
+		public static TestEnum parse(String str) {
+			switch (str) {
+				case "AAA":
+					return A;
+				case "BBB":
+					return B;
+				case "CCC":
+					return C;
+			}
+			return null;
+		}
+
+		public static TestEnum parseByNumber(int i) {
+			switch (i) {
+				case 11:
+					return A;
+				case 22:
+					return B;
+				case 33:
+					return C;
+			}
+			return null;
+		}
+	}
+}