From 899f1384f6b3bcd62b626e4fdc46289b3d94dc43 Mon Sep 17 00:00:00 2001 From: Looly Date: Sat, 5 Mar 2022 22:47:07 +0800 Subject: [PATCH 01/13] add test --- .../hutool/crypto/test/symmetric/TEATest.java | 39 +++++++++++++++++++ 1 file changed, 39 insertions(+) create mode 100644 hutool-crypto/src/test/java/cn/hutool/crypto/test/symmetric/TEATest.java diff --git a/hutool-crypto/src/test/java/cn/hutool/crypto/test/symmetric/TEATest.java b/hutool-crypto/src/test/java/cn/hutool/crypto/test/symmetric/TEATest.java new file mode 100644 index 000000000..8a9d008d6 --- /dev/null +++ b/hutool-crypto/src/test/java/cn/hutool/crypto/test/symmetric/TEATest.java @@ -0,0 +1,39 @@ +package cn.hutool.crypto.test.symmetric; + +import cn.hutool.crypto.symmetric.SymmetricCrypto; +import org.junit.Assert; +import org.junit.Test; + +/** + * TEA(Tiny Encryption Algorithm)和 XTEA算法单元测试 + */ +public class TEATest { + + @Test + public void teaTest(){ + String data = "测试的加密数据 by Hutool"; + + // 密钥必须为128bit + final SymmetricCrypto tea = new SymmetricCrypto("TEA", "MyPassword123456".getBytes()); + final byte[] encrypt = tea.encrypt(data); + + // 解密 + final String decryptStr = tea.decryptStr(encrypt); + + Assert.assertEquals(data, decryptStr); + } + + @Test + public void xteaTest(){ + String data = "测试的加密数据 by Hutool"; + + // 密钥必须为128bit + final SymmetricCrypto tea = new SymmetricCrypto("XTEA", "MyPassword123456".getBytes()); + final byte[] encrypt = tea.encrypt(data); + + // 解密 + final String decryptStr = tea.decryptStr(encrypt); + + Assert.assertEquals(data, decryptStr); + } +} From 115871a355b1c7b212fe52b6c80d127dba14d263 Mon Sep 17 00:00:00 2001 From: VampireAchao Date: Sat, 5 Mar 2022 23:43:27 +0800 Subject: [PATCH 02/13] =?UTF-8?q?=E4=BF=AE=E5=A4=8DAnnotationProxy#annotat?= =?UTF-8?q?ionType=E8=8E=B7=E5=8F=96=E4=B8=8D=E5=88=B0=E6=B3=A8=E8=A7=A3?= =?UTF-8?q?=E7=B1=BB=E5=9E=8B=E7=9A=84=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../main/java/cn/hutool/core/annotation/AnnotationProxy.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/hutool-core/src/main/java/cn/hutool/core/annotation/AnnotationProxy.java b/hutool-core/src/main/java/cn/hutool/core/annotation/AnnotationProxy.java index a61ff6668..bcf2ba4b5 100644 --- a/hutool-core/src/main/java/cn/hutool/core/annotation/AnnotationProxy.java +++ b/hutool-core/src/main/java/cn/hutool/core/annotation/AnnotationProxy.java @@ -39,7 +39,7 @@ public class AnnotationProxy implements Annotation, Invoca @Override public Class annotationType() { - return null; + return type; } @Override From 580e2e9fbe1d3cc43bdc7f6ebaa0f28c96344a05 Mon Sep 17 00:00:00 2001 From: Looly Date: Sat, 5 Mar 2022 23:49:27 +0800 Subject: [PATCH 03/13] add XXTEA --- CHANGELOG.md | 3 +- .../cn/hutool/crypto/symmetric/XXTEA.java | 157 ++++++++++++++++++ .../hutool/crypto/test/symmetric/TEATest.java | 19 ++- 3 files changed, 176 insertions(+), 3 deletions(-) create mode 100644 hutool-crypto/src/main/java/cn/hutool/crypto/symmetric/XXTEA.java diff --git a/CHANGELOG.md b/CHANGELOG.md index fcd3d51bd..74943728c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,12 +2,13 @@ # 🚀Changelog ------------------------------------------------------------------------------------------------------------- -# 5.7.23 (2022-03-04) +# 5.7.23 (2022-03-05) ### 🐣新特性 * 【http 】 HttpRequest.form采用TableMap方式(issue#I4W427@Gitee) * 【core 】 AnnotationUtil增加getAnnotationAlias方法(pr#554@Gitee) * 【core 】 FileUtil.extName增加对tar.gz特殊处理(issue#I4W5FS@Gitee) +* 【crypto 】 增加XXTEA实现(issue#I4WH2X@Gitee) ### 🐞Bug修复 * 【core 】 修复ObjectUtil.hasNull传入null返回true的问题(pr#555@Gitee) diff --git a/hutool-crypto/src/main/java/cn/hutool/crypto/symmetric/XXTEA.java b/hutool-crypto/src/main/java/cn/hutool/crypto/symmetric/XXTEA.java new file mode 100644 index 000000000..4f948cd4f --- /dev/null +++ b/hutool-crypto/src/main/java/cn/hutool/crypto/symmetric/XXTEA.java @@ -0,0 +1,157 @@ +package cn.hutool.crypto.symmetric; + +import cn.hutool.core.io.IoUtil; + +import java.io.InputStream; +import java.io.OutputStream; +import java.io.Serializable; + +/** + * XXTEA(Corrected Block Tiny Encryption Algorithm)算法实现
+ * 来自:https://github.com/xxtea/xxtea-java + * + * @author Ma Bingyao + */ +public class XXTEA implements SymmetricEncryptor, SymmetricDecryptor, Serializable { + private static final long serialVersionUID = 1L; + + private static final int DELTA = 0x9E3779B9; + + private final byte[] key; + + /** + * 构造 + * + * @param key 密钥,16位 + */ + public XXTEA(byte[] key) { + this.key = key; + } + + @Override + public byte[] encrypt(byte[] data) { + if (data.length == 0) { + return data; + } + return toByteArray(encrypt( + toIntArray(data, true), + toIntArray(fixKey(key), false)), false); + } + + @Override + public void encrypt(InputStream data, OutputStream out, boolean isClose) { + IoUtil.write(out, isClose, encrypt(IoUtil.readBytes(data))); + } + + @Override + public byte[] decrypt(byte[] data) { + if (data.length == 0) { + return data; + } + return toByteArray(decrypt( + toIntArray(data, false), + toIntArray(fixKey(key), false)), true); + } + + @Override + public void decrypt(InputStream data, OutputStream out, boolean isClose) { + IoUtil.write(out, isClose, decrypt(IoUtil.readBytes(data))); + } + + //region Private Method + private static int[] encrypt(int[] v, int[] k) { + int n = v.length - 1; + + if (n < 1) { + return v; + } + int p, q = 6 + 52 / (n + 1); + int z = v[n], y, sum = 0, e; + + while (q-- > 0) { + sum = sum + DELTA; + e = sum >>> 2 & 3; + for (p = 0; p < n; p++) { + y = v[p + 1]; + z = v[p] += mx(sum, y, z, p, e, k); + } + y = v[0]; + z = v[n] += mx(sum, y, z, p, e, k); + } + return v; + } + + private static int[] decrypt(int[] v, int[] k) { + int n = v.length - 1; + + if (n < 1) { + return v; + } + int p, q = 6 + 52 / (n + 1); + int z, y = v[0], sum = q * DELTA, e; + + while (sum != 0) { + e = sum >>> 2 & 3; + for (p = n; p > 0; p--) { + z = v[p - 1]; + y = v[p] -= mx(sum, y, z, p, e, k); + } + z = v[n]; + y = v[0] -= mx(sum, y, z, p, e, k); + sum = sum - DELTA; + } + return v; + } + + private static int mx(int sum, int y, int z, int p, int e, int[] k) { + return (z >>> 5 ^ y << 2) + (y >>> 3 ^ z << 4) ^ (sum ^ y) + (k[p & 3 ^ e] ^ z); + } + + private static byte[] fixKey(byte[] key) { + if (key.length == 16) { + return key; + } + byte[] fixedkey = new byte[16]; + System.arraycopy(key, 0, fixedkey, 0, Math.min(key.length, 16)); + return fixedkey; + } + + private static int[] toIntArray(byte[] data, boolean includeLength) { + int n = (((data.length & 3) == 0) + ? (data.length >>> 2) + : ((data.length >>> 2) + 1)); + int[] result; + + if (includeLength) { + result = new int[n + 1]; + result[n] = data.length; + } else { + result = new int[n]; + } + n = data.length; + for (int i = 0; i < n; ++i) { + result[i >>> 2] |= (0x000000ff & data[i]) << ((i & 3) << 3); + } + return result; + } + + private static byte[] toByteArray(int[] data, boolean includeLength) { + int n = data.length << 2; + + if (includeLength) { + int m = data[data.length - 1]; + n -= 4; + if ((m < n - 3) || (m > n)) { + return null; + } + n = m; + } + byte[] result = new byte[n]; + + for (int i = 0; i < n; ++i) { + result[i] = (byte) (data[i >>> 2] >>> ((i & 3) << 3)); + } + return result; + } + //endregion +} diff --git a/hutool-crypto/src/test/java/cn/hutool/crypto/test/symmetric/TEATest.java b/hutool-crypto/src/test/java/cn/hutool/crypto/test/symmetric/TEATest.java index 8a9d008d6..53916926a 100644 --- a/hutool-crypto/src/test/java/cn/hutool/crypto/test/symmetric/TEATest.java +++ b/hutool-crypto/src/test/java/cn/hutool/crypto/test/symmetric/TEATest.java @@ -1,6 +1,7 @@ package cn.hutool.crypto.test.symmetric; import cn.hutool.crypto.symmetric.SymmetricCrypto; +import cn.hutool.crypto.symmetric.XXTEA; import org.junit.Assert; import org.junit.Test; @@ -10,7 +11,7 @@ import org.junit.Test; public class TEATest { @Test - public void teaTest(){ + public void teaTest() { String data = "测试的加密数据 by Hutool"; // 密钥必须为128bit @@ -24,7 +25,7 @@ public class TEATest { } @Test - public void xteaTest(){ + public void xteaTest() { String data = "测试的加密数据 by Hutool"; // 密钥必须为128bit @@ -36,4 +37,18 @@ public class TEATest { Assert.assertEquals(data, decryptStr); } + + @Test + public void xxteaTest() { + String data = "测试的加密数据 by Hutool"; + + // 密钥必须为128bit + final XXTEA tea = new XXTEA("MyPassword123456".getBytes()); + final byte[] encrypt = tea.encrypt(data); + + // 解密 + final String decryptStr = tea.decryptStr(encrypt); + + Assert.assertEquals(data, decryptStr); + } } From 4b22f7cc74c1ba6155228e745bd79c9a46b1eb9f Mon Sep 17 00:00:00 2001 From: Looly Date: Sun, 6 Mar 2022 19:22:29 +0800 Subject: [PATCH 04/13] fix code --- .../java/cn/hutool/script/ScriptRuntimeException.java | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/hutool-script/src/main/java/cn/hutool/script/ScriptRuntimeException.java b/hutool-script/src/main/java/cn/hutool/script/ScriptRuntimeException.java index c609ebbf7..48b62e085 100644 --- a/hutool-script/src/main/java/cn/hutool/script/ScriptRuntimeException.java +++ b/hutool-script/src/main/java/cn/hutool/script/ScriptRuntimeException.java @@ -42,11 +42,11 @@ public class ScriptRuntimeException extends RuntimeException { } /** - * Creates a ScriptException with message, filename and linenumber to be used in error messages. + * Creates a {@code ScriptException} with message, filename and linenumber to be used in error messages. * * @param message The string to use in the message - * @param fileName The file or resource name describing the location of a script error causing the ScriptException to be thrown. - * @param lineNumber A line number describing the location of a script error causing the ScriptException to be thrown. + * @param fileName The file or resource name describing the location of a script error causing the {@code ScriptException} to be thrown. + * @param lineNumber A line number describing the location of a script error causing the {@code ScriptException} to be thrown. */ public ScriptRuntimeException(String message, String fileName, int lineNumber) { super(message); @@ -55,7 +55,7 @@ public class ScriptRuntimeException extends RuntimeException { } /** - * ScriptException constructor specifying message, filename, line number and column number. + * {@code ScriptException} constructor specifying message, filename, line number and column number. * * @param message The message. * @param fileName The filename From 1e113ef83f071485ec24fa5c0bff920f566e29bb Mon Sep 17 00:00:00 2001 From: Looly Date: Sun, 6 Mar 2022 21:01:08 +0800 Subject: [PATCH 05/13] fix code --- .../java/cn/hutool/core/bean/BeanUtil.java | 4 +- .../hutool/core/map/CamelCaseLinkedMap.java | 20 +--------- .../java/cn/hutool/core/map/CamelCaseMap.java | 38 ++++++++++--------- .../core/map/CaseInsensitiveLinkedMap.java | 16 +------- .../hutool/core/map/CaseInsensitiveMap.java | 31 ++++++++------- .../core/map/CaseInsensitiveTreeMap.java | 18 +-------- .../java/cn/hutool/core/map/CustomKeyMap.java | 6 +-- .../hutool/core/map/FixedLinkedHashMap.java | 3 +- .../java/cn/hutool/core/map/FuncKeyMap.java | 9 +++-- .../java/cn/hutool/core/map/MapBuilder.java | 13 ++++++- .../java/cn/hutool/core/map/MapWrapper.java | 5 --- .../cn/hutool/core/bean/BeanUtilTest.java | 16 ++++---- 12 files changed, 75 insertions(+), 104 deletions(-) diff --git a/hutool-core/src/main/java/cn/hutool/core/bean/BeanUtil.java b/hutool-core/src/main/java/cn/hutool/core/bean/BeanUtil.java index b66b15f0d..84e7c5917 100644 --- a/hutool-core/src/main/java/cn/hutool/core/bean/BeanUtil.java +++ b/hutool-core/src/main/java/cn/hutool/core/bean/BeanUtil.java @@ -225,8 +225,8 @@ public class BeanUtil { */ private static Map internalGetPropertyDescriptorMap(Class clazz, boolean ignoreCase) throws BeanException { final PropertyDescriptor[] propertyDescriptors = getPropertyDescriptors(clazz); - final Map map = ignoreCase ? new CaseInsensitiveMap<>(propertyDescriptors.length, 1) - : new HashMap<>((int) (propertyDescriptors.length), 1); + final Map map = ignoreCase ? new CaseInsensitiveMap<>(propertyDescriptors.length, 1f) + : new HashMap<>(propertyDescriptors.length, 1); for (PropertyDescriptor propertyDescriptor : propertyDescriptors) { map.put(propertyDescriptor.getName(), propertyDescriptor); diff --git a/hutool-core/src/main/java/cn/hutool/core/map/CamelCaseLinkedMap.java b/hutool-core/src/main/java/cn/hutool/core/map/CamelCaseLinkedMap.java index fbd2b776c..639bfbfcc 100644 --- a/hutool-core/src/main/java/cn/hutool/core/map/CamelCaseLinkedMap.java +++ b/hutool-core/src/main/java/cn/hutool/core/map/CamelCaseLinkedMap.java @@ -1,7 +1,5 @@ package cn.hutool.core.map; -import cn.hutool.core.util.StrUtil; - import java.util.LinkedHashMap; import java.util.Map; @@ -15,7 +13,7 @@ import java.util.Map; * @param 值类型 * @since 4.0.7 */ -public class CamelCaseLinkedMap extends CustomKeyMap { +public class CamelCaseLinkedMap extends CamelCaseMap { private static final long serialVersionUID = 4043263744224569870L; // ------------------------------------------------------------------------- Constructor start @@ -48,7 +46,7 @@ public class CamelCaseLinkedMap extends CustomKeyMap { * 构造 * * @param loadFactor 加载因子 - * @param m Map + * @param m Map,数据会被默认拷贝到一个新的LinkedHashMap中 */ public CamelCaseLinkedMap(float loadFactor, Map m) { this(m.size(), loadFactor); @@ -65,18 +63,4 @@ public class CamelCaseLinkedMap extends CustomKeyMap { super(new LinkedHashMap<>(initialCapacity, loadFactor)); } // ------------------------------------------------------------------------- Constructor end - - /** - * 将Key转为驼峰风格,如果key为字符串的话 - * - * @param key KEY - * @return 驼峰Key - */ - @Override - protected Object customKey(Object key) { - if (key instanceof CharSequence) { - key = StrUtil.toCamelCase(key.toString()); - } - return key; - } } diff --git a/hutool-core/src/main/java/cn/hutool/core/map/CamelCaseMap.java b/hutool-core/src/main/java/cn/hutool/core/map/CamelCaseMap.java index ea1642d14..4759b7202 100644 --- a/hutool-core/src/main/java/cn/hutool/core/map/CamelCaseMap.java +++ b/hutool-core/src/main/java/cn/hutool/core/map/CamelCaseMap.java @@ -1,24 +1,24 @@ package cn.hutool.core.map; +import cn.hutool.core.util.StrUtil; + import java.util.HashMap; import java.util.Map; -import cn.hutool.core.util.StrUtil; - /** * 驼峰Key风格的Map
* 对KEY转换为驼峰,get("int_value")和get("intValue")获得的值相同,put进入的值也会被覆盖 * - * @author Looly - * * @param 键类型 * @param 值类型 + * @author Looly * @since 4.0.7 */ -public class CamelCaseMap extends CustomKeyMap { +public class CamelCaseMap extends FuncKeyMap { private static final long serialVersionUID = 4043263744224569870L; // ------------------------------------------------------------------------- Constructor start + /** * 构造 */ @@ -48,7 +48,7 @@ public class CamelCaseMap extends CustomKeyMap { * 构造 * * @param loadFactor 加载因子 - * @param m Map + * @param m 初始Map,数据会被默认拷贝到一个新的HashMap中 */ public CamelCaseMap(float loadFactor, Map m) { this(m.size(), loadFactor); @@ -59,24 +59,26 @@ public class CamelCaseMap extends CustomKeyMap { * 构造 * * @param initialCapacity 初始大小 - * @param loadFactor 加载因子 + * @param loadFactor 加载因子 */ public CamelCaseMap(int initialCapacity, float loadFactor) { - super(new HashMap<>(initialCapacity, loadFactor)); + this(MapBuilder.create(new HashMap<>(initialCapacity, loadFactor))); } - // ------------------------------------------------------------------------- Constructor end /** - * 将Key转为驼峰风格,如果key为字符串的话 + * 构造
+ * 注意此构造将传入的Map作为被包装的Map,针对任何修改,传入的Map都会被同样修改。 * - * @param key KEY - * @return 驼峰Key + * @param emptyMapBuilder Map构造器,必须构造空的Map */ - @Override - protected Object customKey(Object key) { - if (key instanceof CharSequence) { - key = StrUtil.toCamelCase(key.toString()); - } - return key; + CamelCaseMap(MapBuilder emptyMapBuilder) { + super(emptyMapBuilder.build(), (key) -> { + if (key instanceof CharSequence) { + key = StrUtil.toCamelCase(key.toString()); + } + //noinspection unchecked + return (K) key; + }); } + // ------------------------------------------------------------------------- Constructor end } diff --git a/hutool-core/src/main/java/cn/hutool/core/map/CaseInsensitiveLinkedMap.java b/hutool-core/src/main/java/cn/hutool/core/map/CaseInsensitiveLinkedMap.java index 51cdd2561..5be588ef3 100644 --- a/hutool-core/src/main/java/cn/hutool/core/map/CaseInsensitiveLinkedMap.java +++ b/hutool-core/src/main/java/cn/hutool/core/map/CaseInsensitiveLinkedMap.java @@ -13,7 +13,7 @@ import java.util.Map; * @param 值类型 * @since 3.3.1 */ -public class CaseInsensitiveLinkedMap extends CustomKeyMap { +public class CaseInsensitiveLinkedMap extends CaseInsensitiveMap { private static final long serialVersionUID = 4043263744224569870L; // ------------------------------------------------------------------------- Constructor start @@ -64,18 +64,4 @@ public class CaseInsensitiveLinkedMap extends CustomKeyMap { super(new LinkedHashMap<>(initialCapacity, loadFactor)); } // ------------------------------------------------------------------------- Constructor end - - /** - * 将Key转为小写 - * - * @param key KEY - * @return 小写KEY - */ - @Override - protected Object customKey(Object key) { - if (key instanceof CharSequence) { - key = key.toString().toLowerCase(); - } - return key; - } } diff --git a/hutool-core/src/main/java/cn/hutool/core/map/CaseInsensitiveMap.java b/hutool-core/src/main/java/cn/hutool/core/map/CaseInsensitiveMap.java index 0c892b268..40f805ad0 100644 --- a/hutool-core/src/main/java/cn/hutool/core/map/CaseInsensitiveMap.java +++ b/hutool-core/src/main/java/cn/hutool/core/map/CaseInsensitiveMap.java @@ -13,7 +13,7 @@ import java.util.Map; * @param 值类型 * @since 3.0.2 */ -public class CaseInsensitiveMap extends CustomKeyMap { +public class CaseInsensitiveMap extends FuncKeyMap { private static final long serialVersionUID = 4043263744224569870L; //------------------------------------------------------------------------- Constructor start @@ -34,9 +34,10 @@ public class CaseInsensitiveMap extends CustomKeyMap { } /** - * 构造 + * 构造
+ * 注意此构造将传入的Map作为被包装的Map,针对任何修改,传入的Map都会被同样修改。 * - * @param m Map + * @param m 被包装的自定义Map创建器 */ public CaseInsensitiveMap(Map m) { this(DEFAULT_LOAD_FACTOR, m); @@ -61,21 +62,23 @@ public class CaseInsensitiveMap extends CustomKeyMap { * @param loadFactor 加载因子 */ public CaseInsensitiveMap(int initialCapacity, float loadFactor) { - super(new HashMap<>(initialCapacity, loadFactor)); + this(MapBuilder.create(new HashMap<>(initialCapacity, loadFactor))); } - //------------------------------------------------------------------------- Constructor end /** - * 将Key转为小写 + * 构造
+ * 注意此构造将传入的Map作为被包装的Map,针对任何修改,传入的Map都会被同样修改。 * - * @param key KEY - * @return 小写KEY + * @param emptyMapBuilder 被包装的自定义Map创建器 */ - @Override - protected Object customKey(Object key) { - if (key instanceof CharSequence) { - key = key.toString().toLowerCase(); - } - return key; + CaseInsensitiveMap(MapBuilder emptyMapBuilder) { + super(emptyMapBuilder.build(), (key)->{ + if (key instanceof CharSequence) { + key = key.toString().toLowerCase(); + } + //noinspection unchecked + return (K) key; + }); } + //------------------------------------------------------------------------- Constructor end } diff --git a/hutool-core/src/main/java/cn/hutool/core/map/CaseInsensitiveTreeMap.java b/hutool-core/src/main/java/cn/hutool/core/map/CaseInsensitiveTreeMap.java index 7f8b065f6..69f3ec538 100755 --- a/hutool-core/src/main/java/cn/hutool/core/map/CaseInsensitiveTreeMap.java +++ b/hutool-core/src/main/java/cn/hutool/core/map/CaseInsensitiveTreeMap.java @@ -15,7 +15,7 @@ import java.util.TreeMap; * @param 值类型 * @since 3.3.1 */ -public class CaseInsensitiveTreeMap extends CustomKeyMap { +public class CaseInsensitiveTreeMap extends CaseInsensitiveMap { private static final long serialVersionUID = 4043263744224569870L; // ------------------------------------------------------------------------- Constructor start @@ -40,7 +40,7 @@ public class CaseInsensitiveTreeMap extends CustomKeyMap { /** * 构造 * - * @param m Map + * @param m Map,初始Map,键值对会被复制到新的TreeMap中 * @since 3.1.2 */ public CaseInsensitiveTreeMap(SortedMap m) { @@ -56,18 +56,4 @@ public class CaseInsensitiveTreeMap extends CustomKeyMap { super(new TreeMap<>(comparator)); } // ------------------------------------------------------------------------- Constructor end - - /** - * 将Key转为小写 - * - * @param key KEY - * @return 小写KEY - */ - @Override - protected Object customKey(Object key) { - if (key instanceof CharSequence) { - key = key.toString().toLowerCase(); - } - return key; - } } diff --git a/hutool-core/src/main/java/cn/hutool/core/map/CustomKeyMap.java b/hutool-core/src/main/java/cn/hutool/core/map/CustomKeyMap.java index 4584943a5..04ed9d371 100644 --- a/hutool-core/src/main/java/cn/hutool/core/map/CustomKeyMap.java +++ b/hutool-core/src/main/java/cn/hutool/core/map/CustomKeyMap.java @@ -18,11 +18,11 @@ public abstract class CustomKeyMap extends MapWrapper { * 构造
* 通过传入一个Map从而确定Map的类型,子类需创建一个空的Map,而非传入一个已有Map,否则值可能会被修改 * - * @param m Map 被包装的Map + * @param emptyMap Map 被包装的Map,必须为空Map,否则自定义key会无效 * @since 3.1.2 */ - public CustomKeyMap(Map m) { - super(m); + public CustomKeyMap(Map emptyMap) { + super(emptyMap); } @Override diff --git a/hutool-core/src/main/java/cn/hutool/core/map/FixedLinkedHashMap.java b/hutool-core/src/main/java/cn/hutool/core/map/FixedLinkedHashMap.java index 1559fde7a..5ddaaa6f9 100644 --- a/hutool-core/src/main/java/cn/hutool/core/map/FixedLinkedHashMap.java +++ b/hutool-core/src/main/java/cn/hutool/core/map/FixedLinkedHashMap.java @@ -3,7 +3,8 @@ package cn.hutool.core.map; import java.util.LinkedHashMap; /** - * 固定大小的{@link LinkedHashMap} 实现 + * 固定大小的{@link LinkedHashMap} 实现
+ * 注意此类非线程安全,由于{@link #get(Object)}操作会修改链表的顺序结构,因此也不可以使用读写锁。 * * @author looly * diff --git a/hutool-core/src/main/java/cn/hutool/core/map/FuncKeyMap.java b/hutool-core/src/main/java/cn/hutool/core/map/FuncKeyMap.java index e5ca6a719..0cc468d61 100644 --- a/hutool-core/src/main/java/cn/hutool/core/map/FuncKeyMap.java +++ b/hutool-core/src/main/java/cn/hutool/core/map/FuncKeyMap.java @@ -19,13 +19,14 @@ public class FuncKeyMap extends CustomKeyMap { // ------------------------------------------------------------------------- Constructor start /** - * 构造 + * 构造
+ * 注意提供的Map中不能有键值对,否则可能导致自定义key失效 * - * @param m Map + * @param emptyMap Map,提供的空map * @param keyFunc 自定义KEY的函数 */ - public FuncKeyMap(Map m, Function keyFunc) { - super(m); + public FuncKeyMap(Map emptyMap, Function keyFunc) { + super(emptyMap); this.keyFunc = keyFunc; } // ------------------------------------------------------------------------- Constructor end diff --git a/hutool-core/src/main/java/cn/hutool/core/map/MapBuilder.java b/hutool-core/src/main/java/cn/hutool/core/map/MapBuilder.java index 5840d9204..2f605d71e 100644 --- a/hutool-core/src/main/java/cn/hutool/core/map/MapBuilder.java +++ b/hutool-core/src/main/java/cn/hutool/core/map/MapBuilder.java @@ -13,7 +13,7 @@ import java.util.function.Supplier; * @param Value类型 * @since 3.1.1 */ -public class MapBuilder implements Builder> { +public class MapBuilder implements Builder> { private static final long serialVersionUID = 1L; private final Map map; @@ -120,6 +120,17 @@ public class MapBuilder implements Builder> { return this; } + /** + * 清空Map + * + * @return this + * @since 5.7.23 + */ + public MapBuilder clear() { + this.map.clear(); + return this; + } + /** * 创建后的map * diff --git a/hutool-core/src/main/java/cn/hutool/core/map/MapWrapper.java b/hutool-core/src/main/java/cn/hutool/core/map/MapWrapper.java index bfd81dd86..3c1ac5936 100644 --- a/hutool-core/src/main/java/cn/hutool/core/map/MapWrapper.java +++ b/hutool-core/src/main/java/cn/hutool/core/map/MapWrapper.java @@ -88,7 +88,6 @@ public class MapWrapper implements Map, Iterable>, S } @Override - @SuppressWarnings("NullableProblems") public void putAll(Map m) { raw.putAll(m); } @@ -99,25 +98,21 @@ public class MapWrapper implements Map, Iterable>, S } @Override - @SuppressWarnings("NullableProblems") public Collection values() { return raw.values(); } @Override - @SuppressWarnings("NullableProblems") public Set keySet() { return raw.keySet(); } @Override - @SuppressWarnings("NullableProblems") public Set> entrySet() { return raw.entrySet(); } @Override - @SuppressWarnings("NullableProblems") public Iterator> iterator() { return this.entrySet().iterator(); } diff --git a/hutool-core/src/test/java/cn/hutool/core/bean/BeanUtilTest.java b/hutool-core/src/test/java/cn/hutool/core/bean/BeanUtilTest.java index a01dc608b..f31190313 100644 --- a/hutool-core/src/test/java/cn/hutool/core/bean/BeanUtilTest.java +++ b/hutool-core/src/test/java/cn/hutool/core/bean/BeanUtilTest.java @@ -5,6 +5,7 @@ import cn.hutool.core.bean.copier.CopyOptions; import cn.hutool.core.bean.copier.ValueProvider; import cn.hutool.core.collection.CollUtil; import cn.hutool.core.collection.ListUtil; +import cn.hutool.core.map.MapBuilder; import cn.hutool.core.map.MapUtil; import cn.hutool.core.thread.ThreadUtil; import cn.hutool.core.util.ArrayUtil; @@ -78,14 +79,15 @@ public class BeanUtilTest { @Test public void fillBeanWithMapIgnoreCaseTest() { - HashMap map = MapUtil.newHashMap(); - map.put("Name", "Joe"); - map.put("aGe", 12); - map.put("openId", "DFDFSDFWERWER"); + Map map = MapBuilder.create() + .put("Name", "Joe") + .put("aGe", 12) + .put("openId", "DFDFSDFWERWER") + .build(); SubPerson person = BeanUtil.fillBeanWithMapIgnoreCase(map, new SubPerson(), false); - Assert.assertEquals(person.getName(), "Joe"); - Assert.assertEquals(person.getAge(), 12); - Assert.assertEquals(person.getOpenid(), "DFDFSDFWERWER"); + Assert.assertEquals("Joe", person.getName()); + Assert.assertEquals(12, person.getAge()); + Assert.assertEquals("DFDFSDFWERWER", person.getOpenid()); } @Test From 04dc6d2b730170d5b8ed68f830febea36eec4945 Mon Sep 17 00:00:00 2001 From: Looly Date: Sun, 6 Mar 2022 21:08:15 +0800 Subject: [PATCH 06/13] fix code --- hutool-core/src/main/java/cn/hutool/core/text/StrBuilder.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/hutool-core/src/main/java/cn/hutool/core/text/StrBuilder.java b/hutool-core/src/main/java/cn/hutool/core/text/StrBuilder.java index a09dc0681..18a18bd92 100644 --- a/hutool-core/src/main/java/cn/hutool/core/text/StrBuilder.java +++ b/hutool-core/src/main/java/cn/hutool/core/text/StrBuilder.java @@ -8,7 +8,8 @@ import java.io.Serializable; import java.util.Arrays; /** - * 可复用的字符串生成器,非线程安全 + * 可复用的字符串生成器,非线程安全
+ * TODO 6.x移除此类,java8的StringBuilder非常完善了,无需重写。 * * @author Looly * @since 4.0.0 From 820db7fa3226683fc91311520aab87674fe82302 Mon Sep 17 00:00:00 2001 From: Looly Date: Mon, 7 Mar 2022 02:39:05 +0800 Subject: [PATCH 07/13] addTable --- .../cn/hutool/core/builder/EqualsBuilder.java | 122 ++++---- .../cn/hutool/core/map/multi/RowKeyTable.java | 272 ++++++++++++++++++ .../java/cn/hutool/core/map/multi/Table.java | 253 ++++++++++++++++ .../hutool/core/map/multi/package-info.java | 4 +- 4 files changed, 588 insertions(+), 63 deletions(-) create mode 100644 hutool-core/src/main/java/cn/hutool/core/map/multi/RowKeyTable.java create mode 100644 hutool-core/src/main/java/cn/hutool/core/map/multi/Table.java diff --git a/hutool-core/src/main/java/cn/hutool/core/builder/EqualsBuilder.java b/hutool-core/src/main/java/cn/hutool/core/builder/EqualsBuilder.java index bb7c7e46c..e68ccdf38 100644 --- a/hutool-core/src/main/java/cn/hutool/core/builder/EqualsBuilder.java +++ b/hutool-core/src/main/java/cn/hutool/core/builder/EqualsBuilder.java @@ -70,7 +70,7 @@ public class EqualsBuilder implements Builder { * Converters value pair into a register pair. *

* - * @param lhs this object + * @param lhs {@code this} object * @param rhs the other object * @return the pair */ @@ -82,15 +82,15 @@ public class EqualsBuilder implements Builder { /** *

- * Returns true if the registry contains the given object pair. + * Returns {@code true} if the registry contains the given object pair. * Used by the reflection methods to avoid infinite loops. * Objects might be swapped therefore a check is needed if the object pair * is registered in given or swapped order. *

* - * @param lhs this object to lookup in registry + * @param lhs {@code this} object to lookup in registry * @param rhs the other object to lookup on registry - * @return boolean true if the registry contains the given object. + * @return boolean {@code true} if the registry contains the given object. * @since 3.0 */ static boolean isRegistered(final Object lhs, final Object rhs) { @@ -108,7 +108,7 @@ public class EqualsBuilder implements Builder { * Used by the reflection methods to avoid infinite loops. *

* - * @param lhs this object to register + * @param lhs {@code this} object to register * @param rhs the other object to register */ static void register(final Object lhs, final Object rhs) { @@ -131,7 +131,7 @@ public class EqualsBuilder implements Builder { *

* Used by the reflection methods to avoid infinite loops. * - * @param lhs this object to unregister + * @param lhs {@code this} object to unregister * @param rhs the other object to unregister * @since 3.0 */ @@ -170,7 +170,7 @@ public class EqualsBuilder implements Builder { * @param lhs 此对象 * @param rhs 另一个对象 * @param excludeFields 排除的字段集合,如果有不参与计算equals的字段加入此集合即可 - * @return 两个对象是否equals,是返回true + * @return 两个对象是否equals,是返回{@code true} */ public static boolean reflectionEquals(final Object lhs, final Object rhs, final Collection excludeFields) { return reflectionEquals(lhs, rhs, ArrayUtil.toArray(excludeFields, String.class)); @@ -182,62 +182,62 @@ public class EqualsBuilder implements Builder { * @param lhs 此对象 * @param rhs 另一个对象 * @param excludeFields 排除的字段集合,如果有不参与计算equals的字段加入此集合即可 - * @return 两个对象是否equals,是返回true + * @return 两个对象是否equals,是返回{@code true} */ public static boolean reflectionEquals(final Object lhs, final Object rhs, final String... excludeFields) { return reflectionEquals(lhs, rhs, false, null, excludeFields); } /** - *

This method uses reflection to determine if the two Objects + *

This method uses reflection to determine if the two {@code Object}s * are equal.

* - *

It uses AccessibleObject.setAccessible to gain access to private + *

It uses {@code AccessibleObject.setAccessible} to gain access to private * fields. This means that it will throw a security exception if run under * a security manager, if the permissions are not set up correctly. It is also * not as efficient as testing explicitly. Non-primitive fields are compared using - * equals().

+ * {@code equals()}.

* - *

If the TestTransients parameter is set to true, transient + *

If the TestTransients parameter is set to {@code true}, transient * members will be tested, otherwise they are ignored, as they are likely - * derived fields, and not part of the value of the Object.

+ * derived fields, and not part of the value of the {@code Object}.

* *

Static fields will not be tested. Superclass fields will be included.

* - * @param lhs this object + * @param lhs {@code this} object * @param rhs the other object * @param testTransients whether to include transient fields - * @return true if the two Objects have tested equals. + * @return {@code true} if the two Objects have tested equals. */ public static boolean reflectionEquals(final Object lhs, final Object rhs, final boolean testTransients) { return reflectionEquals(lhs, rhs, testTransients, null); } /** - *

This method uses reflection to determine if the two Objects + *

This method uses reflection to determine if the two {@code Object}s * are equal.

* - *

It uses AccessibleObject.setAccessible to gain access to private + *

It uses {@code AccessibleObject.setAccessible} to gain access to private * fields. This means that it will throw a security exception if run under * a security manager, if the permissions are not set up correctly. It is also * not as efficient as testing explicitly. Non-primitive fields are compared using - * equals().

+ * {@code equals()}.

* - *

If the testTransients parameter is set to true, transient + *

If the testTransients parameter is set to {@code true}, transient * members will be tested, otherwise they are ignored, as they are likely - * derived fields, and not part of the value of the Object.

+ * derived fields, and not part of the value of the {@code Object}.

* *

Static fields will not be included. Superclass fields will be appended * up to and including the specified superclass. A null superclass is treated * as java.lang.Object.

* - * @param lhs this object + * @param lhs {@code this} object * @param rhs the other object * @param testTransients whether to include transient fields * @param reflectUpToClass the superclass to reflect up to (inclusive), - * may be null + * may be {@code null} * @param excludeFields array of field names to exclude from testing - * @return true if the two Objects have tested equals. + * @return {@code true} if the two Objects have tested equals. * @since 2.0 */ public static boolean reflectionEquals(final Object lhs, final Object rhs, final boolean testTransients, final Class reflectUpToClass, @@ -343,9 +343,9 @@ public class EqualsBuilder implements Builder { //------------------------------------------------------------------------- /** - *

Adds the result of super.equals() to this builder.

+ *

Adds the result of {@code super.equals()} to this builder.

* - * @param superEquals the result of calling super.equals() + * @param superEquals the result of calling {@code super.equals()} * @return EqualsBuilder - used to chain calls. * @since 2.0 */ @@ -360,8 +360,8 @@ public class EqualsBuilder implements Builder { //------------------------------------------------------------------------- /** - *

Test if two Objects are equal using their - * equals method.

+ *

Test if two {@code Object}s are equal using their + * {@code equals} method.

* * @param lhs the left hand object * @param rhs the right hand object @@ -388,11 +388,11 @@ public class EqualsBuilder implements Builder { /** *

- * Test if two long s are equal. + * Test if two {@code long} s are equal. *

* - * @param lhs the left hand long - * @param rhs the right hand long + * @param lhs the left hand {@code long} + * @param rhs the right hand {@code long} * @return EqualsBuilder - used to chain calls. */ public EqualsBuilder append(final long lhs, final long rhs) { @@ -404,10 +404,10 @@ public class EqualsBuilder implements Builder { } /** - *

Test if two ints are equal.

+ *

Test if two {@code int}s are equal.

* - * @param lhs the left hand int - * @param rhs the right hand int + * @param lhs the left hand {@code int} + * @param rhs the right hand {@code int} * @return EqualsBuilder - used to chain calls. */ public EqualsBuilder append(final int lhs, final int rhs) { @@ -419,10 +419,10 @@ public class EqualsBuilder implements Builder { } /** - *

Test if two shorts are equal.

+ *

Test if two {@code short}s are equal.

* - * @param lhs the left hand short - * @param rhs the right hand short + * @param lhs the left hand {@code short} + * @param rhs the right hand {@code short} * @return EqualsBuilder - used to chain calls. */ public EqualsBuilder append(final short lhs, final short rhs) { @@ -434,10 +434,10 @@ public class EqualsBuilder implements Builder { } /** - *

Test if two chars are equal.

+ *

Test if two {@code char}s are equal.

* - * @param lhs the left hand char - * @param rhs the right hand char + * @param lhs the left hand {@code char} + * @param rhs the right hand {@code char} * @return EqualsBuilder - used to chain calls. */ public EqualsBuilder append(final char lhs, final char rhs) { @@ -449,10 +449,10 @@ public class EqualsBuilder implements Builder { } /** - *

Test if two bytes are equal.

+ *

Test if two {@code byte}s are equal.

* - * @param lhs the left hand byte - * @param rhs the right hand byte + * @param lhs the left hand {@code byte} + * @param rhs the right hand {@code byte} * @return EqualsBuilder - used to chain calls. */ public EqualsBuilder append(final byte lhs, final byte rhs) { @@ -464,16 +464,16 @@ public class EqualsBuilder implements Builder { } /** - *

Test if two doubles are equal by testing that the - * pattern of bits returned by doubleToLong are equal.

+ *

Test if two {@code double}s are equal by testing that the + * pattern of bits returned by {@code doubleToLong} are equal.

* - *

This handles NaNs, Infinities, and -0.0.

+ *

This handles NaNs, Infinities, and {@code -0.0}.

* *

It is compatible with the hash code generated by - * HashCodeBuilder.

+ * {@code HashCodeBuilder}.

* - * @param lhs the left hand double - * @param rhs the right hand double + * @param lhs the left hand {@code double} + * @param rhs the right hand {@code double} * @return EqualsBuilder - used to chain calls. */ public EqualsBuilder append(final double lhs, final double rhs) { @@ -484,16 +484,16 @@ public class EqualsBuilder implements Builder { } /** - *

Test if two floats are equal byt testing that the + *

Test if two {@code float}s are equal byt testing that the * pattern of bits returned by doubleToLong are equal.

* - *

This handles NaNs, Infinities, and -0.0.

+ *

This handles NaNs, Infinities, and {@code -0.0}.

* *

It is compatible with the hash code generated by - * HashCodeBuilder.

+ * {@code HashCodeBuilder}.

* - * @param lhs the left hand float - * @param rhs the right hand float + * @param lhs the left hand {@code float} + * @param rhs the right hand {@code float} * @return EqualsBuilder - used to chain calls. */ public EqualsBuilder append(final float lhs, final float rhs) { @@ -504,10 +504,10 @@ public class EqualsBuilder implements Builder { } /** - *

Test if two booleanss are equal.

+ *

Test if two {@code booleans}s are equal.

* - * @param lhs the left hand boolean - * @param rhs the right hand boolean + * @param lhs the left hand {@code boolean} + * @param rhs the right hand {@code boolean} * @return EqualsBuilder - used to chain calls. */ public EqualsBuilder append(final boolean lhs, final boolean rhs) { @@ -519,7 +519,7 @@ public class EqualsBuilder implements Builder { } /** - *

Returns true if the fields that have been checked + *

Returns {@code true} if the fields that have been checked * are all equal.

* * @return boolean @@ -529,11 +529,11 @@ public class EqualsBuilder implements Builder { } /** - *

Returns true if the fields that have been checked + *

Returns {@code true} if the fields that have been checked * are all equal.

* - * @return true if all of the fields that have been checked - * are equal, false otherwise. + * @return {@code true} if all of the fields that have been checked + * are equal, {@code false} otherwise. * @since 3.0 */ @Override @@ -542,7 +542,7 @@ public class EqualsBuilder implements Builder { } /** - * Sets the isEquals value. + * Sets the {@code isEquals} value. * * @param isEquals The value to set. * @return this diff --git a/hutool-core/src/main/java/cn/hutool/core/map/multi/RowKeyTable.java b/hutool-core/src/main/java/cn/hutool/core/map/multi/RowKeyTable.java new file mode 100644 index 000000000..0e8854896 --- /dev/null +++ b/hutool-core/src/main/java/cn/hutool/core/map/multi/RowKeyTable.java @@ -0,0 +1,272 @@ +package cn.hutool.core.map.multi; + +import cn.hutool.core.collection.IterUtil; +import cn.hutool.core.collection.TransIter; +import cn.hutool.core.util.ObjectUtil; +import com.sun.istack.internal.Nullable; + +import java.io.Serializable; +import java.util.AbstractCollection; +import java.util.AbstractSet; +import java.util.Collection; +import java.util.HashMap; +import java.util.Iterator; +import java.util.Map; +import java.util.Objects; +import java.util.Set; +import java.util.function.Supplier; + +public class RowKeyTable implements Table { + + final Map> raw; + final Supplier> supplier; + + public RowKeyTable(Map> raw) { + this(raw, HashMap::new); + } + + public RowKeyTable(Map> raw, Supplier> columnMapSupplier) { + this.raw = raw; + this.supplier = null == columnMapSupplier ? HashMap::new : columnMapSupplier; + } + + @Override + public Map> rowMap() { + return raw; + } + + @Override + public Map> columnMap() { + // TODO 实现columnMap + throw new UnsupportedOperationException("TODO implement this method"); + } + + @Override + public Collection values() { + return this.values; + } + + @Override + public Set> cellSet() { + return this.cellSet; + } + + @Override + public V put(R rowKey, C columnKey, V value) { + return raw.computeIfAbsent(rowKey, (key) -> supplier.get()).put(columnKey, value); + } + + @Override + public void putAll(Table table) { + if (null != table) { + for (Table.Cell cell : table.cellSet()) { + put(cell.getRowKey(), cell.getColumnKey(), cell.getValue()); + } + } + } + + @Override + public V remove(R rowKey, C columnKey) { + final Map map = getRow(rowKey); + if (null == map) { + return null; + } + final V value = map.remove(columnKey); + if (map.isEmpty()) { + raw.remove(rowKey); + } + return value; + } + + @Override + public boolean isEmpty() { + return raw.isEmpty(); + } + + @Override + public void clear() { + this.raw.clear(); + } + + @Override + public Iterator> iterator() { + return new CellIterator(); + } + + @Override + public boolean equals(@Nullable Object obj) { + if (obj == this) { + return true; + } else if (obj instanceof Table) { + final Table that = (Table) obj; + return this.cellSet().equals(that.cellSet()); + } else { + return false; + } + } + + @Override + public int hashCode() { + return cellSet().hashCode(); + } + + @Override + public String toString() { + return rowMap().toString(); + } + + /** + * 基于{@link Cell}的{@link Iterator}实现 + */ + private class CellIterator implements Iterator> { + final Iterator>> rowIterator = raw.entrySet().iterator(); + Map.Entry> rowEntry; + Iterator> columnIterator = IterUtil.empty(); + + @Override + public boolean hasNext() { + return rowIterator.hasNext() || columnIterator.hasNext(); + } + + @Override + public Cell next() { + if (false == columnIterator.hasNext()) { + rowEntry = rowIterator.next(); + columnIterator = rowEntry.getValue().entrySet().iterator(); + } + final Map.Entry columnEntry = columnIterator.next(); + return new SimpleCell<>(rowEntry.getKey(), columnEntry.getKey(), columnEntry.getValue()); + } + + @Override + public void remove() { + columnIterator.remove(); + if (rowEntry.getValue().isEmpty()) { + rowIterator.remove(); + } + } + } + + /** + * 简单{@link Cell} 实现 + * + * @param 行类型 + * @param 列类型 + * @param 值类型 + */ + private static class SimpleCell implements Cell, Serializable { + private static final long serialVersionUID = 1L; + + private final R rowKey; + private final C columnKey; + private final V value; + + SimpleCell(@Nullable R rowKey, @Nullable C columnKey, @Nullable V value) { + this.rowKey = rowKey; + this.columnKey = columnKey; + this.value = value; + } + + @Override + public R getRowKey() { + return rowKey; + } + + @Override + public C getColumnKey() { + return columnKey; + } + + @Override + public V getValue() { + return value; + } + + @Override + public boolean equals(Object obj) { + if (obj == this) { + return true; + } + if (obj instanceof Cell) { + Cell other = (Cell) obj; + return ObjectUtil.equal(rowKey, other.getRowKey()) + && ObjectUtil.equal(columnKey, other.getColumnKey()) + && ObjectUtil.equal(value, other.getValue()); + } + return false; + } + + @Override + public int hashCode() { + return Objects.hash(rowKey, columnKey, value); + } + + @Override + public String toString() { + return "(" + rowKey + "," + columnKey + ")=" + value; + } + } + + private final Collection values = new AbstractCollection() { + @Override + public Iterator iterator() { + return new TransIter<>(cellSet().iterator(), Cell::getValue); + } + + @Override + public boolean contains(Object o) { + //noinspection unchecked + return containsValue((V) o); + } + + @Override + public void clear() { + RowKeyTable.this.clear(); + } + + @Override + public int size() { + return RowKeyTable.this.size(); + } + }; + + private final Set> cellSet = new AbstractSet>() { + @Override + public boolean contains(Object o) { + if (o instanceof Cell) { + @SuppressWarnings("unchecked") final + Cell cell = (Cell) o; + Map row = getRow(cell.getRowKey()); + if (null != row) { + return ObjectUtil.equals(row.get(cell.getColumnKey()), cell.getValue()); + } + } + return false; + } + + @Override + public boolean remove(Object o) { + if (contains(o)) { + @SuppressWarnings("unchecked") + final Cell cell = (Cell) o; + RowKeyTable.this.remove(cell.getRowKey(), cell.getColumnKey()); + } + return false; + } + + @Override + public void clear() { + RowKeyTable.this.clear(); + } + + @Override + public Iterator> iterator() { + return new CellIterator(); + } + + @Override + public int size() { + return RowKeyTable.this.size(); + } + }; +} diff --git a/hutool-core/src/main/java/cn/hutool/core/map/multi/Table.java b/hutool-core/src/main/java/cn/hutool/core/map/multi/Table.java new file mode 100644 index 000000000..cd14029a5 --- /dev/null +++ b/hutool-core/src/main/java/cn/hutool/core/map/multi/Table.java @@ -0,0 +1,253 @@ +package cn.hutool.core.map.multi; + +import cn.hutool.core.lang.Opt; +import cn.hutool.core.lang.func.Consumer3; +import cn.hutool.core.map.MapUtil; + +import java.util.Collection; +import java.util.Map; +import java.util.Set; + +/** + * 表格数据结构定义
+ * 此结构类似于Guava的Table接口,使用两个键映射到一个值,类似于表格结构。 + * + * @param 行键类型 + * @param 列键类型 + * @param 值类型 + * @since 5.7.23 + */ +public interface Table extends Iterable> { + + /** + * 是否包含指定行列的映射
+ * 行和列任意一个不存在都会返回{@code false},如果行和列都存在,值为{@code null},也会返回{@code true} + * + * @param rowKey 行键 + * @param columnKey 列键 + * @return 是否包含映射 + */ + default boolean contains(R rowKey, C columnKey) { + return Opt.ofNullable(getRow(rowKey)).map((map) -> map.containsKey(columnKey)).get(); + } + + //region Row + + /** + * 行是否存在 + * + * @param rowKey 行键 + * @return 行是否存在 + */ + default boolean containsRow(R rowKey) { + return Opt.ofNullable(rowMap()).map((map) -> map.containsKey(rowKey)).get(); + } + + /** + * 获取行 + * + * @param rowKey 行键 + * @return 行映射,返回的键为列键,值为表格的值 + */ + default Map getRow(R rowKey) { + return Opt.ofNullable(rowMap()).map((map) -> map.get(rowKey)).get(); + } + + /** + * 返回所有行的key,行的key不可重复 + * + * @return 行键 + */ + default Set rowKeySet() { + return Opt.ofNullable(rowMap()).map(Map::keySet).get(); + } + + /** + * 返回行列对应的Map + * + * @return map,键为行键,值为列和值的对应map + */ + Map> rowMap(); + //endregion + + //region Column + + /** + * 列是否存在 + * + * @param columnKey 列键 + * @return 列是否存在 + */ + default boolean containsColumn(C columnKey) { + return Opt.ofNullable(columnMap()).map((map) -> map.containsKey(columnKey)).get(); + } + + /** + * 获取列 + * + * @param columnKey 列键 + * @return 列映射,返回的键为行键,值为表格的值 + */ + default Map getColumn(C columnKey) { + return Opt.ofNullable(columnMap()).map((map) -> map.get(columnKey)).get(); + } + + /** + * 返回所有列的key,列的key不可重复 + * + * @return 列set + */ + default Set columnKeySet() { + return Opt.ofNullable(columnMap()).map(Map::keySet).get(); + } + + /** + * 返回列-行对应的map + * + * @return map,键为列键,值为行和值的对应map + */ + Map> columnMap(); + //endregion + + //region value + + /** + * 指定值是否存在 + * + * @param value 值 + * @return 值 + */ + default boolean containsValue(V value){ + final Collection> rows = Opt.ofNullable(rowMap()).map(Map::values).get(); + if(null != rows){ + for (Map row : rows) { + if (row.containsValue(value)) { + return true; + } + } + } + return false; + } + + /** + * 获取指定值 + * + * @param rowKey 行键 + * @param columnKey 列键 + * @return 值,如果值不存在,返回{@code null} + */ + default V get(R rowKey, C columnKey) { + return Opt.ofNullable(getRow(rowKey)).map((map) -> map.get(columnKey)).get(); + } + + /** + * 所有行列值的集合 + * + * @return 值的集合 + */ + Collection values(); + //endregion + + /** + * 所有单元格集合 + * + * @return 单元格集合 + */ + Set> cellSet(); + + /** + * 为表格指定行列赋值,如果不存在,创建之,存在则替换之,返回原值 + * + * @param rowKey 行键 + * @param columnKey 列键 + * @param value 值 + * @return 原值,不存在返回{@code null} + */ + V put(R rowKey, C columnKey, V value); + + /** + * 批量加入 + * + * @param table 其他table + */ + void putAll(Table table); + + /** + * 移除指定值 + * + * @param rowKey 行键 + * @param columnKey 列键 + * @return 移除的值,如果值不存在,返回{@code null} + */ + V remove(R rowKey, C columnKey); + + /** + * 表格是否为空 + * + * @return 是否为空 + */ + boolean isEmpty(); + + /** + * 表格大小,一般为单元格的个数 + * + * @return 表格大小 + */ + default int size(){ + final Map> rowMap = rowMap(); + if(MapUtil.isEmpty(rowMap)){ + return 0; + } + int size = 0; + for (Map map : rowMap.values()) { + size += map.size(); + } + return size; + } + + /** + * 清空表格 + */ + void clear(); + + /** + * 遍历表格的单元格,处理值 + * + * @param consumer 单元格值处理器 + */ + default void forEach(Consumer3 consumer) { + for (Cell cell : this) { + consumer.accept(cell.getRowKey(), cell.getColumnKey(), cell.getValue()); + } + } + + /** + * 单元格,用于表示一个单元格的行、列和值 + * + * @param 行键类型 + * @param 列键类型 + * @param 值类型 + */ + interface Cell { + /** + * 获取行键 + * + * @return 行键 + */ + R getRowKey(); + + /** + * 获取列键 + * + * @return 列键 + */ + C getColumnKey(); + + /** + * 获取值 + * + * @return 值 + */ + V getValue(); + } +} diff --git a/hutool-core/src/main/java/cn/hutool/core/map/multi/package-info.java b/hutool-core/src/main/java/cn/hutool/core/map/multi/package-info.java index b0a2bcd20..2bf5b685f 100644 --- a/hutool-core/src/main/java/cn/hutool/core/map/multi/package-info.java +++ b/hutool-core/src/main/java/cn/hutool/core/map/multi/package-info.java @@ -1,7 +1,7 @@ /** - * 列表类型值的Map实现 + * 多参数类型的Map实现,包括集合类型值的Map和Table * * @author looly * */ -package cn.hutool.core.map.multi; \ No newline at end of file +package cn.hutool.core.map.multi; From 01af68cd0d68561a1f46f856d4ff5dc5a95c20c9 Mon Sep 17 00:00:00 2001 From: Looly Date: Tue, 8 Mar 2022 01:15:13 +0800 Subject: [PATCH 08/13] add Table --- CHANGELOG.md | 4 +- .../cn/hutool/core/collection/IterUtil.java | 15 + .../java/cn/hutool/core/map/AbsEntry.java | 46 +++ .../java/cn/hutool/core/map/SimpleEntry.java | 31 ++ .../java/cn/hutool/core/map/TableMap.java | 49 +-- .../cn/hutool/core/map/multi/AbsTable.java | 236 ++++++++++++ .../cn/hutool/core/map/multi/RowKeyTable.java | 359 +++++++++--------- .../java/cn/hutool/core/map/multi/Table.java | 8 +- .../cn/hutool/core/map/RowKeyTableTest.java | 39 ++ 9 files changed, 552 insertions(+), 235 deletions(-) create mode 100644 hutool-core/src/main/java/cn/hutool/core/map/AbsEntry.java create mode 100644 hutool-core/src/main/java/cn/hutool/core/map/SimpleEntry.java create mode 100644 hutool-core/src/main/java/cn/hutool/core/map/multi/AbsTable.java create mode 100644 hutool-core/src/test/java/cn/hutool/core/map/RowKeyTableTest.java diff --git a/CHANGELOG.md b/CHANGELOG.md index 74943728c..4c9993b9f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,13 +2,15 @@ # 🚀Changelog ------------------------------------------------------------------------------------------------------------- -# 5.7.23 (2022-03-05) +# 5.7.23 (2022-03-08) ### 🐣新特性 * 【http 】 HttpRequest.form采用TableMap方式(issue#I4W427@Gitee) * 【core 】 AnnotationUtil增加getAnnotationAlias方法(pr#554@Gitee) * 【core 】 FileUtil.extName增加对tar.gz特殊处理(issue#I4W5FS@Gitee) * 【crypto 】 增加XXTEA实现(issue#I4WH2X@Gitee) +* 【core 】 增加Table实现(issue#2179@Github) +* ### 🐞Bug修复 * 【core 】 修复ObjectUtil.hasNull传入null返回true的问题(pr#555@Gitee) diff --git a/hutool-core/src/main/java/cn/hutool/core/collection/IterUtil.java b/hutool-core/src/main/java/cn/hutool/core/collection/IterUtil.java index f03451bc3..2634eefae 100644 --- a/hutool-core/src/main/java/cn/hutool/core/collection/IterUtil.java +++ b/hutool-core/src/main/java/cn/hutool/core/collection/IterUtil.java @@ -906,4 +906,19 @@ public class IterUtil { // 当两个Iterable长度不一致时返回false return false == (it1.hasNext() || it2.hasNext()); } + + /** + * 清空指定{@link Iterator},此方法遍历后调用{@link Iterator#remove()}移除每个元素 + * + * @param iterator {@link Iterator} + * @since 5.7.23 + */ + public static void clear(Iterator iterator) { + if (null != iterator) { + while (iterator.hasNext()) { + iterator.next(); + iterator.remove(); + } + } + } } diff --git a/hutool-core/src/main/java/cn/hutool/core/map/AbsEntry.java b/hutool-core/src/main/java/cn/hutool/core/map/AbsEntry.java new file mode 100644 index 000000000..a2ab5885f --- /dev/null +++ b/hutool-core/src/main/java/cn/hutool/core/map/AbsEntry.java @@ -0,0 +1,46 @@ +package cn.hutool.core.map; + +import cn.hutool.core.util.ObjectUtil; + +import java.util.Map; + +/** + * 抽象的{@link Map.Entry}实现,来自Guava
+ * 实现了默认的{@link #equals(Object)}、{@link #hashCode()}、{@link #toString()}方法。
+ * 默认{@link #setValue(Object)}抛出异常。 + * + * @param 键类型 + * @param 值类型 + * @author Guava + * @since 5.7.23 + */ +public abstract class AbsEntry implements Map.Entry { + + @Override + public V setValue(V value) { + throw new UnsupportedOperationException("Entry is read only."); + } + + @Override + public boolean equals(Object object) { + if (object instanceof Map.Entry) { + final Map.Entry that = (Map.Entry) object; + return ObjectUtil.equals(this.getKey(), that.getKey()) + && ObjectUtil.equals(this.getValue(), that.getValue()); + } + return false; + } + + @Override + public int hashCode() { + //copy from 1.8 HashMap.Node + K k = getKey(); + V v = getValue(); + return ((k == null) ? 0 : k.hashCode()) ^ ((v == null) ? 0 : v.hashCode()); + } + + @Override + public String toString() { + return getKey() + "=" + getValue(); + } +} diff --git a/hutool-core/src/main/java/cn/hutool/core/map/SimpleEntry.java b/hutool-core/src/main/java/cn/hutool/core/map/SimpleEntry.java new file mode 100644 index 000000000..e414636c0 --- /dev/null +++ b/hutool-core/src/main/java/cn/hutool/core/map/SimpleEntry.java @@ -0,0 +1,31 @@ +package cn.hutool.core.map; + +/** + * {@link java.util.Map.Entry}简单实现。
+ * 键值对使用不可变字段表示。 + * + * @param 键类型 + * @param 值类型 + * @author looly + * @since 5.7.23 + */ +public class SimpleEntry extends AbsEntry { + + private final K key; + private final V value; + + public SimpleEntry(K key, V value) { + this.key = key; + this.value = value; + } + + @Override + public K getKey() { + return key; + } + + @Override + public V getValue() { + return value; + } +} diff --git a/hutool-core/src/main/java/cn/hutool/core/map/TableMap.java b/hutool-core/src/main/java/cn/hutool/core/map/TableMap.java index 596ec5160..783f1e98a 100644 --- a/hutool-core/src/main/java/cn/hutool/core/map/TableMap.java +++ b/hutool-core/src/main/java/cn/hutool/core/map/TableMap.java @@ -13,7 +13,6 @@ import java.util.Iterator; import java.util.LinkedHashSet; import java.util.List; import java.util.Map; -import java.util.Objects; import java.util.Set; /** @@ -173,7 +172,7 @@ public class TableMap implements Map, Iterable>, Ser public Set> entrySet() { final Set> hashSet = new LinkedHashSet<>(); for (int i = 0; i < size(); i++) { - hashSet.add(new Entry<>(keys.get(i), values.get(i))); + hashSet.add(new SimpleEntry<>(keys.get(i), values.get(i))); } return hashSet; } @@ -191,7 +190,7 @@ public class TableMap implements Map, Iterable>, Ser @Override public Map.Entry next() { - return new Entry<>(keysIter.next(), valuesIter.next()); + return new SimpleEntry<>(keysIter.next(), valuesIter.next()); } @Override @@ -209,48 +208,4 @@ public class TableMap implements Map, Iterable>, Ser ", values=" + values + '}'; } - - private static class Entry implements Map.Entry { - - private final K key; - private final V value; - - public Entry(K key, V value) { - this.key = key; - this.value = value; - } - - @Override - public K getKey() { - return key; - } - - @Override - public V getValue() { - return value; - } - - @Override - public V setValue(V value) { - throw new UnsupportedOperationException("setValue not supported."); - } - - @Override - public final boolean equals(Object o) { - if (o == this) - return true; - if (o instanceof Map.Entry) { - Map.Entry e = (Map.Entry) o; - return Objects.equals(key, e.getKey()) && - Objects.equals(value, e.getValue()); - } - return false; - } - - @Override - public int hashCode() { - //copy from 1.8 HashMap.Node - return Objects.hashCode(key) ^ Objects.hashCode(value); - } - } } diff --git a/hutool-core/src/main/java/cn/hutool/core/map/multi/AbsTable.java b/hutool-core/src/main/java/cn/hutool/core/map/multi/AbsTable.java new file mode 100644 index 000000000..a98e40286 --- /dev/null +++ b/hutool-core/src/main/java/cn/hutool/core/map/multi/AbsTable.java @@ -0,0 +1,236 @@ +package cn.hutool.core.map.multi; + +import cn.hutool.core.collection.IterUtil; +import cn.hutool.core.collection.TransIter; +import cn.hutool.core.util.ObjectUtil; + +import java.io.Serializable; +import java.util.AbstractCollection; +import java.util.AbstractSet; +import java.util.Collection; +import java.util.Iterator; +import java.util.Map; +import java.util.Objects; +import java.util.Set; + +/** + * 抽象{@link Table}接口实现
+ * 默认实现了: + *
    + *
  • {@link #equals(Object)}
  • + *
  • {@link #hashCode()}
  • + *
  • {@link #toString()}
  • + *
  • {@link #values()}
  • + *
  • {@link #cellSet()}
  • + *
  • {@link #iterator()}
  • + *
+ * + * @param 行类型 + * @param 列类型 + * @param 值类型 + * @author Guava, Looly + * @since 5.7.23 + */ +public abstract class AbsTable implements Table { + + @Override + public boolean equals(Object obj) { + if (obj == this) { + return true; + } else if (obj instanceof Table) { + final Table that = (Table) obj; + return this.cellSet().equals(that.cellSet()); + } else { + return false; + } + } + + @Override + public int hashCode() { + return cellSet().hashCode(); + } + + @Override + public String toString() { + return rowMap().toString(); + } + + //region values + @Override + public Collection values() { + Collection result = values; + return (result == null) ? values = new Values() : result; + } + + private Collection values; + private class Values extends AbstractCollection { + @Override + public Iterator iterator() { + return new TransIter<>(cellSet().iterator(), Cell::getValue); + } + + @Override + public boolean contains(Object o) { + //noinspection unchecked + return containsValue((V) o); + } + + @Override + public void clear() { + AbsTable.this.clear(); + } + + @Override + public int size() { + return AbsTable.this.size(); + } + } + //endregion + + //region cellSet + @Override + public Set> cellSet() { + Set> result = cellSet; + return (result == null) ? cellSet = new CellSet() : result; + } + + private Set> cellSet; + + private class CellSet extends AbstractSet> { + @Override + public boolean contains(Object o) { + if (o instanceof Cell) { + @SuppressWarnings("unchecked") final Cell cell = (Cell) o; + Map row = getRow(cell.getRowKey()); + if (null != row) { + return ObjectUtil.equals(row.get(cell.getColumnKey()), cell.getValue()); + } + } + return false; + } + + @Override + public boolean remove(Object o) { + if (contains(o)) { + @SuppressWarnings("unchecked") final Cell cell = (Cell) o; + AbsTable.this.remove(cell.getRowKey(), cell.getColumnKey()); + } + return false; + } + + @Override + public void clear() { + AbsTable.this.clear(); + } + + @Override + public Iterator> iterator() { + return new AbsTable.CellIterator(); + } + + @Override + public int size() { + return AbsTable.this.size(); + } + } + //endregion + + //region iterator + @Override + public Iterator> iterator() { + return new CellIterator(); + } + + /** + * 基于{@link Cell}的{@link Iterator}实现 + */ + private class CellIterator implements Iterator> { + final Iterator>> rowIterator = rowMap().entrySet().iterator(); + Map.Entry> rowEntry; + Iterator> columnIterator = IterUtil.empty(); + + @Override + public boolean hasNext() { + return rowIterator.hasNext() || columnIterator.hasNext(); + } + + @Override + public Cell next() { + if (false == columnIterator.hasNext()) { + rowEntry = rowIterator.next(); + columnIterator = rowEntry.getValue().entrySet().iterator(); + } + final Map.Entry columnEntry = columnIterator.next(); + return new SimpleCell<>(rowEntry.getKey(), columnEntry.getKey(), columnEntry.getValue()); + } + + @Override + public void remove() { + columnIterator.remove(); + if (rowEntry.getValue().isEmpty()) { + rowIterator.remove(); + } + } + } + //endregion + + /** + * 简单{@link Cell} 实现 + * + * @param 行类型 + * @param 列类型 + * @param 值类型 + */ + private static class SimpleCell implements Cell, Serializable { + private static final long serialVersionUID = 1L; + + private final R rowKey; + private final C columnKey; + private final V value; + + SimpleCell(R rowKey, C columnKey, V value) { + this.rowKey = rowKey; + this.columnKey = columnKey; + this.value = value; + } + + @Override + public R getRowKey() { + return rowKey; + } + + @Override + public C getColumnKey() { + return columnKey; + } + + @Override + public V getValue() { + return value; + } + + @Override + public boolean equals(Object obj) { + if (obj == this) { + return true; + } + if (obj instanceof Cell) { + Cell other = (Cell) obj; + return ObjectUtil.equal(rowKey, other.getRowKey()) + && ObjectUtil.equal(columnKey, other.getColumnKey()) + && ObjectUtil.equal(value, other.getValue()); + } + return false; + } + + @Override + public int hashCode() { + return Objects.hash(rowKey, columnKey, value); + } + + @Override + public String toString() { + return "(" + rowKey + "," + columnKey + ")=" + value; + } + } +} diff --git a/hutool-core/src/main/java/cn/hutool/core/map/multi/RowKeyTable.java b/hutool-core/src/main/java/cn/hutool/core/map/multi/RowKeyTable.java index 0e8854896..6094300ac 100644 --- a/hutool-core/src/main/java/cn/hutool/core/map/multi/RowKeyTable.java +++ b/hutool-core/src/main/java/cn/hutool/core/map/multi/RowKeyTable.java @@ -1,68 +1,75 @@ package cn.hutool.core.map.multi; +import cn.hutool.core.builder.Builder; +import cn.hutool.core.collection.ComputeIter; import cn.hutool.core.collection.IterUtil; import cn.hutool.core.collection.TransIter; -import cn.hutool.core.util.ObjectUtil; -import com.sun.istack.internal.Nullable; +import cn.hutool.core.map.AbsEntry; +import cn.hutool.core.map.SimpleEntry; -import java.io.Serializable; -import java.util.AbstractCollection; +import java.util.AbstractMap; import java.util.AbstractSet; -import java.util.Collection; import java.util.HashMap; import java.util.Iterator; import java.util.Map; -import java.util.Objects; import java.util.Set; -import java.util.function.Supplier; -public class RowKeyTable implements Table { +/** + * 将行的键作为主键的{@link Table}实现
+ * 此结构为: 行=(列=值) + * + * @param 行类型 + * @param 列类型 + * @param 值类型 + * @author Guava, Looly + * @since 5.7.23 + */ +public class RowKeyTable extends AbsTable { final Map> raw; - final Supplier> supplier; + /** + * 列的Map创建器,用于定义Table中Value对应Map类型 + */ + final Builder> columnBuilder; + //region 构造 + + /** + * 构造 + */ + public RowKeyTable() { + this(new HashMap<>()); + } + + /** + * 构造 + * + * @param raw 原始Map + */ public RowKeyTable(Map> raw) { this(raw, HashMap::new); } - public RowKeyTable(Map> raw, Supplier> columnMapSupplier) { + /** + * 构造 + * + * @param raw 原始Map + * @param columnMapBuilder 列的map创建器 + */ + public RowKeyTable(Map> raw, Builder> columnMapBuilder) { this.raw = raw; - this.supplier = null == columnMapSupplier ? HashMap::new : columnMapSupplier; + this.columnBuilder = null == columnMapBuilder ? HashMap::new : columnMapBuilder; } + //endregion @Override public Map> rowMap() { return raw; } - @Override - public Map> columnMap() { - // TODO 实现columnMap - throw new UnsupportedOperationException("TODO implement this method"); - } - - @Override - public Collection values() { - return this.values; - } - - @Override - public Set> cellSet() { - return this.cellSet; - } - @Override public V put(R rowKey, C columnKey, V value) { - return raw.computeIfAbsent(rowKey, (key) -> supplier.get()).put(columnKey, value); - } - - @Override - public void putAll(Table table) { - if (null != table) { - for (Table.Cell cell : table.cellSet()) { - put(cell.getRowKey(), cell.getColumnKey(), cell.getValue()); - } - } + return raw.computeIfAbsent(rowKey, (key) -> columnBuilder.build()).put(columnKey, value); } @Override @@ -89,184 +96,164 @@ public class RowKeyTable implements Table { } @Override - public Iterator> iterator() { - return new CellIterator(); - } - - @Override - public boolean equals(@Nullable Object obj) { - if (obj == this) { - return true; - } else if (obj instanceof Table) { - final Table that = (Table) obj; - return this.cellSet().equals(that.cellSet()); - } else { + public boolean containsColumn(C columnKey) { + if (columnKey == null) { return false; } - } - - @Override - public int hashCode() { - return cellSet().hashCode(); - } - - @Override - public String toString() { - return rowMap().toString(); - } - - /** - * 基于{@link Cell}的{@link Iterator}实现 - */ - private class CellIterator implements Iterator> { - final Iterator>> rowIterator = raw.entrySet().iterator(); - Map.Entry> rowEntry; - Iterator> columnIterator = IterUtil.empty(); - - @Override - public boolean hasNext() { - return rowIterator.hasNext() || columnIterator.hasNext(); - } - - @Override - public Cell next() { - if (false == columnIterator.hasNext()) { - rowEntry = rowIterator.next(); - columnIterator = rowEntry.getValue().entrySet().iterator(); - } - final Map.Entry columnEntry = columnIterator.next(); - return new SimpleCell<>(rowEntry.getKey(), columnEntry.getKey(), columnEntry.getValue()); - } - - @Override - public void remove() { - columnIterator.remove(); - if (rowEntry.getValue().isEmpty()) { - rowIterator.remove(); - } - } - } - - /** - * 简单{@link Cell} 实现 - * - * @param 行类型 - * @param 列类型 - * @param 值类型 - */ - private static class SimpleCell implements Cell, Serializable { - private static final long serialVersionUID = 1L; - - private final R rowKey; - private final C columnKey; - private final V value; - - SimpleCell(@Nullable R rowKey, @Nullable C columnKey, @Nullable V value) { - this.rowKey = rowKey; - this.columnKey = columnKey; - this.value = value; - } - - @Override - public R getRowKey() { - return rowKey; - } - - @Override - public C getColumnKey() { - return columnKey; - } - - @Override - public V getValue() { - return value; - } - - @Override - public boolean equals(Object obj) { - if (obj == this) { + for (Map map : raw.values()) { + if (null != map && map.containsKey(columnKey)) { return true; } - if (obj instanceof Cell) { - Cell other = (Cell) obj; - return ObjectUtil.equal(rowKey, other.getRowKey()) - && ObjectUtil.equal(columnKey, other.getColumnKey()) - && ObjectUtil.equal(value, other.getValue()); - } - return false; } + return false; + } - @Override - public int hashCode() { - return Objects.hash(rowKey, columnKey, value); - } + //region columnMap + @Override + public Map> columnMap() { + Map> result = columnMap; + return (result == null) ? columnMap = new ColumnMap() : result; + } + private Map> columnMap; + + private class ColumnMap extends AbstractMap> { @Override - public String toString() { - return "(" + rowKey + "," + columnKey + ")=" + value; + public Set>> entrySet() { + return new ColumnMapEntrySet(); } } - private final Collection values = new AbstractCollection() { - @Override - public Iterator iterator() { - return new TransIter<>(cellSet().iterator(), Cell::getValue); - } + private class ColumnMapEntrySet extends AbstractSet>> { + private final Set columnKeySet = columnKeySet(); @Override - public boolean contains(Object o) { - //noinspection unchecked - return containsValue((V) o); - } - - @Override - public void clear() { - RowKeyTable.this.clear(); + public Iterator>> iterator() { + return new TransIter<>(columnKeySet.iterator(), + c -> new SimpleEntry<>(c, getColumn(c))); } @Override public int size() { - return RowKeyTable.this.size(); + return columnKeySet.size(); } - }; + } + //endregion + + + //region columnKeySet + @Override + public Set columnKeySet() { + Set result = columnKeySet; + return (result == null) ? columnKeySet = new ColumnKeySet() : result; + } + + private Set columnKeySet; + + private class ColumnKeySet extends AbstractSet { - private final Set> cellSet = new AbstractSet>() { @Override - public boolean contains(Object o) { - if (o instanceof Cell) { - @SuppressWarnings("unchecked") final - Cell cell = (Cell) o; - Map row = getRow(cell.getRowKey()); - if (null != row) { - return ObjectUtil.equals(row.get(cell.getColumnKey()), cell.getValue()); + public Iterator iterator() { + return new ColumnKeyIterator(); + } + + @Override + public int size() { + return IterUtil.size(iterator()); + } + } + + private class ColumnKeyIterator extends ComputeIter { + final Map seen = columnBuilder.build(); + final Iterator> mapIterator = raw.values().iterator(); + Iterator> entryIterator = IterUtil.empty(); + + @Override + protected C computeNext() { + while (true) { + if (entryIterator.hasNext()) { + Map.Entry entry = entryIterator.next(); + if (false == seen.containsKey(entry.getKey())) { + seen.put(entry.getKey(), entry.getValue()); + return entry.getKey(); + } + } else if (mapIterator.hasNext()) { + entryIterator = mapIterator.next().entrySet().iterator(); + } else { + return null; } } - return false; + } + } + //endregion + + //region getColumn + + @Override + public Map getColumn(C columnKey) { + return new Column(columnKey); + } + + private class Column extends AbstractMap { + final C columnKey; + + Column(C columnKey) { + this.columnKey = columnKey; } @Override - public boolean remove(Object o) { - if (contains(o)) { - @SuppressWarnings("unchecked") - final Cell cell = (Cell) o; - RowKeyTable.this.remove(cell.getRowKey(), cell.getColumnKey()); + public Set> entrySet() { + return new EntrySet(); + } + + private class EntrySet extends AbstractSet> { + + @Override + public Iterator> iterator() { + return new EntrySetIterator(); + } + + @Override + public int size() { + int size = 0; + for (Map map : raw.values()) { + if (map.containsKey(columnKey)) { + size++; + } + } + return size; } - return false; } - @Override - public void clear() { - RowKeyTable.this.clear(); - } + private class EntrySetIterator extends ComputeIter> { + final Iterator>> iterator = raw.entrySet().iterator(); - @Override - public Iterator> iterator() { - return new CellIterator(); - } + @Override + protected Entry computeNext() { + while (iterator.hasNext()) { + final Entry> entry = iterator.next(); + if (entry.getValue().containsKey(columnKey)) { + return new AbsEntry() { + @Override + public R getKey() { + return entry.getKey(); + } - @Override - public int size() { - return RowKeyTable.this.size(); + @Override + public V getValue() { + return entry.getValue().get(columnKey); + } + + @Override + public V setValue(V value) { + return entry.getValue().put(columnKey, value); + } + }; + } + } + return null; + } } - }; + } + //endregion } diff --git a/hutool-core/src/main/java/cn/hutool/core/map/multi/Table.java b/hutool-core/src/main/java/cn/hutool/core/map/multi/Table.java index cd14029a5..e0fb1c5f4 100644 --- a/hutool-core/src/main/java/cn/hutool/core/map/multi/Table.java +++ b/hutool-core/src/main/java/cn/hutool/core/map/multi/Table.java @@ -170,7 +170,13 @@ public interface Table extends Iterable> { * * @param table 其他table */ - void putAll(Table table); + default void putAll(Table table){ + if (null != table) { + for (Table.Cell cell : table.cellSet()) { + put(cell.getRowKey(), cell.getColumnKey(), cell.getValue()); + } + } + } /** * 移除指定值 diff --git a/hutool-core/src/test/java/cn/hutool/core/map/RowKeyTableTest.java b/hutool-core/src/test/java/cn/hutool/core/map/RowKeyTableTest.java new file mode 100644 index 000000000..fdbaca398 --- /dev/null +++ b/hutool-core/src/test/java/cn/hutool/core/map/RowKeyTableTest.java @@ -0,0 +1,39 @@ +package cn.hutool.core.map; + +import cn.hutool.core.map.multi.RowKeyTable; +import cn.hutool.core.map.multi.Table; +import org.junit.Assert; +import org.junit.Test; + +import java.util.Map; + +public class RowKeyTableTest { + + @Test + public void putGetTest(){ + final Table table = new RowKeyTable<>(); + table.put(1, 2, 3); + table.put(1, 6, 4); + + Assert.assertEquals(new Integer(3), table.get(1, 2)); + Assert.assertNull(table.get(1, 3)); + + //判断row和column确定的二维点是否存在 + Assert.assertTrue(table.contains(1, 2)); + Assert.assertFalse(table.contains(1, 3)); + + //判断列 + Assert.assertTrue(table.containsColumn(2)); + Assert.assertFalse(table.containsColumn(3)); + + // 判断行 + Assert.assertTrue(table.containsRow(1)); + Assert.assertFalse(table.containsRow(2)); + + + // 获取列 + Map column = table.getColumn(6); + Assert.assertEquals(1, column.size()); + Assert.assertEquals(new Integer(4), column.get(1)); + } +} From 5eac491cd4427a15b39efed4eaa3f00a5a9c5cfc Mon Sep 17 00:00:00 2001 From: Looly Date: Tue, 8 Mar 2022 19:00:27 +0800 Subject: [PATCH 09/13] fix code --- .../java/cn/hutool/core/convert/NumberChineseFormatter.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/hutool-core/src/main/java/cn/hutool/core/convert/NumberChineseFormatter.java b/hutool-core/src/main/java/cn/hutool/core/convert/NumberChineseFormatter.java index d6da1d22b..2a315d38d 100644 --- a/hutool-core/src/main/java/cn/hutool/core/convert/NumberChineseFormatter.java +++ b/hutool-core/src/main/java/cn/hutool/core/convert/NumberChineseFormatter.java @@ -188,7 +188,7 @@ public class NumberChineseFormatter { Assert.checkBetween(amount, -999, 999, "Number support only: (-999 ~ 999)!"); final String chinese = thousandToChinese(amount, isUseTraditional); - if(amount < 20 && amount > 10){ + if(amount < 20 && amount >= 10){ // "十一"而非"一十一" return chinese.substring(1); } From 8a8242409748029b52c250e1c0951c0ff30ff423 Mon Sep 17 00:00:00 2001 From: Looly Date: Tue, 8 Mar 2022 19:21:07 +0800 Subject: [PATCH 10/13] fix convert bug --- CHANGELOG.md | 3 ++- .../core/convert/ConverterRegistry.java | 4 ++++ .../core/convert/impl/NumberConverter.java | 4 ++-- .../cn/hutool/core/convert/ConvertTest.java | 22 +++++++++++++++++++ 4 files changed, 30 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 74943728c..b7b36fb55 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,7 +2,7 @@ # 🚀Changelog ------------------------------------------------------------------------------------------------------------- -# 5.7.23 (2022-03-05) +# 5.7.23 (2022-03-08) ### 🐣新特性 * 【http 】 HttpRequest.form采用TableMap方式(issue#I4W427@Gitee) @@ -11,6 +11,7 @@ * 【crypto 】 增加XXTEA实现(issue#I4WH2X@Gitee) ### 🐞Bug修复 * 【core 】 修复ObjectUtil.hasNull传入null返回true的问题(pr#555@Gitee) +* 【core 】 修复NumberConverter对数字转换的问题(issue#I4WPF4@Gitee) ------------------------------------------------------------------------------------------------------------- # 5.7.22 (2022-03-01) diff --git a/hutool-core/src/main/java/cn/hutool/core/convert/ConverterRegistry.java b/hutool-core/src/main/java/cn/hutool/core/convert/ConverterRegistry.java index 96b19c52f..bbc1fd347 100644 --- a/hutool-core/src/main/java/cn/hutool/core/convert/ConverterRegistry.java +++ b/hutool-core/src/main/java/cn/hutool/core/convert/ConverterRegistry.java @@ -77,6 +77,8 @@ import java.util.concurrent.atomic.AtomicIntegerArray; import java.util.concurrent.atomic.AtomicLong; import java.util.concurrent.atomic.AtomicLongArray; import java.util.concurrent.atomic.AtomicReference; +import java.util.concurrent.atomic.DoubleAdder; +import java.util.concurrent.atomic.LongAdder; /** * 转换器登记中心 @@ -389,11 +391,13 @@ public class ConverterRegistry implements Serializable { defaultConverterMap.put(Integer.class, new NumberConverter(Integer.class)); defaultConverterMap.put(AtomicInteger.class, new NumberConverter(AtomicInteger.class));// since 3.0.8 defaultConverterMap.put(Long.class, new NumberConverter(Long.class)); + defaultConverterMap.put(LongAdder.class, new NumberConverter(LongAdder.class)); defaultConverterMap.put(AtomicLong.class, new NumberConverter(AtomicLong.class));// since 3.0.8 defaultConverterMap.put(Byte.class, new NumberConverter(Byte.class)); defaultConverterMap.put(Short.class, new NumberConverter(Short.class)); defaultConverterMap.put(Float.class, new NumberConverter(Float.class)); defaultConverterMap.put(Double.class, new NumberConverter(Double.class)); + defaultConverterMap.put(DoubleAdder.class, new NumberConverter(DoubleAdder.class)); defaultConverterMap.put(Character.class, new CharacterConverter()); defaultConverterMap.put(Boolean.class, new BooleanConverter()); defaultConverterMap.put(AtomicBoolean.class, new AtomicBooleanConverter());// since 3.0.8 diff --git a/hutool-core/src/main/java/cn/hutool/core/convert/impl/NumberConverter.java b/hutool-core/src/main/java/cn/hutool/core/convert/impl/NumberConverter.java index 36242cab9..421628c91 100644 --- a/hutool-core/src/main/java/cn/hutool/core/convert/impl/NumberConverter.java +++ b/hutool-core/src/main/java/cn/hutool/core/convert/impl/NumberConverter.java @@ -186,7 +186,7 @@ public class NumberConverter extends AbstractConverter { return StrUtil.isBlank(valueStr) ? null : NumberUtil.parseFloat(valueStr); } else if (Double.class == targetType) { if (value instanceof Number) { - return ((Number) value).doubleValue(); + return NumberUtil.toDouble((Number) value); } else if (value instanceof Boolean) { return BooleanUtil.toDoubleObj((Boolean) value); } @@ -194,7 +194,7 @@ public class NumberConverter extends AbstractConverter { return StrUtil.isBlank(valueStr) ? null : NumberUtil.parseDouble(valueStr); } else if (DoubleAdder.class == targetType) { //jdk8 新增 - final Number number = convert(value, Long.class, toStrFunc); + final Number number = convert(value, Double.class, toStrFunc); if (null != number) { final DoubleAdder doubleAdder = new DoubleAdder(); doubleAdder.add(number.doubleValue()); diff --git a/hutool-core/src/test/java/cn/hutool/core/convert/ConvertTest.java b/hutool-core/src/test/java/cn/hutool/core/convert/ConvertTest.java index 402140e68..18cd78b14 100644 --- a/hutool-core/src/test/java/cn/hutool/core/convert/ConvertTest.java +++ b/hutool-core/src/test/java/cn/hutool/core/convert/ConvertTest.java @@ -24,6 +24,7 @@ import java.util.Map; import java.util.Set; import java.util.concurrent.atomic.AtomicIntegerArray; import java.util.concurrent.atomic.AtomicLongArray; +import java.util.concurrent.atomic.DoubleAdder; /** * 类型转换工具单元测试 @@ -361,4 +362,25 @@ public class ConvertTest { final float f = Convert.toFloat(value); Assert.assertEquals(406.1F, f, 2); } + + @Test + public void floatToDoubleTest(){ + float a = 0.45f; + double b = Convert.toDouble(a); + Assert.assertEquals(a, b, 5); + } + + @Test + public void floatToDoubleAddrTest(){ + float a = 0.45f; + final DoubleAdder adder = Convert.convert(DoubleAdder.class, a); + Assert.assertEquals(a, adder.doubleValue(), 5); + } + + @Test + public void doubleToFloatTest(){ + double a = 0.45f; + float b = Convert.toFloat(a); + Assert.assertEquals(a, b, 5); + } } From 9229eea85f77432dca67c8703efc48fd2e4466d5 Mon Sep 17 00:00:00 2001 From: Looly Date: Tue, 8 Mar 2022 20:52:43 +0800 Subject: [PATCH 11/13] fix bug --- CHANGELOG.md | 1 + .../cn/hutool/core/util/ModifierUtil.java | 72 ++++++++++++++----- .../java/cn/hutool/core/util/ReflectUtil.java | 20 ++++-- .../core/lang/test/bean/ExamInfoDict.java | 50 ++----------- .../cn/hutool/core/util/ReflectUtilTest.java | 63 +++++++++++++++- 5 files changed, 134 insertions(+), 72 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index dd037ca02..3fd0cf5e6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,6 +14,7 @@ ### 🐞Bug修复 * 【core 】 修复ObjectUtil.hasNull传入null返回true的问题(pr#555@Gitee) * 【core 】 修复NumberConverter对数字转换的问题(issue#I4WPF4@Gitee) +* 【core 】 修复ReflectUtil.getMethods获取接口方法问题(issue#I4WUWR@Gitee) ------------------------------------------------------------------------------------------------------------- # 5.7.22 (2022-03-01) diff --git a/hutool-core/src/main/java/cn/hutool/core/util/ModifierUtil.java b/hutool-core/src/main/java/cn/hutool/core/util/ModifierUtil.java index f2b3f389f..dec42bed0 100644 --- a/hutool-core/src/main/java/cn/hutool/core/util/ModifierUtil.java +++ b/hutool-core/src/main/java/cn/hutool/core/util/ModifierUtil.java @@ -20,35 +20,60 @@ public class ModifierUtil { * @since 4.0.5 */ public enum ModifierType { - /** public修饰符,所有类都能访问 */ + /** + * public修饰符,所有类都能访问 + */ PUBLIC(Modifier.PUBLIC), - /** private修饰符,只能被自己访问和修改 */ + /** + * private修饰符,只能被自己访问和修改 + */ PRIVATE(Modifier.PRIVATE), - /** protected修饰符,自身、子类及同一个包中类可以访问 */ + /** + * protected修饰符,自身、子类及同一个包中类可以访问 + */ PROTECTED(Modifier.PROTECTED), - /** static修饰符,(静态修饰符)指定变量被所有对象共享,即所有实例都可以使用该变量。变量属于这个类 */ + /** + * static修饰符,(静态修饰符)指定变量被所有对象共享,即所有实例都可以使用该变量。变量属于这个类 + */ STATIC(Modifier.STATIC), - /** final修饰符,最终修饰符,指定此变量的值不能变,使用在方法上表示不能被重载 */ + /** + * final修饰符,最终修饰符,指定此变量的值不能变,使用在方法上表示不能被重载 + */ FINAL(Modifier.FINAL), - /** synchronized,同步修饰符,在多个线程中,该修饰符用于在运行前,对他所属的方法加锁,以防止其他线程的访问,运行结束后解锁。 */ + /** + * synchronized,同步修饰符,在多个线程中,该修饰符用于在运行前,对他所属的方法加锁,以防止其他线程的访问,运行结束后解锁。 + */ SYNCHRONIZED(Modifier.SYNCHRONIZED), - /** (易失修饰符)指定该变量可以同时被几个线程控制和修改 */ + /** + * (易失修饰符)指定该变量可以同时被几个线程控制和修改 + */ VOLATILE(Modifier.VOLATILE), - /** (过度修饰符)指定该变量是系统保留,暂无特别作用的临时性变量,序列化时忽略 */ + /** + * (过度修饰符)指定该变量是系统保留,暂无特别作用的临时性变量,序列化时忽略 + */ TRANSIENT(Modifier.TRANSIENT), - /** native,本地修饰符。指定此方法的方法体是用其他语言在程序外部编写的。 */ + /** + * native,本地修饰符。指定此方法的方法体是用其他语言在程序外部编写的。 + */ NATIVE(Modifier.NATIVE), - /** abstract,将一个类声明为抽象类,没有实现的方法,需要子类提供方法实现。 */ + /** + * abstract,将一个类声明为抽象类,没有实现的方法,需要子类提供方法实现。 + */ ABSTRACT(Modifier.ABSTRACT), - /** strictfp,一旦使用了关键字strictfp来声明某个类、接口或者方法时,那么在这个关键字所声明的范围内所有浮点运算都是精确的,符合IEEE-754规范的。 */ + /** + * strictfp,一旦使用了关键字strictfp来声明某个类、接口或者方法时,那么在这个关键字所声明的范围内所有浮点运算都是精确的,符合IEEE-754规范的。 + */ STRICT(Modifier.STRICT); - /** 修饰符枚举对应的int修饰符值 */ + /** + * 修饰符枚举对应的int修饰符值 + */ private final int value; /** * 构造 + * * @param modifier 修饰符int表示,见{@link Modifier} */ ModifierType(int modifier) { @@ -57,6 +82,7 @@ public class ModifierUtil { /** * 获取修饰符枚举对应的int修饰符值,值见{@link Modifier} + * * @return 修饰符枚举对应的int修饰符值 */ public int getValue() { @@ -67,7 +93,7 @@ public class ModifierUtil { /** * 是否同时存在一个或多个修饰符(可能有多个修饰符,如果有指定的修饰符则返回true) * - * @param clazz 类 + * @param clazz 类 * @param modifierTypes 修饰符枚举 * @return 是否有指定修饰符,如果有返回true,否则false,如果提供参数为null返回false */ @@ -81,7 +107,7 @@ public class ModifierUtil { /** * 是否同时存在一个或多个修饰符(可能有多个修饰符,如果有指定的修饰符则返回true) * - * @param constructor 构造方法 + * @param constructor 构造方法 * @param modifierTypes 修饰符枚举 * @return 是否有指定修饰符,如果有返回true,否则false,如果提供参数为null返回false */ @@ -95,7 +121,7 @@ public class ModifierUtil { /** * 是否同时存在一个或多个修饰符(可能有多个修饰符,如果有指定的修饰符则返回true) * - * @param method 方法 + * @param method 方法 * @param modifierTypes 修饰符枚举 * @return 是否有指定修饰符,如果有返回true,否则false,如果提供参数为null返回false */ @@ -109,7 +135,7 @@ public class ModifierUtil { /** * 是否同时存在一个或多个修饰符(可能有多个修饰符,如果有指定的修饰符则返回true) * - * @param field 字段 + * @param field 字段 * @param modifierTypes 修饰符枚举 * @return 是否有指定修饰符,如果有返回true,否则false,如果提供参数为null返回false */ @@ -226,15 +252,27 @@ public class ModifierUtil { return clazz.isSynthetic(); } + /** + * 是否抽象方法 + * + * @param method 方法 + * @return 是否抽象方法 + * @since 5.7.23 + */ + public static boolean isAbstract(Method method) { + return hasModifier(method, ModifierType.ABSTRACT); + } //-------------------------------------------------------------------------------------------------------- Private method start + /** * 多个修饰符做“与”操作,表示同时存在多个修饰符 + * * @param modifierTypes 修饰符列表,元素不能为空 * @return “与”之后的修饰符 */ private static int modifiersToInt(ModifierType... modifierTypes) { int modifier = modifierTypes[0].getValue(); - for(int i = 1; i < modifierTypes.length; i++) { + for (int i = 1; i < modifierTypes.length; i++) { modifier |= modifierTypes[i].getValue(); } return modifier; diff --git a/hutool-core/src/main/java/cn/hutool/core/util/ReflectUtil.java b/hutool-core/src/main/java/cn/hutool/core/util/ReflectUtil.java index de406bfbf..4e4a78faf 100644 --- a/hutool-core/src/main/java/cn/hutool/core/util/ReflectUtil.java +++ b/hutool-core/src/main/java/cn/hutool/core/util/ReflectUtil.java @@ -652,30 +652,36 @@ public class ReflectUtil { } /** - * 获得一个类中所有方法列表,直接反射获取,无缓存 + * 获得一个类中所有方法列表,直接反射获取,无缓存
+ * 接口获取方法和默认方法 * - * @param beanClass 类 - * @param withSuperClassMethods 是否包括父类的方法列表 + * @param beanClass 类或接口 + * @param withSupers 是否包括父类或接口的方法列表 * @return 方法列表 * @throws SecurityException 安全检查异常 */ - public static Method[] getMethodsDirectly(Class beanClass, boolean withSuperClassMethods) throws SecurityException { + public static Method[] getMethodsDirectly(Class beanClass, boolean withSupers) throws SecurityException { Assert.notNull(beanClass); + if(beanClass.isInterface()){ + // 对于接口,直接调用Class.getMethods方法获取所有方法,因为接口都是public方法 + return withSupers ? beanClass.getMethods() : beanClass.getDeclaredMethods(); + } + Method[] allMethods = null; Class searchType = beanClass; Method[] declaredMethods; - while (searchType != null) { + while (searchType != null && searchType != Object.class) { declaredMethods = searchType.getDeclaredMethods(); if (null == allMethods) { allMethods = declaredMethods; } else { allMethods = ArrayUtil.append(allMethods, declaredMethods); } - searchType = withSuperClassMethods ? searchType.getSuperclass() : null; + searchType = (withSupers && false == searchType.isInterface()) ? searchType.getSuperclass() : null; } - return allMethods; + return ArrayUtil.append(allMethods); } /** diff --git a/hutool-core/src/test/java/cn/hutool/core/lang/test/bean/ExamInfoDict.java b/hutool-core/src/test/java/cn/hutool/core/lang/test/bean/ExamInfoDict.java index a39e91238..b5068f2d8 100644 --- a/hutool-core/src/test/java/cn/hutool/core/lang/test/bean/ExamInfoDict.java +++ b/hutool-core/src/test/java/cn/hutool/core/lang/test/bean/ExamInfoDict.java @@ -1,16 +1,18 @@ package cn.hutool.core.lang.test.bean; +import lombok.Data; + import java.io.Serializable; -import java.util.Objects; /** - * + * * @author 质量过关 * */ +@Data public class ExamInfoDict implements Serializable { private static final long serialVersionUID = 3640936499125004525L; - + // 主键 private Integer id; // 可当作题号 // 试题类型 客观题 0主观题 1 @@ -18,49 +20,7 @@ public class ExamInfoDict implements Serializable { // 试题是否作答 private Integer answerIs; - public Integer getId() { - return id; - } public Integer getId(Integer defaultValue) { return this.id == null ? defaultValue : this.id; } - public void setId(Integer id) { - this.id = id; - } - - public Integer getExamType() { - return examType; - } - public void setExamType(Integer examType) { - this.examType = examType; - } - - public Integer getAnswerIs() { - return answerIs; - } - public void setAnswerIs(Integer answerIs) { - this.answerIs = answerIs; - } - - @Override - public boolean equals(Object o) { - if (this == o) { - return true; - } - if (o == null || getClass() != o.getClass()) { - return false; - } - ExamInfoDict that = (ExamInfoDict) o; - return Objects.equals(id, that.id) && Objects.equals(examType, that.examType) && Objects.equals(answerIs, that.answerIs); - } - - @Override - public int hashCode() { - return Objects.hash(id, examType, answerIs); - } - - @Override - public String toString() { - return "ExamInfoDict{" + "id=" + id + ", examType=" + examType + ", answerIs=" + answerIs + '}'; - } } diff --git a/hutool-core/src/test/java/cn/hutool/core/util/ReflectUtilTest.java b/hutool-core/src/test/java/cn/hutool/core/util/ReflectUtilTest.java index 9a90c8fe3..bb9dca5f3 100644 --- a/hutool-core/src/test/java/cn/hutool/core/util/ReflectUtilTest.java +++ b/hutool-core/src/test/java/cn/hutool/core/util/ReflectUtilTest.java @@ -116,7 +116,7 @@ public class ReflectUtilTest { @Ignore public void getMethodBenchTest(){ // 预热 - getMethod(TestBenchClass.class, false, "getH"); + getMethodWithReturnTypeCheck(TestBenchClass.class, false, "getH"); final TimeInterval timer = DateUtil.timer(); timer.start(); @@ -127,7 +127,7 @@ public class ReflectUtilTest { timer.restart(); for (int i = 0; i < 100000000; i++) { - getMethod(TestBenchClass.class, false, "getH"); + getMethodWithReturnTypeCheck(TestBenchClass.class, false, "getH"); } Console.log(timer.interval()); } @@ -150,7 +150,7 @@ public class ReflectUtilTest { private String n; } - public static Method getMethod(Class clazz, boolean ignoreCase, String methodName, Class... paramTypes) throws SecurityException { + public static Method getMethodWithReturnTypeCheck(Class clazz, boolean ignoreCase, String methodName, Class... paramTypes) throws SecurityException { if (null == clazz || StrUtil.isBlank(methodName)) { return null; } @@ -169,4 +169,61 @@ public class ReflectUtilTest { } return res; } + + @Test + public void getMethodsFromClassExtends(){ + // 继承情况下,需解决方法去重问题 + final Method[] methods = ReflectUtil.getMethods(C2.class); + Assert.assertEquals(2, methods.length); + } + + @Test + public void getMethodsFromInterfaceTest(){ + // 对于接口,直接调用Class.getMethods方法获取所有方法,因为接口都是public方法 + // 因此此处得到包括TestInterface1、TestInterface2、TestInterface3中一共4个方法 + final Method[] methods = ReflectUtil.getMethods(TestInterface3.class); + Assert.assertEquals(4, methods.length); + + // 接口里,调用getMethods和getPublicMethods效果相同 + final Method[] publicMethods = ReflectUtil.getPublicMethods(TestInterface3.class); + Assert.assertArrayEquals(methods, publicMethods); + } + + interface TestInterface1{ + void getA(); + void getB(); + + default void getC(){ + + } + } + + interface TestInterface2 extends TestInterface1{ + @Override + void getB(); + } + + interface TestInterface3 extends TestInterface2{ + void get3(); + } + + class C1 implements TestInterface2{ + + @Override + public void getA() { + + } + + @Override + public void getB() { + + } + } + + class C2 extends C1{ + @Override + public void getA() { + + } + } } From 56d490e2ac5ef147acfacafde9a63df4e997669d Mon Sep 17 00:00:00 2001 From: Looly Date: Tue, 8 Mar 2022 22:50:58 +0800 Subject: [PATCH 12/13] fix doc --- .../main/java/cn/hutool/core/lang/ObjectId.java | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/hutool-core/src/main/java/cn/hutool/core/lang/ObjectId.java b/hutool-core/src/main/java/cn/hutool/core/lang/ObjectId.java index e4a8c06fa..d8c7c6955 100644 --- a/hutool-core/src/main/java/cn/hutool/core/lang/ObjectId.java +++ b/hutool-core/src/main/java/cn/hutool/core/lang/ObjectId.java @@ -22,6 +22,21 @@ import java.util.concurrent.atomic.AtomicInteger; * 4. INC 自增计数器。确保同一秒内产生objectId的唯一性。 * * + * + * + * + * + * + * + * + * + * + * + * + * + * + *
时间戳机器ID进程ID自增计数器
4323
+ * * 参考:http://blog.csdn.net/qxc1281/article/details/54021882 * * @author looly From 8f0f3354e3f78fea471f72d24dd1b28d0ff77245 Mon Sep 17 00:00:00 2001 From: Looly Date: Wed, 9 Mar 2022 00:58:10 +0800 Subject: [PATCH 13/13] add UniqueKeySet --- CHANGELOG.md | 3 +- .../hutool/core/collection/UniqueKeySet.java | 152 ++++++++++++++++++ .../java/cn/hutool/core/util/ReflectUtil.java | 89 +++++++--- .../core/collection/UniqueKeySetTest.java | 34 ++++ .../cn/hutool/core/util/ReflectUtilTest.java | 39 +++-- 5 files changed, 285 insertions(+), 32 deletions(-) create mode 100644 hutool-core/src/main/java/cn/hutool/core/collection/UniqueKeySet.java create mode 100644 hutool-core/src/test/java/cn/hutool/core/collection/UniqueKeySetTest.java diff --git a/CHANGELOG.md b/CHANGELOG.md index 3fd0cf5e6..dfefa113b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,7 +2,7 @@ # 🚀Changelog ------------------------------------------------------------------------------------------------------------- -# 5.7.23 (2022-03-08) +# 5.7.23 (2022-03-09) ### 🐣新特性 * 【http 】 HttpRequest.form采用TableMap方式(issue#I4W427@Gitee) @@ -10,6 +10,7 @@ * 【core 】 FileUtil.extName增加对tar.gz特殊处理(issue#I4W5FS@Gitee) * 【crypto 】 增加XXTEA实现(issue#I4WH2X@Gitee) * 【core 】 增加Table实现(issue#2179@Github) +* 【core 】 增加UniqueKeySet(issue#I4WUWR@Gitee) * ### 🐞Bug修复 * 【core 】 修复ObjectUtil.hasNull传入null返回true的问题(pr#555@Gitee) diff --git a/hutool-core/src/main/java/cn/hutool/core/collection/UniqueKeySet.java b/hutool-core/src/main/java/cn/hutool/core/collection/UniqueKeySet.java new file mode 100644 index 000000000..f4fbcb15c --- /dev/null +++ b/hutool-core/src/main/java/cn/hutool/core/collection/UniqueKeySet.java @@ -0,0 +1,152 @@ +package cn.hutool.core.collection; + +import cn.hutool.core.map.MapBuilder; +import cn.hutool.core.util.ObjectUtil; + +import java.io.Serializable; +import java.util.AbstractSet; +import java.util.Collection; +import java.util.HashMap; +import java.util.Iterator; +import java.util.Map; +import java.util.function.Function; + +/** + * 唯一键的Set
+ * 通过自定义唯一键,通过{@link #uniqueGenerator}生成节点对象对应的键作为Map的key,确定唯一
+ * 此Set与HashSet不同的是,HashSet依赖于{@link Object#equals(Object)}确定唯一
+ * 但是很多时候我们无法对对象进行修改,此时在外部定义一个唯一规则,即可完成去重。 + *
+ * {@code Set set = new UniqueKeySet<>(UniqueTestBean::getId);}
+ * 
+ * + * @param 唯一键类型 + * @param 值对象 + * @author looly + * @since 5.7.23 + */ +public class UniqueKeySet extends AbstractSet implements Serializable { + private static final long serialVersionUID = 1L; + + private Map map; + private final Function uniqueGenerator; + + //region 构造 + + /** + * 构造 + * + * @param uniqueGenerator 唯一键生成规则函数,用于生成对象对应的唯一键 + */ + public UniqueKeySet(Function uniqueGenerator) { + this(false, uniqueGenerator); + } + + /** + * 构造 + * + * @param isLinked 是否保持加入顺序 + * @param uniqueGenerator 唯一键生成规则函数,用于生成对象对应的唯一键 + */ + public UniqueKeySet(boolean isLinked, Function uniqueGenerator) { + this(MapBuilder.create(isLinked), uniqueGenerator); + } + + /** + * 构造 + * + * @param initialCapacity 初始容量 + * @param loadFactor 增长因子 + * @param uniqueGenerator 唯一键生成规则函数,用于生成对象对应的唯一键 + */ + public UniqueKeySet(int initialCapacity, float loadFactor, Function uniqueGenerator) { + this(MapBuilder.create(new HashMap<>(initialCapacity, loadFactor)), uniqueGenerator); + } + + /** + * 构造 + * + * @param builder 初始Map,定义了Map类型 + * @param uniqueGenerator 唯一键生成规则函数,用于生成对象对应的唯一键 + */ + public UniqueKeySet(MapBuilder builder, Function uniqueGenerator) { + this.map = builder.build(); + this.uniqueGenerator = uniqueGenerator; + } + //endregion + + @Override + public Iterator iterator() { + return map.values().iterator(); + } + + @Override + public int size() { + return map.size(); + } + + @Override + public boolean isEmpty() { + return map.isEmpty(); + } + + @Override + public boolean contains(Object o) { + //noinspection unchecked + return map.containsKey(this.uniqueGenerator.apply((V) o)); + } + + @Override + public boolean add(V v) { + return null == map.put(this.uniqueGenerator.apply(v), v); + } + + /** + * 加入值,如果值已经存在,则忽略之 + * + * @param v 值 + * @return 是否成功加入 + */ + public boolean addIfAbsent(V v) { + return null == map.putIfAbsent(this.uniqueGenerator.apply(v), v); + } + + /** + * 加入集合中所有的值,如果值已经存在,则忽略之 + * + * @param c 集合 + * @return 是否有一个或多个被加入成功 + */ + public boolean addAllIfAbsent(Collection c) { + boolean modified = false; + for (V v : c) + if (addIfAbsent(v)) { + modified = true; + } + return modified; + } + + @Override + public boolean remove(Object o) { + //noinspection unchecked + return null != map.remove(this.uniqueGenerator.apply((V) o)); + } + + @Override + public void clear() { + map.clear(); + } + + @Override + @SuppressWarnings("unchecked") + public UniqueKeySet clone() { + try { + UniqueKeySet newSet = (UniqueKeySet) super.clone(); + newSet.map = ObjectUtil.clone(this.map); + return newSet; + } catch (CloneNotSupportedException e) { + throw new InternalError(e); + } + } + +} diff --git a/hutool-core/src/main/java/cn/hutool/core/util/ReflectUtil.java b/hutool-core/src/main/java/cn/hutool/core/util/ReflectUtil.java index 4e4a78faf..7b746bc6e 100644 --- a/hutool-core/src/main/java/cn/hutool/core/util/ReflectUtil.java +++ b/hutool-core/src/main/java/cn/hutool/core/util/ReflectUtil.java @@ -3,6 +3,7 @@ package cn.hutool.core.util; import cn.hutool.core.annotation.Alias; import cn.hutool.core.bean.NullWrapperBean; import cn.hutool.core.collection.CollUtil; +import cn.hutool.core.collection.UniqueKeySet; import cn.hutool.core.convert.Convert; import cn.hutool.core.exceptions.UtilException; import cn.hutool.core.lang.Assert; @@ -17,6 +18,7 @@ import java.lang.reflect.Field; import java.lang.reflect.Method; import java.util.AbstractMap; import java.util.ArrayList; +import java.util.Arrays; import java.util.HashMap; import java.util.HashSet; import java.util.List; @@ -347,11 +349,12 @@ public class ReflectUtil { /** * 是否为父类引用字段
* 当字段所在类是对象子类时(对象中定义的非static的class),会自动生成一个以"this$0"为名称的字段,指向父类对象 + * * @param field 字段 * @return 是否为父类引用字段 * @since 5.7.20 */ - public static boolean isOuterClassField(Field field){ + public static boolean isOuterClassField(Field field) { return "this$0".equals(field.getName()); } @@ -648,40 +651,47 @@ public class ReflectUtil { */ public static Method[] getMethods(Class beanClass) throws SecurityException { Assert.notNull(beanClass); - return METHODS_CACHE.get(beanClass, () -> getMethodsDirectly(beanClass, true)); + return METHODS_CACHE.get(beanClass, + () -> getMethodsDirectly(beanClass, true, true)); } /** * 获得一个类中所有方法列表,直接反射获取,无缓存
- * 接口获取方法和默认方法 + * 接口获取方法和默认方法,获取的方法包括: + *
    + *
  • 本类中的所有方法(包括static方法)
  • + *
  • 父类中的所有方法(包括static方法)
  • + *
  • Object中(包括static方法)
  • + *
* - * @param beanClass 类或接口 - * @param withSupers 是否包括父类或接口的方法列表 + * @param beanClass 类或接口 + * @param withSupers 是否包括父类或接口的方法列表 + * @param withMethodFromObject 是否包括Object中的方法 * @return 方法列表 * @throws SecurityException 安全检查异常 */ - public static Method[] getMethodsDirectly(Class beanClass, boolean withSupers) throws SecurityException { + public static Method[] getMethodsDirectly(Class beanClass, boolean withSupers, boolean withMethodFromObject) throws SecurityException { Assert.notNull(beanClass); - if(beanClass.isInterface()){ + if (beanClass.isInterface()) { // 对于接口,直接调用Class.getMethods方法获取所有方法,因为接口都是public方法 return withSupers ? beanClass.getMethods() : beanClass.getDeclaredMethods(); } - Method[] allMethods = null; + final UniqueKeySet result = new UniqueKeySet<>(true, ReflectUtil::getUniqueKey); Class searchType = beanClass; - Method[] declaredMethods; - while (searchType != null && searchType != Object.class) { - declaredMethods = searchType.getDeclaredMethods(); - if (null == allMethods) { - allMethods = declaredMethods; - } else { - allMethods = ArrayUtil.append(allMethods, declaredMethods); + while (searchType != null) { + if (false == withMethodFromObject && Object.class == searchType) { + break; } + result.addAllIfAbsent(Arrays.asList(searchType.getDeclaredMethods())); + result.addAllIfAbsent(getDefaultMethodsFromInterface(searchType)); + + searchType = (withSupers && false == searchType.isInterface()) ? searchType.getSuperclass() : null; } - return ArrayUtil.append(allMethods); + return result.toArray(new Method[0]); } /** @@ -777,10 +787,10 @@ public class ReflectUtil { String name = method.getName(); // 跳过getClass这个特殊方法 - if("getClass".equals(name)){ + if ("getClass".equals(name)) { return false; } - if(ignoreCase){ + if (ignoreCase) { name = name.toLowerCase(); } switch (parameterCount) { @@ -1044,4 +1054,47 @@ public class ReflectUtil { } return accessibleObject; } + + /** + * 获取方法的唯一键,结构为: + *
+	 *     返回类型#方法名:参数1类型,参数2类型...
+	 * 
+ * + * @param method 方法 + * @return 方法唯一键 + */ + private static String getUniqueKey(Method method) { + final StringBuilder sb = new StringBuilder(); + sb.append(method.getReturnType().getName()).append('#'); + sb.append(method.getName()); + Class[] parameters = method.getParameterTypes(); + for (int i = 0; i < parameters.length; i++) { + if (i == 0) { + sb.append(':'); + } else { + sb.append(','); + } + sb.append(parameters[i].getName()); + } + return sb.toString(); + } + + /** + * 获取类对应接口中的非抽象方法(default方法) + * + * @param clazz 类 + * @return 方法列表 + */ + private static List getDefaultMethodsFromInterface(Class clazz) { + List result = new ArrayList<>(); + for (Class ifc : clazz.getInterfaces()) { + for (Method m : ifc.getMethods()) { + if (false == ModifierUtil.isAbstract(m)) { + result.add(m); + } + } + } + return result; + } } diff --git a/hutool-core/src/test/java/cn/hutool/core/collection/UniqueKeySetTest.java b/hutool-core/src/test/java/cn/hutool/core/collection/UniqueKeySetTest.java new file mode 100644 index 000000000..9dd45058f --- /dev/null +++ b/hutool-core/src/test/java/cn/hutool/core/collection/UniqueKeySetTest.java @@ -0,0 +1,34 @@ +package cn.hutool.core.collection; + +import cn.hutool.core.lang.Console; +import lombok.AllArgsConstructor; +import lombok.Data; +import org.junit.Assert; +import org.junit.Test; + +import java.util.Set; + +public class UniqueKeySetTest { + + @Test + public void addTest(){ + Set set = new UniqueKeySet<>(UniqueTestBean::getId); + set.add(new UniqueTestBean("id1", "张三", "地球")); + set.add(new UniqueTestBean("id2", "李四", "火星")); + // id重复,替换之前的元素 + set.add(new UniqueTestBean("id2", "王五", "木星")); + + // 后两个ID重复 + Assert.assertEquals(2, set.size()); + + set.forEach(Console::log); + } + + @Data + @AllArgsConstructor + static class UniqueTestBean{ + private String id; + private String name; + private String address; + } +} diff --git a/hutool-core/src/test/java/cn/hutool/core/util/ReflectUtilTest.java b/hutool-core/src/test/java/cn/hutool/core/util/ReflectUtilTest.java index bb9dca5f3..6affbabb6 100644 --- a/hutool-core/src/test/java/cn/hutool/core/util/ReflectUtilTest.java +++ b/hutool-core/src/test/java/cn/hutool/core/util/ReflectUtilTest.java @@ -23,7 +23,7 @@ public class ReflectUtilTest { @Test public void getMethodsTest() { Method[] methods = ReflectUtil.getMethods(ExamInfoDict.class); - Assert.assertEquals(22, methods.length); + Assert.assertEquals(20, methods.length); //过滤器测试 methods = ReflectUtil.getMethods(ExamInfoDict.class, t -> Integer.class.equals(t.getReturnType())); @@ -35,7 +35,7 @@ public class ReflectUtilTest { //null过滤器测试 methods = ReflectUtil.getMethods(ExamInfoDict.class, null); - Assert.assertEquals(22, methods.length); + Assert.assertEquals(20, methods.length); final Method method2 = methods[0]; Assert.assertNotNull(method2); } @@ -114,7 +114,7 @@ public class ReflectUtilTest { @Test @Ignore - public void getMethodBenchTest(){ + public void getMethodBenchTest() { // 预热 getMethodWithReturnTypeCheck(TestBenchClass.class, false, "getH"); @@ -171,14 +171,26 @@ public class ReflectUtilTest { } @Test - public void getMethodsFromClassExtends(){ + public void getMethodsFromClassExtends() { // 继承情况下,需解决方法去重问题 - final Method[] methods = ReflectUtil.getMethods(C2.class); - Assert.assertEquals(2, methods.length); + Method[] methods = ReflectUtil.getMethods(C2.class); + Assert.assertEquals(15, methods.length); + + // 排除Object中的方法 + // 3个方法包括类 + methods = ReflectUtil.getMethodsDirectly(C2.class, true, false); + Assert.assertEquals(3, methods.length); + + // getA属于本类 + Assert.assertEquals("public void cn.hutool.core.util.ReflectUtilTest$C2.getA()", methods[0].toString()); + // getB属于父类 + Assert.assertEquals("public void cn.hutool.core.util.ReflectUtilTest$C1.getB()", methods[1].toString()); + // getC属于接口中的默认方法 + Assert.assertEquals("public default void cn.hutool.core.util.ReflectUtilTest$TestInterface1.getC()", methods[2].toString()); } @Test - public void getMethodsFromInterfaceTest(){ + public void getMethodsFromInterfaceTest() { // 对于接口,直接调用Class.getMethods方法获取所有方法,因为接口都是public方法 // 因此此处得到包括TestInterface1、TestInterface2、TestInterface3中一共4个方法 final Method[] methods = ReflectUtil.getMethods(TestInterface3.class); @@ -189,25 +201,26 @@ public class ReflectUtilTest { Assert.assertArrayEquals(methods, publicMethods); } - interface TestInterface1{ + interface TestInterface1 { void getA(); + void getB(); - default void getC(){ + default void getC() { } } - interface TestInterface2 extends TestInterface1{ + interface TestInterface2 extends TestInterface1 { @Override void getB(); } - interface TestInterface3 extends TestInterface2{ + interface TestInterface3 extends TestInterface2 { void get3(); } - class C1 implements TestInterface2{ + class C1 implements TestInterface2 { @Override public void getA() { @@ -220,7 +233,7 @@ public class ReflectUtilTest { } } - class C2 extends C1{ + class C2 extends C1 { @Override public void getA() {