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 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 + // ================================ +}