refactor!: 移除 plusone-commons 及 Guava 依赖,内化工具方法

完全移除对外部工具库 plusone-commons 和 Guava 的编译期依赖,
将所需功能内化实现,使项目成为真正的零依赖轻量级 JDBC 封装库。

- 新增 AssertTools: 断言工具(checkArgument / checkNotNull / checkState / checkCondition)
- 新增 NamingTools: 命名转换(camelToSnake)
- 新增 ThrowingConsumer / ThrowingPredicate: 可抛受检异常的函数式接口
- 新增 AssertToolsTests: 完整覆盖断言工具所有方法及边界场景
- pom.xml 移除 plusone-dependencies BOM,直接声明 jsr305 / test 依赖版本
- DefaultBeanRowMapper 使用 NamingTools.camelToSnake 替代 Guava CaseFormat
- ParamBuilder 内联 Optional 展开逻辑,去除 OptionalTools / CollectionTools
- JdbcOperationSupport 用 null/长度判断替代 ArrayTools.isEmpty/isNotEmpty
- NOTICE 移除第三方依赖声明;README 移除无依赖分支说明
- 测试:新增缩写映射(URL/XML/ID/HTML/HTTP)覆盖、TransactionException 构造器测试

BREAKING CHANGE: BatchUpdateStatus 不再实现 IWithIntCode 接口;
plusone-commons 和 Guava 不再作为传递依赖提供。
This commit is contained in:
2026-06-17 17:21:31 +08:00
parent ad0fbb788e
commit a0df4136f4
17 changed files with 1224 additions and 75 deletions

24
NOTICE
View File

@@ -5,36 +5,20 @@ This product is licensed under the Apache License, Version 2.0.
You may obtain a copy of the License at: You may obtain a copy of the License at:
https://www.apache.org/licenses/LICENSE-2.0 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) 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 Copyright 2015-2026 JUnit Team
Licensed under the Eclipse Public License 2.0 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 Copyright (C) 1999-2026, QOS.ch
Licensed under the Eclipse Public License 1.0 Licensed under the Eclipse Public License 1.0
and GNU Lesser General Public License 2.1 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 Copyright 1999-2026 H2 Database Project
Licensed under the MPL 2.0 and EPL 1.0 Licensed under the MPL 2.0 and EPL 1.0
@@ -42,6 +26,6 @@ Test Dependencies (Not included in distribution)
Notes 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. without ORM frameworks.
- For learning and reference purposes only. - For learning and reference purposes only.

View File

