lock, Lock target) {
+ for (int i = 0; i < lock.size(); i++) {
+ if (lock.getAt(i) == target) {
+ return i;
+ }
+ }
+ return -1;
+ }
+}
diff --git a/hutool-core/src/test/java/cn/hutool/core/util/NumberUtilTest.java b/hutool-core/src/test/java/cn/hutool/core/util/NumberUtilTest.java
index 08c27f841..145ad5bb6 100644
--- a/hutool-core/src/test/java/cn/hutool/core/util/NumberUtilTest.java
+++ b/hutool-core/src/test/java/cn/hutool/core/util/NumberUtilTest.java
@@ -144,43 +144,43 @@ public class NumberUtilTest {
@Test
public void roundStrTest() {
final String roundStr = NumberUtil.roundStr(2.647, 2);
- assertEquals(roundStr, "2.65");
+ assertEquals("2.65", roundStr);
final String roundStr1 = NumberUtil.roundStr(0, 10);
- assertEquals(roundStr1, "0.0000000000");
+ assertEquals("0.0000000000", roundStr1);
}
@Test
public void roundHalfEvenTest() {
String roundStr = NumberUtil.roundHalfEven(4.245, 2).toString();
- assertEquals(roundStr, "4.24");
+ assertEquals("4.24", roundStr);
roundStr = NumberUtil.roundHalfEven(4.2450, 2).toString();
- assertEquals(roundStr, "4.24");
+ assertEquals("4.24", roundStr);
roundStr = NumberUtil.roundHalfEven(4.2451, 2).toString();
- assertEquals(roundStr, "4.25");
+ assertEquals("4.25", roundStr);
roundStr = NumberUtil.roundHalfEven(4.2250, 2).toString();
- assertEquals(roundStr, "4.22");
+ assertEquals("4.22", roundStr);
roundStr = NumberUtil.roundHalfEven(1.2050, 2).toString();
- assertEquals(roundStr, "1.20");
+ assertEquals("1.20", roundStr);
roundStr = NumberUtil.roundHalfEven(1.2150, 2).toString();
- assertEquals(roundStr, "1.22");
+ assertEquals("1.22", roundStr);
roundStr = NumberUtil.roundHalfEven(1.2250, 2).toString();
- assertEquals(roundStr, "1.22");
+ assertEquals("1.22", roundStr);
roundStr = NumberUtil.roundHalfEven(1.2350, 2).toString();
- assertEquals(roundStr, "1.24");
+ assertEquals("1.24", roundStr);
roundStr = NumberUtil.roundHalfEven(1.2450, 2).toString();
- assertEquals(roundStr, "1.24");
+ assertEquals("1.24", roundStr);
roundStr = NumberUtil.roundHalfEven(1.2550, 2).toString();
- assertEquals(roundStr, "1.26");
+ assertEquals("1.26", roundStr);
roundStr = NumberUtil.roundHalfEven(1.2650, 2).toString();
- assertEquals(roundStr, "1.26");
+ assertEquals("1.26", roundStr);
roundStr = NumberUtil.roundHalfEven(1.2750, 2).toString();
- assertEquals(roundStr, "1.28");
+ assertEquals("1.28", roundStr);
roundStr = NumberUtil.roundHalfEven(1.2850, 2).toString();
- assertEquals(roundStr, "1.28");
+ assertEquals("1.28", roundStr);
roundStr = NumberUtil.roundHalfEven(1.2950, 2).toString();
- assertEquals(roundStr, "1.30");
+ assertEquals("1.30", roundStr);
}
@Test
@@ -673,4 +673,10 @@ public class NumberUtilTest {
final double result = NumberUtil.add(v1, v2);
assertEquals(91007279.3545, result, 0);
}
+
+ @Test
+ void issueIC1MXETest(){
+ final boolean equals = NumberUtil.equals(104557543L, 104557544);
+ assertFalse(equals);
+ }
}
diff --git a/hutool-cron/pom.xml b/hutool-cron/pom.xml
index bc8617ed0..62638e402 100755
--- a/hutool-cron/pom.xml
+++ b/hutool-cron/pom.xml
@@ -9,7 +9,7 @@
cn.hutool
hutool-parent
- 5.8.37
+ 5.8.38-SNAPSHOT
hutool-cron
diff --git a/hutool-crypto/pom.xml b/hutool-crypto/pom.xml
index 0fa6e0032..375b8a275 100755
--- a/hutool-crypto/pom.xml
+++ b/hutool-crypto/pom.xml
@@ -9,7 +9,7 @@
cn.hutool
hutool-parent
- 5.8.37
+ 5.8.38-SNAPSHOT
hutool-crypto
diff --git a/hutool-crypto/src/main/java/cn/hutool/crypto/digest/Argon2.java b/hutool-crypto/src/main/java/cn/hutool/crypto/digest/Argon2.java
new file mode 100644
index 000000000..f5535664d
--- /dev/null
+++ b/hutool-crypto/src/main/java/cn/hutool/crypto/digest/Argon2.java
@@ -0,0 +1,152 @@
+package cn.hutool.crypto.digest;
+
+import org.bouncycastle.crypto.generators.Argon2BytesGenerator;
+import org.bouncycastle.crypto.params.Argon2Parameters;
+
+/**
+ * Argon2加密实现
+ *
+ * @author changhr2013
+ * @author Looly
+ * @since 5.8.38
+ */
+public class Argon2 {
+
+ /**
+ * 默认hash长度
+ */
+ public static final int DEFAULT_HASH_LENGTH = 32;
+
+ private int hashLength = DEFAULT_HASH_LENGTH;
+ private final Argon2Parameters.Builder paramsBuilder;
+
+ /**
+ * 构造,默认使用{@link Argon2Parameters#ARGON2_id}类型
+ */
+ public Argon2(){
+ this(Argon2Parameters.ARGON2_id);
+ }
+
+ /**
+ * 构造
+ *
+ * @param type {@link Argon2Parameters#ARGON2_d}、{@link Argon2Parameters#ARGON2_i}、{@link Argon2Parameters#ARGON2_id}
+ */
+ public Argon2(int type){
+ this(new Argon2Parameters.Builder(type));
+ }
+
+ /**
+ * 构造
+ *
+ * @param paramsBuilder 参数构造器
+ */
+ public Argon2(Argon2Parameters.Builder paramsBuilder){
+ this.paramsBuilder = paramsBuilder;
+ }
+
+ /**
+ * 设置hash长度
+ *
+ * @param hashLength hash长度
+ * @return this
+ */
+ public Argon2 setHashLength(int hashLength){
+ this.hashLength = hashLength;
+ return this;
+ }
+
+ /**
+ * 设置版本
+ *
+ * @param version 版本
+ * @return this
+ * @see Argon2Parameters#ARGON2_VERSION_10
+ * @see Argon2Parameters#ARGON2_VERSION_13
+ */
+ public Argon2 setVersion(int version){
+ this.paramsBuilder.withVersion(version);
+ return this;
+ }
+
+ /**
+ * 设置盐
+ *
+ * @param salt 盐
+ * @return this
+ */
+ public Argon2 setSalt(byte[] salt){
+ this.paramsBuilder.withSalt(salt);
+ return this;
+ }
+
+ /**
+ * 设置可选的密钥数据,用于增加哈希的复杂性
+ *
+ * @param secret 密钥
+ * @return this
+ */
+ public Argon2 setSecret(byte[] secret){
+ this.paramsBuilder.withSecret(secret);
+ return this;
+ }
+
+ /**
+ * @param additional 附加数据
+ * @return this
+ */
+ public Argon2 setAdditional(byte[] additional){
+ this.paramsBuilder.withAdditional(additional);
+ return this;
+ }
+
+ /**
+ * 设置迭代次数
+ * 迭代次数越多,生成哈希的时间就越长,破解哈希就越困难
+ *
+ * @param iterations 迭代次数
+ * @return this
+ */
+ public Argon2 setIterations(int iterations){
+ this.paramsBuilder.withIterations(iterations);
+ return this;
+ }
+
+ /**
+ * 设置内存,单位KB
+ * 内存越大,生成哈希的时间就越长,破解哈希就越困难
+ *
+ * @param memoryAsKB 内存,单位KB
+ * @return this
+ */
+ public Argon2 setMemoryAsKB(int memoryAsKB){
+ this.paramsBuilder.withMemoryAsKB(memoryAsKB);
+ return this;
+ }
+
+ /**
+ * 设置并行度,即同时使用的核心数
+ * 值越高,生成哈希的时间就越长,破解哈希就越困难
+ *
+ * @param parallelism 并行度
+ * @return this
+ */
+ public Argon2 setParallelism(int parallelism){
+ this.paramsBuilder.withParallelism(parallelism);
+ return this;
+ }
+
+ /**
+ * 生成hash值
+ *
+ * @param password 密码
+ * @return hash值
+ */
+ public byte[] digest(char[] password){
+ final Argon2BytesGenerator generator = new Argon2BytesGenerator();
+ generator.init(paramsBuilder.build());
+ byte[] result = new byte[hashLength];
+ generator.generateBytes(password, result);
+ return result;
+ }
+}
diff --git a/hutool-crypto/src/test/java/cn/hutool/crypto/digest/Argon2Test.java b/hutool-crypto/src/test/java/cn/hutool/crypto/digest/Argon2Test.java
new file mode 100644
index 000000000..5fbe3126f
--- /dev/null
+++ b/hutool-crypto/src/test/java/cn/hutool/crypto/digest/Argon2Test.java
@@ -0,0 +1,22 @@
+package cn.hutool.crypto.digest;
+
+import cn.hutool.core.codec.Base64;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.Test;
+
+public class Argon2Test {
+ @Test
+ public void argon2Test() {
+ Argon2 argon2 = new Argon2();
+ final byte[] digest = argon2.digest("123456".toCharArray());
+ Assertions.assertEquals("wVGMOdzf5EdKGANPeHjaUnaFEJA0BnAq6HcF2psFmFo=", Base64.encode(digest));
+ }
+
+ @Test
+ public void argon2WithSaltTest() {
+ final Argon2 argon2 = new Argon2();
+ argon2.setSalt("123456".getBytes());
+ final byte[] digest = argon2.digest("123456".toCharArray());
+ Assertions.assertEquals("sEpbXTdMWra36JXPVxrZMm3xyoR5GkMlLhtW0Kwp9Ag=", Base64.encode(digest));
+ }
+}
diff --git a/hutool-db/pom.xml b/hutool-db/pom.xml
index b91cf5452..2e1aa3124 100755
--- a/hutool-db/pom.xml
+++ b/hutool-db/pom.xml
@@ -9,7 +9,7 @@
cn.hutool
hutool-parent
- 5.8.37
+ 5.8.38-SNAPSHOT
hutool-db
@@ -185,5 +185,11 @@
23.5.0.24.07
test
+
+ com.sap.cloud.db.jdbc
+ ngdbc
+ 2.24.7
+ test
+
diff --git a/hutool-db/src/main/java/cn/hutool/db/dialect/DialectFactory.java b/hutool-db/src/main/java/cn/hutool/db/dialect/DialectFactory.java
index 7fcc12763..e2636f766 100755
--- a/hutool-db/src/main/java/cn/hutool/db/dialect/DialectFactory.java
+++ b/hutool-db/src/main/java/cn/hutool/db/dialect/DialectFactory.java
@@ -62,6 +62,8 @@ public class DialectFactory implements DriverNamePool {
return new PhoenixDialect();
} else if (DRIVER_DM7.equalsIgnoreCase(driverName)) {
return new DmDialect();
+ } else if (DRIVER_HANA.equalsIgnoreCase(driverName)) {
+ return new HanaDialect();
}
}
// 无法识别可支持的数据库类型默认使用ANSI方言,可兼容大部分SQL语句
@@ -168,6 +170,9 @@ public class DialectFactory implements DriverNamePool {
} else if (nameContainsProductInfo.contains("goldendb")) {
// GoldenDB
driver = DRIVER_GOLDENDB;
+ } else if (nameContainsProductInfo.contains("sap")) {
+ // sap hana
+ driver = DRIVER_HANA;
}
return driver;
diff --git a/hutool-db/src/main/java/cn/hutool/db/dialect/DialectName.java b/hutool-db/src/main/java/cn/hutool/db/dialect/DialectName.java
index 275723b75..fab193b8d 100644
--- a/hutool-db/src/main/java/cn/hutool/db/dialect/DialectName.java
+++ b/hutool-db/src/main/java/cn/hutool/db/dialect/DialectName.java
@@ -9,7 +9,7 @@ import cn.hutool.core.util.StrUtil;
* @author Looly
*/
public enum DialectName {
- ANSI, MYSQL, ORACLE, POSTGRESQL, SQLITE3, H2, SQLSERVER, SQLSERVER2012, PHOENIX, DM;
+ ANSI, MYSQL, ORACLE, POSTGRESQL, SQLITE3, H2, SQLSERVER, SQLSERVER2012, PHOENIX, DM, HANA;
/**
* 是否为指定数据库方言,检查时不分区大小写
diff --git a/hutool-db/src/main/java/cn/hutool/db/dialect/DriverNamePool.java b/hutool-db/src/main/java/cn/hutool/db/dialect/DriverNamePool.java
index 998bc0a0b..95a6a1a1f 100644
--- a/hutool-db/src/main/java/cn/hutool/db/dialect/DriverNamePool.java
+++ b/hutool-db/src/main/java/cn/hutool/db/dialect/DriverNamePool.java
@@ -120,4 +120,8 @@ public interface DriverNamePool {
* JDBC 驱动 GoldenDB
*/
String DRIVER_GOLDENDB = "com.goldendb.jdbc.Driver";
+ /**
+ * JDBC 驱动 Sap Hana
+ */
+ String DRIVER_HANA = "com.sap.db.jdbc.Driver";
}
diff --git a/hutool-db/src/main/java/cn/hutool/db/dialect/impl/HanaDialect.java b/hutool-db/src/main/java/cn/hutool/db/dialect/impl/HanaDialect.java
new file mode 100644
index 000000000..531ad9923
--- /dev/null
+++ b/hutool-db/src/main/java/cn/hutool/db/dialect/impl/HanaDialect.java
@@ -0,0 +1,97 @@
+package cn.hutool.db.dialect.impl;
+
+import cn.hutool.core.util.StrUtil;
+import cn.hutool.db.Entity;
+import cn.hutool.db.Page;
+import cn.hutool.db.StatementUtil;
+import cn.hutool.db.dialect.DialectName;
+import cn.hutool.db.sql.SqlBuilder;
+import cn.hutool.db.sql.Wrapper;
+
+import java.sql.Connection;
+import java.sql.PreparedStatement;
+import java.sql.SQLException;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+/**
+ * Hana数据库方言
+ *
+ * @author daoyou.dev
+ */
+public class HanaDialect extends AnsiSqlDialect {
+ private static final long serialVersionUID = 1L;
+
+ /**
+ * 构造
+ */
+ public HanaDialect() {
+ wrapper = new Wrapper('"');
+ }
+
+ @Override
+ public String dialectName() {
+ return DialectName.HANA.name();
+ }
+
+ @Override
+ protected SqlBuilder wrapPageSql(SqlBuilder find, Page page) {
+ // SAP HANA 使用 OFFSET LIMIT 分页
+ return find.append(" LIMIT ").append(page.getPageSize())
+ .append(" OFFSET ").append(page.getStartPosition());
+ }
+
+ /**
+ * 构建用于upsert的{@link PreparedStatement}。
+ * SAP HANA 使用 MERGE INTO 语法来实现 UPSERT 操作。
+ *
+ * 生成 SQL 语法为:
+ *
+ * MERGE INTO demo AS target
+ * USING (SELECT ? AS a, ? AS b, ? AS c FROM DUMMY) AS source
+ * ON target.id = source.id
+ * WHEN MATCHED THEN
+ * UPDATE SET target.a = source.a, target.b = source.b, target.c = source.c
+ * WHEN NOT MATCHED THEN
+ * INSERT (a, b, c) VALUES (source.a, source.b, source.c);
+ *
+ *
+ * @param conn 数据库连接对象
+ * @param entity 数据实体类(包含表名)
+ * @param keys 主键字段数组,通常用于确定匹配条件(联合主键)
+ * @return PreparedStatement
+ * @throws SQLException SQL 执行异常
+ */
+ @Override
+ public PreparedStatement psForUpsert(Connection conn, Entity entity, String... keys) throws SQLException {
+ SqlBuilder.validateEntity(entity);
+ final SqlBuilder builder = SqlBuilder.create(wrapper);
+
+ final List columns = new ArrayList<>();
+ final List