diff --git a/hutool-core/src/main/java/cn/hutool/core/lang/reflect/ActualTypeMapperPool.java b/hutool-core/src/main/java/cn/hutool/core/lang/reflect/ActualTypeMapperPool.java index ea213ec1c..8b89c554a 100755 --- a/hutool-core/src/main/java/cn/hutool/core/lang/reflect/ActualTypeMapperPool.java +++ b/hutool-core/src/main/java/cn/hutool/core/lang/reflect/ActualTypeMapperPool.java @@ -2,8 +2,10 @@ package cn.hutool.core.lang.reflect; import cn.hutool.core.convert.Convert; import cn.hutool.core.map.WeakConcurrentMap; +import cn.hutool.core.util.ArrayUtil; import cn.hutool.core.util.TypeUtil; +import java.lang.reflect.GenericArrayType; import java.lang.reflect.ParameterizedType; import java.lang.reflect.Type; import java.lang.reflect.TypeVariable; @@ -57,6 +59,22 @@ public class ActualTypeMapperPool { return result; } + public static Type getActualType(Type type, GenericArrayType genericArrayType) { + final Map typeTypeMap = get(type); + Type actualType = typeTypeMap.get(genericArrayType); + + if (actualType == null) { + Type componentType = typeTypeMap.get(genericArrayType.getGenericComponentType()); + if (!(componentType instanceof Class)) { + return null; + } + actualType = ArrayUtil.getArrayType((Class) componentType); + typeTypeMap.put(genericArrayType, actualType); + } + + return actualType; + } + /** * 获取指定泛型变量对应的真实类型
* 由于子类中泛型参数实现和父类(接口)中泛型定义位置是一一对应的,因此可以通过对应关系找到泛型实现类型
diff --git a/hutool-core/src/main/java/cn/hutool/core/util/TypeUtil.java b/hutool-core/src/main/java/cn/hutool/core/util/TypeUtil.java index 4ca0f7885..ab734df30 100755 --- a/hutool-core/src/main/java/cn/hutool/core/util/TypeUtil.java +++ b/hutool-core/src/main/java/cn/hutool/core/util/TypeUtil.java @@ -3,12 +3,7 @@ package cn.hutool.core.util; import cn.hutool.core.lang.ParameterizedTypeImpl; import cn.hutool.core.lang.reflect.ActualTypeMapperPool; -import java.lang.reflect.Field; -import java.lang.reflect.Method; -import java.lang.reflect.ParameterizedType; -import java.lang.reflect.Type; -import java.lang.reflect.TypeVariable; -import java.lang.reflect.WildcardType; +import java.lang.reflect.*; import java.util.ArrayList; import java.util.List; import java.util.Map; @@ -412,6 +407,9 @@ public class TypeUtil { if (typeVariable instanceof TypeVariable) { return ActualTypeMapperPool.getActualType(type, (TypeVariable) typeVariable); } + if (typeVariable instanceof GenericArrayType) { + return ActualTypeMapperPool.getActualType(type, (GenericArrayType) typeVariable); + } // 没有需要替换的泛型变量,原样输出 return typeVariable; diff --git a/hutool-core/src/main/java/cn/hutool/core/util/VersionUtil.java b/hutool-core/src/main/java/cn/hutool/core/util/VersionUtil.java new file mode 100644 index 000000000..6602b7569 --- /dev/null +++ b/hutool-core/src/main/java/cn/hutool/core/util/VersionUtil.java @@ -0,0 +1,196 @@ +package cn.hutool.core.util; + +import cn.hutool.core.collection.CollUtil; +import cn.hutool.core.exceptions.UtilException; + +import java.util.Collection; +import java.util.List; +import java.util.Objects; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +/** + * 版本对比工具 + * 对 {@link cn.hutool.core.comparator.VersionComparator} 的封装 + * 最主要功能包括: + * + * + *
+ * 1. 版本表达式匹配
+ * 2. 单个版本匹配
+ * 
+ * + * @author winlans + * @see cn.hutool.core.comparator.VersionComparator + */ +public class VersionUtil { + + private static final Pattern COMPARE_REG = Pattern.compile("^[<>≥≤]=?"); + // 默认多版本分隔符 + private static final String defaultVersionsDelimiter = ";"; + + /** + * 是否匹配任意一个版本 + * + * @param currentVersion 当前版本 + * @param compareVersions 待匹配的版本列表 + * @return true 包含待匹配的版本 + */ + public static boolean anyMatch(String currentVersion, Collection compareVersions) { + return matchEl(currentVersion, CollUtil.join(compareVersions, defaultVersionsDelimiter)); + } + + public static boolean anyMatch(String currentVersion, String... compareVersions) { + return matchEl(currentVersion, ArrayUtil.join(compareVersions, defaultVersionsDelimiter)); + } + + /** + * 当前版本大于待比较版本 + * + * @param currentVersion 当前本本 + * @param compareVersion 待比较版本 + * @return true 当前版本大于待比较版本 + */ + public static boolean isGreaterThan(String currentVersion, String compareVersion) { + return matchEl(currentVersion, ">" + compareVersion); + } + + /** + * 当前版本大于等于待比较版本 + * + * @param currentVersion 当前本本 + * @param compareVersion 待比较版本 + * @return true 当前版本大于等于待比较版本 + */ + public static boolean isGreaterThanOrEqual(String currentVersion, String compareVersion) { + return matchEl(currentVersion, ">=" + compareVersion); + } + + /** + * 当前版本小于待比较版本 + * + * @param currentVersion 当前本本 + * @param compareVersion 待比较版本 + * @return true 当前版本小于待比较版本 + */ + public static boolean isLessThan(String currentVersion, String compareVersion) { + return matchEl(currentVersion, "<" + compareVersion); + } + + /** + * 当前版本小于等于待比较版本 + * + * @param currentVersion 当前本本 + * @param compareVersion 待比较版本 + * @return true 当前版本小于等于待比较版本 + */ + public static boolean isLessThanOrEqual(String currentVersion, String compareVersion) { + return matchEl(currentVersion, "<=" + compareVersion); + } + + /** + * 当前版本是否满足版本表达式 + *
+	 *     matchEl("1.0.2", ">=1.0.2") == true
+	 *     matchEl("1.0.2", "<1.0.1;1.0.2") == true
+	 *     matchEl("1.0.2", "<1.0.2") == false
+	 *     matchEl("1.0.2", "1.0.0-1.1.1") == true
+	 *     matchEl("1.0.2", "1.0.0-1.1.1") == true
+	 * 
+ * + * @param currentVersion 当前本本 + * @param versionEl 版本表达式 + * @return true 当前版本小于等于待比较版本 + */ + public static boolean matchEl(String currentVersion, String versionEl) { + return matchEl(currentVersion, versionEl, defaultVersionsDelimiter); + } + + /** + * 当前版本是否满足版本表达式 + *
+	 *     matchEl("1.0.2", ">=1.0.2", ";") == true
+	 *     matchEl("1.0.2", "<1.0.1,1.0.2", ",") == true
+	 *     matchEl("1.0.2", "<1.0.2", ";") == false
+	 *     matchEl("1.0.2", "1.0.0-1.1.1", ",") == true
+	 *     matchEl("1.0.2", "1.0.1,1.0.2-1.1.1", ",") == true
+	 * 
+ * + * @param currentVersion 当前本本 + * @param versionEl 版本表达式(可以匹配多个条件,使用指定的分隔符(默认;)分隔), + * {@code '-'}表示范围包含左右版本,如果 {@code '-'}的左边没有,表示小于等于某个版本号, 右边表示大于等于某个版本号。 + * 支持比较符号{@code '>'},{@code '<'}, {@code '>='},{@code '<='},{@code '≤'},{@code '≥'} + * + * + * @param versionsDelimiter 多表达式分隔符 + * @return true 当前版本小于等于待比较版本 + */ + public static boolean matchEl(String currentVersion, String versionEl, String versionsDelimiter) { + if (StrUtil.isBlank(versionsDelimiter) + || StrUtil.equals("-", versionsDelimiter) + || ReUtil.isMatch(COMPARE_REG, versionsDelimiter)) { + throw new UtilException("非法的版本分隔符:" + versionsDelimiter); + } + + if (StrUtil.isBlank(versionEl) || StrUtil.isBlank(currentVersion)) { + return false; + } + String trimmedVersion = StrUtil.trim(currentVersion); + + List els = StrUtil.split(versionEl, versionsDelimiter, true, true); + if (CollUtil.isEmpty(els)) { + return false; + } + + for (String el : els) { + el = el.trim(); + Matcher matcher = COMPARE_REG.matcher(el); + if (matcher.find()) { + String op = matcher.group(); + String ver = StrUtil.removePrefix(el, op); + switch (op) { + case ">=": + case "≥": + if (StrUtil.compareVersion(trimmedVersion, ver) >= 0) { + return true; + } + break; + case "<=": + case "≤": + if (StrUtil.compareVersion(trimmedVersion, ver) <= 0) { + return true; + } + break; + case "<": + if (StrUtil.compareVersion(trimmedVersion, ver) < 0) { + return true; + } + break; + case ">": + if (StrUtil.compareVersion(trimmedVersion, ver) > 0) { + return true; + } + break; + default: + return false; + } + } else if (StrUtil.contains(el, "-")) { + String[] pair = el.split("-"); + String left = StrUtil.blankToDefault(StrUtil.trim(pair[0]), ""); + String right = StrUtil.blankToDefault(StrUtil.trim(pair[1]), ""); + + boolean leftMatch = StrUtil.isBlank(left) || StrUtil.compareVersion(left, trimmedVersion) <= 0; + boolean rightMatch = StrUtil.isBlank(right) || StrUtil.compareVersion(right, trimmedVersion) >= 0; + if (leftMatch && rightMatch) { + return true; + } + } else if (Objects.equals(trimmedVersion, el)) { + return true; + } + } + return false; + } +} diff --git a/hutool-core/src/test/java/cn/hutool/core/util/TypeUtilTest.java b/hutool-core/src/test/java/cn/hutool/core/util/TypeUtilTest.java index d05e46cfa..42a7e5665 100644 --- a/hutool-core/src/test/java/cn/hutool/core/util/TypeUtilTest.java +++ b/hutool-core/src/test/java/cn/hutool/core/util/TypeUtilTest.java @@ -4,8 +4,12 @@ import java.lang.reflect.Method; import java.lang.reflect.Type; import java.util.ArrayList; import java.util.List; + +import cn.hutool.core.lang.TypeReference; import lombok.Data; + import static org.junit.jupiter.api.Assertions.*; + import org.junit.jupiter.api.Test; public class TypeUtilTest { @@ -97,4 +101,29 @@ public class TypeUtilTest { private T level; } + + /** + * fix github:issue#3873 + */ + @Test + public void getActualTypeForGenericArrayTest() { + TypeReference> typeReference = new TypeReference>() { + + }; + + Type levelType = TypeUtil.getFieldType(GenericArray.class, "level"); + Type actualType = TypeUtil.getActualType(typeReference.getType(), levelType); + assertEquals(ArrayUtil.getArrayType(GenericArrayEle.class), actualType); + } + + @Data + public static class GenericArray { + private T[] level; + } + + @Data + public static class GenericArrayEle { + private Long uid; + } + } diff --git a/hutool-core/src/test/java/cn/hutool/core/util/VersionUtilTest.java b/hutool-core/src/test/java/cn/hutool/core/util/VersionUtilTest.java new file mode 100644 index 000000000..4b3dc7893 --- /dev/null +++ b/hutool-core/src/test/java/cn/hutool/core/util/VersionUtilTest.java @@ -0,0 +1,79 @@ +package cn.hutool.core.util; + +import cn.hutool.core.collection.ListUtil; +import cn.hutool.core.exceptions.UtilException; +import org.junit.jupiter.api.Test; + +import java.util.List; + +import static org.junit.jupiter.api.Assertions.*; + +class VersionUtilTest { + + @Test + void isGreaterThan() { + String currentVersion = " 1.0.2"; + assertTrue(VersionUtil.isGreaterThan(currentVersion, "1.0.1")); + assertTrue(VersionUtil.isGreaterThan(currentVersion, "1")); + assertFalse(VersionUtil.isGreaterThan(currentVersion, "1.1")); + } + + @Test + void isGreaterThanOrEqual() { + String currentVersion = "1.0.2 "; + assertTrue(VersionUtil.isGreaterThanOrEqual(currentVersion, "1.0.1")); + assertTrue(VersionUtil.isGreaterThanOrEqual(currentVersion, "1.0.2")); + assertFalse(VersionUtil.isGreaterThanOrEqual(currentVersion, "1.1")); + } + + @Test + void isLessThan() { + String currentVersion = "1.0.2"; + assertTrue(VersionUtil.isLessThan(currentVersion, "1.0.3")); + assertFalse(VersionUtil.isLessThan(currentVersion, "1")); + assertTrue(VersionUtil.isLessThan(currentVersion, "1.1")); + assertFalse(VersionUtil.isLessThan(currentVersion, "1.0.2")); + } + + @Test + void isLessThanOrEqual() { + String currentVersion = "1.0.2"; + assertTrue(VersionUtil.isLessThanOrEqual(currentVersion, "1.0.2")); + assertFalse(VersionUtil.isLessThanOrEqual(currentVersion, "1.0.1")); + assertFalse(VersionUtil.isLessThanOrEqual(currentVersion, "1.1")); + } + + @Test + void matchEl() { + String currentVersion = "1.0.2"; + assertTrue(VersionUtil.matchEl(currentVersion, "1.0.1;1.0.2")); + assertFalse(VersionUtil.matchEl(currentVersion, "1.0.1;1.0.3")); + assertTrue(VersionUtil.matchEl(currentVersion, "1.0.9;1.0.1-1.0.2")); + assertTrue(VersionUtil.matchEl(currentVersion, "1.0.9;1.0.1-1.0.3")); + + assertTrue(VersionUtil.matchEl(currentVersion, "1.0.9,1.0.1-1.0.3", ",")); + } + + @Test + void matchEl_Exception_whenVersionDelimiterIllegal() { + List illegalDelimiters = ListUtil.of("-", ">", ">=", "<", "<=", "≥", "≤", null, "", " "); + + for (String illegalDelimiter : illegalDelimiters) { + assertThrows(UtilException.class, () -> { + String currentVersion = "1.0.2"; + VersionUtil.matchEl(currentVersion, "1.0.1;1.0.2", illegalDelimiter); + }); + } + } + + @Test + void anyMatch() { + String currentVersion = "1.0.2"; + assertTrue(VersionUtil.anyMatch(currentVersion, ListUtil.of("1.0.1", "1.0.3", "1.0.2"))); + assertTrue(VersionUtil.anyMatch(currentVersion, "1.0.1", "1.0.2")); + } + + @Test + void testMatchEl() { + } +} diff --git a/hutool-json/src/test/java/cn/hutool/json/JSONUtilTest.java b/hutool-json/src/test/java/cn/hutool/json/JSONUtilTest.java index fbf928000..2b5c2c04d 100644 --- a/hutool-json/src/test/java/cn/hutool/json/JSONUtilTest.java +++ b/hutool-json/src/test/java/cn/hutool/json/JSONUtilTest.java @@ -4,7 +4,9 @@ import cn.hutool.core.collection.CollectionUtil; import cn.hutool.core.collection.ListUtil; import cn.hutool.core.date.DateUtil; import cn.hutool.core.lang.Console; +import cn.hutool.core.lang.TypeReference; import cn.hutool.core.map.MapUtil; +import cn.hutool.core.util.ArrayUtil; import cn.hutool.core.util.NumberUtil; import cn.hutool.json.test.bean.Price; import cn.hutool.json.test.bean.UserA; @@ -290,4 +292,27 @@ public class JSONUtilTest { final String jsonStr = JSONUtil.toJsonStr(userId); assertEquals("{}", jsonStr); } + + /** + * 类型引用数组泛型丢失 + */ + @Test + public void issue3873Test() { + String json = "{\"results\":[{\"uid\":\"1\"}],\"offset\":0,\"limit\":20,\"total\":0}"; + Results deserialize = JSONUtil.toBean(json, (new TypeReference>() { + }), false); + + assertEquals(Results.class, deserialize.getClass()); + assertEquals(ArrayUtil.getArrayType(Index.class), deserialize.results.getClass()); + } + + @Data + public static class Results { + public T[] results; + } + + @Data + public static class Index { + public String uid; + } }