@@ -4,8 +4,6 @@
> 注:本项目基于 [Apache License 2.0](https://www.apache.org/licenses/LICENSE-2.0) 开源协议发布。 > 注:本项目基于 [Apache License 2.0](https://www.apache.org/licenses/LICENSE-2.0) 开源协议发布。
> 💡 **无外部依赖说明**:主分支(`dev`)的发布版本依赖 `plusone-commons` 工具库(含 Guava。若您的项目属于不方便引入新依赖的场景如老项目改造可使用 `feature/no-dependencies` 分支,直接复制源码进行使用,无需考虑依赖问题。该分支已移除所有编译期与运行期外部依赖,将所需的工具方法(如断言检查)内化实现,仅保留测试相关的依赖项。
--- ---
## 1. ✨ 核心特性 ## 1. ✨ 核心特性

28
pom.xml
View File

@@ -16,7 +16,11 @@
<maven.compiler.target>8</maven.compiler.target> <maven.compiler.target>8</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<plusone-commons.version>1.1.0-RC2</plusone-commons.version> <!-- Dependency Versions -->
<jsr305.version>3.0.2</jsr305.version>
<junit-jupiter.version>5.14.4</junit-jupiter.version>
<logback.version>1.3.16</logback.version>
<h2.version>2.2.224</h2.version>
</properties> </properties>
<licenses> <licenses>
@@ -40,43 +44,35 @@
<url>https://gitea.zhouxy.xyz/plusone/simple-jdbc</url> <url>https://gitea.zhouxy.xyz/plusone/simple-jdbc</url>
</scm> </scm>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>xyz.zhouxy.plusone</groupId>
<artifactId>plusone-dependencies</artifactId>
<version>${plusone-commons.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<dependencies> <dependencies>
<dependency> <dependency>
<groupId>xyz.zhouxy.plusone</groupId> <groupId>com.google.code.findbugs</groupId>
<artifactId>plusone-commons</artifactId> <artifactId>jsr305</artifactId>
<version>${plusone-commons.version}</version> <version>${jsr305.version}</version>
</dependency> </dependency>
<dependency> <dependency>
<groupId>org.junit.jupiter</groupId> <groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter</artifactId> <artifactId>junit-jupiter</artifactId>
<version>${junit-jupiter.version}</version>
<scope>test</scope> <scope>test</scope>
</dependency> </dependency>
<dependency> <dependency>
<groupId>ch.qos.logback</groupId> <groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId> <artifactId>logback-classic</artifactId>
<version>${logback.version}</version>
<scope>test</scope> <scope>test</scope>
</dependency> </dependency>
<dependency> <dependency>
<groupId>com.h2database</groupId> <groupId>com.h2database</groupId>
<artifactId>h2</artifactId> <artifactId>h2</artifactId>
<version>${h2.version}</version>
<scope>test</scope> <scope>test</scope>
</dependency> </dependency>
</dependencies> </dependencies>
<profiles> <profiles>
<profile> <profile>
<id>release</id> <id>release</id>

View File

@@ -15,8 +15,6 @@
*/ */
package xyz.zhouxy.jdbc; 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
* @see BatchUpdateResult#getStatus() * @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() { public int getCode() {
return code; return code;
} }

View File

@@ -34,9 +34,7 @@ import java.util.stream.Collectors;
import javax.annotation.Nullable; import javax.annotation.Nullable;
import com.google.common.base.CaseFormat; import xyz.zhouxy.jdbc.util.NamingTools;
import xyz.zhouxy.plusone.commons.annotation.StaticFactoryMethod;
/** /**
* DefaultBeanRowMapper * DefaultBeanRowMapper
@@ -90,7 +88,6 @@ public class DefaultBeanRowMapper<T> implements RowMapper<T> {
* @return DefaultBeanRowMapper 对象 * @return DefaultBeanRowMapper 对象
* @throws SQLException 创建 {@code DefaultBeanRowMapper} 出现错误的异常时抛出 * @throws SQLException 创建 {@code DefaultBeanRowMapper} 出现错误的异常时抛出
*/ */
@StaticFactoryMethod(DefaultBeanRowMapper.class)
public static <T> DefaultBeanRowMapper<T> of(Class<T> beanType) throws SQLException { public static <T> DefaultBeanRowMapper<T> of(Class<T> beanType) throws SQLException {
return of(beanType, null); return of(beanType, null);
} }
@@ -104,7 +101,6 @@ public class DefaultBeanRowMapper<T> implements RowMapper<T> {
* @return {@code DefaultBeanRowMapper} 对象 * @return {@code DefaultBeanRowMapper} 对象
* @throws SQLException 创建 {@code DefaultBeanRowMapper} 出现错误的异常时抛出 * @throws SQLException 创建 {@code DefaultBeanRowMapper} 出现错误的异常时抛出
*/ */
@StaticFactoryMethod(DefaultBeanRowMapper.class)
public static <T> DefaultBeanRowMapper<T> of(Class<T> beanType, @Nullable Map<String, String> propertyColMap) public static <T> DefaultBeanRowMapper<T> of(Class<T> beanType, @Nullable Map<String, String> propertyColMap)
throws SQLException { throws SQLException {
try { try {
@@ -167,14 +163,14 @@ public class DefaultBeanRowMapper<T> implements RowMapper<T> {
// Bean 的属性名为小驼峰,对应的列名为下划线 // Bean 的属性名为小驼峰,对应的列名为下划线
Function<? super PropertyDescriptor, String> keyMapper; Function<? super PropertyDescriptor, String> keyMapper;
if (propertyColMap == null || propertyColMap.isEmpty()) { if (propertyColMap == null || propertyColMap.isEmpty()) {
keyMapper = p -> CaseFormat.LOWER_CAMEL.to(CaseFormat.LOWER_UNDERSCORE, p.getName()); keyMapper = p -> NamingTools.camelToSnake(p.getName());
} }
else { else {
keyMapper = p -> { keyMapper = p -> {
String propertyName = p.getName(); String propertyName = p.getName();
String colName = propertyColMap.get(propertyName); String colName = propertyColMap.get(propertyName);
return colName != null ? colName return colName != null ? colName
: CaseFormat.LOWER_CAMEL.to(CaseFormat.LOWER_UNDERSCORE, propertyName); : NamingTools.camelToSnake(propertyName);
}; };
} }
return Arrays.stream(propertyDescriptors) return Arrays.stream(propertyDescriptors)

View File

@@ -16,8 +16,8 @@
package xyz.zhouxy.jdbc; package xyz.zhouxy.jdbc;
import static xyz.zhouxy.plusone.commons.util.AssertTools.checkArgument; import static xyz.zhouxy.jdbc.util.AssertTools.checkArgument;
import static xyz.zhouxy.plusone.commons.util.AssertTools.checkArgumentNotNull; import static xyz.zhouxy.jdbc.util.AssertTools.checkArgumentNotNull;
import java.sql.BatchUpdateException; import java.sql.BatchUpdateException;
import java.sql.Connection; import java.sql.Connection;
@@ -37,8 +37,6 @@ import java.util.List;
import javax.annotation.Nonnull; import javax.annotation.Nonnull;
import javax.annotation.Nullable; import javax.annotation.Nullable;
import xyz.zhouxy.plusone.commons.util.ArrayTools;
/** /**
* JdbcOperationSupport * JdbcOperationSupport
* *
@@ -162,7 +160,7 @@ class JdbcOperationSupport {
throws SQLException { throws SQLException {
assertConnectionNotNull(conn); assertConnectionNotNull(conn);
assertSqlNotNull(sql); assertSqlNotNull(sql);
if (ArrayTools.isNotEmpty(params)) { if (params != null && params.length > 0) {
try (PreparedStatement stmt = conn.prepareStatement(sql)) { try (PreparedStatement stmt = conn.prepareStatement(sql)) {
fillStatement(stmt, params); fillStatement(stmt, params);
return stmt.executeUpdate(); return stmt.executeUpdate();
@@ -191,7 +189,7 @@ class JdbcOperationSupport {
assertConnectionNotNull(conn); assertConnectionNotNull(conn);
assertSqlNotNull(sql); assertSqlNotNull(sql);
assertRowMapperNotNull(rowMapper); assertRowMapperNotNull(rowMapper);
if (ArrayTools.isNotEmpty(params)) { if (params != null && params.length > 0) {
try (PreparedStatement stmt = conn.prepareStatement(sql, Statement.RETURN_GENERATED_KEYS)) { try (PreparedStatement stmt = conn.prepareStatement(sql, Statement.RETURN_GENERATED_KEYS)) {
fillStatement(stmt, params); fillStatement(stmt, params);
stmt.executeUpdate(); stmt.executeUpdate();
@@ -308,7 +306,7 @@ class JdbcOperationSupport {
@Nullable Object[] params, @Nullable Object[] params,
@Nonnull ResultHandler<T> resultHandler) @Nonnull ResultHandler<T> resultHandler)
throws SQLException { throws SQLException {
if (ArrayTools.isNotEmpty(params)) { if (params != null && params.length > 0) {
try (PreparedStatement stmt = createPreparedStatementInternal(conn, sql, params); try (PreparedStatement stmt = createPreparedStatementInternal(conn, sql, params);
ResultSet rs = stmt.executeQuery()) { ResultSet rs = stmt.executeQuery()) {
return resultHandler.handle(rs); return resultHandler.handle(rs);

View File

@@ -29,10 +29,7 @@ import java.util.OptionalLong;
import java.util.function.Function; import java.util.function.Function;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import xyz.zhouxy.plusone.commons.collection.CollectionTools; import xyz.zhouxy.jdbc.util.AssertTools;
import xyz.zhouxy.plusone.commons.util.ArrayTools;
import xyz.zhouxy.plusone.commons.util.AssertTools;
import xyz.zhouxy.plusone.commons.util.OptionalTools;
/** /**
* ParamBuilder * ParamBuilder
@@ -66,7 +63,7 @@ public class ParamBuilder {
* @return 参数数组 * @return 参数数组
*/ */
public static Object[] buildParams(final Object... params) { public static Object[] buildParams(final Object... params) {
if (ArrayTools.isEmpty(params)) { if (params == null || params.length == 0) {
return EMPTY_OBJECT_ARRAY; return EMPTY_OBJECT_ARRAY;
} }
return Arrays.stream(params) return Arrays.stream(params)
@@ -91,16 +88,16 @@ public class ParamBuilder {
return param; return param;
} }
if (param instanceof Optional) { if (param instanceof Optional) {
return OptionalTools.orElseNull((Optional<?>) param); return ((Optional<?>) param).orElse(null);
} }
if (param instanceof OptionalInt) { if (param instanceof OptionalInt) {
return OptionalTools.toInteger((OptionalInt) param); return ((OptionalInt) param).isPresent() ? ((OptionalInt) param).getAsInt() : null;
} }
if (param instanceof OptionalLong) { if (param instanceof OptionalLong) {
return OptionalTools.toLong((OptionalLong) param); return ((OptionalLong) param).isPresent() ? ((OptionalLong) param).getAsLong() : null;
} }
if (param instanceof OptionalDouble) { if (param instanceof OptionalDouble) {
return OptionalTools.toDouble((OptionalDouble) param); return ((OptionalDouble) param).isPresent() ? ((OptionalDouble) param).getAsDouble() : null;
} }
return param; return param;
} }
@@ -121,7 +118,7 @@ public class ParamBuilder {
public static <T> List<Object[]> buildBatchParams(final Collection<T> c, final Function<T, Object[]> func) { public static <T> List<Object[]> buildBatchParams(final Collection<T> c, final Function<T, Object[]> func) {
AssertTools.checkNotNull(c, "The collection can not be null."); AssertTools.checkNotNull(c, "The collection can not be null.");
AssertTools.checkNotNull(func, "The func can not be null."); AssertTools.checkNotNull(func, "The func can not be null.");
if (CollectionTools.isEmpty(c)) { if (c.isEmpty()) {
return Collections.emptyList(); return Collections.emptyList();
} }
return c.stream().map(func).collect(Collectors.toList()); return c.stream().map(func).collect(Collectors.toList());

View File

@@ -26,7 +26,7 @@ import javax.annotation.Nonnull;
import javax.annotation.Nullable; import javax.annotation.Nullable;
import javax.sql.DataSource; import javax.sql.DataSource;
import xyz.zhouxy.plusone.commons.util.AssertTools; import xyz.zhouxy.jdbc.util.AssertTools;
/** /**
* JDBC 操作的模板类,对原生 JDBC 进行轻量封装,提供查询、更新、批量操作等便捷方法。 * JDBC 操作的模板类,对原生 JDBC 进行轻量封装,提供查询、更新、批量操作等便捷方法。

View File

@@ -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;
/**
* 可抛出受检异常的函数式接口。
*
* <p>
* 类似于 {@link java.util.function.Consumer},但 {@code accept} 方法允许抛出受检异常。
* </p>
*
* @param <T> 输入类型
* @param <E> 允许抛出的异常类型
* @author ZhouXY
* @since 1.1.0
*/
@FunctionalInterface
public interface ThrowingConsumer<T, E extends Exception> {
/**
* 对给定参数执行此操作。
*
* @param t 输入参数
* @throws E 异常
*/
void accept(T t) throws E;
}

View File

@@ -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;
/**
* 可抛出受检异常的谓词函数式接口。
*
* <p>
* 类似于 {@link java.util.function.Predicate},但 {@code test} 方法允许抛出受检异常。
* </p>
*
* @param <T> 输入类型
* @param <E> 允许抛出的异常类型
* @author ZhouXY
* @since 1.1.0
*/
@FunctionalInterface
public interface ThrowingPredicate<T, E extends Exception> {
/**
* 对给定参数执行此谓词判断。
*
* @param t 输入参数
* @return 谓词判断结果
* @throws E 异常
*/
boolean test(T t) throws E;
}

View File

@@ -26,9 +26,7 @@ import javax.annotation.Nonnull;
import javax.annotation.Nullable; import javax.annotation.Nullable;
import javax.sql.DataSource; import javax.sql.DataSource;
import xyz.zhouxy.plusone.commons.function.ThrowingConsumer; import xyz.zhouxy.jdbc.util.AssertTools;
import xyz.zhouxy.plusone.commons.function.ThrowingPredicate;
import xyz.zhouxy.plusone.commons.util.AssertTools;
/** /**
* 事务模板,提供事务执行能力。 * 事务模板,提供事务执行能力。
@@ -64,7 +62,12 @@ public class TransactionTemplate {
@Nonnull @Nonnull
private final DataSource dataSource; private final DataSource dataSource;
public TransactionTemplate(@Nonnull DataSource dataSource) { /**
* 构造一个 {@code TransactionTemplate} 实例
*
* @param dataSource 数据源,用于获取数据库连接;不可为 {@code null}
*/
public TransactionTemplate(DataSource dataSource) {
AssertTools.checkNotNull(dataSource); AssertTools.checkNotNull(dataSource);
this.dataSource = dataSource; this.dataSource = dataSource;
} }

View File

@@ -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;
/**
* 断言工具
*
* <p>
* 本工具类不封装过多判断逻辑,鼓励充分使用项目中的工具类进行逻辑判断。
*
* <pre>
* checkArgument(StringUtils.hasText(str), "The argument cannot be blank.");
* checkState(ArrayUtils.isNotEmpty(result), "The result cannot be empty.");
* checkCondition(!CollectionUtils.isEmpty(roles),
* () -&gt; new InvalidInputException("The roles cannot be empty."));
* checkCondition(RegexTools.matches(email, PatternConsts.EMAIL),
* "must be a well-formed email address");
* </pre>
*
* @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<String> 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 <T> 入参类型
* @param obj 入参
* @return 校验通过时返回入参
* @throws IllegalArgumentException 当 {@code obj} 为 {@code null} 时抛出
*/
public static <T> T checkArgumentNotNull(T obj) {
if (obj == null) {
throw new IllegalArgumentException();
}
return obj;
}
/**
* 判断入参不为 {@code null}
*
* @param <T> 入参类型
* @param obj 入参
* @param errorMessage 异常信息
* @return 校验通过时返回入参
* @throws IllegalArgumentException 当 {@code obj} 为 {@code null} 时抛出
*/
public static <T> T checkArgumentNotNull(T obj, String errorMessage) {
if (obj == null) {
throw new IllegalArgumentException(errorMessage);
}
return obj;
}
/**
* 判断入参不为 {@code null}
*
* @param <T> 入参类型
* @param obj 入参
* @param errorMessageSupplier 异常信息
* @return 校验通过时返回入参
* @throws IllegalArgumentException 当 {@code obj} 为 {@code null} 时抛出
*/
public static <T> T checkArgumentNotNull(T obj, Supplier<String> errorMessageSupplier) {
if (obj == null) {
throw new IllegalArgumentException(errorMessageSupplier.get());
}
return obj;
}
/**
* 判断入参不为 {@code null}
*
* @param <T> 入参类型
* @param obj 入参
* @param errorMessageTemplate 异常信息模板
* @param errorMessageArgs 异常信息参数
* @return 校验通过时返回入参
* @throws IllegalArgumentException 当 {@code obj} 为 {@code null} 时抛出
*/
public static <T> 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<String> 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 <T> 入参类型
* @param obj 入参
* @throws NullPointerException 当 {@code obj} 为 {@code null} 时抛出
*/
public static <T> void checkNotNull(T obj) {
if (obj == null) {
throw new NullPointerException();
}
}
/**
* 判空
*
* @param <T> 入参类型
* @param obj 入参
* @param errorMessage 异常信息
* @throws NullPointerException 当 {@code obj} 为 {@code null} 时抛出
*/
public static <T> void checkNotNull(T obj, String errorMessage) {
if (obj == null) {
throw new NullPointerException(errorMessage);
}
}
/**
* 判空
*
* @param <T> 入参类型
* @param obj 入参
* @param errorMessageSupplier 异常信息
* @throws NullPointerException 当 {@code obj} 为 {@code null} 时抛出
*/
public static <T> void checkNotNull(T obj, Supplier<String> errorMessageSupplier) {
if (obj == null) {
throw new NullPointerException(errorMessageSupplier.get());
}
}
/**
* 判空
*
* @param <T> 入参类型
* @param obj 入参
* @param errorMessageTemplate 异常信息模板
* @param errorMessageArgs 异常信息参数
* @throws NullPointerException 当 {@code obj} 为 {@code null} 时抛出
*/
public static <T> void checkNotNull(T obj,
String errorMessageTemplate, Object... errorMessageArgs) {
if (obj == null) {
throw new NullPointerException(String.format(errorMessageTemplate, errorMessageArgs));
}
}
// ================================
// #endregion - NotNull
// ================================
// ================================
// #region - Condition
// ================================
/**
* 当条件不满足时抛出异常。
*
* @param <T> 异常类型
* @param condition 条件
* @param e 异常
* @throws T 当条件不满足时抛出异常
*/
public static <T extends Exception> void checkCondition(boolean condition, Supplier<T> e)
throws T {
if (!condition) {
throw e.get();
}
}
// ================================
// #endregion
// ================================
// ================================
// #region - constructor
// ================================
private AssertTools() {
throw new IllegalStateException("Utility class");
}
// ================================
// #endregion
// ================================
}

View File

@@ -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
*
* <p>转换规则:
* <ul>
* <li>小写→大写边界插入下划线:{@code userName → user_name}</li>
* <li>连续大写缩写视为整体,在其末尾小写边界插入下划线:{@code XMLParser → xml_parser}</li>
* <li>纯小写保持不变:{@code username → username}</li>
* <li>{@code null} 或空字符串返回原值</li>
* </ul>
*
* @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
// ================================
}

View File

@@ -6,6 +6,7 @@ import static xyz.zhouxy.jdbc.ParamBuilder.*;
import java.sql.SQLException; import java.sql.SQLException;
import java.sql.Statement; import java.sql.Statement;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections; import java.util.Collections;
import java.util.List; import java.util.List;
import java.util.Optional; import java.util.Optional;
@@ -16,8 +17,6 @@ import org.junit.jupiter.api.Test;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import com.google.common.collect.Lists;
import xyz.zhouxy.jdbc.BatchUpdateErrorInfo; import xyz.zhouxy.jdbc.BatchUpdateErrorInfo;
import xyz.zhouxy.jdbc.BatchUpdateResult; import xyz.zhouxy.jdbc.BatchUpdateResult;
import xyz.zhouxy.jdbc.BatchUpdateStatus; import xyz.zhouxy.jdbc.BatchUpdateStatus;
@@ -170,7 +169,7 @@ class BatchUpdateTest extends BaseH2Test {
// #region - 包含错误数据 // #region - 包含错误数据
// ================================ // ================================
final List<User> userListContainingInvalidData = Lists.newArrayList( final List<User> userListContainingInvalidData = Arrays.asList(
// batch 0 // batch 0
new User("test_0001", "test_0001@example.com", 1, 1L, true), new User("test_0001", "test_0001@example.com", 1, 1L, true),
new User("test_0002", "test_0002@example.com", 1, 1L, true), new User("test_0002", "test_0002@example.com", 1, 1L, true),

View File

@@ -157,6 +157,63 @@ class RowMapperTest extends BaseH2Test {
assertEquals("alice", user.get().getUsername()); 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", "<div>test</div>", "ApacheHttpClient"});
RowMapper<AcronymBean> rowMapper = RowMapper.beanRowMapper(AcronymBean.class);
Optional<AcronymBean> 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("<div>test</div>", 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<User> rowMapper = RowMapper.beanRowMapper(User.class);
Optional<User> 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 ==================== // ==================== HASH_MAP_MAPPER ====================
@Test @Test
@@ -253,4 +310,74 @@ class RowMapperTest extends BaseH2Test {
this.name = name; this.name = name;
} }
} }
/**
* 包含连续大写缩写属性的 Bean用于验证 camelToSnake 的缩写处理。
*
* <p>覆盖场景:
* <ul>
* <li>homeURL — 缩写在末尾(三字母 URL</li>
* <li>xmlParser — 缩写在前(三字母 XML</li>
* <li>parseURL — 缩写在末尾</li>
* <li>userID — 两字母缩写在末尾ID</li>
* <li>parseHTML — 四字母缩写在末尾HTML</li>
* <li>multiHttpClient — 缩写夹在词中HTTP</li>
* </ul>
*/
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;
}
}
} }

View File

@@ -234,6 +234,15 @@ class TransactionTest extends BaseH2Test {
// ==================== TransactionException ==================== // ==================== 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 @Test
@DisplayName("TransactionException双参构造器") @DisplayName("TransactionException双参构造器")
void testTransactionExceptionWithMessage() { void testTransactionExceptionWithMessage() {
@@ -242,4 +251,12 @@ class TransactionTest extends BaseH2Test {
assertEquals("自定义消息", ex.getMessage()); assertEquals("自定义消息", ex.getMessage());
assertSame(cause, ex.getCause()); assertSame(cause, ex.getCause());
} }
@Test
@DisplayName("TransactionExceptionnull cause")
void testTransactionExceptionNullCause() {
TransactionException ex = new TransactionException(null);
assertEquals("Transaction failed during execution", ex.getMessage());
assertNull(ex.getCause());
}
} }

View File

@@ -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<String> 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<String> 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<String> 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<String> 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<String> 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<String> 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<String> 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<String> 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
// ================================
}