diff --git a/NOTICE b/NOTICE
index db204a7..9758da1 100644
--- a/NOTICE
+++ b/NOTICE
@@ -5,36 +5,20 @@ This product is licensed under the Apache License, Version 2.0.
You may obtain a copy of the License at:
https://www.apache.org/licenses/LICENSE-2.0
-================================================================================
-Third-Party Dependencies
-================================================================================
-
-1. plusone-commons (xyz.zhouxy.plusone:plusone-commons)
- Copyright ZhouXY
- Licensed under the Apache License, Version 2.0
-
-2. Google Guava (com.google.guava:guava)
- Copyright (C) The Guava Authors
- Licensed under the Apache License, Version 2.0
-
-3. JSR-305 Annotations (com.google.code.findbugs:jsr305)
- Copyright (C) FindBugs
- Licensed under the Apache License, Version 2.0
-
================================================================================
Test Dependencies (Not included in distribution)
================================================================================
-4. JUnit Jupiter (org.junit.jupiter:junit-jupiter)
+1. JUnit Jupiter (org.junit.jupiter:junit-jupiter)
Copyright 2015-2026 JUnit Team
Licensed under the Eclipse Public License 2.0
-5. Logback (ch.qos.logback:logback-classic)
+2. Logback (ch.qos.logback:logback-classic)
Copyright (C) 1999-2026, QOS.ch
Licensed under the Eclipse Public License 1.0
and GNU Lesser General Public License 2.1
-6. H2 Database (com.h2database:h2)
+3. H2 Database (com.h2database:h2)
Copyright 1999-2026 H2 Database Project
Licensed under the MPL 2.0 and EPL 1.0
@@ -42,6 +26,6 @@ Test Dependencies (Not included in distribution)
Notes
================================================================================
-- This project is a lightweight JDBC wrapper designed for legacy projects
+- This project is a lightweight JDBC wrapper designed for legacy projects
without ORM frameworks.
- For learning and reference purposes only.
diff --git a/README.md b/README.md
index 84a445a..3e6a66d 100644
--- a/README.md
+++ b/README.md
@@ -4,8 +4,6 @@
> 注:本项目基于 [Apache License 2.0](https://www.apache.org/licenses/LICENSE-2.0) 开源协议发布。
-> 💡 **无外部依赖说明**:主分支(`dev`)的发布版本依赖 `plusone-commons` 工具库(含 Guava)。若您的项目属于不方便引入新依赖的场景(如老项目改造),可使用 `feature/no-dependencies` 分支,直接复制源码进行使用,无需考虑依赖问题。该分支已移除所有编译期与运行期外部依赖,将所需的工具方法(如断言检查)内化实现,仅保留测试相关的依赖项。
-
---
## 1. ✨ 核心特性
diff --git a/pom.xml b/pom.xml
index 444a2a8..86adc30 100644
--- a/pom.xml
+++ b/pom.xml
@@ -16,7 +16,11 @@
8
UTF-8
- 1.1.0-RC2
+
+ 3.0.2
+ 5.14.4
+ 1.3.16
+
2.2.224
@@ -40,43 +44,35 @@
https://gitea.zhouxy.xyz/plusone/simple-jdbc
-
-
-
- xyz.zhouxy.plusone
- plusone-dependencies
- ${plusone-commons.version}
- pom
- import
-
-
-
-
- xyz.zhouxy.plusone
- plusone-commons
- ${plusone-commons.version}
+ com.google.code.findbugs
+ jsr305
+ ${jsr305.version}
org.junit.jupiter
junit-jupiter
+ ${junit-jupiter.version}
test
ch.qos.logback
logback-classic
+ ${logback.version}
test
com.h2database
h2
+ ${h2.version}
test
+
release
diff --git a/src/main/java/xyz/zhouxy/jdbc/BatchUpdateStatus.java b/src/main/java/xyz/zhouxy/jdbc/BatchUpdateStatus.java
index 4f8e701..ad7c42e 100644
--- a/src/main/java/xyz/zhouxy/jdbc/BatchUpdateStatus.java
+++ b/src/main/java/xyz/zhouxy/jdbc/BatchUpdateStatus.java
@@ -15,8 +15,6 @@
*/
package xyz.zhouxy.jdbc;
-import xyz.zhouxy.plusone.commons.base.IWithIntCode;
-
/**
* 批量更新状态
*
@@ -27,7 +25,7 @@ import xyz.zhouxy.plusone.commons.base.IWithIntCode;
* @see BatchUpdateResult
* @see BatchUpdateResult#getStatus()
*/
-public enum BatchUpdateStatus implements IWithIntCode {
+public enum BatchUpdateStatus {
/**
* 成功
@@ -62,9 +60,10 @@ public enum BatchUpdateStatus implements IWithIntCode {
}
/**
- * {@inheritDoc}
+ * 获取状态码
+ *
+ * @return 状态码
*/
- @Override
public int getCode() {
return code;
}
diff --git a/src/main/java/xyz/zhouxy/jdbc/DefaultBeanRowMapper.java b/src/main/java/xyz/zhouxy/jdbc/DefaultBeanRowMapper.java
index e82e36f..7dcdca4 100644
--- a/src/main/java/xyz/zhouxy/jdbc/DefaultBeanRowMapper.java
+++ b/src/main/java/xyz/zhouxy/jdbc/DefaultBeanRowMapper.java
@@ -34,9 +34,7 @@ import java.util.stream.Collectors;
import javax.annotation.Nullable;
-import com.google.common.base.CaseFormat;
-
-import xyz.zhouxy.plusone.commons.annotation.StaticFactoryMethod;
+import xyz.zhouxy.jdbc.util.NamingTools;
/**
* DefaultBeanRowMapper
@@ -90,7 +88,6 @@ public class DefaultBeanRowMapper implements RowMapper {
* @return DefaultBeanRowMapper 对象
* @throws SQLException 创建 {@code DefaultBeanRowMapper} 出现错误的异常时抛出
*/
- @StaticFactoryMethod(DefaultBeanRowMapper.class)
public static DefaultBeanRowMapper of(Class beanType) throws SQLException {
return of(beanType, null);
}
@@ -104,7 +101,6 @@ public class DefaultBeanRowMapper implements RowMapper {
* @return {@code DefaultBeanRowMapper} 对象
* @throws SQLException 创建 {@code DefaultBeanRowMapper} 出现错误的异常时抛出
*/
- @StaticFactoryMethod(DefaultBeanRowMapper.class)
public static DefaultBeanRowMapper of(Class beanType, @Nullable Map propertyColMap)
throws SQLException {
try {
@@ -167,14 +163,14 @@ public class DefaultBeanRowMapper implements RowMapper {
// Bean 的属性名为小驼峰,对应的列名为下划线
Function super PropertyDescriptor, String> keyMapper;
if (propertyColMap == null || propertyColMap.isEmpty()) {
- keyMapper = p -> CaseFormat.LOWER_CAMEL.to(CaseFormat.LOWER_UNDERSCORE, p.getName());
+ keyMapper = p -> NamingTools.camelToSnake(p.getName());
}
else {
keyMapper = p -> {
String propertyName = p.getName();
String colName = propertyColMap.get(propertyName);
return colName != null ? colName
- : CaseFormat.LOWER_CAMEL.to(CaseFormat.LOWER_UNDERSCORE, propertyName);
+ : NamingTools.camelToSnake(propertyName);
};
}
return Arrays.stream(propertyDescriptors)
diff --git a/src/main/java/xyz/zhouxy/jdbc/JdbcOperationSupport.java b/src/main/java/xyz/zhouxy/jdbc/JdbcOperationSupport.java
index c71ca9c..0a79b36 100644
--- a/src/main/java/xyz/zhouxy/jdbc/JdbcOperationSupport.java
+++ b/src/main/java/xyz/zhouxy/jdbc/JdbcOperationSupport.java
@@ -16,8 +16,8 @@
package xyz.zhouxy.jdbc;
-import static xyz.zhouxy.plusone.commons.util.AssertTools.checkArgument;
-import static xyz.zhouxy.plusone.commons.util.AssertTools.checkArgumentNotNull;
+import static xyz.zhouxy.jdbc.util.AssertTools.checkArgument;
+import static xyz.zhouxy.jdbc.util.AssertTools.checkArgumentNotNull;
import java.sql.BatchUpdateException;
import java.sql.Connection;
@@ -37,8 +37,6 @@ import java.util.List;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
-import xyz.zhouxy.plusone.commons.util.ArrayTools;
-
/**
* JdbcOperationSupport
*
@@ -162,7 +160,7 @@ class JdbcOperationSupport {
throws SQLException {
assertConnectionNotNull(conn);
assertSqlNotNull(sql);
- if (ArrayTools.isNotEmpty(params)) {
+ if (params != null && params.length > 0) {
try (PreparedStatement stmt = conn.prepareStatement(sql)) {
fillStatement(stmt, params);
return stmt.executeUpdate();
@@ -191,7 +189,7 @@ class JdbcOperationSupport {
assertConnectionNotNull(conn);
assertSqlNotNull(sql);
assertRowMapperNotNull(rowMapper);
- if (ArrayTools.isNotEmpty(params)) {
+ if (params != null && params.length > 0) {
try (PreparedStatement stmt = conn.prepareStatement(sql, Statement.RETURN_GENERATED_KEYS)) {
fillStatement(stmt, params);
stmt.executeUpdate();
@@ -308,7 +306,7 @@ class JdbcOperationSupport {
@Nullable Object[] params,
@Nonnull ResultHandler resultHandler)
throws SQLException {
- if (ArrayTools.isNotEmpty(params)) {
+ if (params != null && params.length > 0) {
try (PreparedStatement stmt = createPreparedStatementInternal(conn, sql, params);
ResultSet rs = stmt.executeQuery()) {
return resultHandler.handle(rs);
diff --git a/src/main/java/xyz/zhouxy/jdbc/ParamBuilder.java b/src/main/java/xyz/zhouxy/jdbc/ParamBuilder.java
index 0075e65..def22af 100644
--- a/src/main/java/xyz/zhouxy/jdbc/ParamBuilder.java
+++ b/src/main/java/xyz/zhouxy/jdbc/ParamBuilder.java
@@ -29,10 +29,7 @@ import java.util.OptionalLong;
import java.util.function.Function;
import java.util.stream.Collectors;
-import xyz.zhouxy.plusone.commons.collection.CollectionTools;
-import xyz.zhouxy.plusone.commons.util.ArrayTools;
-import xyz.zhouxy.plusone.commons.util.AssertTools;
-import xyz.zhouxy.plusone.commons.util.OptionalTools;
+import xyz.zhouxy.jdbc.util.AssertTools;
/**
* ParamBuilder
@@ -66,7 +63,7 @@ public class ParamBuilder {
* @return 参数数组
*/
public static Object[] buildParams(final Object... params) {
- if (ArrayTools.isEmpty(params)) {
+ if (params == null || params.length == 0) {
return EMPTY_OBJECT_ARRAY;
}
return Arrays.stream(params)
@@ -91,16 +88,16 @@ public class ParamBuilder {
return param;
}
if (param instanceof Optional) {
- return OptionalTools.orElseNull((Optional>) param);
+ return ((Optional>) param).orElse(null);
}
if (param instanceof OptionalInt) {
- return OptionalTools.toInteger((OptionalInt) param);
+ return ((OptionalInt) param).isPresent() ? ((OptionalInt) param).getAsInt() : null;
}
if (param instanceof OptionalLong) {
- return OptionalTools.toLong((OptionalLong) param);
+ return ((OptionalLong) param).isPresent() ? ((OptionalLong) param).getAsLong() : null;
}
if (param instanceof OptionalDouble) {
- return OptionalTools.toDouble((OptionalDouble) param);
+ return ((OptionalDouble) param).isPresent() ? ((OptionalDouble) param).getAsDouble() : null;
}
return param;
}
@@ -121,7 +118,7 @@ public class ParamBuilder {
public static List buildBatchParams(final Collection c, final Function func) {
AssertTools.checkNotNull(c, "The collection can not be null.");
AssertTools.checkNotNull(func, "The func can not be null.");
- if (CollectionTools.isEmpty(c)) {
+ if (c.isEmpty()) {
return Collections.emptyList();
}
return c.stream().map(func).collect(Collectors.toList());
diff --git a/src/main/java/xyz/zhouxy/jdbc/SimpleJdbcTemplate.java b/src/main/java/xyz/zhouxy/jdbc/SimpleJdbcTemplate.java
index d03c6f4..6a02976 100644
--- a/src/main/java/xyz/zhouxy/jdbc/SimpleJdbcTemplate.java
+++ b/src/main/java/xyz/zhouxy/jdbc/SimpleJdbcTemplate.java
@@ -26,7 +26,7 @@ import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import javax.sql.DataSource;
-import xyz.zhouxy.plusone.commons.util.AssertTools;
+import xyz.zhouxy.jdbc.util.AssertTools;
/**
* JDBC 操作的模板类,对原生 JDBC 进行轻量封装,提供查询、更新、批量操作等便捷方法。
diff --git a/src/main/java/xyz/zhouxy/jdbc/ThrowingConsumer.java b/src/main/java/xyz/zhouxy/jdbc/ThrowingConsumer.java
new file mode 100644
index 0000000..115fed0
--- /dev/null
+++ b/src/main/java/xyz/zhouxy/jdbc/ThrowingConsumer.java
@@ -0,0 +1,41 @@
+/*
+ * Copyright 2026-present ZhouXY
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package xyz.zhouxy.jdbc;
+
+/**
+ * 可抛出受检异常的函数式接口。
+ *
+ *
+ * 类似于 {@link java.util.function.Consumer},但 {@code accept} 方法允许抛出受检异常。
+ *
+ *
+ * @param 输入类型
+ * @param 允许抛出的异常类型
+ * @author ZhouXY
+ * @since 1.1.0
+ */
+@FunctionalInterface
+public interface ThrowingConsumer {
+
+ /**
+ * 对给定参数执行此操作。
+ *
+ * @param t 输入参数
+ * @throws E 异常
+ */
+ void accept(T t) throws E;
+}
diff --git a/src/main/java/xyz/zhouxy/jdbc/ThrowingPredicate.java b/src/main/java/xyz/zhouxy/jdbc/ThrowingPredicate.java
new file mode 100644
index 0000000..ef57ad3
--- /dev/null
+++ b/src/main/java/xyz/zhouxy/jdbc/ThrowingPredicate.java
@@ -0,0 +1,42 @@
+/*
+ * Copyright 2026-present ZhouXY
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package xyz.zhouxy.jdbc;
+
+/**
+ * 可抛出受检异常的谓词函数式接口。
+ *
+ *
+ * 类似于 {@link java.util.function.Predicate},但 {@code test} 方法允许抛出受检异常。
+ *
+ *
+ * @param 输入类型
+ * @param 允许抛出的异常类型
+ * @author ZhouXY
+ * @since 1.1.0
+ */
+@FunctionalInterface
+public interface ThrowingPredicate {
+
+ /**
+ * 对给定参数执行此谓词判断。
+ *
+ * @param t 输入参数
+ * @return 谓词判断结果
+ * @throws E 异常
+ */
+ boolean test(T t) throws E;
+}
diff --git a/src/main/java/xyz/zhouxy/jdbc/TransactionTemplate.java b/src/main/java/xyz/zhouxy/jdbc/TransactionTemplate.java
index b64a5c3..f5a1c87 100644
--- a/src/main/java/xyz/zhouxy/jdbc/TransactionTemplate.java
+++ b/src/main/java/xyz/zhouxy/jdbc/TransactionTemplate.java
@@ -26,9 +26,7 @@ import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import javax.sql.DataSource;
-import xyz.zhouxy.plusone.commons.function.ThrowingConsumer;
-import xyz.zhouxy.plusone.commons.function.ThrowingPredicate;
-import xyz.zhouxy.plusone.commons.util.AssertTools;
+import xyz.zhouxy.jdbc.util.AssertTools;
/**
* 事务模板,提供事务执行能力。
@@ -64,7 +62,12 @@ public class TransactionTemplate {
@Nonnull
private final DataSource dataSource;
- public TransactionTemplate(@Nonnull DataSource dataSource) {
+ /**
+ * 构造一个 {@code TransactionTemplate} 实例
+ *
+ * @param dataSource 数据源,用于获取数据库连接;不可为 {@code null}
+ */
+ public TransactionTemplate(DataSource dataSource) {
AssertTools.checkNotNull(dataSource);
this.dataSource = dataSource;
}
diff --git a/src/main/java/xyz/zhouxy/jdbc/util/AssertTools.java b/src/main/java/xyz/zhouxy/jdbc/util/AssertTools.java
new file mode 100644
index 0000000..8a45cf3
--- /dev/null
+++ b/src/main/java/xyz/zhouxy/jdbc/util/AssertTools.java
@@ -0,0 +1,334 @@
+/*
+ * Copyright 2024-present ZhouXY
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package xyz.zhouxy.jdbc.util;
+
+import java.util.function.Supplier;
+
+/**
+ * 断言工具
+ *
+ *
+ * 本工具类不封装过多判断逻辑,鼓励充分使用项目中的工具类进行逻辑判断。
+ *
+ *
+ * checkArgument(StringUtils.hasText(str), "The argument cannot be blank.");
+ * checkState(ArrayUtils.isNotEmpty(result), "The result cannot be empty.");
+ * checkCondition(!CollectionUtils.isEmpty(roles),
+ * () -> new InvalidInputException("The roles cannot be empty."));
+ * checkCondition(RegexTools.matches(email, PatternConsts.EMAIL),
+ * "must be a well-formed email address");
+ *
+ *
+ * @author ZhouXY
+ */
+public class AssertTools {
+
+ // ================================
+ // #region - Argument
+ // ================================
+
+ /**
+ * 检查实参
+ *
+ * @param condition 判断参数是否符合条件的结果
+ * @throws IllegalArgumentException 当条件不满足时抛出
+ */
+ public static void checkArgument(boolean condition) {
+ if (!condition) {
+ throw new IllegalArgumentException();
+ }
+ }
+
+ /**
+ * 检查实参
+ *
+ * @param condition 判断参数是否符合条件的结果
+ * @param errorMessage 异常信息
+ * @throws IllegalArgumentException 当条件不满足时抛出
+ */
+ public static void checkArgument(boolean condition, String errorMessage) {
+ if (!condition) {
+ throw new IllegalArgumentException(errorMessage);
+ }
+ }
+
+ /**
+ * 检查实参
+ *
+ * @param condition 判断参数是否符合条件的结果
+ * @param errorMessageSupplier 异常信息
+ * @throws IllegalArgumentException 当条件不满足时抛出
+ */
+ public static void checkArgument(boolean condition, Supplier errorMessageSupplier) {
+ if (!condition) {
+ throw new IllegalArgumentException(errorMessageSupplier.get());
+ }
+ }
+
+ /**
+ * 检查实参
+ *
+ * @param condition 判断参数是否符合条件的结果
+ * @param errorMessageTemplate 异常信息模板
+ * @param errorMessageArgs 异常信息参数
+ * @throws IllegalArgumentException 当条件不满足时抛出
+ */
+ public static void checkArgument(boolean condition,
+ String errorMessageTemplate, Object... errorMessageArgs) {
+ if (!condition) {
+ throw new IllegalArgumentException(String.format(errorMessageTemplate, errorMessageArgs));
+ }
+ }
+
+ // ================================
+ // #endregion - Argument
+ // ================================
+
+ // ================================
+ // #region - ArgumentNotNull
+ // ================================
+
+ /**
+ * 判断入参不为 {@code null}
+ *
+ * @param 入参类型
+ * @param obj 入参
+ * @return 校验通过时返回入参
+ * @throws IllegalArgumentException 当 {@code obj} 为 {@code null} 时抛出
+ */
+ public static T checkArgumentNotNull(T obj) {
+ if (obj == null) {
+ throw new IllegalArgumentException();
+ }
+ return obj;
+ }
+
+ /**
+ * 判断入参不为 {@code null}
+ *
+ * @param 入参类型
+ * @param obj 入参
+ * @param errorMessage 异常信息
+ * @return 校验通过时返回入参
+ * @throws IllegalArgumentException 当 {@code obj} 为 {@code null} 时抛出
+ */
+ public static T checkArgumentNotNull(T obj, String errorMessage) {
+ if (obj == null) {
+ throw new IllegalArgumentException(errorMessage);
+ }
+ return obj;
+ }
+
+ /**
+ * 判断入参不为 {@code null}
+ *
+ * @param 入参类型
+ * @param obj 入参
+ * @param errorMessageSupplier 异常信息
+ * @return 校验通过时返回入参
+ * @throws IllegalArgumentException 当 {@code obj} 为 {@code null} 时抛出
+ */
+ public static T checkArgumentNotNull(T obj, Supplier errorMessageSupplier) {
+ if (obj == null) {
+ throw new IllegalArgumentException(errorMessageSupplier.get());
+ }
+ return obj;
+ }
+
+ /**
+ * 判断入参不为 {@code null}
+ *
+ * @param 入参类型
+ * @param obj 入参
+ * @param errorMessageTemplate 异常信息模板
+ * @param errorMessageArgs 异常信息参数
+ * @return 校验通过时返回入参
+ * @throws IllegalArgumentException 当 {@code obj} 为 {@code null} 时抛出
+ */
+ public static T checkArgumentNotNull(T obj,
+ String errorMessageTemplate, Object... errorMessageArgs) {
+ if (obj == null) {
+ throw new IllegalArgumentException(String.format(errorMessageTemplate, errorMessageArgs));
+ }
+ return obj;
+ }
+
+ // ================================
+ // #endregion - ArgumentNotNull
+ // ================================
+
+ // ================================
+ // #region - State
+ // ================================
+
+ /**
+ * 检查状态
+ *
+ * @param condition 判断状态是否符合条件的结果
+ * @throws IllegalStateException 当条件不满足时抛出
+ */
+ public static void checkState(boolean condition) {
+ if (!condition) {
+ throw new IllegalStateException();
+ }
+ }
+
+ /**
+ * 检查状态
+ *
+ * @param condition 判断状态是否符合条件的结果
+ * @param errorMessage 异常信息
+ * @throws IllegalStateException 当条件不满足时抛出
+ */
+ public static void checkState(boolean condition, String errorMessage) {
+ if (!condition) {
+ throw new IllegalStateException(errorMessage);
+ }
+ }
+
+ /**
+ * 检查状态
+ *
+ * @param condition 判断状态是否符合条件的结果
+ * @param errorMessageSupplier 异常信息
+ * @throws IllegalStateException 当条件不满足时抛出
+ */
+ public static void checkState(boolean condition, Supplier errorMessageSupplier) {
+ if (!condition) {
+ throw new IllegalStateException(errorMessageSupplier.get());
+ }
+ }
+
+ /**
+ * 检查状态
+ *
+ * @param condition 判断状态是否符合条件的结果
+ * @param errorMessageTemplate 异常信息模板
+ * @param errorMessageArgs 异常信息参数
+ * @throws IllegalStateException 当条件不满足时抛出
+ */
+ public static void checkState(boolean condition,
+ String errorMessageTemplate, Object... errorMessageArgs) {
+ if (!condition) {
+ throw new IllegalStateException(String.format(errorMessageTemplate, errorMessageArgs));
+ }
+ }
+
+ // ================================
+ // #endregion - State
+ // ================================
+
+ // ================================
+ // #region - NotNull
+ // ================================
+
+ /**
+ * 判空
+ *
+ * @param 入参类型
+ * @param obj 入参
+ * @throws NullPointerException 当 {@code obj} 为 {@code null} 时抛出
+ */
+ public static void checkNotNull(T obj) {
+ if (obj == null) {
+ throw new NullPointerException();
+ }
+ }
+
+ /**
+ * 判空
+ *
+ * @param 入参类型
+ * @param obj 入参
+ * @param errorMessage 异常信息
+ * @throws NullPointerException 当 {@code obj} 为 {@code null} 时抛出
+ */
+ public static void checkNotNull(T obj, String errorMessage) {
+ if (obj == null) {
+ throw new NullPointerException(errorMessage);
+ }
+ }
+
+ /**
+ * 判空
+ *
+ * @param 入参类型
+ * @param obj 入参
+ * @param errorMessageSupplier 异常信息
+ * @throws NullPointerException 当 {@code obj} 为 {@code null} 时抛出
+ */
+ public static void checkNotNull(T obj, Supplier errorMessageSupplier) {
+ if (obj == null) {
+ throw new NullPointerException(errorMessageSupplier.get());
+ }
+ }
+
+ /**
+ * 判空
+ *
+ * @param 入参类型
+ * @param obj 入参
+ * @param errorMessageTemplate 异常信息模板
+ * @param errorMessageArgs 异常信息参数
+ * @throws NullPointerException 当 {@code obj} 为 {@code null} 时抛出
+ */
+ public static void checkNotNull(T obj,
+ String errorMessageTemplate, Object... errorMessageArgs) {
+ if (obj == null) {
+ throw new NullPointerException(String.format(errorMessageTemplate, errorMessageArgs));
+ }
+ }
+
+ // ================================
+ // #endregion - NotNull
+ // ================================
+
+ // ================================
+ // #region - Condition
+ // ================================
+
+ /**
+ * 当条件不满足时抛出异常。
+ *
+ * @param 异常类型
+ * @param condition 条件
+ * @param e 异常
+ * @throws T 当条件不满足时抛出异常
+ */
+ public static void checkCondition(boolean condition, Supplier e)
+ throws T {
+ if (!condition) {
+ throw e.get();
+ }
+ }
+
+ // ================================
+ // #endregion
+ // ================================
+
+ // ================================
+ // #region - constructor
+ // ================================
+
+ private AssertTools() {
+ throw new IllegalStateException("Utility class");
+ }
+
+ // ================================
+ // #endregion
+ // ================================
+}
diff --git a/src/main/java/xyz/zhouxy/jdbc/util/NamingTools.java b/src/main/java/xyz/zhouxy/jdbc/util/NamingTools.java
new file mode 100644
index 0000000..648e04e
--- /dev/null
+++ b/src/main/java/xyz/zhouxy/jdbc/util/NamingTools.java
@@ -0,0 +1,96 @@
+/*
+ * Copyright 2026-present ZhouXY
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package xyz.zhouxy.jdbc.util;
+
+/**
+ * 字符串工具
+ *
+ * @author ZhouXY
+ * @since 1.0.0
+ */
+public class NamingTools {
+
+ /**
+ * 将小驼峰命名转换为小写下划线命名(snake_case)。
+ *
+ * 转换规则:
+ *
+ * 小写→大写边界插入下划线:{@code userName → user_name}
+ * 连续大写缩写视为整体,在其末尾小写边界插入下划线:{@code XMLParser → xml_parser}
+ * 纯小写保持不变:{@code username → username}
+ * {@code null} 或空字符串返回原值
+ *
+ *
+ * @param camelCase 小驼峰命名字符串,可空
+ * @return snake_case 命名字符串;{@code null} 输入返回 {@code null}
+ */
+ public static String camelToSnake(String camelCase) {
+ if (camelCase == null || camelCase.isEmpty()) {
+ return camelCase;
+ }
+
+ StringBuilder sb = new StringBuilder(camelCase.length() * 2);
+ int len = camelCase.length();
+
+ for (int i = 0; i < len; i++) {
+ char c = camelCase.charAt(i);
+ if (isUpperCaseAscii(c)) {
+ if (shouldInsertUnderscore(camelCase, i)) {
+ sb.append('_');
+ }
+ sb.append((char) (c + 32)); // 转小写
+ } else {
+ sb.append(c);
+ }
+ }
+ return sb.toString();
+ }
+
+ private static boolean shouldInsertUnderscore(String str, int index) {
+ if (index == 0) {
+ return false;
+ }
+
+ char prev = str.charAt(index - 1);
+ char next = (index + 1 < str.length()) ? str.charAt(index + 1) : 0;
+
+ boolean prevIsBoundary = !isUpperCaseAscii(prev);
+ boolean nextIsLower = isLowerCaseAscii(next);
+
+ return prevIsBoundary || nextIsLower;
+ }
+
+ private static boolean isUpperCaseAscii(char c) {
+ return c >= 'A' && c <= 'Z';
+ }
+
+ private static boolean isLowerCaseAscii(char c) {
+ return c >= 'a' && c <= 'z';
+ }
+
+ // ================================
+ // #region - constructor
+ // ================================
+
+ private NamingTools() {
+ throw new IllegalStateException("Utility class");
+ }
+
+ // ================================
+ // #endregion
+ // ================================
+}
diff --git a/src/test/java/xyz/zhouxy/jdbc/test/BatchUpdateTest.java b/src/test/java/xyz/zhouxy/jdbc/test/BatchUpdateTest.java
index 66de33e..a5b8590 100644
--- a/src/test/java/xyz/zhouxy/jdbc/test/BatchUpdateTest.java
+++ b/src/test/java/xyz/zhouxy/jdbc/test/BatchUpdateTest.java
@@ -6,6 +6,7 @@ import static xyz.zhouxy.jdbc.ParamBuilder.*;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.ArrayList;
+import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Optional;
@@ -16,8 +17,6 @@ import org.junit.jupiter.api.Test;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
-import com.google.common.collect.Lists;
-
import xyz.zhouxy.jdbc.BatchUpdateErrorInfo;
import xyz.zhouxy.jdbc.BatchUpdateResult;
import xyz.zhouxy.jdbc.BatchUpdateStatus;
@@ -170,7 +169,7 @@ class BatchUpdateTest extends BaseH2Test {
// #region - 包含错误数据
// ================================
- final List userListContainingInvalidData = Lists.newArrayList(
+ final List userListContainingInvalidData = Arrays.asList(
// batch 0
new User("test_0001", "test_0001@example.com", 1, 1L, true),
new User("test_0002", "test_0002@example.com", 1, 1L, true),
diff --git a/src/test/java/xyz/zhouxy/jdbc/test/RowMapperTest.java b/src/test/java/xyz/zhouxy/jdbc/test/RowMapperTest.java
index 8ff3619..f9bc5a0 100644
--- a/src/test/java/xyz/zhouxy/jdbc/test/RowMapperTest.java
+++ b/src/test/java/xyz/zhouxy/jdbc/test/RowMapperTest.java
@@ -157,6 +157,63 @@ class RowMapperTest extends BaseH2Test {
assertEquals("alice", user.get().getUsername());
}
+ // ==================== DefaultBeanRowMapper 连续大写缩写映射 ====================
+
+ @Test
+ @DisplayName("DefaultBeanRowMapper:连续大写缩写属性正确映射为 snake_case")
+ void testDefaultBeanRowMapperAcronymMapping() throws SQLException {
+ SimpleJdbcTemplate template = createTemplate();
+
+ // 创建测试表,列名使用 snake_case
+ template.update("CREATE TABLE acronym_test ("
+ + "id BIGINT AUTO_INCREMENT PRIMARY KEY,"
+ + "home_url VARCHAR(100),"
+ + "xml_parser VARCHAR(100),"
+ + "parse_url VARCHAR(100),"
+ + "user_id VARCHAR(100),"
+ + "parse_html VARCHAR(100),"
+ + "multi_http_client VARCHAR(100))");
+ template.update(
+ "INSERT INTO acronym_test (home_url, xml_parser, parse_url, user_id, parse_html, multi_http_client)"
+ + " VALUES (?, ?, ?, ?, ?, ?)",
+ new Object[]{"https://example.com", "SAXParser", "/api/v1",
+ "user-001", "test
", "ApacheHttpClient"});
+
+ RowMapper rowMapper = RowMapper.beanRowMapper(AcronymBean.class);
+ Optional result = template.queryFirst(
+ "SELECT * FROM acronym_test WHERE id = ?",
+ new Object[]{1L}, rowMapper);
+
+ assertTrue(result.isPresent());
+ AcronymBean bean = result.get();
+ assertEquals("https://example.com", bean.getHomeURL());
+ assertEquals("SAXParser", bean.getXmlParser());
+ assertEquals("/api/v1", bean.getParseURL());
+ assertEquals("user-001", bean.getUserID());
+ assertEquals("test
", bean.getParseHTML());
+ assertEquals("ApacheHttpClient", bean.getMultiHttpClient());
+
+ logger.info("缩写映射: homeURL={}, xmlParser={}, parseURL={}, userID={}, parseHTML={}, multiHttpClient={}",
+ bean.getHomeURL(), bean.getXmlParser(), bean.getParseURL(),
+ bean.getUserID(), bean.getParseHTML(), bean.getMultiHttpClient());
+ }
+
+ @Test
+ @DisplayName("DefaultBeanRowMapper:纯小写属性名映射为同名列")
+ void testDefaultBeanRowMapperAllLowercaseMapping() throws SQLException {
+ // 通过 User Bean 验证纯小写属性映射(username → username, email → email)
+ SimpleJdbcTemplate template = createTemplate();
+ RowMapper rowMapper = RowMapper.beanRowMapper(User.class);
+
+ Optional user = template.queryFirst(
+ "SELECT username, email FROM users WHERE username = ?",
+ new Object[]{"alice"}, rowMapper);
+
+ assertTrue(user.isPresent());
+ assertEquals("alice", user.get().getUsername());
+ assertEquals("alice@example.com", user.get().getEmail());
+ }
+
// ==================== HASH_MAP_MAPPER ====================
@Test
@@ -253,4 +310,74 @@ class RowMapperTest extends BaseH2Test {
this.name = name;
}
}
+
+ /**
+ * 包含连续大写缩写属性的 Bean,用于验证 camelToSnake 的缩写处理。
+ *
+ * 覆盖场景:
+ *
+ * homeURL — 缩写在末尾(三字母 URL)
+ * xmlParser — 缩写在前(三字母 XML)
+ * parseURL — 缩写在末尾
+ * userID — 两字母缩写在末尾(ID)
+ * parseHTML — 四字母缩写在末尾(HTML)
+ * multiHttpClient — 缩写夹在词中(HTTP)
+ *
+ */
+ public static class AcronymBean {
+ private String homeURL;
+ private String xmlParser;
+ private String parseURL;
+ private String userID;
+ private String parseHTML;
+ private String multiHttpClient;
+
+ public String getHomeURL() {
+ return homeURL;
+ }
+
+ public void setHomeURL(String homeURL) {
+ this.homeURL = homeURL;
+ }
+
+ public String getXmlParser() {
+ return xmlParser;
+ }
+
+ public void setXmlParser(String xmlParser) {
+ this.xmlParser = xmlParser;
+ }
+
+ public String getParseURL() {
+ return parseURL;
+ }
+
+ public void setParseURL(String parseURL) {
+ this.parseURL = parseURL;
+ }
+
+ public String getUserID() {
+ return userID;
+ }
+
+ public void setUserID(String userID) {
+ this.userID = userID;
+ }
+
+ public String getParseHTML() {
+ return parseHTML;
+ }
+
+ public void setParseHTML(String parseHTML) {
+ this.parseHTML = parseHTML;
+ }
+
+ public String getMultiHttpClient() {
+ return multiHttpClient;
+ }
+
+ public void setMultiHttpClient(String multiHttpClient) {
+ this.multiHttpClient = multiHttpClient;
+ }
+ }
}
diff --git a/src/test/java/xyz/zhouxy/jdbc/test/TransactionTest.java b/src/test/java/xyz/zhouxy/jdbc/test/TransactionTest.java
index a3cb9fa..ffd7679 100644
--- a/src/test/java/xyz/zhouxy/jdbc/test/TransactionTest.java
+++ b/src/test/java/xyz/zhouxy/jdbc/test/TransactionTest.java
@@ -234,6 +234,15 @@ class TransactionTest extends BaseH2Test {
// ==================== TransactionException ====================
+ @Test
+ @DisplayName("TransactionException:单参构造器(仅 cause)")
+ void testTransactionExceptionSingleArg() {
+ RuntimeException cause = new RuntimeException("原始异常");
+ TransactionException ex = new TransactionException(cause);
+ assertEquals("Transaction failed during execution", ex.getMessage());
+ assertSame(cause, ex.getCause());
+ }
+
@Test
@DisplayName("TransactionException:双参构造器")
void testTransactionExceptionWithMessage() {
@@ -242,4 +251,12 @@ class TransactionTest extends BaseH2Test {
assertEquals("自定义消息", ex.getMessage());
assertSame(cause, ex.getCause());
}
+
+ @Test
+ @DisplayName("TransactionException:null cause")
+ void testTransactionExceptionNullCause() {
+ TransactionException ex = new TransactionException(null);
+ assertEquals("Transaction failed during execution", ex.getMessage());
+ assertNull(ex.getCause());
+ }
}
diff --git a/src/test/java/xyz/zhouxy/jdbc/test/util/AssertToolsTests.java b/src/test/java/xyz/zhouxy/jdbc/test/util/AssertToolsTests.java
new file mode 100644
index 0000000..c84d218
--- /dev/null
+++ b/src/test/java/xyz/zhouxy/jdbc/test/util/AssertToolsTests.java
@@ -0,0 +1,522 @@
+/*
+ * Copyright 2024-2025 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package xyz.zhouxy.jdbc.test.util;
+
+import static org.junit.jupiter.api.Assertions.*;
+import static xyz.zhouxy.jdbc.util.AssertTools.*;
+
+import java.lang.reflect.Constructor;
+import java.time.LocalDate;
+import java.util.Arrays;
+import java.util.function.Supplier;
+
+import org.junit.jupiter.api.Test;
+
+import xyz.zhouxy.jdbc.util.AssertTools;
+
+class AssertToolsTests {
+
+ // #region - Argument
+
+ @Test
+ void testCheckArgument_true() {
+ checkArgument(true);
+ }
+
+ @Test
+ void testCheckArgument_true_withMessage() {
+ final String IGNORE_ME = "IGNORE_ME"; // NOSONAR
+ checkArgument(true, IGNORE_ME);
+ }
+
+ @Test
+ void testCheckArgument_true_withNullMessage() {
+ final String IGNORE_ME = null; // NOSONAR
+ checkArgument(true, IGNORE_ME);
+ }
+
+ @Test
+ void testCheckArgument_true_withMessageSupplier() {
+ checkArgument(true, () -> "Error message: " + LocalDate.now());
+ }
+
+ @Test
+ void testCheckArgument_true_withNullMessageSupplier() {
+ final Supplier IGNORE_ME = null; // NOSONAR
+ checkArgument(true, IGNORE_ME);
+ }
+
+ @Test
+ void testCheckArgument_true_withMessageFormat() {
+ LocalDate today = LocalDate.now();
+ checkArgument(true, "String format: %s", today);
+ }
+
+ @Test
+ void testCheckArgument_true_withNullMessageFormat() {
+ checkArgument(true, null, LocalDate.now());
+ }
+
+ @Test
+ void testCheckArgument_false() {
+ final IllegalArgumentException e = assertThrows(IllegalArgumentException.class,
+ () -> checkArgument(false));
+
+ assertNull(e.getMessage());
+ assertNull(e.getCause());
+ }
+
+ @Test
+ void testCheckArgument_false_withMessage() {
+ final String message = "testCheckArgument_false_withMessage";
+
+ final IllegalArgumentException e = assertThrows(IllegalArgumentException.class,
+ () -> checkArgument(false, message));
+
+ assertEquals(message, e.getMessage());
+ assertNull(e.getCause());
+ }
+
+ @Test
+ void testCheckArgument_false_withNullMessage() {
+ final String message = null;
+
+ final IllegalArgumentException e = assertThrows(IllegalArgumentException.class,
+ () -> checkArgument(false, message));
+
+ assertNull(e.getMessage());
+ assertNull(e.getCause());
+ }
+
+ @Test
+ void testCheckArgument_false_withMessageSupplier() {
+ final LocalDate today = LocalDate.now();
+ final IllegalArgumentException e = assertThrows(IllegalArgumentException.class,
+ () -> checkArgument(false, () -> "Error message: " + today));
+
+ assertEquals("Error message: " + today, e.getMessage());
+ assertNull(e.getCause());
+ }
+
+ @Test
+ void testCheckArgument_false_withNullMessageSupplier() {
+ Supplier messageSupplier = null;
+ assertThrows(NullPointerException.class,
+ () -> checkArgument(false, messageSupplier));
+ }
+
+ @Test
+ void testCheckArgument_false_withMessageFormat() {
+ LocalDate today = LocalDate.now();
+ final IllegalArgumentException e = assertThrows(IllegalArgumentException.class,
+ () -> checkArgument(false, "String format: %s", today));
+ assertEquals(String.format("String format: %s", today), e.getMessage());
+ }
+
+ @Test
+ void testCheckArgument_false_withNullMessageFormat() {
+ LocalDate today = LocalDate.now();
+ assertThrows(NullPointerException.class,
+ () -> checkArgument(false, null, today));
+ }
+
+ // #endregion - Argument
+
+ // #region - ArgumentNotNull
+
+ @Test
+ void testCheckArgumentNotNull_notNull() {
+ final Object object = new Object();
+ assertEquals(object, checkArgumentNotNull(object));
+ }
+
+ @Test
+ void testCheckArgumentNotNull_notNull_withMessage() {
+ final Object object = new Object();
+ final String IGNORE_ME = "IGNORE_ME"; // NOSONAR
+ assertEquals(object, checkArgumentNotNull(object, IGNORE_ME));
+ }
+
+ @Test
+ void testCheckArgumentNotNull_notNull_withNullMessage() {
+ final Object object = new Object();
+ final String IGNORE_ME = null; // NOSONAR
+ assertEquals(object, checkArgumentNotNull(object, IGNORE_ME));
+ }
+
+ @Test
+ void testCheckArgumentNotNull_notNull_withMessageSupplier() {
+ final Object object = new Object();
+ assertEquals(object, checkArgumentNotNull(object, () -> "Error message: " + LocalDate.now()));
+ }
+
+ @Test
+ void testCheckArgumentNotNull_notNull_withNullMessageSupplier() {
+ final Object object = new Object();
+ final Supplier IGNORE_ME = null; // NOSONAR
+ assertEquals(object, checkArgumentNotNull(object, IGNORE_ME));
+ }
+
+ @Test
+ void testCheckArgumentNotNull_notNull_withMessageFormat() {
+ final Object object = new Object();
+ LocalDate today = LocalDate.now();
+ assertEquals(object, checkArgumentNotNull(object, "String format: %s", today));
+ }
+
+ @Test
+ void testCheckArgumentNotNull_notNull_withNullMessageFormat() {
+ final Object object = new Object();
+ assertEquals(object, checkArgumentNotNull(object, null, LocalDate.now()));
+ }
+
+ @Test
+ void testCheckArgumentNotNull_null() {
+ final Object object = null;
+ final IllegalArgumentException e = assertThrows(IllegalArgumentException.class,
+ () -> checkArgumentNotNull(object));
+
+ assertNull(e.getMessage());
+ assertNull(e.getCause());
+ }
+
+ @Test
+ void testCheckArgumentNotNull_null_withMessage() {
+ final Object object = null;
+ final String message = "testCheckArgumentNotNull_null_withMessage";
+
+ final IllegalArgumentException e = assertThrows(IllegalArgumentException.class,
+ () -> checkArgumentNotNull(object, message));
+
+ assertEquals(message, e.getMessage());
+ assertNull(e.getCause());
+ }
+
+ @Test
+ void testCheckArgumentNotNull_null_withNullMessage() {
+ final Object object = null;
+ final String message = null;
+
+ final IllegalArgumentException e = assertThrows(IllegalArgumentException.class,
+ () -> checkArgumentNotNull(object, message));
+
+ assertNull(e.getMessage());
+ assertNull(e.getCause());
+ }
+
+ @Test
+ void testCheckArgumentNotNull_null_withMessageSupplier() {
+ final Object object = null;
+ final LocalDate today = LocalDate.now();
+ final IllegalArgumentException e = assertThrows(IllegalArgumentException.class,
+ () -> checkArgumentNotNull(object, () -> "Error message: " + today));
+
+ assertEquals("Error message: " + today, e.getMessage());
+ assertNull(e.getCause());
+ }
+
+ @Test
+ void testCheckArgumentNotNull_null_withNullMessageSupplier() {
+ final Object object = null;
+ Supplier messageSupplier = null;
+ assertThrows(NullPointerException.class,
+ () -> checkArgumentNotNull(object, messageSupplier));
+ }
+
+ @Test
+ void testCheckArgumentNotNull_null_withMessageFormat() {
+ final Object object = null;
+ LocalDate today = LocalDate.now();
+ final IllegalArgumentException e = assertThrows(IllegalArgumentException.class,
+ () -> checkArgumentNotNull(object, "String format: %s", today));
+ assertEquals(String.format("String format: %s", today), e.getMessage());
+ }
+
+ @Test
+ void testCheckArgumentNotNull_null_withNullMessageFormat() {
+ final Object object = null;
+ LocalDate today = LocalDate.now();
+ assertThrows(NullPointerException.class,
+ () -> checkArgumentNotNull(object, null, today));
+ }
+
+ // #endregion - ArgumentNotNull
+
+ // #region - State
+
+ @Test
+ void testCheckState_true() {
+ checkState(true);
+ }
+
+ @Test
+ void testCheckState_true_withMessage() {
+ final String IGNORE_ME = "IGNORE_ME"; // NOSONAR
+ checkState(true, IGNORE_ME);
+ }
+
+ @Test
+ void testCheckState_true_withNullMessage() {
+ final String IGNORE_ME = null; // NOSONAR
+ checkState(true, IGNORE_ME);
+ }
+
+ @Test
+ void testCheckState_true_withMessageSupplier() {
+ checkState(true, () -> "Error message: " + LocalDate.now());
+ }
+
+ @Test
+ void testCheckState_true_withNullMessageSupplier() {
+ final Supplier IGNORE_ME = null; // NOSONAR
+ checkState(true, IGNORE_ME);
+ }
+
+ @Test
+ void testCheckState_true_withMessageFormat() {
+ LocalDate today = LocalDate.now();
+ checkState(true, "String format: %s", today);
+ }
+
+ @Test
+ void testCheckState_true_withNullMessageFormat() {
+ checkState(true, null, LocalDate.now());
+ }
+
+ @Test
+ void testCheckState_false() {
+ final IllegalStateException e = assertThrows(IllegalStateException.class,
+ () -> checkState(false));
+
+ assertNull(e.getMessage());
+ assertNull(e.getCause());
+ }
+
+ @Test
+ void testCheckState_false_withMessage() {
+ final String message = "testCheckState_false_withMessage";
+
+ final IllegalStateException e = assertThrows(IllegalStateException.class,
+ () -> checkState(false, message));
+
+ assertEquals(message, e.getMessage());
+ assertNull(e.getCause());
+ }
+
+ @Test
+ void testCheckState_false_withNullMessage() {
+ final String message = null;
+
+ final IllegalStateException e = assertThrows(IllegalStateException.class,
+ () -> checkState(false, message));
+
+ assertNull(e.getMessage());
+ assertNull(e.getCause());
+ }
+
+ @Test
+ void testCheckState_false_withMessageSupplier() {
+ final LocalDate today = LocalDate.now();
+ final IllegalStateException e = assertThrows(IllegalStateException.class,
+ () -> checkState(false, () -> "Error message: " + today));
+
+ assertEquals("Error message: " + today, e.getMessage());
+ assertNull(e.getCause());
+ }
+
+ @Test
+ void testCheckState_false_withNullMessageSupplier() {
+ Supplier messageSupplier = null;
+ assertThrows(NullPointerException.class,
+ () -> checkState(false, messageSupplier));
+ }
+
+ @Test
+ void testCheckState_false_withMessageFormat() {
+ LocalDate today = LocalDate.now();
+ final IllegalStateException e = assertThrows(IllegalStateException.class,
+ () -> checkState(false, "String format: %s", today));
+ assertEquals(String.format("String format: %s", today), e.getMessage());
+ }
+
+ @Test
+ void testCheckState_false_withNullMessageFormat() {
+ LocalDate today = LocalDate.now();
+ assertThrows(NullPointerException.class,
+ () -> checkState(false, null, today));
+ }
+
+ // #endregion - State
+
+ // #region - NotNull
+
+ @Test
+ void testCheckNotNull_notNull() {
+ final Object object = new Object();
+ checkNotNull(object);
+ }
+
+ @Test
+ void testCheckNotNull_notNull_withMessage() {
+ final Object object = new Object();
+ final String IGNORE_ME = "IGNORE_ME"; // NOSONAR
+ checkNotNull(object, IGNORE_ME);
+ }
+
+ @Test
+ void testCheckNotNull_notNull_withNullMessage() {
+ final Object object = new Object();
+ final String IGNORE_ME = null; // NOSONAR
+ checkNotNull(object, IGNORE_ME);
+ }
+
+ @Test
+ void testCheckNotNull_notNull_withMessageSupplier() {
+ final Object object = new Object();
+ checkNotNull(object, () -> "Error message: " + LocalDate.now());
+ }
+
+ @Test
+ void testCheckNotNull_notNull_withNullMessageSupplier() {
+ final Object object = new Object();
+ final Supplier IGNORE_ME = null; // NOSONAR
+ checkNotNull(object, IGNORE_ME);
+ }
+
+ @Test
+ void testCheckNotNull_notNull_withMessageFormat() {
+ final Object object = new Object();
+ LocalDate today = LocalDate.now();
+ checkNotNull(object, "String format: %s", today);
+ }
+
+ @Test
+ void testCheckNotNull_notNull_withNullMessageFormat() {
+ final Object object = new Object();
+ checkNotNull(object, null, LocalDate.now());
+ }
+
+ @Test
+ void testCheckNotNull_null() {
+ final Object object = null;
+ final NullPointerException e = assertThrows(NullPointerException.class,
+ () -> checkNotNull(object));
+
+ assertNull(e.getMessage());
+ assertNull(e.getCause());
+ }
+
+ @Test
+ void testCheckNotNull_null_withMessage() {
+ final Object object = null;
+ final String message = "testCheckNotNull_null_withMessage";
+
+ final NullPointerException e = assertThrows(NullPointerException.class,
+ () -> checkNotNull(object, message));
+
+ assertEquals(message, e.getMessage());
+ assertNull(e.getCause());
+ }
+
+ @Test
+ void testCheckNotNull_null_withNullMessage() {
+ final Object object = null;
+ final String message = null;
+
+ final NullPointerException e = assertThrows(NullPointerException.class,
+ () -> checkNotNull(object, message));
+
+ assertNull(e.getMessage());
+ assertNull(e.getCause());
+ }
+
+ @Test
+ void testCheckNotNull_null_withMessageSupplier() {
+ final Object object = null;
+ final LocalDate today = LocalDate.now();
+ final NullPointerException e = assertThrows(NullPointerException.class,
+ () -> checkNotNull(object, () -> "Error message: " + today));
+
+ assertEquals("Error message: " + today, e.getMessage());
+ assertNull(e.getCause());
+ }
+
+ @Test
+ void testCheckNotNull_null_withNullMessageSupplier() {
+ final Object object = null;
+ Supplier messageSupplier = null;
+ assertThrows(NullPointerException.class,
+ () -> checkNotNull(object, messageSupplier));
+ }
+
+ @Test
+ void testCheckNotNull_null_withMessageFormat() {
+ final Object object = null;
+ LocalDate today = LocalDate.now();
+ final NullPointerException e = assertThrows(NullPointerException.class,
+ () -> checkNotNull(object, "String format: %s", today));
+ assertEquals(String.format("String format: %s", today), e.getMessage());
+ }
+
+ @Test
+ void testCheckNotNull_null_withNullMessageFormat() {
+ final Object object = null;
+ LocalDate today = LocalDate.now();
+ assertThrows(NullPointerException.class,
+ () -> checkNotNull(object, null, today));
+ }
+
+ // #endregion - NotNull
+
+ // #region - Condition
+
+ static final class MyException extends RuntimeException {}
+
+ @Test
+ void testCheckCondition() {
+
+ checkCondition(true, MyException::new);
+
+ final MyException me = new MyException();
+ MyException e = assertThrows(MyException.class, () -> checkCondition(false, () -> me));
+ assertEquals(me, e);
+ }
+
+ // #endregion - Condition
+
+ // ================================
+ // #region - invoke constructor
+ // ================================
+
+ @Test
+ void test_constructor_isNotAccessible_ThrowsIllegalStateException() {
+ Constructor>[] constructors = AssertTools.class.getDeclaredConstructors();
+ Arrays.stream(constructors)
+ .forEach(constructor -> {
+ assertFalse(constructor.isAccessible());
+ constructor.setAccessible(true);
+ Throwable cause = assertThrows(Exception.class, constructor::newInstance)
+ .getCause();
+ assertInstanceOf(IllegalStateException.class, cause);
+ assertEquals("Utility class", cause.getMessage());
+ });
+ }
+
+ // ================================
+ // #endregion - invoke constructor
+ // ================================
+}