13 Commits

Author SHA1 Message Date
473e1ce223 release: 1.0.3 2025-04-19 02:03:11 +08:00
3b9f0a30c2 chore: 更新依赖 2025-04-19 02:02:31 +08:00
34076294a7 fix: 修复 JDK17+ 环境下测试用例 PagingAndSortingQueryParamsTests#testGson 不通过的问题
该用例在 JDK17+ 环境下使用 gson 进行序列化时,报 `com.google.gson.JsonIOException: Failed making field 'java.time.LocalDateTime#date' accessible; either increase its visibility or write a custom TypeAdapter for its declaring type`。

See: https://github.com/google/gson/blob/main/Troubleshooting.md#reflection-inaccessible
2025-04-19 02:01:32 +08:00
c6df5cd925 docs: fix param name 2025-04-19 02:00:52 +08:00
91c0c18960 1.0.2 2025-04-03 11:55:26 +08:00
bc7b3eefa8 fix: 补充 ThrowingPredicate 缺失的 FunctionalInterface 注解 2025-04-03 11:51:32 +08:00
746603f939 1.0.1 2025-03-28 12:10:44 +08:00
73a99b630c doc: 修改 since 信息
1.0.0 之前新增的,其 since 修改为 1.0.0,统一以 1.0.0 作为初始版本

fix plusone/plusone-commons#30
2025-03-28 11:35:12 +08:00
f5b04a7ae8 docs: 改正 PredicateTools 的 javadoc (#39)
Reviewed-on: http://zhouxy.xyz:3000/plusone/plusone-commons/pulls/39
2025-03-28 10:39:56 +08:00
dde3d1d172 docs: 删除 IdWorker 的 author 信息
Reviewed-on: http://zhouxy.xyz:3000/plusone/plusone-commons/pulls/37
2025-03-28 09:31:02 +08:00
e3d60ffe97 docs: 删除 IdWorker 的 author 信息
该工具来自 seata,并非本项目原创,写 javadoc 时忘记修改模板中的 author,而seata 源代码中早就删除了该类的 author 信息,故应先删除。

见:https://github.com/apache/incubator-seata/pull/6179

close plusone/plusone-commons#31
2025-03-28 09:22:55 +08:00
aadd1857de docs: 改正 OptionalTools 的 javadoc
Reviewed-on: http://zhouxy.xyz:3000/plusone/plusone-commons/pulls/35
2025-03-28 09:08:23 +08:00
e98fe66b65 docs: fix javadoc
fix issue plusone/plusone-commons#29
2025-03-28 08:55:35 +08:00
90 changed files with 666 additions and 4211 deletions

220
README.md
View File

@@ -1,220 +0,0 @@
## 一、annotation - 注解
### 1. StaticFactoryMethod
标识静态工厂方法。 *《Effective Java》***Item1** 建议考虑用静态工厂方法替换构造器, 因而考虑有一个注解可以标记一下静态工厂方法,以和其它方法进行区分。
### 2. ReaderMethod 和 WriterMethod
分别标识读方法(如 getter或写方法如 setter
最早是写了一个集合类,为了方便判断使用读写锁时,哪些情况下使用读锁,哪些情况下使用写锁。
### 3. UnsupportedOperation
标识该方法不被支持或没有实现,将抛出 `UnsupportedOperationException`。 为了方便在使用时,不需要点进源码,就能知道该方法没有实现。
### 4. Virtual
Java 非 final 的实例方法,对应 C++/C# 中的虚方法,允许被子类覆写。 Virtual 注解旨在设计父类时,强调该方法父类虽然有默认实现,但子类可以根据自己的需要覆写。
### 5. ValueObject
标记一个类,表示其作为值对象,区别于 Entity。
## 二、base - 基础组件
### 1. Ref
`Ref` 包装了一个值,表示对该值的应用。
灵感来自于 C# 的 ref 参数修饰符。C# 允许通过以下方式,将值返回给调用端:
```C#
void Method(ref int refArgument)
{
refArgument = refArgument + 44;
}
int number = 1;
Method(ref number);
Console.WriteLine(number); // Output: 45
```
`Ref` 使 Java 可以达到类似的效果,如:
```java
void method(Ref<Integer> refArgument) {
refArgument.transformValue(i -> i + 44);
}
Ref<Integer> number = Ref.of(1);
method(number);
System.out.println(number.getValue()); // Output: 45
```
当一个方法需要产生多个结果时,无法有多个返回值,可以使用 `Ref` 作为参数传入,方法内部修改 `Ref` 的值。 调用方在调用方法之后,使用 `getValue()` 获取结果。
```java
String method(Ref<Integer> intRefArgument, Ref<String> strRefArgument) {
intRefArgument.transformValue(i -> i + 44);
strRefArgument.setValue("Hello " + strRefArgument.getValue());
return "Return string";
}
Ref<Integer> number = Ref.of(1);
Ref<String> str = Ref.of("Java");
String result = method(number, str);
System.out.println(number.getValue()); // Output: 45
System.out.println(str.getValue()); // Output: Hello Java
System.out.println(result); // Output: Return string
```
### 2. IWithCode
类似于枚举这样的类型,通常需要设置固定的码值表示对应的含义。 可实现 `IWithCode`、`IWithIntCode`、`IWithLongCode`,便于在需要的地方对这些接口的实现进行处理。
## 三、collection - 集合
### 1. CollectionTools
集合工具类
## 四、constant - 常量
### 1. 正则常量
`RegexConsts` 包含常见正则表达式;`PatternConsts` 包含对应的 `Pattern` 对象
## 五、exception - 异常
### 1. MultiTypesException - 多类型异常
异常在不同场景下被抛出,可以用不同的枚举值,表示不同的场景类型。
异常实现 `MultiTypesException` 的 `MultiTypesException#getType` 方法,返回对应的场景类型。
表示场景类型的枚举实现 `MultiTypesException.ExceptionType`,其中的工厂方法用于创建类型对象。
```java
public final class LoginException
extends RuntimeException
implements MultiTypesException<LoginException, LoginException.Type> {
private final Type type;
private LoginException(@Nonnull Type type, @Nonnull String message) {
super(message);
this.type = type;
}
private LoginException(@Nonnull Type type, @Nonnull Throwable cause) {
super(cause);
this.type = type;
}
private LoginException(@Nonnull Type type,
@Nonnull String message,
@Nonnull Throwable cause) {
super(message, cause);
this.type = type;
}
@Override
public @Nonnull Type getType() {
return this.type;
}
// ...
public enum Type implements ExceptionType {
DEFAULT("00", "当前会话未登录"),
NOT_TOKEN("10", "未提供token"),
INVALID_TOKEN("20", "token无效"),
TOKEN_TIMEOUT("30", "token已过期"),
BE_REPLACED("40", "token已被顶下线"),
KICK_OUT("50", "token已被踢下线"),
;
@Nonnull
private final String code;
@Nonnull
private final String defaultMessage;
Type(@Nonnull String code, @Nonnull String defaultMessage) {
this.code = code;
this.defaultMessage = defaultMessage;
}
@Override
public @Nonnull String getCode() {
return code;
}
@Override
public @Nonnull String getDefaultMessage() {
return defaultMessage;
}
@Override
public @Nonnull LoginException create() {
return new LoginException(this, this.defaultMessage);
}
@Override
public @Nonnull LoginException create(String message) {
return new LoginException(this, message);
}
@Override
public @Nonnull LoginException create(Throwable cause) {
return new LoginException(this, cause);
}
@Override
public @Nonnull LoginException create(String message, Throwable cause) {
return new LoginException(this, message, cause);
}
}
}
```
使用时,可以使用这种方式创建并抛出异常:
```java
throw LoginException.Type.TOKEN_TIMEOUT.create();
```
### 2. 业务异常
预设常见的业务异常。可继承 `BizException` 自定义业务异常。
### 3. 系统异常
预设常见的系统异常。可继承 `SysException` 自定义系统异常。
## 六、function - 函数式编程
### 1. PredicateTools
`PredicateTools` 用于 `Predicate` 的相关操作。
### 2. Functional interfaces
补充可能用得上的函数式接口:
| Group | FunctionalInterface | method |
| ------------- | -------------------- | -------------------------------- |
| UnaryOperator | BoolUnaryOperator | boolean applyAsBool (boolean) |
| UnaryOperator | CharUnaryOperator | char applyAsChar(char) |
| Throwing | Executable | void execute() throws E |
| Throwing | ThrowingConsumer | void accept(T) throws E |
| Throwing | ThrowingFunction | R apply(T) throws E |
| Throwing | ThrowingPredicate | boolean test(T) throws E |
| Throwing | ThrowingSupplier | T get() throws E |
| Optional | OptionalSupplier | Optional<T> get() throws E |
| Optional | ToOptionalBiFunction | Optional<R> apply(T,U) |
| Optional | ToOptionalFunction | Optional<R> apply(T) |
## 七、model - 业务建模组件
包含业务建模可能用到的性别、身份证等元素,也包含数据传输对象,如分页查询参数、响应结果、分页结果等。
### 数据传输对象
#### 1. 分页
分页组件由 `PagingAndSortingQueryParams` 作为入参, 因为分页必须伴随着排序,不然可能出现同一个对象重复出现在不同页,有的对象不被查询到的情况, 所以分页查询的入参必须包含排序条件。
用户可继承 `PagingAndSortingQueryParams` 构建自己的分页查询入参,需在构造器中调用 `PagingAndSortingQueryParams` 的构造器,传入一个 Map 作为白名单, key 是供前端指定用于排序的**属性名**value 是对应数据库中的**字段名**,只有在白名单中指定的属性名才允许作为排序条件。
`PagingAndSortingQueryParams` 包含三个主要的属性:
- **size** - 每页显示的记录数
- **pageNum** - 当前页码
- **orderBy** - 排序条件
其中 `orderBy` 是一个 List可以指定多个排序条件每个排序条件是一个字符串 格式为“**属性名-ASC**”或“**属性名-DESC**”,分别表示升序和降序。
比如前端传入的 orderBy 为 ["name-ASC","age-DESC"],意味着要按 name 进行升序name 相同的情况下则按 age 进行降序。
使用时调用 `PagingAndSortingQueryParams#buildPagingParams()` 方法获取分页参数 `PagingParams`。
分页结果可以存放到 `PageResult` 中,作为出参。
#### 2. UnifiedResponse
UnifiedResponse 对返回给前端的数据进行封装,包含 `code`、`message`、`data。`
可使用 `UnifiedResponses` 快速构建 `UnifiedResponse` 对象。 `UnifiedResponses` 默认的成功代码为 "2000000" 用户按测试类 `CustomUnifiedResponseFactoryTests` 中所示范的,继承 `UnifiedResponses` 实现自己的工厂类, 自定义 `SUCCESS_CODE` 和 `DEFAULT_SUCCESS_MSG` 和工厂方法。 见 [issue#22](http://zhouxy.xyz:3000/plusone/plusone-commons/issues/22)。
## 八、time - 时间 API
### 1. 季度
模仿 JDK 的 `java.time.Month` 和 `java.time.YearMonth` 实现 `Quarter`、`YearQuarter`,对季度进行建模。
## 九、util - 工具类
包含树构建器(`TreeBuilder`)、断言工具(`AssertTools`、ID 生成器(`IdGenerator`)及其它实用工具类。

View File

@@ -1,45 +0,0 @@
{
"version": "0.2",
"ignorePaths": [
"src/test"
],
"dictionaryDefinitions": [],
"dictionaries": [],
"words": [],
"ignoreWords": [
"aliyun",
"baomidou",
"Batis",
"Consolas",
"cspell",
"databind",
"datasource",
"fasterxml",
"findbugs",
"gson",
"Hikari",
"hutool",
"Jdbc",
"joda",
"logback",
"mapstruct",
"Multimap",
"Multiset",
"mybatis",
"Nonnull",
"NOSONAR",
"okhttp",
"overriden",
"plusone",
"println",
"projectlombok",
"regexs",
"Seata",
"sonarlint",
"springframework",
"TWEPOCH",
"Validatable",
"zhouxy"
],
"import": []
}

View File

@@ -6,7 +6,7 @@
<groupId>xyz.zhouxy.plusone</groupId>
<artifactId>plusone-commons</artifactId>
<version>1.1.0-SNAPSHOT</version>
<version>1.0.3</version>
<properties>
<!-- Basic properties -->

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2023-2025 the original author or authors.
* Copyright 2023-2024 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.

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2023-2025 the original author or authors.
* Copyright 2023-2024 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.

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2023-2025 the original author or authors.
* Copyright 2023-2024 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.

View File

@@ -1,64 +0,0 @@
/*
* Copyright 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.
*/
/**
* <h2>注解</h2>
*
* <h3>
* 1. {@link StaticFactoryMethod}
* </h3>
* <p>
* 标识<b>静态工厂方法</b>。
* 《Effective Java》的 Item1 建议考虑用静态工厂方法替换构造器,
* 因而考虑有一个注解可以标记一下静态工厂方法,以和其它方法进行区分。
* </p>
*
* <h3>
* 2. {@link ReaderMethod} 和 {@link WriterMethod}
* </h3>
* <p>
* 分别标识<b>读方法</b>(如 getter或<b>写方法</b>(如 setter
* </p>
* <p>
* 最早是写了一个集合类,为了方便判断使用读写锁时,哪些情况下使用读锁,哪些情况下使用写锁。
* </p>
*
* <h3>
* 3. {@link UnsupportedOperation}
* </h3>
* <p>
* 标识该方法不被支持或没有实现,将抛出 {@link UnsupportedOperationException}。
* 为了方便在使用时,不需要点进源码,就能知道该方法没有实现。
* </p>
*
* <h3>
* 4. {@link Virtual}
* </h3>
* <p>
* Java 非 final 的实例方法,对应 C++/C# 中的虚方法,允许被子类覆写。
* {@link Virtual} 注解旨在设计父类时,强调该方法父类虽然有默认实现,但子类可以根据自己的需要覆写。
* </p>
*
* <h3>
* 5. {@link ValueObject}
* </h3>
* <p>
* 标记一个类,表示其作为值对象,区别于 Entity。
* </p>
*
* @author <a href="http://zhouxy.xyz:3000/ZhouXY108">ZhouXY</a>
*/
package xyz.zhouxy.plusone.commons.annotation;

View File

@@ -29,50 +29,21 @@ import javax.annotation.Nullable;
* @author <a href="http://zhouxy.xyz:3000/ZhouXY108">ZhouXY</a>
*/
public interface IWithCode<T> {
/**
* 获取码值
* @return 码值
*/
@Nonnull
T getCode();
/**
* 判断 {@code code} 与给定的值是否相等
*
* @param code 用于判断的值
* @return 判断结果
*/
default boolean isCodeEquals(@Nullable T code) {
return Objects.equals(getCode(), code);
}
/**
* 判断是否与给定的 {@link IWithCode} 有着相等的 {@code code}
*
* @param other 用于比较的对象
* @return 判断结果
*/
default boolean isSameCodeAs(@Nullable IWithCode<?> other) {
return other != null && Objects.equals(getCode(), other.getCode());
}
/**
* 判断是否与给定的 {@link IWithIntCode} 有着相等的 {@code code}
*
* @param other 用于比较的对象
* @return 判断结果
*/
default boolean isSameCodeAs(@Nullable IWithIntCode other) {
return other != null && Objects.equals(getCode(), other.getCode());
}
/**
* 判断是否与给定的 {@link IWithLongCode} 有着相等的 {@code code}
*
* @param other 用于比较的对象
* @return 判断结果
*/
default boolean isSameCodeAs(@Nullable IWithLongCode other) {
return other != null && Objects.equals(getCode(), other.getCode());
}

View File

@@ -28,49 +28,20 @@ import javax.annotation.Nullable;
* @author <a href="http://zhouxy.xyz:3000/ZhouXY108">ZhouXY</a>
*/
public interface IWithIntCode {
/**
* 获取码值
* @return 码值
*/
int getCode();
/**
* 判断 {@code code} 与给定的值是否相等
*
* @param code 用于判断的值
* @return 判断结果
*/
default boolean isCodeEquals(int code) {
return getCode() == code;
}
/**
* 判断是否与给定的 {@link IWithCode} 有着相等的 {@code code}
*
* @param other 用于比较的对象
* @return 判断结果
*/
default boolean isSameCodeAs(@Nullable IWithCode<?> other) {
return other != null && Objects.equals(getCode(), other.getCode());
}
/**
* 判断是否与给定的 {@link IWithIntCode} 有着相等的 {@code code}
*
* @param other 用于比较的对象
* @return 判断结果
*/
default boolean isSameCodeAs(@Nullable IWithIntCode other) {
return other != null && getCode() == other.getCode();
}
/**
* 判断是否与给定的 {@link IWithLongCode} 有着相等的 {@code code}
*
* @param other 用于比较的对象
* @return 判断结果
*/
default boolean isSameCodeAs(@Nullable IWithLongCode other) {
return other != null && getCode() == other.getCode();
}

View File

@@ -28,49 +28,20 @@ import javax.annotation.Nullable;
* @author <a href="http://zhouxy.xyz:3000/ZhouXY108">ZhouXY</a>
*/
public interface IWithLongCode {
/**
* 获取码值
* @return 码值
*/
long getCode();
/**
* 判断 {@code code} 与给定的值是否相等
*
* @param code 用于判断的值
* @return 判断结果
*/
default boolean isCodeEquals(long code) {
return getCode() == code;
}
/**
* 判断是否与给定的 {@link IWithCode} 有着相等的 {@code code}
*
* @param other 用于比较的对象
* @return 判断结果
*/
default boolean isSameCodeAs(@Nullable IWithCode<?> other) {
return other != null && Objects.equals(getCode(), other.getCode());
}
/**
* 判断是否与给定的 {@link IWithIntCode} 有着相等的 {@code code}
*
* @param other 用于比较的对象
* @return 判断结果
*/
default boolean isSameCodeAs(@Nullable IWithIntCode other) {
return other != null && getCode() == other.getCode();
}
/**
* 判断是否与给定的 {@link IWithLongCode} 有着相等的 {@code code}
*
* @param other 用于比较的对象
* @return 判断结果
*/
default boolean isSameCodeAs(@Nullable IWithLongCode other) {
return other != null && getCode() == other.getCode();
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2024-2025 the original author or authors.
* Copyright 2024 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.
@@ -79,100 +79,43 @@ public final class Ref<T> {
this.value = value;
}
/**
* 创建对象引用
*
* @param <T> 引用的类型
* @param value 引用的对象
* @return {@code Ref} 对象
*/
public static <T> Ref<T> of(@Nullable T value) {
return new Ref<>(value);
}
/**
* 创建空引用
*
* @param <T> 引用的类型
* @return 空引用
*/
public static <T> Ref<T> empty() {
return new Ref<>(null);
}
/**
* 获取引用的对象
*
* @return 引用的对象
*/
@Nullable
public T getValue() {
return value;
}
/**
* 设置引用的对象
*
* @param value 要引用的对象
*/
public void setValue(@Nullable T value) {
this.value = value;
}
/**
* 使用 {@link UnaryOperator} 修改 {@code Ref} 内部引用的对象
*
* @param operator 修改逻辑
*/
public void transformValue(UnaryOperator<T> operator) {
this.value = operator.apply(this.value);
}
/**
* 使用 {@link Function} 修改所引用的对象,返回新的 {@code Ref}
*
* @param <R> 结果的引用类型
* @param function 修改逻辑
* @return 修改后的对象的引用
*/
public <R> Ref<R> transform(Function<? super T, R> function) {
return Ref.of(function.apply(this.value));
}
/**
* 使用 {@link Predicate} 检查引用的对象
*
* @param predicate 判断逻辑
* @return 判断结果
*/
public boolean checkValue(Predicate<? super T> predicate) {
return predicate.test(this.value);
}
/**
* 将引用的对象作为入参,执行 {@link Consumer} 的逻辑
*
* @param consumer 要执行的逻辑
*/
public void execute(Consumer<? super T> consumer) {
consumer.accept(value);
}
/**
* 判断所引用的对象是否为 {@code null}
*
* @return 是否为 {@code null}
*/
public boolean isNull() {
return this.value == null;
}
/**
* 判断所引用的对象是否不为 {@code null}
*
* @return 是否不为 {@code null}
*/
public boolean isNotNull() {
return this.value != null;
}

View File

@@ -15,9 +15,9 @@
*/
/**
* <h2>基础组件</h2>
* 基础组件
*
* <h3>1. Ref</h3>
* <h2>Ref</h2>
* <p>
* {@link Ref} 包装了一个值,表示对该值的应用。
* </p>
@@ -61,13 +61,11 @@
* System.out.println(result); // Output: Return string
* </pre>
*
* <h3>2. IWithCode</h3>
* <h2>IWithCode</h2>
* <p>
* 类似于枚举这样的类,通常需要设置固定的码值表示对应的含义。
* 类似于枚举之类的类,通常需要设置固定的码值表示对应的含义。
* 可实现 {@link IWithCode}、{@link IWithIntCode}、{@link IWithLongCode},便于在需要的地方对这些接口的实现进行处理。
* </p>
*
* @author <a href="http://zhouxy.xyz:3000/ZhouXY108">ZhouXY</a>
*/
@CheckReturnValue
@ParametersAreNonnullByDefault

View File

@@ -42,62 +42,26 @@ public class CollectionTools {
// #region - isEmpty
// ================================
/**
* 判断集合是否为空
*
* @param collection 集合
* @return 是否为空
*/
public static boolean isEmpty(@Nullable Collection<?> collection) {
return collection == null || collection.isEmpty();
}
/**
* 判断集合是否为空
*
* @param map 集合
* @return 是否为空
*/
public static boolean isEmpty(@Nullable Map<?, ?> map) {
return map == null || map.isEmpty();
}
/**
* 判断集合是否为空
*
* @param table 集合
* @return 是否为空
*/
public static boolean isEmpty(@Nullable Table<?, ?, ?> table) {
return table == null || table.isEmpty();
}
/**
* 判断集合是否为空
*
* @param map 集合
* @return 是否为空
*/
public static boolean isEmpty(@Nullable Multimap<?, ?> map) {
return map == null || map.isEmpty();
}
/**
* 判断集合是否为空
*
* @param set 集合
* @return 是否为空
*/
public static boolean isEmpty(@Nullable Multiset<?> set) {
return set == null || set.isEmpty();
}
/**
* 判断集合是否为空
*
* @param set 集合
* @return 是否为空
*/
public static boolean isEmpty(@Nullable RangeSet<?> set) {
return set == null || set.isEmpty();
}
@@ -110,62 +74,26 @@ public class CollectionTools {
// #region - isNotEmpty
// ================================
/**
* 判断集合是否不为空
*
* @param collection 集合
* @return 是否不为空
*/
public static boolean isNotEmpty(@Nullable Collection<?> collection) {
return collection != null && !collection.isEmpty();
}
/**
* 判断集合是否不为空
*
* @param map 集合
* @return 是否不为空
*/
public static boolean isNotEmpty(@Nullable Map<?, ?> map) {
return map != null && !map.isEmpty();
}
/**
* 判断集合是否不为空
*
* @param table 集合
* @return 是否不为空
*/
public static boolean isNotEmpty(@Nullable Table<?, ?, ?> table) {
return table != null && !table.isEmpty();
}
/**
* 判断集合是否不为空
*
* @param map 集合
* @return 是否不为空
*/
public static boolean isNotEmpty(@Nullable Multimap<?, ?> map) {
return map != null && !map.isEmpty();
}
/**
* 判断集合是否不为空
*
* @param set 集合
* @return 是否不为空
*/
public static boolean isNotEmpty(@Nullable Multiset<?> set) {
return set != null && !set.isEmpty();
}
/**
* 判断集合是否不为空
*
* @param set 集合
* @return 是否不为空
*/
public static boolean isNotEmpty(@Nullable RangeSet<?> set) {
return set != null && !set.isEmpty();
}
@@ -178,41 +106,16 @@ public class CollectionTools {
// #region - nullToEmpty
// ================================
/**
* 将 {@code null} 转为空 {@code List}
*
* @param <T> List 元素的类型
* @param list list
* @return 如果 {@code list} 为 {@code null},返回空列表;
* 如果 {@code list} 不为 {@code null},返回 {@code list} 本身
*/
@Nonnull
public static <T> List<T> nullToEmptyList(@Nullable List<T> list) {
return list == null ? Collections.emptyList() : list;
}
/**
* 将 {@code null} 转为空 {@code Set}
*
* @param <T> Set 元素的类型
* @param set set
* @return 如果 {@code set} 为 {@code null},返回空集合;
* 如果 {@code set} 不为 {@code null},返回 {@code set} 本身
*/
@Nonnull
public static <T> Set<T> nullToEmptySet(@Nullable Set<T> set) {
return set == null ? Collections.emptySet() : set;
}
/**
* 将 {@code null} 转为空 {@code Map}
*
* @param <K> Map 的键的类型
* @param <V> Map 的值的类型
* @param map map
* @return 如果 {@code map} 为 {@code null},返回空集合;
* 如果 {@code map} 不为 {@code null},返回 {@code map} 本身
*/
@Nonnull
public static <K, V> Map<K, V> nullToEmptyMap(@Nullable Map<K, V> map) {
return map == null ? Collections.emptyMap() : map;

View File

@@ -1,30 +0,0 @@
/*
* Copyright 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.
*/
/**
* <h2>集合<h2>
*
* <h3>
* 1. {@link CollectionTools}
* </h3>
* 集合工具类
*
* @author <a href="http://zhouxy.xyz:3000/ZhouXY108">ZhouXY</a>
*/
@ParametersAreNonnullByDefault
package xyz.zhouxy.plusone.commons.collection;
import javax.annotation.ParametersAreNonnullByDefault;

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2023-2025 the original author or authors.
* Copyright 2023-2024 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.
@@ -61,7 +61,7 @@ public final class PatternConsts {
*
* @see RegexConsts#EMAIL
*/
public static final Pattern EMAIL = Pattern.compile(RegexConsts.EMAIL, Pattern.CASE_INSENSITIVE);
public static final Pattern EMAIL = Pattern.compile(RegexConsts.EMAIL);
/**
* 中国大陆手机号

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2022-2025 the original author or authors.
* Copyright 2022-2024 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.
@@ -32,9 +32,6 @@ public final class RegexConsts {
public static final String CAPTCHA = "^\\w{4,6}$";
/**
* from https://emailregex.com/
*/
public static final String EMAIL
= "(?:[a-z0-9!#$%&'*+/=?^_`{|}~-]+(?:\\.[a-z0-9!#$%&'*+/=?^_`{|}~-]+)*|\"(?:[\\x01-\\x08\\x0b\\x0c\\x0e-\\x1f\\x21\\x23-\\x5b\\x5d-\\x7f]|\\\\[\\x01-\\x09\\x0b\\x0c\\x0e-\\x7f])*\")"
+ "@(?:(?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\\.)+[a-z0-9](?:[a-z0-9-]*[a-z0-9])?|\\[(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?|[a-z0-9-]*[a-z0-9]:(?:[\\x01-\\x08\\x0b\\x0c\\x0e-\\x1f\\x21-\\x5a\\x53-\\x7f]|\\\\[\\x01-\\x09\\x0b\\x0c\\x0e-\\x7f])+)\\])";

View File

@@ -1,29 +0,0 @@
/*
* Copyright 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.
*/
/**
* <h2>常量<h2>
*
* <h3>
* 1. 正则常量
* </h3>
* {@link RegexConsts} 包含常见正则表达式;{@link PatternConsts} 包含对应的 {@link Pattern} 对象。
*
* @author <a href="http://zhouxy.xyz:3000/ZhouXY108">ZhouXY</a>
*/
package xyz.zhouxy.plusone.commons.constant;
import java.util.regex.Pattern;

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2024-2025 the original author or authors.
* Copyright 2024 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.
@@ -26,40 +26,18 @@ public final class DataNotExistsException extends Exception {
private static final long serialVersionUID = 6536955800679703111L;
/**
* 使用默认 message 构造新的 {@code DataNotExistsException}。
* {@code cause} 未初始化,后面可能会通过调用 {@link #initCause} 进行初始化。
*/
public DataNotExistsException() {
super();
}
/**
* 使用指定的 {@code message} 构造新的 {@code DataNotExistsException}。
* {@code cause} 未初始化,后面可能会通过调用 {@link #initCause} 进行初始化。
*
* @param message 异常信息
*/
public DataNotExistsException(String message) {
super(message);
}
/**
* 使用指定的 {@code cause} 构造新的 {@code DataNotExistsException}。
* {@code message} 为 (cause==null ? null : cause.toString())。
*
* @param cause 包装的异常
*/
public DataNotExistsException(Throwable cause) {
super(cause);
}
/**
* 使用指定的 {@code message} 和 {@code cause} 构造新的 {@code DataNotExistsException}。
*
* @param message 异常信息
* @param cause 包装的异常
*/
public DataNotExistsException(String message, Throwable cause) {
super(message, cause);
}

View File

@@ -124,66 +124,26 @@ import xyz.zhouxy.plusone.commons.base.IWithCode;
*/
public interface MultiTypesException<E extends Exception, T extends MultiTypesException.ExceptionType<E>> {
/**
* 异常类型
*
* @return 异常类型。通常是实现了 {@link ExceptionType} 的枚举。
*/
@Nonnull
T getType();
/**
* 获取异常类型编码
*
* @return 异常类型编码
*/
default @Nonnull String getTypeCode() {
return getType().getCode();
}
/**
* 异常类型
*/
public static interface ExceptionType<E extends Exception> extends IWithCode<String> {
/**
* 默认异常信息
*/
String getDefaultMessage();
/**
* 创建异常
*
* @return 异常对象
*/
@Nonnull
E create();
/**
* 使用指定 {@code message} 创建异常
*
* @param message 异常信息
* @return 异常对象
*/
@Nonnull
E create(String message);
/**
* 使用指定 {@code cause} 创建异常
*
* @param cause 包装的异常
* @return 异常对象
*/
@Nonnull
E create(Throwable cause);
/**
* 使用指定 {@code message} 和 {@code cause} 创建异常
*
* @param message 异常信息
* @param cause 包装的异常
* @return 异常对象
*/
@Nonnull
E create(String message, Throwable cause);

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2024-2025 the original author or authors.
* Copyright 2024 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.
@@ -59,56 +59,22 @@ public final class ParsingFailureException
this.type = type;
}
/**
* 创建默认类型的 {@code ParsingFailureException}。
* {@code type} 为 {@link Type#DEFAULT}
* {@code message} 为 {@link Type#DEFAULT} 的默认信息。
* {@code cause} 未初始化,后面可能会通过调用 {@link #initCause} 进行初始化。
*/
public ParsingFailureException() {
this(Type.DEFAULT, Type.DEFAULT.getDefaultMessage());
}
/**
* 使用指定 {@code message} 创建默认类型的 {@code ParsingFailureException}。
* {@code type} 为 {@link Type#DEFAULT}
* {@code cause} 未初始化,后面可能会通过调用 {@link #initCause} 进行初始化。
*
* @param message 异常信息
*/
public ParsingFailureException(String message) {
this(Type.DEFAULT, message);
}
/**
* 使用指定的 {@code cause} 创建默认类型的 {@code ParsingFailureException}。
* {@code type} 为 {@link Type#DEFAULT}
* {@code message} 为 (cause==null ? null : cause.toString())。
*
* @param cause 包装的异常
*/
public ParsingFailureException(Throwable cause) {
this(Type.DEFAULT, cause);
}
/**
* 使用指定的 {@code message} 和 {@code cause} 创建默认类型的 {@code ParsingFailureException}。
* {@code type} 为 {@link Type#DEFAULT}。
*
* @param message 异常信息
* @param cause 包装的异常
*/
public ParsingFailureException(String message, Throwable cause) {
this(Type.DEFAULT, message, cause);
}
/**
* 将 {@link DateTimeParseException} 包装为 {@link ParsingFailureException}。
* {@code type} 为 {@link Type#DATE_TIME_PARSING_FAILURE}。
*
* @param cause 包装的 {@code DateTimeParseException}
* @return ParsingFailureException
*/
public static ParsingFailureException of(DateTimeParseException cause) {
if (cause == null) {
return Type.DATE_TIME_PARSING_FAILURE.create();
@@ -116,25 +82,10 @@ public final class ParsingFailureException
return Type.DATE_TIME_PARSING_FAILURE.create(cause.getMessage(), cause);
}
/**
* 将 {@link DateTimeParseException} 包装为 {@link ParsingFailureException}。
* {@code type} 为 {@link Type#DATE_TIME_PARSING_FAILURE}。
*
* @param message 异常信息
* @param cause 包装的 {@code DateTimeParseException}
* @return ParsingFailureException
*/
public static ParsingFailureException of(String message, DateTimeParseException cause) {
return Type.DATE_TIME_PARSING_FAILURE.create(message, cause);
}
/**
* 将 {@link NumberFormatException} 包装为 {@link ParsingFailureException}。
* {@code type} 为 {@link Type#NUMBER_PARSING_FAILURE}。
*
* @param cause 包装的 {@code NumberFormatException}
* @return ParsingFailureException
*/
public static ParsingFailureException of(NumberFormatException cause) {
if (cause == null) {
return Type.NUMBER_PARSING_FAILURE.create();
@@ -142,14 +93,6 @@ public final class ParsingFailureException
return Type.NUMBER_PARSING_FAILURE.create(cause.getMessage(), cause);
}
/**
* 将 {@link NumberFormatException} 包装为 {@link ParsingFailureException}。
* {@code type} 为 {@link Type#NUMBER_PARSING_FAILURE}。
*
* @param message 异常信息
* @param cause {@code NumberFormatException}
* @return ParsingFailureException
*/
public static ParsingFailureException of(String message, NumberFormatException cause) {
return Type.NUMBER_PARSING_FAILURE.create(message, cause);
}
@@ -160,15 +103,10 @@ public final class ParsingFailureException
return type;
}
/** 默认类型 */
public static final Type DEFAULT = Type.DEFAULT;
/** 数字转换失败 */
public static final Type NUMBER_PARSING_FAILURE = Type.NUMBER_PARSING_FAILURE;
/** 时间解析失败 */
public static final Type DATE_TIME_PARSING_FAILURE = Type.DATE_TIME_PARSING_FAILURE;
/** JSON 解析失败 */
public static final Type JSON_PARSING_FAILURE = Type.JSON_PARSING_FAILURE;
/** XML 解析失败 */
public static final Type XML_PARSING_FAILURE = Type.XML_PARSING_FAILURE;
public enum Type implements ExceptionType<ParsingFailureException> {

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2024-2025 the original author or authors.
* Copyright 2024 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.
@@ -33,40 +33,18 @@ public class BizException extends RuntimeException {
private static final String DEFAULT_MSG = "业务异常";
/**
* 使用默认 message 构造新的业务异常。
* {@code cause} 未初始化,后面可能会通过调用 {@link #initCause} 进行初始化。
*/
public BizException() {
super(DEFAULT_MSG);
}
/**
* 使用指定的 {@code message} 构造新的业务异常。
* {@code cause} 未初始化,后面可能会通过调用 {@link #initCause} 进行初始化。
*
* @param message 异常信息
*/
public BizException(String message) {
super(message);
}
/**
* 使用指定的 {@code cause} 构造新的业务异常。
* {@code message} 为 (cause==null ? null : cause.toString())。
*
* @param cause 包装的异常
*/
public BizException(Throwable cause) {
super(cause);
}
/**
* 使用指定的 {@code message} 和 {@code cause} 构造新的业务异常。
*
* @param message 异常信息
* @param cause 包装的异常
*/
public BizException(String message, Throwable cause) {
super(message, cause);
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2024-2025 the original author or authors.
* Copyright 2024 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.
@@ -60,45 +60,18 @@ public final class InvalidInputException
this.type = type;
}
/**
* 创建默认类型的 {@code InvalidInputException}。
* {@code type} 为 {@link Type#DEFAULT}
* {@code message} 为 {@link Type#DEFAULT} 的默认信息。
* {@code cause} 未初始化,后面可能会通过调用 {@link #initCause} 进行初始化。
*/
public InvalidInputException() {
this(Type.DEFAULT);
}
/**
* 使用指定 {@code message} 创建默认类型的 {@code InvalidInputException}。
* {@code type} 为 {@link Type#DEFAULT}
* {@code cause} 未初始化,后面可能会通过调用 {@link #initCause} 进行初始化。
*
* @param message 异常信息
*/
public InvalidInputException(String message) {
this(Type.DEFAULT, message);
}
/**
* 使用指定的 {@code cause} 创建默认类型的 {@code InvalidInputException}。
* {@code type} 为 {@link Type#DEFAULT}
* {@code message} 为 (cause==null ? null : cause.toString())。
*
* @param cause 包装的异常
*/
public InvalidInputException(Throwable cause) {
this(Type.DEFAULT, cause);
}
/**
* 使用指定的 {@code message} 和 {@code cause} 创建默认类型的 {@code InvalidInputException}。
* {@code type} 为 {@link Type#DEFAULT}。
*
* @param message 异常信息
* @param cause 包装的异常
*/
public InvalidInputException(String message, Throwable cause) {
this(Type.DEFAULT, message, cause);
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2024-2025 the original author or authors.
* Copyright 2024 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.
@@ -30,40 +30,18 @@ public class RequestParamsException extends BizException {
private static final String DEFAULT_MSG = "用户请求参数错误";
/**
* 使用默认 message 构造新的 {@code RequestParamsException}。
* {@code cause} 未初始化,后面可能会通过调用 {@link #initCause} 进行初始化。
*/
public RequestParamsException() {
super(DEFAULT_MSG);
}
/**
* 使用指定的 {@code message} 构造新的 {@code RequestParamsException}。
* {@code cause} 未初始化,后面可能会通过调用 {@link #initCause} 进行初始化。
*
* @param message 异常信息
*/
public RequestParamsException(String message) {
super(message);
}
/**
* 使用指定的 {@code cause} 构造新的 {@code RequestParamsException}。
* {@code message} 为 (cause==null ? null : cause.toString())。
*
* @param cause 包装的异常
*/
public RequestParamsException(Throwable cause) {
super(cause);
}
/**
* 使用指定的 {@code message} 和 {@code cause} 构造新的 {@code RequestParamsException}。
*
* @param message 异常信息
* @param cause 包装的异常
*/
public RequestParamsException(String message, Throwable cause) {
super(message, cause);
}

View File

@@ -1,22 +0,0 @@
/*
* Copyright 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.
*/
/**
* 业务异常
*
* @author <a href="http://zhouxy.xyz:3000/ZhouXY108">ZhouXY</a>
*/
package xyz.zhouxy.plusone.commons.exception.business;

View File

@@ -1,129 +0,0 @@
/*
* Copyright 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.
*/
/**
* <h2>异常<h2>
*
* <h3>1. {@link MultiTypesException} - 多类型异常</h3>
* <p>
* 异常在不同场景下被抛出,可以用不同的枚举值,表示不同的场景类型。
* </p>
* <p>
* 异常实现 {@link MultiTypesException} 的 {@link MultiTypesException#getType} 方法,返回对应的场景类型。
* </p>
* <p>
* 表示场景类型的枚举实现 {@link MultiTypesException.ExceptionType},其中的工厂方法用于创建类型对象。
* </p>
*
* <pre>
* public final class LoginException
* extends RuntimeException
* implements MultiTypesException&lt;LoginException, LoginException.Type&gt; {
* private final Type type;
* private LoginException(&#64;Nonnull Type type, &#64;Nonnull String message) {
* super(message);
* this.type = type;
* }
*
* private LoginException(&#64;Nonnull Type type, &#64;Nonnull Throwable cause) {
* super(cause);
* this.type = type;
* }
*
* private LoginException(&#64;Nonnull Type type,
* &#64;Nonnull String message,
* &#64;Nonnull Throwable cause) {
* super(message, cause);
* this.type = type;
* }
*
* &#64;Override
* public &#64;Nonnull Type getType() {
* return this.type;
* }
*
* // ...
*
* public enum Type implements ExceptionType<LoginException> {
* DEFAULT("00", "当前会话未登录"),
* NOT_TOKEN("10", "未提供token"),
* INVALID_TOKEN("20", "token无效"),
* TOKEN_TIMEOUT("30", "token已过期"),
* BE_REPLACED("40", "token已被顶下线"),
* KICK_OUT("50", "token已被踢下线"),
* ;
*
* &#64;Nonnull
* private final String code;
* &#64;Nonnull
* private final String defaultMessage;
*
* Type(&#64;Nonnull String code, &#64;Nonnull String defaultMessage) {
* this.code = code;
* this.defaultMessage = defaultMessage;
* }
*
* &#64;Override
* public &#64;Nonnull String getCode() {
* return code;
* }
*
* &#64;Override
* public &#64;Nonnull String getDefaultMessage() {
* return defaultMessage;
* }
*
* &#64;Override
* public &#64;Nonnull LoginException create() {
* return new LoginException(this, this.defaultMessage);
* }
*
* &#64;Override
* public &#64;Nonnull LoginException create(String message) {
* return new LoginException(this, message);
* }
*
* &#64;Override
* public &#64;Nonnull LoginException create(Throwable cause) {
* return new LoginException(this, cause);
* }
*
* &#64;Override
* public &#64;Nonnull LoginException create(String message, Throwable cause) {
* return new LoginException(this, message, cause);
* }
* }
* }
* </pre>
*
* 使用时,可以使用这种方式创建并抛出异常:
* <pre>
* throw LoginException.Type.TOKEN_TIMEOUT.create();
* </pre>
* </p>
*
* <h3>2. 业务异常</h3>
* 预设常见的业务异常。可继承 {@link BizException} 自定义业务异常。
*
* <h3>3. 系统异常</h3>
* 预设常见的系统异常。可继承 {@link SysException} 自定义系统异常。
*
* @author <a href="http://zhouxy.xyz:3000/ZhouXY108">ZhouXY</a>
*/
package xyz.zhouxy.plusone.commons.exception;
import xyz.zhouxy.plusone.commons.exception.business.*;
import xyz.zhouxy.plusone.commons.exception.system.*;

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2024-2025 the original author or authors.
* Copyright 2024 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.
@@ -35,40 +35,18 @@ public final class DataOperationResultException extends SysException {
private static final String DEFAULT_MSG = "数据操作的结果不符合预期";
/**
* 使用默认 message 构造新的 {@code DataOperationResultException}。
* {@code cause} 未初始化,后面可能会通过调用 {@link #initCause} 进行初始化。
*/
public DataOperationResultException() {
super(DEFAULT_MSG);
}
/**
* 使用指定的 {@code message} 构造新的 {@code DataOperationResultException}。
* {@code cause} 未初始化,后面可能会通过调用 {@link #initCause} 进行初始化。
*
* @param message 异常信息
*/
public DataOperationResultException(String message) {
super(message);
}
/**
* 使用指定的 {@code cause} 构造新的 {@code DataOperationResultException}。
* {@code message} 为 (cause==null ? null : cause.toString())。
*
* @param cause 包装的异常
*/
public DataOperationResultException(Throwable cause) {
super(cause);
}
/**
* 使用指定的 {@code message} 和 {@code cause} 构造新的 {@code DataOperationResultException}。
*
* @param message 异常信息
* @param cause 包装的异常
*/
public DataOperationResultException(String message, Throwable cause) {
super(message, cause);
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2023-2025 the original author or authors.
* Copyright 2023-2024 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.
@@ -29,40 +29,18 @@ package xyz.zhouxy.plusone.commons.exception.system;
public class NoAvailableMacFoundException extends SysException {
private static final long serialVersionUID = 152827098461071551L;
/**
* 使用默认 message 构造新的 {@code NoAvailableMacFoundException}。
* {@code cause} 未初始化,后面可能会通过调用 {@link #initCause} 进行初始化。
*/
public NoAvailableMacFoundException() {
super();
}
/**
* 使用指定的 {@code message} 构造新的 {@code NoAvailableMacFoundException}。
* {@code cause} 未初始化,后面可能会通过调用 {@link #initCause} 进行初始化。
*
* @param message 异常信息
*/
public NoAvailableMacFoundException(String message) {
super(message);
}
/**
* 使用指定的 {@code cause} 构造新的 {@code NoAvailableMacFoundException}。
* {@code message} 为 (cause==null ? null : cause.toString())。
*
* @param cause 包装的异常
*/
public NoAvailableMacFoundException(Throwable cause) {
super(cause);
}
/**
* 使用指定的 {@code message} 和 {@code cause} 构造新的 {@code NoAvailableMacFoundException}。
*
* @param message 异常信息
* @param cause 包装的异常
*/
public NoAvailableMacFoundException(String message, Throwable cause) {
super(message, cause);
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2024-2025 the original author or authors.
* Copyright 2024 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.
@@ -30,40 +30,18 @@ public class SysException extends RuntimeException {
private static final String DEFAULT_MSG = "系统异常";
/**
* 使用默认 message 构造新的系统异常。
* {@code cause} 未初始化,后面可能会通过调用 {@link #initCause} 进行初始化。
*/
public SysException() {
super(DEFAULT_MSG);
}
/**
* 使用指定的 {@code message} 构造新的系统异常。
* {@code cause} 未初始化,后面可能会通过调用 {@link #initCause} 进行初始化。
*
* @param message 异常信息
*/
public SysException(String message) {
super(message);
}
/**
* 使用指定的 {@code cause} 构造新的系统异常。
* {@code message} 为 (cause==null ? null : cause.toString())。
*
* @param cause 包装的异常
*/
public SysException(Throwable cause) {
super(cause);
}
/**
* 使用指定的 {@code message} 和 {@code cause} 构造新的系统异常。
*
* @param message 异常信息
* @param cause 包装的异常
*/
public SysException(String message, Throwable cause) {
super(message, cause);
}

View File

@@ -1,22 +0,0 @@
/*
* Copyright 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.
*/
/**
* 系统异常
*
* @author <a href="http://zhouxy.xyz:3000/ZhouXY108">ZhouXY</a>
*/
package xyz.zhouxy.plusone.commons.exception.system;

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2024-2025 the original author or authors.
* Copyright 2024 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.
@@ -18,35 +18,11 @@ package xyz.zhouxy.plusone.commons.function;
import com.google.common.annotations.Beta;
/**
* BoolUnaryOperator
*
* <p>
* 一个特殊的 {@link java.util.function.UnaryOperator}。
* 表示对 {@code boolean} 值的一元操作。
* </p>
*
* @author <a href="http://zhouxy.xyz:3000/ZhouXY108">ZhouXY</a>
* @since 1.0.0
* @see java.util.function.UnaryOperator
*/
@Beta
@FunctionalInterface
public interface BoolUnaryOperator {
/**
* 将此函数应用于给定的 {@code boolean} 参数,返回一个 {@code boolean} 结果。
*
* @param operand 操作数
* @return 结果
*/
boolean applyAsBool(boolean operand);
/**
* 返回一个 {@code BoolUnaryOperator},该操作符将给定的操作数取反。
*
* @return {@code BoolUnaryOperator}
*/
static BoolUnaryOperator not() {
return b -> !b;
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2024-2025 the original author or authors.
* Copyright 2024 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.
@@ -18,27 +18,8 @@ package xyz.zhouxy.plusone.commons.function;
import com.google.common.annotations.Beta;
/**
* CharUnaryOperator
*
* <p>
* 一个特殊的 {@link java.util.function.UnaryOperator}。
* 表示对 {@code char} 的一元操作。
* </p>
*
* @author <a href="http://zhouxy.xyz:3000/ZhouXY108">ZhouXY</a>
* @since 1.0.0
* @see java.util.function.UnaryOperator
*/
@Beta
@FunctionalInterface
public interface CharUnaryOperator {
/**
* 将此函数应用于给定的 {@code char} 参数,返回一个 {@code char} 结果。
*
* @param operand 操作数
* @return 结果
*/
char applyAsChar(char operand);
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2024-2025 the original author or authors.
* Copyright 2024 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.
@@ -16,26 +16,9 @@
package xyz.zhouxy.plusone.commons.function;
/**
* Executable
*
* <p>
* 表示一个无入参无返回值的操作,可抛出异常。
* </p>
*
* @param <E> 可抛出的异常类型
*
* @author <a href="http://zhouxy.xyz:3000/ZhouXY108">ZhouXY</a>
* @since 1.0.0
*/
@FunctionalInterface
public interface Executable<E extends Throwable> {
/**
* 执行
*
* @throws E 可抛出的异常
*/
void execute() throws E;
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2023-2025 the original author or authors.
* Copyright 2023-2024 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.
@@ -24,7 +24,6 @@ import java.util.function.Supplier;
*
* <p>
* 返回 {@code Optional&lt;T&gt;} 对象。
* </p>
*
* @author <a href="http://zhouxy.xyz:3000/ZhouXY108">ZhouXY</a>
* @since 1.0.0

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2023-2025 the original author or authors.
* Copyright 2023-2024 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.

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2024-2025 the original author or authors.
* Copyright 2024 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.
@@ -16,24 +16,13 @@
package xyz.zhouxy.plusone.commons.function;
/**
* ThrowingConsumer
*
* <p>
* 允许抛出异常的消费操作。是一个特殊的 {@link java.util.function.Consumer}。
* </p>
*
* @author <a href="http://zhouxy.xyz:3000/ZhouXY108">ZhouXY</a>
* @since 1.0.0
* @see java.util.function.Consumer
*/
@FunctionalInterface
public interface ThrowingConsumer<T, E extends Throwable> {
/**
* 消费给定的参数,允许抛出异常
* Consume the supplied argument, potentially throwing an exception.
*
* @param t 要消费的参数
* @param t the argument to consume
*/
void accept(T t) throws E;

View File

@@ -15,29 +15,14 @@
*/
package xyz.zhouxy.plusone.commons.function;
/**
* ThrowingFunction
*
* <p>
* 接收一个参数,并返回一个结果,可以抛出异常。
* </p>
*
* @param <T> 入参类型
* @param <R> 返回结果类型
* @param <E> 异常类型
*
* @author <a href="http://zhouxy.xyz:3000/ZhouXY108">ZhouXY</a>
* @since 1.0
* @see java.util.function.Function
*/
@FunctionalInterface
public interface ThrowingFunction<T, R, E extends Throwable> {
/**
* 接收一个参数,并返回一个结果,可以抛出异常。
* Applies this function to the given argument.
*
* @param t 入参
* @return 函数结果
* @param t the function argument
* @return the function result
*/
R apply(T t) throws E;

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2024-2025 the original author or authors.
* Copyright 2024 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.
@@ -16,25 +16,15 @@
package xyz.zhouxy.plusone.commons.function;
/**
* ThrowingPredicate
*
* <p>
* 接收一个参数,返回一个布尔值,可抛出异常。
* </p>
*
* @author <a href="http://zhouxy.xyz:3000/ZhouXY108">ZhouXY</a>
* @since 1.0.0
* @see java.util.function.Predicate
*/
@FunctionalInterface
public interface ThrowingPredicate<T, E extends Throwable> {
/**
* 对给定的参数进行评估
* Evaluates this predicate on the given argument.
*
* @param t 入参
* @return 入参符合条件时返回 {@code true},否则返回 {@code false}
* @param t the input argument
* @return {@code true} if the input argument matches the predicate,
* otherwise {@code false}
*/
boolean test(T t) throws E;
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2024-2025 the original author or authors.
* Copyright 2024 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.
@@ -16,27 +16,13 @@
package xyz.zhouxy.plusone.commons.function;
/**
* ThrowingSupplier
*
* <p>
* 允许抛出异常的 Supplier 接口。
* </p>
*
* @param <T> 结果类型
* @param <E> 异常类型
*
* @author <a href="http://zhouxy.xyz:3000/ZhouXY108">ZhouXY</a>
* @since 1.0.0
* @see java.util.function.Supplier
*/
@FunctionalInterface
public interface ThrowingSupplier<T, E extends Throwable> {
/**
* 获取一个结果,允许抛出异常。
* Get a result, potentially throwing an exception.
*
* @return 结果
* @return a result
*/
T get() throws E;

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2023-2025 the original author or authors.
* Copyright 2023-2024 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.

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2023-2025 the original author or authors.
* Copyright 2023-2024 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.

View File

@@ -15,16 +15,16 @@
*/
/**
* <h2>函数式编程</h2>
* 函数式编程
*
* <h3>1. PredicateTools</h3>
* <h2>PredicateTools</h2>
* <p>
* {@link PredicateTools} 用于 {@link java.util.function.Predicate} 的相关操作。
* </p>
*
* <h3>2. Functional interfaces</h3>
* <h2>Functional interfaces</h2>
* <p>
* 补充可能用得上的函数式接口:
* 补充一些 JDK 没有,而项目中可能用得上的函数式接口:
* <pre>
* | Group | FunctionalInterface | method |
* | ------------- | -------------------- | -------------------------------- |
@@ -40,7 +40,5 @@
* | Optional | ToOptionalFunction | Optional&lt;R&gt; apply(T) |
* </pre>
* </p>
*
* @author <a href="http://zhouxy.xyz:3000/ZhouXY108">ZhouXY</a>
*/
package xyz.zhouxy.plusone.commons.function;

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2024-2025 the original author or authors.
* Copyright 2024 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.
@@ -16,15 +16,11 @@
package xyz.zhouxy.plusone.commons.model;
import java.io.Serializable;
import java.time.LocalDate;
import java.time.format.DateTimeFormatter;
import java.util.Map;
import java.util.Objects;
import java.util.regex.Matcher;
import javax.annotation.Nullable;
import com.google.common.base.Strings;
import com.google.common.collect.ImmutableMap;
import com.google.errorprone.annotations.Immutable;
@@ -49,11 +45,8 @@ import xyz.zhouxy.plusone.commons.util.StringTools;
@ValueObject
@Immutable
public class Chinese2ndGenIDCardNumber
implements IDCardNumber, Comparable<Chinese2ndGenIDCardNumber>, Serializable {
private static final long serialVersionUID = 5655592250204184210L;
/** 身份证号码 */
private final String value;
extends ValidatableStringRecord<Chinese2ndGenIDCardNumber>
implements IDCardNumber {
/** 省份编码 */
private final String provinceCode;
@@ -68,129 +61,79 @@ public class Chinese2ndGenIDCardNumber
private static final DateTimeFormatter DATE_FORMATTER = DateTimeFormatter.ofPattern("yyyyMMdd");
private Chinese2ndGenIDCardNumber(String value,
String provinceCode, String cityCode, String countyCode,
Gender gender, LocalDate birthDate) {
this.value = value;
this.provinceCode = provinceCode;
this.cityCode = cityCode;
this.countyCode = countyCode;
this.gender = gender;
this.birthDate = birthDate;
}
private Chinese2ndGenIDCardNumber(String value) {
super(value.toUpperCase(), PatternConsts.CHINESE_2ND_ID_CARD_NUMBER, () -> "二代居民身份证校验失败:" + value);
final Matcher matcher = getMatcher();
final String provinceCodeValue = matcher.group("province");
AssertTools.checkArgument(Chinese2ndGenIDCardNumber.PROVINCE_CODES.containsKey(provinceCodeValue));
final String cityCodeValue = matcher.group("city");
final String countyCodeValue = matcher.group("county");
final Gender genderValue;
final LocalDate birthDateValue;
/**
* 根据身份证号码创建 {@code Chinese2ndGenIDCardNumber} 对象
*
* @param idCardNumber 身份证号码值
* @return {@code Chinese2ndGenIDCardNumber} 对象
*/
public static Chinese2ndGenIDCardNumber of(final String idCardNumber) {
try {
AssertTools.checkArgument(StringTools.isNotBlank(idCardNumber), "二代居民身份证校验失败:号码为空");
final String value = idCardNumber.toUpperCase();
final Matcher matcher = PatternConsts.CHINESE_2ND_ID_CARD_NUMBER.matcher(value);
AssertTools.checkArgument(matcher.matches(), () -> "二代居民身份证校验失败:" + value);
final String provinceCode = matcher.group("province");
AssertTools.checkArgument(Chinese2ndGenIDCardNumber.PROVINCE_CODES.containsKey(provinceCode));
final String cityCode = matcher.group("city");
final String countyCode = matcher.group("county");
// 出生日期
final String birthDateStr = matcher.group("birthDate");
final LocalDate birthDate = LocalDate.parse(birthDateStr, DATE_FORMATTER);
birthDateValue = LocalDate.parse(birthDateStr, DATE_FORMATTER);
// 性别
final int genderCode = Integer.parseInt(matcher.group("gender"));
final Gender gender = genderCode % 2 == 0 ? Gender.FEMALE : Gender.MALE;
return new Chinese2ndGenIDCardNumber(value, provinceCode, cityCode, countyCode, gender, birthDate);
}
catch (IllegalArgumentException e) {
throw e;
genderValue = genderCode % 2 == 0 ? Gender.FEMALE : Gender.MALE;
}
catch (Exception e) {
throw new IllegalArgumentException(e);
}
this.provinceCode = provinceCodeValue;
this.cityCode = cityCodeValue;
this.countyCode = countyCodeValue;
this.gender = genderValue;
this.birthDate = birthDateValue;
}
public static Chinese2ndGenIDCardNumber of(final String value) {
AssertTools.checkArgument(StringTools.isNotBlank(value), "二代居民身份证校验失败:号码为空");
return new Chinese2ndGenIDCardNumber(value);
}
// ================================
// #region - reader methods
// ================================
@Override
@ReaderMethod
public String value() {
return value;
}
/**
* 所属省份代码
*
* @return 所属省份代码
*/
@ReaderMethod
public String getProvinceCode() {
return provinceCode;
}
/**
* 所属省份名称
*
* @return 所属省份名称
*/
@ReaderMethod
public String getProvinceName() {
return PROVINCE_CODES.get(this.provinceCode);
}
/**
* 所属省份完整行政区划代码
*
* @return 所属省份完整行政区划代码
*/
@ReaderMethod
public String getFullProvinceCode() {
return Strings.padEnd(this.provinceCode, 12, '0');
}
/**
* 所属市级代码
*
* @return 所属市级代码
*/
@ReaderMethod
public String getCityCode() {
return cityCode;
}
/**
* 所属市级完整行政区划代码
*
* @return 所属市级完整行政区划代码
*/
@ReaderMethod
public String getFullCityCode() {
return Strings.padEnd(this.cityCode, 12, '0');
}
/**
* 所属县级代码
*
* @return 所属县级代码
*/
@ReaderMethod
public String getCountyCode() {
return countyCode;
}
/**
* 所属县级完整行政区划代码
*
* @return 所属县级完整行政区划代码
*/
@ReaderMethod
public String getFullCountyCode() {
return Strings.padEnd(this.countyCode, 12, '0');
@@ -212,72 +155,59 @@ public class Chinese2ndGenIDCardNumber
// #endregion - reader methods
// ================================
/** 省份代码表 */
public static final Map<String, String> PROVINCE_CODES = ImmutableMap.<String, String>builder()
.put("11", "北京")
.put("12", "天津")
.put("13", "河北")
.put("14", "山西")
.put("15", "内蒙古")
.put("21", "辽宁")
.put("22", "吉林")
.put("23", "黑龙江")
.put("31", "上海")
.put("32", "江苏")
.put("33", "浙江")
.put("34", "安徽")
.put("35", "福建")
.put("36", "江西")
.put("37", "山东")
.put("41", "河南")
.put("42", "湖北")
.put("43", "湖南")
.put("44", "广东")
.put("45", "广西")
.put("46", "海南")
.put("50", "重庆")
.put("51", "四川")
.put("52", "贵州")
.put("53", "云南")
.put("54", "西藏")
.put("61", "陕西")
.put("62", "甘肃")
.put("63", "青海")
.put("64", "宁夏")
.put("65", "新疆")
.put("71", "台湾")
.put("81", "香港")
.put("82", "澳门")
.put("83", "台湾") // 台湾身份证号码以83开头但是行政区划为71
.put("91", "国外")
.build();
/**
* 省份代码表
*/
public static final Map<String, String> PROVINCE_CODES;
@SuppressWarnings("null")
@Override
public int compareTo(Chinese2ndGenIDCardNumber o) {
return value.compareTo(o.value);
static {
PROVINCE_CODES = ImmutableMap.<String, String>builder()
.put("11", "北京")
.put("12", "天津")
.put("13", "河北")
.put("14", "山西")
.put("15", "内蒙古")
.put("21", "辽宁")
.put("22", "吉林")
.put("23", "黑龙江")
.put("31", "上海")
.put("32", "江苏")
.put("33", "浙江")
.put("34", "安徽")
.put("35", "福建")
.put("36", "江西")
.put("37", "山东")
.put("41", "河南")
.put("42", "湖北")
.put("43", "湖南")
.put("44", "广东")
.put("45", "广西")
.put("46", "海南")
.put("50", "重庆")
.put("51", "四川")
.put("52", "贵州")
.put("53", "云南")
.put("54", "西藏")
.put("61", "陕西")
.put("62", "甘肃")
.put("63", "青海")
.put("64", "宁夏")
.put("65", "新疆")
.put("71", "台湾")
.put("81", "香港")
.put("82", "澳门")
.put("83", "台湾") // 台湾身份证号码以83开头但是行政区划为71
.put("91", "国外")
.build();
}
@Override
public int hashCode() {
return Objects.hash(value);
return super.hashCode();
}
@Override
public boolean equals(@Nullable Object obj) {
if (this == obj) {
return true;
}
if (!(obj instanceof Chinese2ndGenIDCardNumber)) {
return false;
}
Chinese2ndGenIDCardNumber other = (Chinese2ndGenIDCardNumber) obj;
return Objects.equals(value, other.value);
public boolean equals(Object obj) {
return super.equals(obj);
}
@Override
public String toString() {
return value();
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2024-2025 the original author or authors.
* Copyright 2024 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.
@@ -43,41 +43,20 @@ public enum Gender implements IWithIntCode {
this.displayNameZh = displayNameZh;
}
/**
* 根据码值获取对应枚举
*
* @param value 码值
* @return 枚举值
*/
public static Gender of(int value) {
AssertTools.checkCondition(0 <= value && value < VALUES.length,
() -> new EnumConstantNotPresentException(Gender.class, String.valueOf(value)));
return VALUES[value];
}
/**
* 获取枚举码值
*
* @return 码值
*/
public int getValue() {
return value;
}
/**
* 枚举名称
*
* @return 枚举名称
*/
public String getDisplayName() {
return displayName;
}
/**
* 枚举中文名称
*
* @return 枚举中文名称
*/
public String getDisplayNameZh() {
return displayNameZh;
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2024-2025 the original author or authors.
* Copyright 2024 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.
@@ -19,43 +19,30 @@ package xyz.zhouxy.plusone.commons.model;
import java.time.LocalDate;
import java.time.Period;
import xyz.zhouxy.plusone.commons.util.StringTools;
import xyz.zhouxy.plusone.commons.util.AssertTools;
/**
* 身份证号
*/
public interface IDCardNumber {
char DEFAULT_REPLACED_CHAR = '*';
int DEFAULT_DISPLAY_FRONT = 1;
int DEFAULT_DISPLAY_END = 2;
static final char DEFAULT_REPLACED_CHAR = '*';
static final int DEFAULT_DISPLAY_FRONT = 1;
static final int DEFAULT_DISPLAY_END = 2;
/**
* 身份证号
*
* @return 身份证号
*/
String value();
/**
* 根据身份证号判断性别
*
* @return {@link Gender} 对象
*/
Gender getGender();
/**
* 获取出生日期
*
* @return 出生日期
*/
LocalDate getBirthDate();
/**
* 计算年龄
*
* @return 年龄
*/
/** 计算年龄 */
default int getAge() {
LocalDate now = LocalDate.now();
return Period.between(getBirthDate(), now).getYears();
@@ -65,36 +52,23 @@ public interface IDCardNumber {
// #region - toString
// ================================
/**
* 脱敏字符串(前面留 1 位,后面留 2 位)
*
* @return 脱敏字符串
*/
default String toDesensitizedString() {
return StringTools.desensitize(value(), DEFAULT_REPLACED_CHAR, DEFAULT_DISPLAY_FRONT, DEFAULT_DISPLAY_END);
return toDesensitizedString(DEFAULT_REPLACED_CHAR, DEFAULT_DISPLAY_FRONT, DEFAULT_DISPLAY_END);
}
/**
* 脱敏字符串
*
* @param front 前面保留的字符数
* @param end 后面保留的字符数
* @return 脱敏字符串
*/
default String toDesensitizedString(int front, int end) {
return StringTools.desensitize(value(), DEFAULT_REPLACED_CHAR, front, end);
return toDesensitizedString(DEFAULT_REPLACED_CHAR, front, end);
}
/**
* 脱敏字符串
*
* @param replacedChar 替换字符
* @param front 前面保留的字符数
* @param end 后面保留的字符数
* @return 脱敏字符串
*/
default String toDesensitizedString(char replacedChar, int front, int end) {
return StringTools.desensitize(value(), replacedChar, front, end);
final String value = value();
AssertTools.checkArgument(front >= 0 && end >= 0);
AssertTools.checkArgument((front + end) <= value.length(), "需要截取的长度不能大于身份证号长度");
final char[] charArray = value.toCharArray();
for (int i = front; i < charArray.length - end; i++) {
charArray[i] = replacedChar;
}
return String.valueOf(charArray);
}
// ================================

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2023-2025 the original author or authors.
* Copyright 2023-2024 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.
@@ -22,7 +22,6 @@ import java.util.regex.Matcher;
import java.util.regex.Pattern;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import xyz.zhouxy.plusone.commons.annotation.ReaderMethod;
import xyz.zhouxy.plusone.commons.util.AssertTools;
@@ -32,11 +31,8 @@ import xyz.zhouxy.plusone.commons.util.AssertTools;
*
* @author <a href="http://zhouxy.xyz:3000/ZhouXY108">ZhouXY</a>
* @since 1.0.0
*
* @deprecated 弃用。使用工厂方法创建对象,并在其中进行校验即可。
*/
@Deprecated
public abstract class ValidatableStringRecord<T extends ValidatableStringRecord<T>> // NOSONAR 暂不删除
public abstract class ValidatableStringRecord<T extends ValidatableStringRecord<T>>
implements Comparable<T> {
@Nonnull
@@ -44,36 +40,17 @@ public abstract class ValidatableStringRecord<T extends ValidatableStringRecord<
private final Matcher matcher;
/**
* 构造字符串值对象
*
* @param value 字符串值
* @param pattern 正则
*/
protected ValidatableStringRecord(String value, Pattern pattern) {
protected ValidatableStringRecord(@Nonnull String value, @Nonnull Pattern pattern) {
this(value, pattern, "Invalid value");
}
/**
* 构造字符串值对象
*
* @param value 字符串值
* @param pattern 正则
* @param errorMessageSupplier 正则不匹配时的错误信息
*/
protected ValidatableStringRecord(String value, Pattern pattern,
Supplier<String> errorMessageSupplier) {
protected ValidatableStringRecord(@Nonnull String value, @Nonnull Pattern pattern,
@Nonnull Supplier<String> errorMessageSupplier) {
this(value, pattern, errorMessageSupplier.get());
}
/**
* 构造字符串值对象
*
* @param value 字符串值
* @param pattern 正则
* @param errorMessage 正则不匹配时的错误信息
*/
protected ValidatableStringRecord(String value, Pattern pattern, String errorMessage) {
protected ValidatableStringRecord(@Nonnull String value, @Nonnull Pattern pattern,
@Nonnull String errorMessage) {
AssertTools.checkArgument(Objects.nonNull(value), "The value cannot be null.");
AssertTools.checkArgument(Objects.nonNull(pattern), "The pattern cannot be null.");
this.matcher = pattern.matcher(value);
@@ -92,7 +69,7 @@ public abstract class ValidatableStringRecord<T extends ValidatableStringRecord<
}
@Override
public int compareTo(@SuppressWarnings("null") T o) {
public int compareTo(T o) {
return this.value.compareTo(o.value());
}
@@ -102,7 +79,7 @@ public abstract class ValidatableStringRecord<T extends ValidatableStringRecord<
}
@Override
public boolean equals(@Nullable Object obj) {
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
@@ -119,11 +96,6 @@ public abstract class ValidatableStringRecord<T extends ValidatableStringRecord<
return this.value();
}
/**
* 获取正则匹配结果
*
* @return {@code Matcher} 对象
*/
protected final Matcher getMatcher() {
return matcher;
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2024-2025 the original author or authors.
* Copyright 2024 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.
@@ -19,8 +19,6 @@ package xyz.zhouxy.plusone.commons.model.dto;
import java.util.Collections;
import java.util.List;
import javax.annotation.Nullable;
import xyz.zhouxy.plusone.commons.annotation.StaticFactoryMethod;
import xyz.zhouxy.plusone.commons.collection.CollectionTools;
@@ -38,49 +36,25 @@ public class PageResult<T> {
private final List<T> content;
private PageResult(@Nullable final List<T> content, final long total) {
private PageResult(List<T> content, long total) {
this.content = CollectionTools.nullToEmptyList(content);
this.total = total;
}
/**
* 创建一个分页查询的结果
*
* @param <T> 内容类型
* @param content 一页数据
* @param total 总数据量
* @return 分页查询的结果
*/
@StaticFactoryMethod(PageResult.class)
public static <T> PageResult<T> of(@Nullable final List<T> content, final long total) {
public static <T> PageResult<T> of(List<T> content, long total) {
return new PageResult<>(content, total);
}
/**
* 创建一个空的分页查询的结果
*
* @param <T> 内容类型
* @return 空结果
*/
@StaticFactoryMethod(PageResult.class)
public static <T> PageResult<T> empty() {
return new PageResult<>(Collections.emptyList(), 0L);
}
/**
* 总数据量
*
* @return 总数据量
*/
public long getTotal() {
return total;
}
/**
* 一页数据
*
* @return 一页数据
*/
public List<T> getContent() {
return content;
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2022-2025 the original author or authors.
* Copyright 2022-2024 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.
@@ -21,6 +21,7 @@ import java.util.Map;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import com.google.common.collect.ImmutableMap;
@@ -50,16 +51,11 @@ public class PagingAndSortingQueryParams {
private Long pageNum;
private List<String> orderBy;
private static final Pattern SORT_STR_PATTERN = Pattern.compile("^[a-zA-Z][\\w-]{0,63}-(desc|asc|DESC|ASC)$");
private static final Pattern SORT_STR_PATTERN = Pattern.compile("^[a-zA-Z]\\w+-(desc|asc|DESC|ASC)$");
private final Map<String, String> sortableProperties;
/**
* 构造分页排序查询参数
*
* @param sortableProperties 可排序的属性。不可为空。
*/
public PagingAndSortingQueryParams(Map<String, String> sortableProperties) {
public PagingAndSortingQueryParams(@Nonnull Map<String, String> sortableProperties) {
AssertTools.checkArgument(CollectionTools.isNotEmpty(sortableProperties),
"Sortable properties can not be empty.");
sortableProperties.forEach((k, v) ->
@@ -70,58 +66,28 @@ public class PagingAndSortingQueryParams {
// Setters
/**
* 设置排序规则
*
* @param orderBy 排序规则,不能为空
*/
public final void setOrderBy(List<String> orderBy) {
public final void setOrderBy(@Nullable List<String> orderBy) {
this.orderBy = orderBy;
}
/**
* 设置每页大小
*
* @param size 每页大小
*/
public final void setSize(@Nullable Integer size) {
this.size = size;
}
/**
* 设置页码
*
* @param pageNum 页码
*/
public final void setPageNum(@Nullable Long pageNum) {
this.pageNum = pageNum;
}
// Setters end
/**
* 构建分页参数
*
* @return {@code PagingParams} 对象
*/
public final PagingParams buildPagingParams() {
final int sizeValue = this.size != null ? this.size : defaultSizeInternal();
final long pageNumValue = this.pageNum != null ? this.pageNum : 1L;
AssertTools.checkArgument(CollectionTools.isNotEmpty(this.orderBy),
"The 'orderBy' cannot be empty");
final List<SortableProperty> propertiesToSort = this.orderBy.stream()
.map(this::generateSortableProperty)
final List<SortableProperty> propertiesToSort = this.orderBy.stream().map(this::generateSortableProperty)
.collect(Collectors.toList());
return new PagingParams(sizeValue, pageNumValue, propertiesToSort);
}
/**
* 默认每页大小
*
* <p>NOTE: 可覆写此方法</p>
*
* @return 默认每页大小
*/
@Virtual
protected int defaultSizeInternal() {
return DEFAULT_PAGE_SIZE;
@@ -138,7 +104,6 @@ public class PagingAndSortingQueryParams {
}
private SortableProperty generateSortableProperty(String orderByStr) {
AssertTools.checkArgument(StringTools.isNotBlank(orderByStr));
AssertTools.checkArgument(RegexTools.matches(orderByStr, SORT_STR_PATTERN));
String[] propertyNameAndOrderType = orderByStr.split("-");
AssertTools.checkArgument(propertyNameAndOrderType.length == 2);
@@ -151,9 +116,6 @@ public class PagingAndSortingQueryParams {
return new SortableProperty(propertyName, columnName, orderType);
}
/**
* 可排序属性
*/
public static final class SortableProperty {
private final String propertyName;
private final String columnName;
@@ -170,38 +132,18 @@ public class PagingAndSortingQueryParams {
this.sqlSnippet = this.propertyName + " " + this.orderType;
}
/**
* 属性名
*
* @return 属性名
*/
public String getPropertyName() {
return propertyName;
}
/**
* 对应数据库中列名称
*
* @return 列名称
*/
public String getColumnName() {
return columnName;
}
/**
* 排序方式
*
* @return 排序方式
*/
public String getOrderType() {
return orderType;
}
/**
* SQL 片段
*
* @return SQL 片段
*/
public String getSqlSnippet() {
return sqlSnippet;
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2024-2025 the original author or authors.
* Copyright 2024 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.
@@ -23,13 +23,9 @@ import xyz.zhouxy.plusone.commons.model.dto.PagingAndSortingQueryParams.Sortable
public class PagingParams {
/** 每页大小 */
private final int size;
/** 当前页码 */
private final long pageNum;
/** 偏移量 */
private final long offset;
/** 排序 */
private final List<SortableProperty> orderBy;
PagingParams(int size, long pageNum, List<SortableProperty> orderBy) {
@@ -41,38 +37,18 @@ public class PagingParams {
// Getters
/**
* 排序规则
*
* @return 排序规则
*/
public final List<SortableProperty> getOrderBy() {
return Collections.unmodifiableList(this.orderBy);
}
/**
* 每页大小
*
* @return 每页大小
*/
public final int getSize() {
return this.size;
}
/**
* 当前页码
*
* @return 当前页码
*/
public final long getPageNum() {
return this.pageNum;
}
/**
* 偏移量
*
* @return 偏移量
*/
public final long getOffset() {
return this.offset;
}

View File

@@ -26,32 +26,19 @@ import javax.annotation.Nullable;
*/
public class UnifiedResponse<T> {
private final String code;
private final String message;
private String code;
private String message;
private final @Nullable T data;
private @Nullable T data;
// ================================
// #region - Constructors
// ================================
/**
* 构造 {@code UnifiedResponse}
*
* @param code 状态码
* @param message 响应信息
*/
UnifiedResponse(String code, @Nullable String message) {
this(code, message, null);
}
/**
* 构造 {@code UnifiedResponse}
*
* @param code 状态码
* @param message 响应信息
* @param data 响应数据
*/
UnifiedResponse(String code, @Nullable String message, @Nullable T data) {
this.code = Objects.requireNonNull(code);
this.message = message == null ? "" : message;
@@ -66,29 +53,14 @@ public class UnifiedResponse<T> {
// #region - Getters
// ================================
/**
* 状态码
*
* @return 状态码
*/
public String getCode() {
return code;
}
/**
* 响应信息
*
* @return 响应信息
*/
public String getMessage() {
return message;
}
/**
* 响应数据
*
* @return 响应数据
*/
@Nullable
public T getData() {
return data;
@@ -104,7 +76,7 @@ public class UnifiedResponse<T> {
this.code, this.message, transValue(this.data));
}
private static String transValue(@Nullable Object value) {
private static String transValue(Object value) {
if (value == null) {
return null;
}

View File

@@ -34,36 +34,14 @@ public class UnifiedResponses {
// #region - success
// ================================
/**
* 默认成功响应结果
*
* @return {@code UnifiedResponse} 对象。
* {@code code} = "2000000", {@code message} = "SUCCESS", {@code data} = null
*/
public static UnifiedResponse<Void> success() {
return new UnifiedResponse<>(SUCCESS_CODE, DEFAULT_SUCCESS_MSG);
}
/**
* 使用指定 {@code message} 创建成功响应结果
*
* @param message 成功信息
* @return {@code UnifiedResponse} 对象。
* {@code code} = "2000000", {@code data} = null
*/
public static UnifiedResponse<Void> success(@Nullable String message) {
return new UnifiedResponse<>(SUCCESS_CODE, message);
}
/**
* 使用指定 {@code message} 和 {@code data} 创建成功响应结果
*
* @param <T> data 类型
* @param message 成功信息
* @param data 携带数据
* @return {@code UnifiedResponse} 对象。
* {@code code} = "2000000"
*/
public static <T> UnifiedResponse<T> success(@Nullable String message, @Nullable T data) {
return new UnifiedResponse<>(SUCCESS_CODE, message, data);
}
@@ -76,39 +54,14 @@ public class UnifiedResponses {
// #region - error
// ================================
/**
* 创建错误响应结果
*
* @param code 错误码
* @param message 错误信息
* @return {@code UnifiedResponse} 对象({@code data} 为 {@code null}
*/
public static UnifiedResponse<Void> error(String code, @Nullable String message) {
return new UnifiedResponse<>(code, message);
}
/**
* 创建错误响应结果
*
* @param <T> data 类型
* @param code 错误码
* @param message 错误信息
* @param data 携带数据
* @return {@code UnifiedResponse} 对象
*/
public static <T> UnifiedResponse<T> error(String code, @Nullable String message, @Nullable T data) {
return new UnifiedResponse<>(code, message, data);
}
/**
* 创建错误响应结果
*
* @param code 错误码
* @param e 异常
* @return {@code UnifiedResponse} 对象。
* {@code message} 为异常的 {@code message}
* {@code data} 为 {@code null}。
*/
public static UnifiedResponse<Void> error(String code, Throwable e) {
return new UnifiedResponse<>(code, e.getMessage());
}
@@ -121,26 +74,10 @@ public class UnifiedResponses {
// #region - of
// ================================
/**
* 创建响应结果
*
* @param code 状态码
* @param message 响应信息
* @return {@code UnifiedResponse} 对象({@code data} 为 {@code null}
*/
public static UnifiedResponse<Void> of(String code, @Nullable String message) {
return new UnifiedResponse<>(code, message);
}
/**
* 创建响应结果
*
* @param <T> data 类型
* @param code 状态码
* @param message 响应信息
* @param data 携带数据
* @return {@code UnifiedResponse} 对象
*/
public static <T> UnifiedResponse<T> of(String code, @Nullable String message, @Nullable T data) {
return new UnifiedResponse<>(code, message, data);
}

View File

@@ -1,70 +0,0 @@
/*
* Copyright 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.
*/
/**
* <h2>数据传输对象</h2>
*
* <h3>1. 分页</h3>
* <p>
* 分页组件由 {@link PagingAndSortingQueryParams} 作为入参,
* 因为分页必须伴随着排序,不然可能出现同一个对象重复出现在不同页,有的对象不被查询到的情况,
* 所以分页查询的入参必须包含排序条件。
* </p>
* <p>
* 用户可继承 {@link PagingAndSortingQueryParams}
* 构建自己的分页查询入参,需在构造器中调用 {@link PagingAndSortingQueryParams} 的构造器,传入一个 Map 作为白名单,
* key 是供前端指定用于排序的属性名value 是对应数据库中的字段名,只有在白名单中指定的属性名才允许作为排序条件。
* </p>
* <p>
* {@link PagingAndSortingQueryParams} 包含三个主要的属性:
* <ul>
* <li>size - 每页显示的记录数</li>
* <li>pageNum - 当前页码</li>
* <li>orderBy - 排序条件</li>
* </ul>
* 其中 orderBy 是一个 List可以指定多个排序条件每个排序条件是一个字符串
* 格式为“属性名-ASC”或“属性名-DESC”分别表示升序和降序。
* </p>
* <p>
* 比如前端传入的 orderBy 为 ["name-ASC","age-DESC"],意味着要按 name 进行升序name 相同的情况下则按 age 进行降序。
* </p>
* <p>
* 使用时调用 {@link PagingAndSortingQueryParams#buildPagingParams()} 方法获取分页参数 {@link PagingParams}。
* </p>
* <p>
* 分页结果可以存放到 {@link PageResult} 中,作为出参。
* </p>
*
* <h3>2. {@link UnifiedResponse}</h3>
* <p>
* {@link UnifiedResponse} 对返回给前端的数据进行封装,包含 code、message、data。
* </p>
* <p>
* 可使用 {@link UnifiedResponses} 快速构建 {@link UnifiedResponse} 对象。
* {@link UnifiedResponses} 默认的成功代码为 "2000000"
* 用户按测试类
* <a href="http://zhouxy.xyz:3000/plusone/plusone-commons/src/branch/main/src/test/java/xyz/zhouxy/plusone/commons/model/dto/CustomUnifiedResponseFactoryTests.java">CustomUnifiedResponseFactoryTests</a>
* 中所示范的,继承 {@link UnifiedResponses} 实现自己的工厂类,
* 自定义 SUCCESS_CODE 和 DEFAULT_SUCCESS_MSG 和工厂方法。
* 见 <a href="http://zhouxy.xyz:3000/plusone/plusone-commons/issues/22">issue#22</a>。
* </p>
*
* @author <a href="http://zhouxy.xyz:3000/ZhouXY108">ZhouXY</a>
*/
@ParametersAreNonnullByDefault
package xyz.zhouxy.plusone.commons.model.dto;
import javax.annotation.ParametersAreNonnullByDefault;

View File

@@ -1,28 +0,0 @@
/*
* Copyright 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.
*/
/**
* <h2>业务建模组件</h2>
* <p>
* 包含业务建模可能用到的性别、身份证等元素,也包含 DTO 相关类,如分页查询参数,响应结果,分页结果等。
* </p>
*
* @author <a href="http://zhouxy.xyz:3000/ZhouXY108">ZhouXY</a>
*/
@ParametersAreNonnullByDefault
package xyz.zhouxy.plusone.commons.model;
import javax.annotation.ParametersAreNonnullByDefault;

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2024-2025 the original author or authors.
* Copyright 2024 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.
@@ -46,7 +46,6 @@ public enum Quarter implements IWithIntCode {
/** 季度值 (1/2/3/4) */
private final int value;
/** 月份范围 */
private final Range<Integer> monthRange;
/** 常量值 */
@@ -64,9 +63,7 @@ public enum Quarter implements IWithIntCode {
this.monthRange = Range.closed(firstMonth, lastMonth);
}
// ================================
// #region - StaticFactoryMethods
// ================================
// StaticFactoryMethods
/**
* 根据给定的月份值返回对应的季度
@@ -89,7 +86,6 @@ public enum Quarter implements IWithIntCode {
*/
@StaticFactoryMethod(Quarter.class)
public static Quarter fromMonth(Month month) {
AssertTools.checkNotNull(month);
final int monthValue = month.getValue();
return of(computeQuarterValueInternal(monthValue));
}
@@ -117,48 +113,23 @@ public enum Quarter implements IWithIntCode {
return ENUMS[checkValidIntValue(value) - 1];
}
// ================================
// #endregion - StaticFactoryMethods
// ================================
// StaticFactoryMethods end
// ================================
// #region - computes
// ================================
// computes
/**
* 加上指定数量的季度
*
* @param quarters 所添加的季度数量
* @return 计算结果
*/
public Quarter plus(long quarters) {
final int amount = (int) ((quarters % 4) + 4);
return ENUMS[(ordinal() + amount) % 4];
}
/**
* 减去指定数量的季度
*
* @param quarters 所减去的季度数量
* @return 计算结果
*/
public Quarter minus(long quarters) {
return plus(-(quarters % 4));
}
// ================================
// #endregion - computes
// ================================
// computes end
// ================================
// #region - Getters
// ================================
// Getters
/**
* 获取季度值
*
* @return 季度值
*/
public int getValue() {
return value;
}
@@ -168,92 +139,45 @@ public enum Quarter implements IWithIntCode {
return getValue();
}
/**
* 该季度的第一个月
*
* @return {@code Month} 对象
*/
public Month firstMonth() {
return Month.of(firstMonthValue());
}
/**
* 该季度的第一个月
*
* @return 月份值从 1 开始1 表示 1月以此类推。
*/
public int firstMonthValue() {
return this.monthRange.lowerEndpoint();
}
/**
* 该季度的最后一个月
*
* @return {@code Month} 对象
*/
public Month lastMonth() {
return Month.of(lastMonthValue());
}
/**
* 该季度的最后一个月
*
* @return 月份值从 1 开始1 表示 1月以此类推。
*/
public int lastMonthValue() {
return this.monthRange.upperEndpoint();
}
/**
* 该季度的第一天
*
* @return {@code MonthDay} 对象
*/
public MonthDay firstMonthDay() {
return MonthDay.of(firstMonth(), 1);
}
/**
* 该季度的最后一天
*
* @return {@code MonthDay} 对象
*/
public MonthDay lastMonthDay() {
// 季度的最后一个月不可能是 2 月
final Month month = lastMonth();
return MonthDay.of(month, month.maxLength());
}
/**
* 计算该季度的第一天为当年的第几天
*
* @param leapYear 是否为闰年
* @return day of year
*/
public int firstDayOfYear(boolean leapYear) {
return firstMonth().firstDayOfYear(leapYear);
}
// ================================
// #endregion - Getters
// ================================
// Getters end
/**
* 检查给定的季度值是否有效
*
* @param value 季度值
* @return 如果给定的季度值有效则返回该值
* @throws DateTimeException 如果给定的季度值不在有效范围内1到4将抛出异常
*/
public static int checkValidIntValue(int value) {
AssertTools.checkCondition(value >= 1 && value <= 4,
() -> new DateTimeException("Invalid value for Quarter: " + value));
return value;
}
// ================================
// #region - Internal
// ================================
// Internal
/**
* 计算给定月份对应的季度值
@@ -264,8 +188,4 @@ public enum Quarter implements IWithIntCode {
private static int computeQuarterValueInternal(int monthValue) {
return (monthValue - 1) / 3 + 1;
}
// ================================
// #endregion - Internal
// ================================
}

View File

@@ -27,7 +27,7 @@ import java.util.Calendar;
import java.util.Date;
import java.util.Objects;
import javax.annotation.Nullable;
import javax.annotation.Nonnull;
import com.google.errorprone.annotations.Immutable;
@@ -52,7 +52,7 @@ public final class YearQuarter implements Comparable<YearQuarter>, Serializable
/** 季度结束日期 */
private final LocalDate lastDate;
private YearQuarter(int year, Quarter quarter) {
private YearQuarter(int year, @Nonnull Quarter quarter) {
this.year = year;
this.quarter = quarter;
this.firstDate = quarter.firstMonthDay().atYear(year);
@@ -139,11 +139,6 @@ public final class YearQuarter implements Comparable<YearQuarter>, Serializable
return of(yearMonth.getYear(), Quarter.fromMonth(yearMonth.getMonth()));
}
/**
* 根据现在的日期,判断所在的年份与季度,创建 {@link YearQuarter} 实例
*
* @return {@link YearQuarter} 实例
*/
@StaticFactoryMethod(YearQuarter.class)
public static YearQuarter now() {
return of(LocalDate.now());
@@ -153,101 +148,46 @@ public final class YearQuarter implements Comparable<YearQuarter>, Serializable
// #region - Getters
/**
* 年份
*
* @return 年份
*/
public int getYear() {
return this.year;
}
/**
* 季度
*
* @return 季度
*/
public Quarter getQuarter() {
return this.quarter;
}
/**
* 季度值。从 1 开始。
*
* @return 季度值
*/
public int getQuarterValue() {
return this.quarter.getValue();
}
/**
* 该季度第一个月
*
* @return {@link YearMonth} 对象
*/
public YearMonth firstYearMonth() {
return YearMonth.of(this.year, this.quarter.firstMonth());
}
/**
* 该季度第一个月
*
* @return {@link Month} 对象
*/
public Month firstMonth() {
return this.quarter.firstMonth();
}
/**
* 该季度的第一个月
*
* @return 结果。月份值从 1 开始1 表示 1月以此类推。
*/
public int firstMonthValue() {
return this.quarter.firstMonthValue();
}
/**
* 该季度的最后一个月
*
* @return {@link YearMonth} 对象
*/
public YearMonth lastYearMonth() {
return YearMonth.of(this.year, this.quarter.lastMonth());
}
/**
* 该季度的最后一个月
*
* @return {@link Month} 对象
*/
public Month lastMonth() {
return this.quarter.lastMonth();
}
/**
* 该季度的最后一个月
*
* @return 结果。月份值从 1 开始1 表示 1月以此类推。
*/
public int lastMonthValue() {
return this.quarter.lastMonthValue();
}
/**
* 该季度的第一天
*
* @return {@link LocalDate} 对象
*/
public LocalDate firstDate() {
return firstDate;
}
/**
* 该季度的最后一天
*
* @return {@link LocalDate} 对象
*/
public LocalDate lastDate() {
return lastDate;
}
@@ -283,7 +223,7 @@ public final class YearQuarter implements Comparable<YearQuarter>, Serializable
if (yearsToAdd == 0L) {
return this;
}
int newYear = YEAR.checkValidIntValue(this.year + yearsToAdd); // safe overflow
int newYear = YEAR.checkValidIntValue(this.year + yearsToAdd); // safe overflow
return new YearQuarter(newYear, this.quarter);
}
@@ -309,7 +249,7 @@ public final class YearQuarter implements Comparable<YearQuarter>, Serializable
}
@Override
public boolean equals(@Nullable Object obj) {
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
@@ -324,7 +264,6 @@ public final class YearQuarter implements Comparable<YearQuarter>, Serializable
// #region - compare
@SuppressWarnings("null")
@Override
public int compareTo(YearQuarter other) {
int cmp = (this.year - other.year);

View File

@@ -1,30 +0,0 @@
/*
* Copyright 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.
*/
/**
* <h2>时间 API<h2>
*
* <h3>1. 季度 API</h3>
*
* 模仿 JDK 的 {@link java.time.Month} 和 {@link java.time.YearMonth}
* 实现 {@link Quarter}{@link YearQuarter},对季度进行建模。
*
* @author <a href="http://zhouxy.xyz:3000/ZhouXY108">ZhouXY</a>
*/
@ParametersAreNonnullByDefault
package xyz.zhouxy.plusone.commons.time;
import javax.annotation.ParametersAreNonnullByDefault;

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2024-2025 the original author or authors.
* Copyright 2024 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.
@@ -26,6 +26,7 @@ import java.util.Objects;
import java.util.function.Predicate;
import java.util.stream.Collectors;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
/**
@@ -54,9 +55,9 @@ public class ArrayTools {
public static final int NOT_FOUND_INDEX = -1;
// #region - isEmpty
// #region - isNullOrEmpty
// isEmpty
// isNullOrEmpty
/**
* 检查给定数组是否为空
@@ -65,84 +66,84 @@ public class ArrayTools {
* @param <T> 数组中元素的类型
* @return 如果数组为 {@code null} 或长度为 0则返回 {@code true};否则返回 {@code false}
*/
public static <T> boolean isEmpty(@Nullable T[] arr) {
public static <T> boolean isNullOrEmpty(@Nullable T[] arr) {
return arr == null || arr.length == 0;
}
// isEmpty - char
// isNullOrEmpty - char
/**
* 检查给定数组是否为空
*
* @param arr 待检查的数组,可以为 {@code null}
* @return 如果数组为 {@code null} 或长度为 0则返回 {@code true};否则返回 {@code false}
*/
public static boolean isEmpty(@Nullable char[] arr) {
public static boolean isNullOrEmpty(@Nullable char[] arr) {
return arr == null || arr.length == 0;
}
// isEmpty - byte
// isNullOrEmpty - byte
/**
* 检查给定数组是否为空
*
* @param arr 待检查的数组,可以为 {@code null}
* @return 如果数组为 {@code null} 或长度为 0则返回 {@code true};否则返回 {@code false}
*/
public static boolean isEmpty(@Nullable byte[] arr) {
public static boolean isNullOrEmpty(@Nullable byte[] arr) {
return arr == null || arr.length == 0;
}
// isEmpty - short
// isNullOrEmpty - short
/**
* 检查给定数组是否为空
*
* @param arr 待检查的数组,可以为 {@code null}
* @return 如果数组为 {@code null} 或长度为 0则返回 {@code true};否则返回 {@code false}
*/
public static boolean isEmpty(@Nullable short[] arr) {
public static boolean isNullOrEmpty(@Nullable short[] arr) {
return arr == null || arr.length == 0;
}
// isEmpty - int
// isNullOrEmpty - int
/**
* 检查给定数组是否为空
*
* @param arr 待检查的数组,可以为 {@code null}
* @return 如果数组为 {@code null} 或长度为 0则返回 {@code true};否则返回 {@code false}
*/
public static boolean isEmpty(@Nullable int[] arr) {
public static boolean isNullOrEmpty(@Nullable int[] arr) {
return arr == null || arr.length == 0;
}
// isEmpty - long
// isNullOrEmpty - long
/**
* 检查给定数组是否为空
*
* @param arr 待检查的数组,可以为 {@code null}
* @return 如果数组为 {@code null} 或长度为 0则返回 {@code true};否则返回 {@code false}
*/
public static boolean isEmpty(@Nullable long[] arr) {
public static boolean isNullOrEmpty(@Nullable long[] arr) {
return arr == null || arr.length == 0;
}
// isEmpty - float
// isNullOrEmpty - float
/**
* 检查给定数组是否为空
*
* @param arr 待检查的数组,可以为 {@code null}
* @return 如果数组为 {@code null} 或长度为 0则返回 {@code true};否则返回 {@code false}
*/
public static boolean isEmpty(@Nullable float[] arr) {
public static boolean isNullOrEmpty(@Nullable float[] arr) {
return arr == null || arr.length == 0;
}
// isEmpty - double
// isNullOrEmpty - double
/**
* 检查给定数组是否为空
*
* @param arr 待检查的数组,可以为 {@code null}
* @return 如果数组为 {@code null} 或长度为 0则返回 {@code true};否则返回 {@code false}
*/
public static boolean isEmpty(@Nullable double[] arr) {
public static boolean isNullOrEmpty(@Nullable double[] arr) {
return arr == null || arr.length == 0;
}
@@ -258,7 +259,7 @@ public class ArrayTools {
*
* @throws IllegalArgumentException 当参数为空时抛出
*/
public static <T> boolean isAllElementsNotNull(final T[] arr) {
public static <T> boolean isAllElementsNotNull(@Nonnull final T[] arr) {
AssertTools.checkArgument(arr != null, "The array cannot be null.");
return Arrays.stream(arr).allMatch(Objects::nonNull);
}
@@ -470,24 +471,10 @@ public class ArrayTools {
// repeat - char
/**
* 重复数组中的元素
*
* @param arr 重复内容
* @param times 重复次数
* @return 重复后的数组
*/
public static char[] repeat(char[] arr, int times) {
return repeat(arr, times, Integer.MAX_VALUE);
}
/**
* 重复数组中的元素
* @param arr 重复内容
* @param times 重复次数
* @param maxLength 最大长度
* @return 重复后的数组
*/
public static char[] repeat(char[] arr, int times, int maxLength) {
AssertTools.checkArgument(Objects.nonNull(arr));
AssertTools.checkArgument(times >= 0,
@@ -505,24 +492,10 @@ public class ArrayTools {
// repeat - byte
/**
* 重复数组中的元素
*
* @param arr 重复内容
* @param times 重复次数
* @return 重复后的数组
*/
public static byte[] repeat(byte[] arr, int times) {
return repeat(arr, times, Integer.MAX_VALUE);
}
/**
* 重复数组中的元素
* @param arr 重复内容
* @param times 重复次数
* @param maxLength 最大长度
* @return 重复后的数组
*/
public static byte[] repeat(byte[] arr, int times, int maxLength) {
AssertTools.checkArgument(Objects.nonNull(arr));
AssertTools.checkArgument(times >= 0,
@@ -540,24 +513,10 @@ public class ArrayTools {
// repeat - short
/**
* 重复数组中的元素
*
* @param arr 重复内容
* @param times 重复次数
* @return 重复后的数组
*/
public static short[] repeat(short[] arr, int times) {
return repeat(arr, times, Integer.MAX_VALUE);
}
/**
* 重复数组中的元素
* @param arr 重复内容
* @param times 重复次数
* @param maxLength 最大长度
* @return 重复后的数组
*/
public static short[] repeat(short[] arr, int times, int maxLength) {
AssertTools.checkArgument(Objects.nonNull(arr));
AssertTools.checkArgument(times >= 0,
@@ -575,24 +534,10 @@ public class ArrayTools {
// repeat - int
/**
* 重复数组中的元素
*
* @param arr 重复内容
* @param times 重复次数
* @return 重复后的数组
*/
public static int[] repeat(int[] arr, int times) {
return repeat(arr, times, Integer.MAX_VALUE);
}
/**
* 重复数组中的元素
* @param arr 重复内容
* @param times 重复次数
* @param maxLength 最大长度
* @return 重复后的数组
*/
public static int[] repeat(int[] arr, int times, int maxLength) {
AssertTools.checkArgument(Objects.nonNull(arr));
AssertTools.checkArgument(times >= 0,
@@ -610,24 +555,10 @@ public class ArrayTools {
// repeat - long
/**
* 重复数组中的元素
*
* @param arr 重复内容
* @param times 重复次数
* @return 重复后的数组
*/
public static long[] repeat(long[] arr, int times) {
return repeat(arr, times, Integer.MAX_VALUE);
}
/**
* 重复数组中的元素
* @param arr 重复内容
* @param times 重复次数
* @param maxLength 最大长度
* @return 重复后的数组
*/
public static long[] repeat(long[] arr, int times, int maxLength) {
AssertTools.checkArgument(Objects.nonNull(arr));
AssertTools.checkArgument(times >= 0,
@@ -645,24 +576,10 @@ public class ArrayTools {
// repeat - float
/**
* 重复数组中的元素
*
* @param arr 重复内容
* @param times 重复次数
* @return 重复后的数组
*/
public static float[] repeat(float[] arr, int times) {
return repeat(arr, times, Integer.MAX_VALUE);
}
/**
* 重复数组中的元素
* @param arr 重复内容
* @param times 重复次数
* @param maxLength 最大长度
* @return 重复后的数组
*/
public static float[] repeat(float[] arr, int times, int maxLength) {
AssertTools.checkArgument(Objects.nonNull(arr));
AssertTools.checkArgument(times >= 0,
@@ -680,24 +597,10 @@ public class ArrayTools {
// repeat - double
/**
* 重复数组中的元素
*
* @param arr 重复内容
* @param times 重复次数
* @return 重复后的数组
*/
public static double[] repeat(double[] arr, int times) {
return repeat(arr, times, Integer.MAX_VALUE);
}
/**
* 重复数组中的元素
* @param arr 重复内容
* @param times 重复次数
* @param maxLength 最大长度
* @return 重复后的数组
*/
public static double[] repeat(double[] arr, int times, int maxLength) {
AssertTools.checkArgument(Objects.nonNull(arr));
AssertTools.checkArgument(times >= 0,
@@ -719,35 +622,15 @@ public class ArrayTools {
// fill - char
/**
* 填充数组
*
* @param a 要填充的数组
* @param values 填充内容
*/
public static void fill(char[] a, @Nullable char[] values) {
public static void fill(char[] a, char[] values) {
fill(a, 0, a.length, values);
}
/**
* 填充数组
*
* @param a 要填充的数组
* @param values 填充内容
*/
public static void fill(char[] a, @Nullable String values) {
public static void fill(char[] a, String values) {
fill(a, 0, a.length, values != null ? values.toCharArray() : EMPTY_CHAR_ARRAY);
}
/**
* 填充数组
*
* @param a 要填充的数组
* @param fromIndex 开始位置
* @param toIndex 结束位置
* @param values 填充内容
*/
public static void fill(char[] a, int fromIndex, int toIndex, @Nullable char[] values) {
public static void fill(char[] a, int fromIndex, int toIndex, char[] values) {
AssertTools.checkArgument(Objects.nonNull(a));
if (values == null || values.length == 0) {
return;
@@ -772,25 +655,11 @@ public class ArrayTools {
// fill - byte
/**
* 填充数组
*
* @param a 要填充的数组
* @param values 填充内容
*/
public static void fill(byte[] a, @Nullable byte[] values) {
public static void fill(byte[] a, byte[] values) {
fill(a, 0, a.length, values);
}
/**
* 填充数组
*
* @param a 要填充的数组
* @param fromIndex 开始位置
* @param toIndex 结束位置
* @param values 填充内容
*/
public static void fill(byte[] a, int fromIndex, int toIndex, @Nullable byte[] values) {
public static void fill(byte[] a, int fromIndex, int toIndex, byte[] values) {
AssertTools.checkArgument(Objects.nonNull(a));
if (values == null || values.length == 0) {
return;
@@ -815,25 +684,11 @@ public class ArrayTools {
// fill - short
/**
* 填充数组
*
* @param a 要填充的数组
* @param values 填充内容
*/
public static void fill(short[] a, @Nullable short[] values) {
public static void fill(short[] a, short[] values) {
fill(a, 0, a.length, values);
}
/**
* 填充数组
*
* @param a 要填充的数组
* @param fromIndex 开始位置
* @param toIndex 结束位置
* @param values 填充内容
*/
public static void fill(short[] a, int fromIndex, int toIndex, @Nullable short[] values) {
public static void fill(short[] a, int fromIndex, int toIndex, short[] values) {
AssertTools.checkArgument(Objects.nonNull(a));
if (values == null || values.length == 0) {
return;
@@ -858,25 +713,11 @@ public class ArrayTools {
// fill - int
/**
* 填充数组
*
* @param a 要填充的数组
* @param values 填充内容
*/
public static void fill(int[] a, @Nullable int[] values) {
public static void fill(int[] a, int[] values) {
fill(a, 0, a.length, values);
}
/**
* 填充数组
*
* @param a 要填充的数组
* @param fromIndex 开始位置
* @param toIndex 结束位置
* @param values 填充内容
*/
public static void fill(int[] a, int fromIndex, int toIndex, @Nullable int[] values) {
public static void fill(int[] a, int fromIndex, int toIndex, int[] values) {
AssertTools.checkArgument(Objects.nonNull(a));
if (values == null || values.length == 0) {
return;
@@ -901,25 +742,11 @@ public class ArrayTools {
// fill - long
/**
* 填充数组
*
* @param a 要填充的数组
* @param values 填充内容
*/
public static void fill(long[] a, @Nullable long[] values) {
public static void fill(long[] a, long[] values) {
fill(a, 0, a.length, values);
}
/**
* 填充数组
*
* @param a 要填充的数组
* @param fromIndex 开始位置
* @param toIndex 结束位置
* @param values 填充内容
*/
public static void fill(long[] a, int fromIndex, int toIndex, @Nullable long[] values) {
public static void fill(long[] a, int fromIndex, int toIndex, long[] values) {
AssertTools.checkArgument(Objects.nonNull(a));
if (values == null || values.length == 0) {
return;
@@ -944,25 +771,11 @@ public class ArrayTools {
// fill - float
/**
* 填充数组
*
* @param a 要填充的数组
* @param values 填充内容
*/
public static void fill(float[] a, @Nullable float[] values) {
public static void fill(float[] a, float[] values) {
fill(a, 0, a.length, values);
}
/**
* 填充数组
*
* @param a 要填充的数组
* @param fromIndex 开始位置
* @param toIndex 结束位置
* @param values 填充内容
*/
public static void fill(float[] a, int fromIndex, int toIndex, @Nullable float[] values) {
public static void fill(float[] a, int fromIndex, int toIndex, float[] values) {
AssertTools.checkArgument(Objects.nonNull(a));
if (values == null || values.length == 0) {
return;
@@ -987,25 +800,11 @@ public class ArrayTools {
// fill - double
/**
* 填充数组
*
* @param a 要填充的数组
* @param values 填充内容
*/
public static void fill(double[] a, @Nullable double[] values) {
public static void fill(double[] a, double[] values) {
fill(a, 0, a.length, values);
}
/**
* 填充数组
*
* @param a 要填充的数组
* @param fromIndex 开始位置
* @param toIndex 结束位置
* @param values 填充内容
*/
public static void fill(double[] a, int fromIndex, int toIndex, @Nullable double[] values) {
public static void fill(double[] a, int fromIndex, int toIndex, double[] values) {
AssertTools.checkArgument(Objects.nonNull(a));
if (values == null || values.length == 0) {
return;
@@ -1030,37 +829,15 @@ public class ArrayTools {
// fill - T
/**
* 填充数组
*
* @param a 要填充的数组
* @param values 填充内容
*/
public static <T> void fill(T[] a, @Nullable T[] values) {
public static <T> void fill(@Nonnull T[] a, T[] values) {
fillInternal(a, 0, a.length, values);
}
/**
* 填充数组
*
* @param a 要填充的数组
* @param fromIndex 开始位置
* @param toIndex 结束位置
* @param values 填充内容
*/
public static <T> void fill(T[] a, int fromIndex, int toIndex, @Nullable T[] values) {
public static <T> void fill(@Nonnull T[] a, int fromIndex, int toIndex, T[] values) {
fillInternal(a, fromIndex, toIndex, values);
}
/**
* 填充数组
*
* @param a 要填充的数组
* @param fromIndex 开始位置
* @param toIndex 结束位置
* @param values 填充内容
*/
private static <T> void fillInternal(T[] a, int fromIndex, int toIndex, @Nullable T[] values) {
private static <T> void fillInternal(@Nonnull T[] a, int fromIndex, int toIndex, @Nullable T[] values) {
AssertTools.checkArgument(Objects.nonNull(a));
if (values == null || values.length == 0) {
return;
@@ -1087,9 +864,9 @@ public class ArrayTools {
// #region - indexOf
public static <T> int indexOf(@Nullable T[] arr, Predicate<? super T> predicate) {
public static <T> int indexOfWithPredicate(T[] arr, Predicate<? super T> predicate) {
AssertTools.checkNotNull(predicate);
if (arr == null || arr.length == 0) {
if (isNullOrEmpty(arr)) {
return NOT_FOUND_INDEX;
}
for (int i = 0; i < arr.length; i++) {
@@ -1100,12 +877,12 @@ public class ArrayTools {
return NOT_FOUND_INDEX;
}
public static <T> int indexOf(@Nullable T[] arr, @Nullable T obj) {
return indexOf(arr, item -> Objects.equals(item, obj));
public static <T> int indexOf(T[] arr, T obj) {
return indexOfWithPredicate(arr, item -> Objects.equals(item, obj));
}
public static int indexOf(@Nullable char[] arr, char value) {
if (arr == null || arr.length == 0) {
public static int indexOf(char[] arr, char value) {
if (isNullOrEmpty(arr)) {
return NOT_FOUND_INDEX;
}
for (int i = 0; i < arr.length; i++) {
@@ -1116,8 +893,8 @@ public class ArrayTools {
return NOT_FOUND_INDEX;
}
public static int indexOf(@Nullable byte[] arr, byte value) {
if (arr == null || arr.length == 0) {
public static int indexOf(byte[] arr, byte value) {
if (isNullOrEmpty(arr)) {
return NOT_FOUND_INDEX;
}
for (int i = 0; i < arr.length; i++) {
@@ -1128,8 +905,8 @@ public class ArrayTools {
return NOT_FOUND_INDEX;
}
public static int indexOf(@Nullable short[] arr, short value) {
if (arr == null || arr.length == 0) {
public static int indexOf(short[] arr, short value) {
if (isNullOrEmpty(arr)) {
return NOT_FOUND_INDEX;
}
for (int i = 0; i < arr.length; i++) {
@@ -1140,8 +917,8 @@ public class ArrayTools {
return NOT_FOUND_INDEX;
}
public static int indexOf(@Nullable int[] arr, int value) {
if (arr == null || arr.length == 0) {
public static int indexOf(int[] arr, int value) {
if (isNullOrEmpty(arr)) {
return NOT_FOUND_INDEX;
}
for (int i = 0; i < arr.length; i++) {
@@ -1152,8 +929,8 @@ public class ArrayTools {
return NOT_FOUND_INDEX;
}
public static int indexOf(@Nullable long[] arr, long value) {
if (arr == null || arr.length == 0) {
public static int indexOf(long[] arr, long value) {
if (isNullOrEmpty(arr)) {
return NOT_FOUND_INDEX;
}
for (int i = 0; i < arr.length; i++) {
@@ -1164,8 +941,8 @@ public class ArrayTools {
return NOT_FOUND_INDEX;
}
public static int indexOf(@Nullable float[] arr, float value) {
if (arr == null || arr.length == 0) {
public static int indexOf(float[] arr, float value) {
if (isNullOrEmpty(arr)) {
return NOT_FOUND_INDEX;
}
for (int i = 0; i < arr.length; i++) {
@@ -1176,8 +953,8 @@ public class ArrayTools {
return NOT_FOUND_INDEX;
}
public static int indexOf(@Nullable double[] arr, double value) {
if (arr == null || arr.length == 0) {
public static int indexOf(double[] arr, double value) {
if (isNullOrEmpty(arr)) {
return NOT_FOUND_INDEX;
}
for (int i = 0; i < arr.length; i++) {
@@ -1192,9 +969,9 @@ public class ArrayTools {
// #region - lastIndexOf
public static <T> int lastIndexOf(@Nullable T[] arr, Predicate<? super T> predicate) {
public static <T> int lastIndexOfWithPredicate(T[] arr, @Nonnull Predicate<? super T> predicate) {
AssertTools.checkNotNull(predicate);
if (arr == null || arr.length == 0) {
if (isNullOrEmpty(arr)) {
return NOT_FOUND_INDEX;
}
for (int i = arr.length - 1; i >= 0; i--) {
@@ -1206,11 +983,11 @@ public class ArrayTools {
}
public static <T> int lastIndexOf(T[] arr, T obj) {
return lastIndexOf(arr, item -> Objects.equals(item, obj));
return lastIndexOfWithPredicate(arr, item -> Objects.equals(item, obj));
}
public static int lastIndexOf(@Nullable char[] arr, char value) {
if (arr == null || arr.length == 0) {
public static int lastIndexOf(char[] arr, char value) {
if (isNullOrEmpty(arr)) {
return NOT_FOUND_INDEX;
}
for (int i = arr.length - 1; i >= 0; i--) {
@@ -1221,8 +998,8 @@ public class ArrayTools {
return NOT_FOUND_INDEX;
}
public static int lastIndexOf(@Nullable byte[] arr, byte value) {
if (arr == null || arr.length == 0) {
public static int lastIndexOf(byte[] arr, byte value) {
if (isNullOrEmpty(arr)) {
return NOT_FOUND_INDEX;
}
for (int i = arr.length - 1; i >= 0; i--) {
@@ -1233,8 +1010,8 @@ public class ArrayTools {
return NOT_FOUND_INDEX;
}
public static int lastIndexOf(@Nullable short[] arr, short value) {
if (arr == null || arr.length == 0) {
public static int lastIndexOf(short[] arr, short value) {
if (isNullOrEmpty(arr)) {
return NOT_FOUND_INDEX;
}
for (int i = arr.length - 1; i >= 0; i--) {
@@ -1245,8 +1022,8 @@ public class ArrayTools {
return NOT_FOUND_INDEX;
}
public static int lastIndexOf(@Nullable int[] arr, int value) {
if (arr == null || arr.length == 0) {
public static int lastIndexOf(int[] arr, int value) {
if (isNullOrEmpty(arr)) {
return NOT_FOUND_INDEX;
}
for (int i = arr.length - 1; i >= 0; i--) {
@@ -1257,8 +1034,8 @@ public class ArrayTools {
return NOT_FOUND_INDEX;
}
public static int lastIndexOf(@Nullable long[] arr, long value) {
if (arr == null || arr.length == 0) {
public static int lastIndexOf(long[] arr, long value) {
if (isNullOrEmpty(arr)) {
return NOT_FOUND_INDEX;
}
for (int i = arr.length - 1; i >= 0; i--) {
@@ -1269,8 +1046,8 @@ public class ArrayTools {
return NOT_FOUND_INDEX;
}
public static int lastIndexOf(@Nullable float[] arr, float value) {
if (arr == null || arr.length == 0) {
public static int lastIndexOf(float[] arr, float value) {
if (isNullOrEmpty(arr)) {
return NOT_FOUND_INDEX;
}
for (int i = arr.length - 1; i >= 0; i--) {
@@ -1281,8 +1058,8 @@ public class ArrayTools {
return NOT_FOUND_INDEX;
}
public static int lastIndexOf(@Nullable double[] arr, double value) {
if (arr == null || arr.length == 0) {
public static int lastIndexOf(double[] arr, double value) {
if (isNullOrEmpty(arr)) {
return NOT_FOUND_INDEX;
}
for (int i = arr.length - 1; i >= 0; i--) {
@@ -1297,40 +1074,40 @@ public class ArrayTools {
// #region - contains
public static <T> boolean contains(@Nullable T[] arr, @Nullable T obj) {
public static <T> boolean contains(T[] arr, T obj) {
return indexOf(arr, obj) > NOT_FOUND_INDEX;
}
public static boolean contains(@Nullable char[] arr, char obj) {
public static boolean contains(char[] arr, char obj) {
return indexOf(arr, obj) > NOT_FOUND_INDEX;
}
public static boolean contains(@Nullable byte[] arr, byte obj) {
public static boolean contains(byte[] arr, byte obj) {
return indexOf(arr, obj) > NOT_FOUND_INDEX;
}
public static boolean contains(@Nullable short[] arr, short obj) {
public static boolean contains(short[] arr, short obj) {
return indexOf(arr, obj) > NOT_FOUND_INDEX;
}
public static boolean contains(@Nullable int[] arr, int obj) {
public static boolean contains(int[] arr, int obj) {
return indexOf(arr, obj) > NOT_FOUND_INDEX;
}
public static boolean contains(@Nullable long[] arr, long obj) {
public static boolean contains(long[] arr, long obj) {
return indexOf(arr, obj) > NOT_FOUND_INDEX;
}
public static boolean contains(@Nullable float[] arr, float obj) {
public static boolean contains(float[] arr, float obj) {
return indexOf(arr, obj) > NOT_FOUND_INDEX;
}
public static boolean contains(@Nullable double[] arr, double obj) {
public static boolean contains(double[] arr, double obj) {
return indexOf(arr, obj) > NOT_FOUND_INDEX;
}
public static boolean containsValue(@Nullable BigDecimal[] arr, @Nullable BigDecimal obj) {
return indexOf(arr, item -> BigDecimals.equalsValue(item, obj)) > NOT_FOUND_INDEX;
public static boolean containsValue(BigDecimal[] arr, BigDecimal obj) {
return indexOfWithPredicate(arr, item -> BigDecimals.equalsValue(item, obj)) > NOT_FOUND_INDEX;
}
// #endregion

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2024-2025 the original author or authors.
* Copyright 2024 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.
@@ -16,10 +16,11 @@
package xyz.zhouxy.plusone.commons.util;
import java.util.Objects;
import java.util.Optional;
import java.util.function.Supplier;
import javax.annotation.Nullable;
import javax.annotation.Nonnull;
import xyz.zhouxy.plusone.commons.exception.DataNotExistsException;
import xyz.zhouxy.plusone.commons.exception.system.DataOperationResultException;
@@ -48,355 +49,143 @@ public class AssertTools {
// #region - Argument
// ================================
/**
* 检查实参
*
* @param condition 判断参数是否符合条件的结果
* @throws IllegalArgumentException 当条件不满足时抛出
*/
/** Throw {@link IllegalArgumentException} if the {@code condition} is false. */
public static void checkArgument(boolean condition) {
checkCondition(condition, IllegalArgumentException::new);
}
/**
* 检查实参
*
* @param condition 判断参数是否符合条件的结果
* @param errorMessage 异常信息
* @throws IllegalArgumentException 当条件不满足时抛出
*/
public static void checkArgument(boolean condition, @Nullable String errorMessage) {
checkCondition(condition, () -> new IllegalArgumentException(errorMessage));
/** Throw {@link IllegalArgumentException} if the {@code condition} is false. */
public static void checkArgument(boolean condition, String errMsg) {
checkCondition(condition, () -> new IllegalArgumentException(errMsg));
}
/**
* 检查实参
*
* @param condition 判断参数是否符合条件的结果
* @param errorMessageSupplier 异常信息
* @throws IllegalArgumentException 当条件不满足时抛出
*/
public static void checkArgument(boolean condition, Supplier<String> errorMessageSupplier) {
checkCondition(condition, () -> new IllegalArgumentException(errorMessageSupplier.get()));
/** Throw {@link IllegalArgumentException} if the {@code condition} is false. */
public static void checkArgument(boolean condition, @Nonnull Supplier<String> messageSupplier) {
checkCondition(condition, () -> new IllegalArgumentException(messageSupplier.get()));
}
/**
* 检查实参
*
* @param condition 判断参数是否符合条件的结果
* @param errorMessageTemplate 异常信息模板
* @param errorMessageArgs 异常信息参数
* @throws IllegalArgumentException 当条件不满足时抛出
*/
public static void checkArgument(boolean condition,
String errorMessageTemplate, Object... errorMessageArgs) {
checkCondition(condition,
() -> new IllegalArgumentException(String.format(errorMessageTemplate, errorMessageArgs)));
/** Throw {@link IllegalArgumentException} if the {@code condition} is false. */
public static void checkArgument(boolean condition, String format, Object... args) {
checkCondition(condition, () -> new IllegalArgumentException(String.format(format, args)));
}
// ================================
// #endregion - Argument
// ================================
// ================================
// #region - ArgumentNotNull
// ================================
/**
* 判断入参不为 {@code null}
*
* @param <T> 入参类型
* @param obj 入参
* @throws IllegalArgumentException 当 {@code obj} 为 {@code null} 时抛出
*/
public static <T> T checkArgumentNotNull(@Nullable T obj) {
checkCondition(obj != null, IllegalArgumentException::new);
return obj;
}
/**
* 判断入参不为 {@code null}
*
* @param <T> 入参类型
* @param obj 入参
* @param errorMessage 异常信息
* @throws IllegalArgumentException 当 {@code obj} 为 {@code null} 时抛出
*/
public static <T> T checkArgumentNotNull(@Nullable T obj, String errorMessage) {
checkCondition(obj != null, () -> new IllegalArgumentException(errorMessage));
return obj;
}
/**
* 判断入参不为 {@code null}
*
* @param <T> 入参类型
* @param obj 入参
* @param errorMessageSupplier 异常信息
* @throws IllegalArgumentException 当 {@code obj} 为 {@code null} 时抛出
*/
public static <T> T checkArgumentNotNull(@Nullable T obj, Supplier<String> errorMessageSupplier) {
checkCondition(obj != null, () -> new IllegalArgumentException(errorMessageSupplier.get()));
return obj;
}
/**
* 判断入参不为 {@code null}
*
* @param <T> 入参类型
* @param obj 入参
* @param errorMessageTemplate 异常信息模板
* @param errorMessageArgs 异常信息参数
* @throws IllegalArgumentException 当 {@code obj} 为 {@code null} 时抛出
*/
public static <T> T checkArgumentNotNull(@Nullable T obj,
String errorMessageTemplate, Object... errorMessageArgs) {
checkCondition(obj != null,
() -> new IllegalArgumentException(String.format(errorMessageTemplate, errorMessageArgs)));
return obj;
}
// ================================
// #endregion - ArgumentNotNull
// ================================
// ================================
// #region - State
// ================================
/**
* 检查状态
*
* @param condition 判断状态是否符合条件的结果
* @throws IllegalStateException 当条件不满足时抛出
*/
/** Throw {@link IllegalStateException} if the {@code condition} is false. */
public static void checkState(boolean condition) {
checkCondition(condition, IllegalStateException::new);
}
/**
* 检查状态
*
* @param condition 判断状态是否符合条件的结果
* @param errorMessage 异常信息
* @throws IllegalStateException 当条件不满足时抛出
*/
public static void checkState(boolean condition, @Nullable String errorMessage) {
checkCondition(condition, () -> new IllegalStateException(errorMessage));
/** Throw {@link IllegalStateException} if the {@code condition} is false. */
public static void checkState(boolean condition, String errMsg) {
checkCondition(condition, () -> new IllegalStateException(errMsg));
}
/**
* 检查状态
*
* @param condition 判断状态是否符合条件的结果
* @param errorMessageSupplier 异常信息
* @throws IllegalStateException 当条件不满足时抛出
*/
public static void checkState(boolean condition, Supplier<String> errorMessageSupplier) {
checkCondition(condition, () -> new IllegalStateException(errorMessageSupplier.get()));
/** Throw {@link IllegalStateException} if the {@code condition} is false. */
public static void checkState(boolean condition, @Nonnull Supplier<String> messageSupplier) {
checkCondition(condition, () -> new IllegalStateException(messageSupplier.get()));
}
/**
* 检查状态
*
* @param condition 判断状态是否符合条件的结果
* @param errorMessageTemplate 异常信息模板
* @param errorMessageArgs 异常信息参数
* @throws IllegalStateException 当条件不满足时抛出
*/
public static void checkState(boolean condition,
String errorMessageTemplate, Object... errorMessageArgs) {
checkCondition(condition,
() -> new IllegalStateException(String.format(errorMessageTemplate, errorMessageArgs)));
/** Throw {@link IllegalStateException} if the {@code condition} is false. */
public static void checkState(boolean condition, String format, Object... args) {
checkCondition(condition, () -> new IllegalStateException(String.format(format, args)));
}
// ================================
// #endregion - State
// #endregion
// ================================
// ================================
// #region - NotNull
// ================================
/**
* 判空
*
* @param <T> 入参类型
* @param obj 入参
* @throws NullPointerException 当 {@code obj} 为 {@code null} 时抛出
*/
public static <T> void checkNotNull(@Nullable T obj) {
/** Throw {@link NullPointerException} if the {@code obj} is null. */
public static <T> void checkNotNull(T obj) {
checkCondition(obj != null, NullPointerException::new);
}
/**
* 判空
*
* @param <T> 入参类型
* @param obj 入参
* @param errorMessage 异常信息
* @throws NullPointerException 当 {@code obj} 为 {@code null} 时抛出
*/
public static <T> void checkNotNull(@Nullable T obj, String errorMessage) {
checkCondition(obj != null, () -> new NullPointerException(errorMessage));
/** Throw {@link NullPointerException} if the {@code obj} is null. */
public static <T> void checkNotNull(T obj, String errMsg) {
checkCondition(obj != null, () -> new NullPointerException(errMsg));
}
/**
* 判空
*
* @param <T> 入参类型
* @param obj 入参
* @param errorMessageSupplier 异常信息
* @throws NullPointerException 当 {@code obj} 为 {@code null} 时抛出
*/
public static <T> void checkNotNull(@Nullable T obj, Supplier<String> errorMessageSupplier) {
checkCondition(obj != null, () -> new NullPointerException(errorMessageSupplier.get()));
/** Throw {@link NullPointerException} if the {@code obj} is null. */
public static <T> void checkNotNull(T obj, @Nonnull Supplier<String> messageSupplier) {
checkCondition(obj != null, () -> new NullPointerException(messageSupplier.get()));
}
/**
* 判空
*
* @param <T> 入参类型
* @param obj 入参
* @param errorMessageTemplate 异常信息模板
* @param errorMessageArgs 异常信息参数
* @throws NullPointerException 当 {@code obj} 为 {@code null} 时抛出
*/
public static <T> void checkNotNull(@Nullable T obj,
String errorMessageTemplate, Object... errorMessageArgs) {
checkCondition(obj != null,
() -> new NullPointerException(String.format(errorMessageTemplate, errorMessageArgs)));
/** Throw {@link NullPointerException} if the {@code obj} is null. */
public static <T> void checkNotNull(T obj, String format, Object... args) {
checkCondition(obj != null, () -> new NullPointerException(String.format(format, args)));
}
// ================================
// #endregion - NotNull
// #endregion
// ================================
// ================================
// #region - Exists
// ================================
/**
* 检查数据是否存在
*
* @param <T> 入参类型
* @param obj 入参
* @return 如果 {@code obj} 存在,返回 {@code obj} 本身
* @throws DataNotExistsException 当 {@code obj} 不存在时抛出
*/
public static <T> T checkExists(@Nullable T obj)
/** Throw {@link DataNotExistsException} if the {@code obj} is null. */
public static <T> T checkExists(T obj)
throws DataNotExistsException {
checkCondition(obj != null, DataNotExistsException::new);
checkCondition(Objects.nonNull(obj), DataNotExistsException::new);
return obj;
}
/**
* 检查数据是否存在
*
* @param <T> 入参类型
* @param obj 入参
* @param errorMessage 异常信息
* @return 如果 {@code obj} 存在,返回 {@code obj} 本身
* @throws DataNotExistsException 当 {@code obj} 不存在时抛出
*/
public static <T> T checkExists(@Nullable T obj, String errorMessage)
/** Throw {@link DataNotExistsException} if the {@code obj} is null. */
public static <T> T checkExists(T obj, String message)
throws DataNotExistsException {
checkCondition(obj != null, () -> new DataNotExistsException(errorMessage));
checkCondition(Objects.nonNull(obj), () -> new DataNotExistsException(message));
return obj;
}
/**
* 检查数据是否存在
*
* @param <T> 入参类型
* @param obj 入参
* @param errorMessageSupplier 异常信息
* @return 如果 {@code obj} 存在,返回 {@code obj} 本身
* @throws DataNotExistsException 当 {@code obj} 不存在时抛出
*/
public static <T> T checkExists(@Nullable T obj, Supplier<String> errorMessageSupplier)
/** Throw {@link DataNotExistsException} if the {@code obj} is null. */
public static <T> T checkExists(T obj, @Nonnull Supplier<String> messageSupplier)
throws DataNotExistsException {
checkCondition(obj != null, () -> new DataNotExistsException(errorMessageSupplier.get()));
checkCondition(Objects.nonNull(obj), () -> new DataNotExistsException(messageSupplier.get()));
return obj;
}
/**
* 检查数据是否存在
*
* @param <T> 入参类型
* @param obj 入参
* @param errorMessageTemplate 异常信息模板
* @param errorMessageArgs 异常信息参数
* @return 如果 {@code obj} 存在,返回 {@code obj} 本身
* @throws DataNotExistsException 当 {@code obj} 不存在时抛出
*/
public static <T> T checkExists(@Nullable T obj,
String errorMessageTemplate, Object... errorMessageArgs)
/** Throw {@link DataNotExistsException} if the {@code obj} is null. */
public static <T> T checkExists(T obj, String format, Object... args)
throws DataNotExistsException {
checkCondition(obj != null,
() -> new DataNotExistsException(String.format(errorMessageTemplate, errorMessageArgs)));
checkCondition(Objects.nonNull(obj), () -> new DataNotExistsException(String.format(format, args)));
return obj;
}
/**
* 检查数据是否存在
*
* @param <T> 入参类型
* @param optional 入参
* @return 如果 {@code optional} 存在,返回 {@code optional} 包含的值
* @throws DataNotExistsException 当 {@code optional} 的值不存在时抛出
*/
public static <T> T checkExists(Optional<T> optional)
/** Throw {@link DataNotExistsException} if the {@code optional} is present. */
public static <T> T checkExists(@Nonnull Optional<T> optional)
throws DataNotExistsException {
checkCondition(optional.isPresent(), DataNotExistsException::new);
return optional.get();
}
/**
* 检查数据是否存在
*
* @param <T> 入参类型
* @param optional 入参
* @param errorMessage 异常信息
* @return 如果 {@code optional} 存在,返回 {@code optional} 包含的值
* @throws DataNotExistsException 当 {@code optional} 的值不存在时抛出
*/
public static <T> T checkExists(Optional<T> optional, String errorMessage)
/** Throw {@link DataNotExistsException} if the {@code optional} is present. */
public static <T> T checkExists(@Nonnull Optional<T> optional, String message)
throws DataNotExistsException {
checkCondition(optional.isPresent(), () -> new DataNotExistsException(errorMessage));
checkCondition(optional.isPresent(), () -> new DataNotExistsException(message));
return optional.get();
}
/**
* 检查数据是否存在
*
* @param <T> 入参类型
* @param optional 入参
* @param errorMessageSupplier 异常信息
* @return 如果 {@code optional} 存在,返回 {@code optional} 包含的值
* @throws DataNotExistsException 当 {@code optional} 的值不存在时抛出
*/
public static <T> T checkExists(Optional<T> optional, Supplier<String> errorMessageSupplier)
/** Throw {@link DataNotExistsException} if the {@code optional} is present. */
public static <T> T checkExists(@Nonnull Optional<T> optional, @Nonnull Supplier<String> messageSupplier)
throws DataNotExistsException {
checkCondition(optional.isPresent(), () -> new DataNotExistsException(errorMessageSupplier.get()));
checkCondition(optional.isPresent(), () -> new DataNotExistsException(messageSupplier.get()));
return optional.get();
}
/**
* 检查数据是否存在
*
* @param <T> 入参类型
* @param optional 入参
* @param errorMessageTemplate 异常信息模板
* @param errorMessageArgs 异常信息参数
* @return 如果 {@code optional} 存在,返回 {@code optional} 包含的值
* @throws DataNotExistsException 当 {@code optional} 的值不存在时抛出
*/
public static <T> T checkExists(Optional<T> optional,
String errorMessageTemplate, Object... errorMessageArgs)
/** Throw {@link DataNotExistsException} if the {@code optional} is present. */
public static <T> T checkExists(@Nonnull Optional<T> optional, String format, Object... args)
throws DataNotExistsException {
checkCondition(optional.isPresent(),
() -> new DataNotExistsException(String.format(errorMessageTemplate, errorMessageArgs)));
checkCondition(optional.isPresent(), () -> new DataNotExistsException(String.format(format, args)));
return optional.get();
}
@@ -408,186 +197,78 @@ public class AssertTools {
// #region - AffectedRows
// ================================
/**
* 当影响的数据量与预计不同时抛出 {@link DataOperationResultException}。
*
* @param expectedValue 预计的数量
* @param result 实际影响的数据量
*/
public static void checkAffectedRows(int expectedValue, int result) {
checkAffectedRows(expectedValue, result,
"The number of rows affected is expected to be %d, but is: %d", expectedValue, result);
}
/**
* 当影响的数据量与预计不同时抛出 {@link DataOperationResultException}。
*
* @param expectedValue 预计的数量
* @param result 实际影响的数据量
* @param errorMessage 异常信息
*/
public static void checkAffectedRows(int expectedValue, int result, @Nullable String errorMessage) {
checkCondition(expectedValue == result, () -> new DataOperationResultException(errorMessage));
public static void checkAffectedRows(int expectedValue, int result, String message) {
checkCondition(expectedValue == result, () -> new DataOperationResultException(message));
}
/**
* 当影响的数据量与预计不同时抛出 {@link DataOperationResultException}。
*
* @param expectedValue 预计的数量
* @param result 实际影响的数据量
* @param errorMessageSupplier 异常信息
*/
public static void checkAffectedRows(int expectedValue, int result,
Supplier<String> errorMessageSupplier) {
@Nonnull Supplier<String> messageSupplier) {
checkCondition(expectedValue == result,
() -> new DataOperationResultException(errorMessageSupplier.get()));
() -> new DataOperationResultException(messageSupplier.get()));
}
/**
* 当影响的数据量与预计不同时抛出 {@link DataOperationResultException}。
*
* @param expectedValue 预计的数量
* @param result 实际影响的数据量
* @param errorMessageTemplate 异常信息模板
* @param errorMessageArgs 异常信息参数
*/
public static void checkAffectedRows(int expectedValue, int result,
String errorMessageTemplate, Object... errorMessageArgs) {
public static void checkAffectedRows(int expectedValue, int result, String format, Object... args) {
checkCondition(expectedValue == result,
() -> new DataOperationResultException(String.format(errorMessageTemplate, errorMessageArgs)));
() -> new DataOperationResultException(String.format(format, args)));
}
/**
* 当影响的数据量与预计不同时抛出 {@link DataOperationResultException}。
*
* @param expectedValue 预计的数量
* @param result 实际影响的数据量
*/
public static void checkAffectedRows(long expectedValue, long result) {
checkAffectedRows(expectedValue, result,
"The number of rows affected is expected to be %d, but is: %d", expectedValue, result);
}
/**
* 当影响的数据量与预计不同时抛出 {@link DataOperationResultException}。
*
* @param expectedValue 预计的数量
* @param result 实际影响的数据量
* @param errorMessage 异常信息
*/
public static void checkAffectedRows(long expectedValue, long result, @Nullable String errorMessage) {
checkCondition(expectedValue == result, () -> new DataOperationResultException(errorMessage));
public static void checkAffectedRows(long expectedValue, long result, String message) {
checkCondition(expectedValue == result, () -> new DataOperationResultException(message));
}
/**
* 当影响的数据量与预计不同时抛出 {@link DataOperationResultException}。
*
* @param expectedValue 预计的数量
* @param result 实际影响的数据量
* @param errorMessageSupplier 异常信息
*/
public static void checkAffectedRows(long expectedValue, long result,
Supplier<String> errorMessageSupplier) {
@Nonnull Supplier<String> messageSupplier) {
checkCondition(expectedValue == result,
() -> new DataOperationResultException(errorMessageSupplier.get()));
() -> new DataOperationResultException(messageSupplier.get()));
}
/**
* 当影响的数据量与预计不同时抛出 {@link DataOperationResultException}。
*
* @param expectedValue 预计的数量
* @param result 实际影响的数据量
* @param errorMessageTemplate 异常信息模板
* @param errorMessageArgs 异常信息参数
*/
public static void checkAffectedRows(long expectedValue, long result,
String errorMessageTemplate, Object... errorMessageArgs) {
public static void checkAffectedRows(long expectedValue, long result, String format, Object... args) {
checkCondition(expectedValue == result,
() -> new DataOperationResultException(String.format(errorMessageTemplate, errorMessageArgs)));
() -> new DataOperationResultException(String.format(format, args)));
}
/**
* 当影响的数据量不为 1 时抛出 {@link DataOperationResultException}。
*
* @param result 实际影响的数据量
*/
public static void checkAffectedOneRow(int result) {
checkAffectedRows(1, result,
() -> "The number of rows affected is expected to be 1, but is: " + result);
}
/**
* 当影响的数据量不为 1 时抛出 {@link DataOperationResultException}。
*
* @param result 实际影响的数据量
* @param errorMessage 异常信息
*/
public static void checkAffectedOneRow(int result, String errorMessage) {
checkAffectedRows(1, result, errorMessage);
public static void checkAffectedOneRow(int result, String message) {
checkAffectedRows(1, result, message);
}
/**
* 当影响的数据量不为 1 时抛出 {@link DataOperationResultException}。
*
* @param result 实际影响的数据量
* @param errorMessageSupplier 异常信息
*/
public static void checkAffectedOneRow(int result, Supplier<String> errorMessageSupplier) {
checkAffectedRows(1, result, errorMessageSupplier);
public static void checkAffectedOneRow(int result, @Nonnull Supplier<String> messageSupplier) {
checkAffectedRows(1, result, messageSupplier);
}
/**
* 当影响的数据量不为 1 时抛出 {@link DataOperationResultException}。
*
* @param result 实际影响的数据量
* @param errorMessageTemplate 异常信息模板
* @param errorMessageArgs 异常信息参数
*/
public static void checkAffectedOneRow(int result,
String errorMessageTemplate, Object... errorMessageArgs) {
checkAffectedRows(1, result, errorMessageTemplate, errorMessageArgs);
public static void checkAffectedOneRow(int result, String format, Object... args) {
checkAffectedRows(1, result, format, args);
}
/**
* 当影响的数据量不为 1 时抛出 {@link DataOperationResultException}。
*
* @param result 实际影响的数据量
*/
public static void checkAffectedOneRow(long result) {
checkAffectedRows(1L, result,
() -> "The number of rows affected is expected to be 1, but is: " + result);
}
/**
* 当影响的数据量不为 1 时抛出 {@link DataOperationResultException}。
*
* @param result 实际影响的数据量
* @param errorMessage 异常信息
*/
public static void checkAffectedOneRow(long result, String errorMessage) {
checkAffectedRows(1L, result, errorMessage);
public static void checkAffectedOneRow(long result, String message) {
checkAffectedRows(1L, result, message);
}
/**
* 当影响的数据量不为 1 时抛出 {@link DataOperationResultException}。
*
* @param result 实际影响的数据量
* @param errorMessageSupplier 异常信息
*/
public static void checkAffectedOneRow(long result, Supplier<String> errorMessageSupplier) {
checkAffectedRows(1L, result, errorMessageSupplier);
public static void checkAffectedOneRow(long result, @Nonnull Supplier<String> messageSupplier) {
checkAffectedRows(1L, result, messageSupplier);
}
/**
* 当影响的数据量不为 1 时抛出 {@link DataOperationResultException}。
*
* @param result 实际影响的数据量
* @param errorMessageTemplate 异常信息模板
* @param errorMessageArgs 异常信息参数
*/
public static void checkAffectedOneRow(long result,
String errorMessageTemplate, Object... errorMessageArgs) {
checkAffectedRows(1L, result, errorMessageTemplate, errorMessageArgs);
public static void checkAffectedOneRow(long result, String format, Object... args) {
checkAffectedRows(1L, result, format, args);
}
// ================================
@@ -598,15 +279,7 @@ public class AssertTools {
// #region - Condition
// ================================
/**
* 当条件不满足时抛出异常。
*
* @param <T> 异常类型
* @param condition 条件
* @param e 异常
* @throws T 当条件不满足时抛出异常
*/
public static <T extends Exception> void checkCondition(boolean condition, Supplier<T> e)
public static <T extends Exception> void checkCondition(boolean condition, @Nonnull Supplier<T> e)
throws T {
if (!condition) {
throw e.get();

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2023-2025 the original author or authors.
* Copyright 2023-2024 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.
@@ -35,77 +35,32 @@ import xyz.zhouxy.plusone.commons.annotation.StaticFactoryMethod;
*/
public class BigDecimals {
/**
* 判断两个 {@code BigDecimal} 的值是否相等
*
* @param a 第一个 {@code BigDecimal}
* @param b 第二个 {@code BigDecimal}
* @return 当两个 {@code BigDecimal} 的值相等时返回 {@code true}
*/
public static boolean equalsValue(@Nullable BigDecimal a, @Nullable BigDecimal b) {
return (a == b) || (a != null && b != null && a.compareTo(b) == 0);
}
/**
* 判断两个 {@code BigDecimal} 的值 - 大于
*
* @param a 第一个 {@code BigDecimal}
* @param b 第二个 {@code BigDecimal}
* @return 当 {@code a} 大于 {@code b} 时返回 {@code true}
*/
public static boolean gt(BigDecimal a, BigDecimal b) {
AssertTools.checkNotNull(a, "Parameter could not be null.");
AssertTools.checkNotNull(b, "Parameter could not be null.");
return (a != b) && (a.compareTo(b) > 0);
}
/**
* 判断两个 {@code BigDecimal} 的值 - 大于等于
*
* @param a 第一个 {@code BigDecimal}
* @param b 第二个 {@code BigDecimal}
* @return 当 {@code a} 大于等于 {@code b} 时返回 {@code true}
*/
public static boolean ge(BigDecimal a, BigDecimal b) {
AssertTools.checkNotNull(a, "Parameter could not be null.");
AssertTools.checkNotNull(b, "Parameter could not be null.");
return (a == b) || (a.compareTo(b) >= 0);
return gt(a, b) || equalsValue(a, b);
}
/**
* 判断两个 {@code BigDecimal} 的值 - 小于
*
* @param a 第一个 {@code BigDecimal}
* @param b 第二个 {@code BigDecimal}
* @return 当 {@code a} 小于 {@code b} 时返回 {@code true}
*/
public static boolean lt(BigDecimal a, BigDecimal b) {
AssertTools.checkNotNull(a, "Parameter could not be null.");
AssertTools.checkNotNull(b, "Parameter could not be null.");
return (a != b) && (a.compareTo(b) < 0);
}
/**
* 判断两个 {@code BigDecimal} 的值 - 小于等于
*
* @param a 第一个 {@code BigDecimal}
* @param b 第二个 {@code BigDecimal}
* @return 当 {@code a} 小于等于 {@code b} 时返回 {@code true}
*/
public static boolean le(BigDecimal a, BigDecimal b) {
AssertTools.checkNotNull(a, "Parameter could not be null.");
AssertTools.checkNotNull(b, "Parameter could not be null.");
return (a == b) || (a.compareTo(b) <= 0);
return lt(a, b) || equalsValue(a, b);
}
/**
* 求和
*
* @param numbers {@code BigDecimal} 数组
* @return 求和结果
*/
public static BigDecimal sum(final BigDecimal... numbers) {
if (ArrayTools.isEmpty(numbers)) {
if (ArrayTools.isNullOrEmpty(numbers)) {
return BigDecimal.ZERO;
}
BigDecimal result = BigDecimals.nullToZero(numbers[0]);
@@ -118,25 +73,13 @@ public class BigDecimals {
return result;
}
/**
* 将 {@code null} 转换为 {@link BigDecimal#ZERO}
*
* @param val BigDecimal 对象
* @return 如果 {@code val} 为 {@code null},则返回 {@link BigDecimal#ZERO},否则返回 {@code val}
*/
@Nonnull
public static BigDecimal nullToZero(@Nullable final BigDecimal val) {
return val != null ? val : BigDecimal.ZERO;
}
/**
* 获取字符串所表示的数值转换为 {@code BigDecimal}
*
* @param val 表示数值的字符串
* @return {@code BigDecimal} 对象
*/
@StaticFactoryMethod(BigDecimal.class)
public static BigDecimal of(@Nullable final String val) {
public static BigDecimal of(final String val) {
return (StringTools.isNotBlank(val)) ? new BigDecimal(val) : BigDecimal.ZERO;
}

View File

@@ -44,9 +44,7 @@ import xyz.zhouxy.plusone.commons.time.YearQuarter;
*/
public class DateTimeTools {
// ================================
// #region - toString
// ================================
public static String toYearString(int year) {
return Integer.toString(YEAR.checkValidIntValue(year));
@@ -72,13 +70,9 @@ public class DateTimeTools {
return String.format("%02d", month.getValue());
}
// ================================
// #endregion
// ================================
// ================================
// #region - toDate
// ================================
/**
* 将时间戳转换为 {@link Date} 对象
@@ -143,13 +137,9 @@ public class DateTimeTools {
return Date.from(ZonedDateTime.of(localDate, localTime, zone).toInstant());
}
// ================================
// #endregion
// ================================
// ================================
// #region - toInstant
// ================================
/**
* 将时间戳转换为 {@link Instant} 对象
@@ -202,13 +192,9 @@ public class DateTimeTools {
return ZonedDateTime.of(localDateTime, zone).toInstant();
}
// ================================
// #endregion
// ================================
// ================================
// #region - toZonedDateTime
// ================================
/**
* 获取时间戳在指定时区的地区时间。
@@ -299,13 +285,9 @@ public class DateTimeTools {
return ZonedDateTime.of(localDateTime, zone);
}
// ================================
// #endregion
// ================================
// ================================
// #region - toLocalDateTime
// ================================
/**
* 获取时间戳在指定时区的地区时间。
@@ -373,13 +355,11 @@ public class DateTimeTools {
return LocalDateTime.ofInstant(zonedDateTime.toInstant(), zone);
}
// ================================
// #endregion
// ================================
// ================================
// ====================
// #region - toJodaInstant
// ================================
/**
* 将 {@link java.time.Instant} 转换为 {@link org.joda.time.Instant}
@@ -412,13 +392,9 @@ public class DateTimeTools {
return toJodaInstant(java.time.ZonedDateTime.of(localDateTime, zone));
}
// ================================
// #endregion
// ================================
// ================================
// #region - toJavaInstant
// ================================
/**
* 将 {@link org.joda.time.Instant} 对象转换为 {@link java.time.Instant} 对象
@@ -456,13 +432,9 @@ public class DateTimeTools {
return toJavaInstant(localDateTime.toDateTime(zone));
}
// ================================
// #endregion
// ================================
// ================================
// #region - toJodaDateTime
// ================================
/**
* 将 Java 中表示日期时间的 {@link java.time.ZonedDateTime} 对象
@@ -507,13 +479,9 @@ public class DateTimeTools {
return toJodaInstant(instant).toDateTime(dateTimeZone);
}
// ================================
// #endregion
// ================================
// ================================
// #region - toZonedDateTime
// ================================
/**
* 将 joda-time 中带时区的日期时间,转换为 java.time 中带时区的日期时间
@@ -543,7 +511,7 @@ public class DateTimeTools {
}
/**
* 获取 joda-time 中的 {@link org.joda.time.Instant} 在指定时区的时间,用 Java 8+ 的
* 获取 joda-time 中的 {@link org.joda.time.Instant} 在指定时区的时间,用 Java 8
* {@link java.time.ZonedDateTime} 表示
*
* @param instant joda-time 中的时间戳
@@ -557,13 +525,9 @@ public class DateTimeTools {
return toJavaInstant(instant).atZone(zone);
}
// ================================
// #endregion
// ================================
// ================================
// #region - toJodaLocalDateTime
// ================================
/**
* 将 {@link java.time.LocalDateTime} 转换为 {@link org.joda.time.LocalDateTime}
@@ -577,13 +541,9 @@ public class DateTimeTools {
return toJodaInstant(localDateTime, javaZone).toDateTime(jodaZone).toLocalDateTime();
}
// ================================
// #endregion
// ================================
// ================================
// #region - toJavaLocalDateTime
// ================================
/**
* 将 {@link org.joda.time.LocalDateTime} 转换为 {@link java.time.LocalDateTime}
@@ -597,13 +557,9 @@ public class DateTimeTools {
return toJavaInstant(localDateTime, jodaZone).atZone(javaZone).toLocalDateTime();
}
// ================================
// #endregion
// ================================
// ================================
// #region - ZoneId <--> DateTimeZone
// ================================
/**
* 转换 Java API 和 joda-time API 表示时区的对象
@@ -625,13 +581,9 @@ public class DateTimeTools {
return org.joda.time.DateTimeZone.forID(zone.getId());
}
// ================================
// #endregion
// ================================
// ================================
// #region - YearQuarter & Quarter
// ================================
/**
* 获取指定日期所在季度
@@ -694,246 +646,36 @@ public class DateTimeTools {
return YearQuarter.of(date);
}
// ================================
// #endregion
// ================================
// ================================
// #region - start & end
// ================================
/**
* 获取指定年份的开始日期
*
* @param year 年份
* @return 指定年份的开始日期
*/
public static LocalDate startDateOfYear(int year) {
return LocalDate.ofYearDay(year, 1);
}
/**
* 获取指定年份的结束日期
*
* @param year 年份
* @return 指定年份的结束日期
*/
public static LocalDate endDateOfYear(int year) {
return LocalDate.of(year, 12, 31);
}
/**
* 获取指定日期的第二天的开始时间
*
* @param date 日期
* @return 指定日期的第二天的开始时间
*/
public static LocalDateTime startOfNextDate(LocalDate date) {
return date.plusDays(1L).atStartOfDay();
}
/**
* 获取指定日期的第二天的开始时间
*
* @param date 日期
* @param zone 时区
* @return 指定日期的第二天的开始时间
*/
public static ZonedDateTime startOfNextDate(LocalDate date, ZoneId zone) {
return date.plusDays(1L).atStartOfDay(zone);
}
// ================================
// #endregion - start & end
// ================================
// ================================
// #region - isFuture
// ================================
/**
* 判断指定日期时间是否在将来
*
* @param date 日期时间
* @return 指定日期时间是否在将来
*/
public static boolean isFuture(Date date) {
return date.after(new Date());
}
/**
* 判断指定日期时间是否在将来
*
* @param calendar 日期时间
* @return 指定日期时间是否在将来
*/
public static boolean isFuture(Calendar calendar) {
return calendar.after(Calendar.getInstance());
}
/**
* 判断指定时刻是否在将来
*
* @param instant 时刻
* @return 指定时刻是否在将来
*/
public static boolean isFuture(Instant instant) {
return instant.isAfter(Instant.now());
}
/**
* 判断指定时间戳是否在将来
*
* @param timeMillis 时间戳
* @return 指定时间戳是否在将来
*/
public static boolean isFuture(long timeMillis) {
return timeMillis > System.currentTimeMillis();
}
/**
* 判断指定日期是否在将来
*
* @param date 日期
* @return 指定日期是否在将来
*/
public static boolean isFuture(LocalDate date) {
return date.isAfter(LocalDate.now());
}
/**
* 判断指定日期时间是否在将来
*
* @param dateTime 日期时间
* @return 指定日期时间是否在将来
*/
public static boolean isFuture(LocalDateTime dateTime) {
return dateTime.isAfter(LocalDateTime.now());
}
/**
* 判断指定日期时间是否在将来
*
* @param dateTime 日期时间
* @return 指定日期时间是否在将来
*/
public static boolean isFuture(ZonedDateTime dateTime) {
return dateTime.isAfter(ZonedDateTime.now());
}
// ================================
// #endregion - isFuture
// ================================
// ================================
// #region - isPast
// ================================
/**
* 判断指定日期时间是否在过去
*
* @param date 日期时间
* @return 指定日期时间是否在过去
*/
public static boolean isPast(Date date) {
return date.before(new Date());
}
/**
* 判断指定日期时间是否在过去
*
* @param calendar 日期时间
* @return 指定日期时间是否在过去
*/
public static boolean isPast(Calendar calendar) {
return calendar.before(Calendar.getInstance());
}
/**
* 判断指定时刻是否在过去
*
* @param instant 时刻
* @return 指定时刻是否在过去
*/
public static boolean isPast(Instant instant) {
return instant.isBefore(Instant.now());
}
/**
* 判断指定时间戳是否在过去
*
* @param timeMillis 时间戳
* @return 指定时间戳是否在过去
*/
public static boolean isPast(long timeMillis) {
return timeMillis < System.currentTimeMillis();
}
/**
* 判断指定日期是否在过去
*
* @param date 日期
* @return 指定日期是否在过去
*/
public static boolean isPast(LocalDate date) {
return date.isBefore(LocalDate.now());
}
/**
* 判断指定日期时间是否在过去
*
* @param dateTime 日期时间
* @return 指定日期时间是否在过去
*/
public static boolean isPast(LocalDateTime dateTime) {
return dateTime.isBefore(LocalDateTime.now());
}
/**
* 判断指定日期时间是否在过去
*
* @param dateTime 日期时间
* @return 指定日期时间是否在过去
*/
public static boolean isPast(ZonedDateTime dateTime) {
return dateTime.isBefore(ZonedDateTime.now());
}
// ================================
// #endregion - isPast
// ================================
// ================================
// #region - others
// ================================
/**
* 获取指定日期的时间范围
*
* @param date 日期
* @return 指定日期的时间范围
*/
public static LocalDate startDateOfYear(int year) {
return LocalDate.ofYearDay(year, 1);
}
public static LocalDate endDateOfYear(int year) {
return LocalDate.of(year, 12, 31);
}
public static LocalDateTime startOfNextDate(LocalDate date) {
return date.plusDays(1L).atStartOfDay();
}
public static ZonedDateTime startOfNextDate(LocalDate date, ZoneId zone) {
return date.plusDays(1L).atStartOfDay(zone);
}
public static Range<LocalDateTime> toDateTimeRange(LocalDate date) {
return Range.closedOpen(date.atStartOfDay(), startOfNextDate(date));
}
/**
* 获取指定日期的时间范围
*
* @param date 日期
* @param zone 时区
* @return 指定日期的时间范围
*/
public static Range<ZonedDateTime> toDateTimeRange(LocalDate date, ZoneId zone) {
return Range.closedOpen(date.atStartOfDay(zone), startOfNextDate(date, zone));
}
/**
* 判断指定年份是否为闰年
*
* @param year 年份
* @return 指定年份是否为闰年
*/
public static boolean isLeapYear(int year) {
return IsoChronology.INSTANCE.isLeapYear(year);
}
@@ -943,7 +685,7 @@ public class DateTimeTools {
// ================================
/**
* 私有构造方法
* 私有构造方法,明确标识该常量类的作用。
*/
private DateTimeTools() {
throw new IllegalStateException("Utility class");

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2022-2025 the original author or authors.
* Copyright 2022-2024 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.
@@ -18,6 +18,7 @@ package xyz.zhouxy.plusone.commons.util;
import java.util.function.Supplier;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
/**
@@ -41,7 +42,7 @@ public final class EnumTools {
* @deprecated 不推荐使用枚举的 ordinal。
*/
@Deprecated
private static <E extends Enum<?>> E valueOfInternal(Class<E> enumType, int ordinal) { // NOSONAR 该方法弃用,但不删掉
private static <E extends Enum<?>> E valueOfInternal(@Nonnull Class<E> enumType, int ordinal) { // NOSONAR 该方法弃用,但不删掉
E[] values = enumType.getEnumConstants();
AssertTools.checkCondition((ordinal >= 0 && ordinal < values.length),
() -> new EnumConstantNotPresentException(enumType, Integer.toString(ordinal)));
@@ -75,7 +76,7 @@ public final class EnumTools {
*/
@Deprecated
public static <E extends Enum<?>> E valueOf(Class<E> enumType, // NOSONAR 该方法弃用,但不删掉
@Nullable Integer ordinal, @Nullable E defaultValue) {
@Nullable Integer ordinal, E defaultValue) {
AssertTools.checkNotNull(enumType);
return null == ordinal ? defaultValue : valueOfInternal(enumType, ordinal);
}
@@ -189,7 +190,7 @@ public final class EnumTools {
*/
@Nullable
private static <E extends Enum<?>> Integer checkOrdinalOrDefaultInternal(
Class<E> enumType,
@Nonnull Class<E> enumType,
@Nullable Integer ordinal,
@Nullable Integer defaultValue) {
return ordinal != null
@@ -199,9 +200,9 @@ public final class EnumTools {
@Nullable
private static <E extends Enum<?>> Integer checkOrdinalOrGetInternal(
Class<E> enumType,
@Nonnull Class<E> enumType,
@Nullable Integer ordinal,
Supplier<Integer> defaultValueSupplier) {
@Nonnull Supplier<Integer> defaultValueSupplier) {
return ordinal != null
? checkOrdinal(enumType, ordinal)
: defaultValueSupplier.get();

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2022-2025 the original author or authors.
* Copyright 2022-2024 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.
@@ -24,8 +24,6 @@ import java.util.Objects;
import java.util.function.Function;
import java.util.stream.Collectors;
import javax.annotation.Nullable;
import xyz.zhouxy.plusone.commons.annotation.StaticFactoryMethod;
/**
@@ -48,25 +46,14 @@ public abstract class Enumeration<T extends Enumeration<T>> // NOSONAR 暂不移
this.name = name;
}
/**
* 枚举整数码值
*
* @return 整数码值
*/
public final int getId() {
return id;
}
/**
* 枚举名称
*
* @return 枚举名称
*/
public final String getName() {
return name;
}
@SuppressWarnings("null")
@Override
public final int compareTo(final T o) {
return Integer.compare(this.id, o.id);
@@ -78,7 +65,7 @@ public abstract class Enumeration<T extends Enumeration<T>> // NOSONAR 暂不移
}
@Override
public final boolean equals(@Nullable final Object obj) {
public final boolean equals(final Object obj) {
if (this == obj)
return true;
if (obj == null)
@@ -94,9 +81,6 @@ public abstract class Enumeration<T extends Enumeration<T>> // NOSONAR 暂不移
return getClass().getSimpleName() + '(' + id + ":" + name + ')';
}
/**
* 枚举值集合
*/
protected static final class ValueSet<T extends Enumeration<T>> {
private final Map<Integer, T> valueMap;
@@ -104,13 +88,6 @@ public abstract class Enumeration<T extends Enumeration<T>> // NOSONAR 暂不移
this.valueMap = valueMap;
}
/**
* 创建枚举值集合
*
* @param <T> 枚举类型
* @param values 枚举值
* @return 枚举值集合
*/
@StaticFactoryMethod(ValueSet.class)
public static <T extends Enumeration<T>> ValueSet<T> of(T[] values) {
Map<Integer, T> temp = Arrays.stream(values)
@@ -118,22 +95,11 @@ public abstract class Enumeration<T extends Enumeration<T>> // NOSONAR 暂不移
return new ValueSet<>(Collections.unmodifiableMap(temp));
}
/**
* 根据整数码值获取枚举对象
*
* @param id 整数码
* @return 枚举对象
*/
public T get(int id) {
AssertTools.checkArgument(this.valueMap.containsKey(id), "[%s] 对应的值不存在", id);
return this.valueMap.get(id);
}
/**
* 获取所有枚举对象
*
* @return 所有枚举对象
*/
public Collection<T> getValues() {
return this.valueMap.values();
}

View File

@@ -21,6 +21,8 @@ import java.util.Objects;
import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;
import javax.annotation.Nonnull;
/**
* ID 生成器
*
@@ -36,40 +38,19 @@ public class IdGenerator {
// ===== UUID =====
/**
* 生成 UUID
*
* @return UUID
*/
public static UUID newUuid() {
return UUID.randomUUID();
}
/**
* 生成 UUID 字符串
*
* @return UUID 字符串
*/
public static String uuidString() {
return UUID.randomUUID().toString();
}
/**
* 生成 UUID 字符串(无分隔符)
*
* @return UUID 字符串
*/
public static String simpleUuidString() {
return toSimpleString(UUID.randomUUID());
}
/**
* 生成 UUID 字符串(无分隔符)
*
* @param uuid UUID
* @return UUID 字符串
*/
public static String toSimpleString(UUID uuid) {
public static String toSimpleString(@Nonnull UUID uuid) {
AssertTools.checkArgument(Objects.nonNull(uuid));
return (uuidDigits(uuid.getMostSignificantBits() >> 32, 8) +
uuidDigits(uuid.getMostSignificantBits() >> 16, 4) +
@@ -88,23 +69,11 @@ public class IdGenerator {
private static final Map<Long, IdWorker> snowflakePool = new ConcurrentHashMap<>();
/**
* 生成雪花ID
*
* @param workerId 工作机器ID
* @return 雪花ID
*/
public static long nextSnowflakeId(long workerId) {
IdWorker generator = getSnowflakeIdGenerator(workerId);
return generator.nextId();
}
/**
* 获取雪花ID生成器
*
* @param workerId 工作机器ID
* @return {@code IdWorker} 对象。来自 Seata 的修改版雪花ID生成器。
*/
public static IdWorker getSnowflakeIdGenerator(long workerId) {
return snowflakePool.computeIfAbsent(workerId, wid -> new IdWorker(workerId));
}

View File

@@ -22,8 +22,6 @@ import java.util.Enumeration;
import java.util.concurrent.ThreadLocalRandom;
import java.util.concurrent.atomic.AtomicLong;
import javax.annotation.Nullable;
import xyz.zhouxy.plusone.commons.exception.system.NoAvailableMacFoundException;
/**
@@ -116,7 +114,7 @@ public class IdWorker {
* init workerId
* @param workerId if null, then auto generate one
*/
private void initWorkerId(@Nullable Long workerId) {
private void initWorkerId(Long workerId) {
if (workerId == null) {
workerId = generateWorkerId();
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2022-2025 the original author or authors.
* Copyright 2022-2024 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.
@@ -23,7 +23,7 @@ import javax.annotation.Nonnull;
import javax.annotation.Nullable;
/**
* 数字工具类
* Numbers
*
* @author <a href="http://zhouxy.xyz:3000/ZhouXY108">ZhouXY</a>
*/
@@ -31,12 +31,6 @@ public class Numbers {
// #region - sum
/**
* 求和
*
* @param numbers 数据数组
* @return 求和结果
*/
public static int sum(final short... numbers) {
int result = 0;
for (short number : numbers) {
@@ -45,12 +39,6 @@ public class Numbers {
return result;
}
/**
* 求和
*
* @param numbers 数据数组
* @return 求和结果
*/
public static long sum(final int... numbers) {
long result = 0L;
for (int number : numbers) {
@@ -59,12 +47,6 @@ public class Numbers {
return result;
}
/**
* 求和
*
* @param numbers 数据数组
* @return 求和结果
*/
public static long sum(final long... numbers) {
long result = 0L;
for (long number : numbers) {
@@ -73,12 +55,6 @@ public class Numbers {
return result;
}
/**
* 求和
*
* @param numbers 数据数组
* @return 求和结果
*/
public static double sum(final float... numbers) {
double result = 0.00;
for (float number : numbers) {
@@ -87,12 +63,6 @@ public class Numbers {
return result;
}
/**
* 求和
*
* @param numbers 数据数组
* @return 求和结果
*/
public static double sum(final double... numbers) {
double result = 0.00;
for (double number : numbers) {
@@ -101,14 +71,8 @@ public class Numbers {
return result;
}
/**
* 求和
*
* @param numbers 数据数组
* @return 求和结果
*/
public static BigInteger sum(final BigInteger... numbers) {
if (ArrayTools.isEmpty(numbers)) {
if (ArrayTools.isNullOrEmpty(numbers)) {
return BigInteger.ZERO;
}
BigInteger result = Numbers.nullToZero(numbers[0]);
@@ -121,12 +85,6 @@ public class Numbers {
return result;
}
/**
* 求和
*
* @param numbers 数据数组
* @return 求和结果
*/
public static BigDecimal sum(final BigDecimal... numbers) {
return BigDecimals.sum(numbers);
}
@@ -135,83 +93,35 @@ public class Numbers {
// #region - nullToZero
/**
* 将 {@code null} 转换为 {@code 0}
*
* @param val 待转换的值
* @return 如果 {@code val} 不为 {@code null},则返回该值;如果值为 {@code null},则返回 {@code 0}
*/
public static byte nullToZero(@Nullable final Byte val) {
return val != null ? val : 0;
}
/**
* 将 {@code null} 转换为 {@code 0}
*
* @param val 待转换的值
* @return 如果 {@code val} 不为 {@code null},则返回该值;如果值为 {@code null},则返回 {@code 0}
*/
public static short nullToZero(@Nullable final Short val) {
return val != null ? val : 0;
}
/**
* 将 {@code null} 转换为 {@code 0}
*
* @param val 待转换的值
* @return 如果 {@code val} 不为 {@code null},则返回该值;如果值为 {@code null},则返回 {@code 0}
*/
public static int nullToZero(@Nullable final Integer val) {
return val != null ? val : 0;
}
/**
* 将 {@code null} 转换为 {@code 0}
*
* @param val 待转换的值
* @return 如果 {@code val} 不为 {@code null},则返回该值;如果值为 {@code null},则返回 {@code 0}
*/
public static long nullToZero(@Nullable final Long val) {
return val != null ? val : 0L;
}
/**
* 将 {@code null} 转换为 {@code 0}
*
* @param val 待转换的值
* @return 如果 {@code val} 不为 {@code null},则返回该值;如果值为 {@code null},则返回 {@code 0}
*/
public static float nullToZero(@Nullable final Float val) {
return val != null ? val : 0.0F;
}
/**
* 将 {@code null} 转换为 {@code 0}
*
* @param val 待转换的值
* @return 如果 {@code val} 不为 {@code null},则返回该值;如果值为 {@code null},则返回 {@code 0}
*/
public static double nullToZero(@Nullable final Double val) {
return val != null ? val : 0.0;
}
/**
* 将 {@code null} 转换为 {@code 0}
*
* @param val 待转换的值
* @return 如果 {@code val} 不为 {@code null},则返回该值;如果值为 {@code null},则返回 {@code 0}
*/
@Nonnull
public static BigInteger nullToZero(@Nullable final BigInteger val) {
return val != null ? val : BigInteger.ZERO;
}
/**
* 将 {@code null} 转换为 {@code 0}
*
* @param val 待转换的值
* @return 如果 {@code val} 不为 {@code null},则返回该值;如果值为 {@code null},则返回 {@code 0}
*/
@Nonnull
public static BigDecimal nullToZero(@Nullable final BigDecimal val) {
return BigDecimals.nullToZero(val);

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2023-2025 the original author or authors.
* Copyright 2023-2024 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.
@@ -64,7 +64,7 @@ public class OptionalTools {
* @return {@link OptionalInt} 实例
*/
public static OptionalInt toOptionalInt(Optional<Integer> optionalObj) {
return optionalObj.map(OptionalInt::of).orElseGet(OptionalInt::empty);
return optionalObj.isPresent() ? OptionalInt.of(optionalObj.get()) : OptionalInt.empty();
}
/**
@@ -91,7 +91,7 @@ public class OptionalTools {
* @return {@link OptionalLong} 实例
*/
public static OptionalLong toOptionalLong(Optional<Long> optionalObj) {
return optionalObj.map(OptionalLong::of).orElseGet(OptionalLong::empty);
return optionalObj.isPresent() ? OptionalLong.of(optionalObj.get()) : OptionalLong.empty();
}
/**
@@ -118,7 +118,7 @@ public class OptionalTools {
* @return {@link OptionalDouble} 实例
*/
public static OptionalDouble toOptionalDouble(Optional<Double> optionalObj) {
return optionalObj.map(OptionalDouble::of).orElseGet(OptionalDouble::empty);
return optionalObj.isPresent() ? OptionalDouble.of(optionalObj.get()) : OptionalDouble.empty();
}
/**
@@ -130,43 +130,21 @@ public class OptionalTools {
* @return the value of the optional object if present, otherwise {@code null}.
*/
@Beta
@Nullable
public static <T> T orElseNull(Optional<T> optionalObj) {
return optionalObj.orElse(null);
}
/**
* 将 {@link OptionalInt} 转为 {@link Integer}
*
* @param optionalObj optional 对象
* @return {@link Integer} 对象。如果 {@code OptionalInt} 的值缺失,返回 {@code null}。
*/
@Beta
@Nullable
public static Integer toInteger(OptionalInt optionalObj) {
return optionalObj.isPresent() ? optionalObj.getAsInt() : null;
}
/**
* 将 {@link OptionalLong} 转为 {@link Long}
*
* @param optionalObj optional 对象
* @return {@link Long} 对象。如果 {@code OptionalLong} 的值缺失,返回 {@code null}。
*/
@Beta
@Nullable
public static Long toLong(OptionalLong optionalObj) {
return optionalObj.isPresent() ? optionalObj.getAsLong() : null;
}
/**
* 将 {@link OptionalDouble} 转为 {@link Double}
*
* @param optionalObj optional 对象
* @return {@link Double} 对象。如果 {@code OptionalDouble} 的值缺失,返回 {@code null}。
*/
@Beta
@Nullable
public static Double toDouble(OptionalDouble optionalObj) {
return optionalObj.isPresent() ? optionalObj.getAsDouble() : null;
}

View File

@@ -22,6 +22,8 @@ import java.util.Objects;
import java.util.Random;
import java.util.concurrent.ThreadLocalRandom;
import javax.annotation.Nonnull;
/**
* 随机工具类
* <p>
@@ -66,20 +68,20 @@ public final class RandomTools {
* @param length 字符串长度
* @return 随机字符串
*/
public static String randomStr(Random random, char[] sourceCharacters, int length) {
public static String randomStr(@Nonnull Random random, @Nonnull char[] sourceCharacters, int length) {
AssertTools.checkArgument(Objects.nonNull(random), "Random cannot be null.");
AssertTools.checkArgument(Objects.nonNull(sourceCharacters), "Source characters cannot be null.");
AssertTools.checkArgument(length >= 0, "The length should be greater than or equal to zero.");
return randomStrInternal(random, sourceCharacters, length);
}
public static String randomStr(char[] sourceCharacters, int length) {
public static String randomStr(@Nonnull char[] sourceCharacters, int length) {
AssertTools.checkArgument(Objects.nonNull(sourceCharacters), "Source characters cannot be null.");
AssertTools.checkArgument(length >= 0, "The length should be greater than or equal to zero.");
return randomStrInternal(ThreadLocalRandom.current(), sourceCharacters, length);
}
public static String secureRandomStr(char[] sourceCharacters, int length) {
public static String secureRandomStr(@Nonnull char[] sourceCharacters, int length) {
AssertTools.checkArgument(Objects.nonNull(sourceCharacters), "Source characters cannot be null.");
AssertTools.checkArgument(length >= 0, "The length should be greater than or equal to zero.");
return randomStrInternal(DEFAULT_SECURE_RANDOM, sourceCharacters, length);
@@ -95,20 +97,20 @@ public final class RandomTools {
* @param length 字符串长度
* @return 随机字符串
*/
public static String randomStr(Random random, String sourceCharacters, int length) {
public static String randomStr(@Nonnull Random random, @Nonnull String sourceCharacters, int length) {
AssertTools.checkArgument(Objects.nonNull(random), "Random cannot be null.");
AssertTools.checkArgument(Objects.nonNull(sourceCharacters), "Source characters cannot be null.");
AssertTools.checkArgument(length >= 0, "The length should be greater than or equal to zero.");
return randomStrInternal(random, sourceCharacters, length);
}
public static String randomStr(String sourceCharacters, int length) {
public static String randomStr(@Nonnull String sourceCharacters, int length) {
AssertTools.checkArgument(Objects.nonNull(sourceCharacters), "Source characters cannot be null.");
AssertTools.checkArgument(length >= 0, "The length should be greater than or equal to zero.");
return randomStrInternal(ThreadLocalRandom.current(), sourceCharacters, length);
}
public static String secureRandomStr(String sourceCharacters, int length) {
public static String secureRandomStr(@Nonnull String sourceCharacters, int length) {
AssertTools.checkArgument(Objects.nonNull(sourceCharacters), "Source characters cannot be null.");
AssertTools.checkArgument(length >= 0, "The length should be greater than or equal to zero.");
return randomStrInternal(DEFAULT_SECURE_RANDOM, sourceCharacters, length);
@@ -124,7 +126,7 @@ public final class RandomTools {
* @param length 字符串长度
* @return 随机字符串
*/
private static String randomStrInternal(Random random, char[] sourceCharacters, int length) {
private static String randomStrInternal(@Nonnull Random random, @Nonnull char[] sourceCharacters, int length) {
if (length == 0) {
return StringTools.EMPTY_STRING;
}
@@ -145,7 +147,7 @@ public final class RandomTools {
* @param length 字符串长度
* @return 随机字符串
*/
private static String randomStrInternal(Random random, String sourceCharacters, int length) {
private static String randomStrInternal(@Nonnull Random random, @Nonnull String sourceCharacters, int length) {
if (length == 0) {
return StringTools.EMPTY_STRING;
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2023-2025 the original author or authors.
* Copyright 2023-2024 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.
@@ -18,6 +18,7 @@ package xyz.zhouxy.plusone.commons.util;
import java.util.Arrays;
import java.util.Map;
import java.util.Objects;
import java.util.concurrent.ConcurrentHashMap;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
@@ -69,7 +70,8 @@ public final class RegexTools {
* @return {@link Pattern} 实例数组
*/
public static Pattern[] getPatterns(final String[] patterns, final boolean cachePattern) {
AssertTools.checkArgument(ArrayTools.isAllElementsNotNull(patterns));
AssertTools.checkNotNull(patterns);
AssertTools.checkArgument(allNotNull(patterns));
return cachePattern
? cacheAndGetPatternsInternal(patterns)
: getPatternsInternal(patterns);
@@ -82,7 +84,8 @@ public final class RegexTools {
* @return {@link Pattern} 实例数组
*/
public static Pattern[] getPatterns(final String[] patterns) {
AssertTools.checkArgument(ArrayTools.isAllElementsNotNull(patterns));
AssertTools.checkNotNull(patterns);
AssertTools.checkArgument(allNotNull(patterns));
return getPatternsInternal(patterns);
}
@@ -106,7 +109,8 @@ public final class RegexTools {
* @return 判断结果
*/
public static boolean matchesOne(@Nullable final CharSequence input, final Pattern[] patterns) {
AssertTools.checkArgument(ArrayTools.isAllElementsNotNull(patterns));
AssertTools.checkNotNull(patterns);
AssertTools.checkArgument(allNotNull(patterns));
return matchesOneInternal(input, patterns);
}
@@ -118,7 +122,8 @@ public final class RegexTools {
* @return 判断结果
*/
public static boolean matchesAll(@Nullable final CharSequence input, final Pattern[] patterns) {
AssertTools.checkArgument(ArrayTools.isAllElementsNotNull(patterns));
AssertTools.checkNotNull(patterns);
AssertTools.checkArgument(allNotNull(patterns));
return matchesAllInternal(input, patterns);
}
@@ -161,7 +166,8 @@ public final class RegexTools {
*/
public static boolean matchesOne(@Nullable final CharSequence input, final String[] patterns,
final boolean cachePattern) {
AssertTools.checkArgument(ArrayTools.isAllElementsNotNull(patterns));
AssertTools.checkNotNull(patterns);
AssertTools.checkArgument(allNotNull(patterns));
final Pattern[] patternSet = cachePattern
? cacheAndGetPatternsInternal(patterns)
: getPatternsInternal(patterns);
@@ -176,7 +182,8 @@ public final class RegexTools {
* @return 判断结果
*/
public static boolean matchesOne(@Nullable final CharSequence input, final String[] patterns) {
AssertTools.checkArgument(ArrayTools.isAllElementsNotNull(patterns));
AssertTools.checkNotNull(patterns);
AssertTools.checkArgument(allNotNull(patterns));
final Pattern[] patternSet = getPatternsInternal(patterns);
return matchesOneInternal(input, patternSet);
}
@@ -191,7 +198,8 @@ public final class RegexTools {
*/
public static boolean matchesAll(@Nullable final CharSequence input, final String[] patterns,
final boolean cachePattern) {
AssertTools.checkArgument(ArrayTools.isAllElementsNotNull(patterns));
AssertTools.checkNotNull(patterns);
AssertTools.checkArgument(allNotNull(patterns));
final Pattern[] patternSet = cachePattern
? cacheAndGetPatternsInternal(patterns)
: getPatternsInternal(patterns);
@@ -206,7 +214,8 @@ public final class RegexTools {
* @return 判断结果
*/
public static boolean matchesAll(@Nullable final CharSequence input, final String[] patterns) {
AssertTools.checkArgument(ArrayTools.isAllElementsNotNull(patterns));
AssertTools.checkNotNull(patterns);
AssertTools.checkArgument(allNotNull(patterns));
final Pattern[] patternSet = getPatternsInternal(patterns);
return matchesAllInternal(input, patternSet);
}
@@ -263,7 +272,7 @@ public final class RegexTools {
* @return {@link Pattern} 实例
*/
@Nonnull
private static Pattern cacheAndGetPatternInternal(final String pattern) {
private static Pattern cacheAndGetPatternInternal(@Nonnull final String pattern) {
if (PATTERN_CACHE.size() < MAX_CACHE_SIZE) {
return PATTERN_CACHE.computeIfAbsent(pattern, Pattern::compile);
}
@@ -281,7 +290,7 @@ public final class RegexTools {
* @return {@link Pattern} 实例
*/
@Nonnull
private static Pattern getPatternInternal(final String pattern) {
private static Pattern getPatternInternal(@Nonnull final String pattern) {
Pattern result = PATTERN_CACHE.get(pattern);
if (result == null) {
result = Pattern.compile(pattern);
@@ -296,7 +305,7 @@ public final class RegexTools {
* @return {@link Pattern} 实例数组
*/
@Nonnull
private static Pattern[] cacheAndGetPatternsInternal(final String[] patterns) {
private static Pattern[] cacheAndGetPatternsInternal(@Nonnull final String[] patterns) {
return Arrays.stream(patterns)
.map(RegexTools::cacheAndGetPatternInternal)
.toArray(Pattern[]::new);
@@ -309,7 +318,7 @@ public final class RegexTools {
* @return {@link Pattern} 实例数组
*/
@Nonnull
private static Pattern[] getPatternsInternal(final String[] patterns) {
private static Pattern[] getPatternsInternal(@Nonnull final String[] patterns) {
return Arrays.stream(patterns)
.map(RegexTools::getPatternInternal)
.toArray(Pattern[]::new);
@@ -322,36 +331,26 @@ public final class RegexTools {
* @param pattern 正则
* @return 判断结果
*/
private static boolean matchesInternal(@Nullable final CharSequence input, final Pattern pattern) {
private static boolean matchesInternal(@Nullable final CharSequence input, @Nonnull final Pattern pattern) {
return input != null && pattern.matcher(input).matches();
}
/**
* 判断 {@code input} 是否匹配至少一个正则。
*
* @param input 输入
* @param patterns 正则表达式
* @return 判断结果
*/
private static boolean matchesOneInternal(@Nullable final CharSequence input, final Pattern[] patterns) {
private static boolean matchesOneInternal(@Nullable final CharSequence input, @Nonnull final Pattern[] patterns) {
return input != null
&& Arrays.stream(patterns)
.anyMatch(pattern -> pattern.matcher(input).matches());
}
/**
* 判断 {@code input} 是否匹配全部正则。
*
* @param input 输入
* @param patterns 正则表达式
* @return 判断结果
*/
private static boolean matchesAllInternal(@Nullable final CharSequence input, final Pattern[] patterns) {
private static boolean matchesAllInternal(@Nullable final CharSequence input, @Nonnull final Pattern[] patterns) {
return input != null
&& Arrays.stream(patterns)
.allMatch(pattern -> pattern.matcher(input).matches());
}
private static <T> boolean allNotNull(T[] array) {
return Arrays.stream(array).allMatch(Objects::nonNull);
}
private RegexTools() {
// 不允许实例化
throw new IllegalStateException("Utility class");

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2024-2025 the original author or authors.
* Copyright 2024 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.
@@ -16,44 +16,16 @@
package xyz.zhouxy.plusone.commons.util;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.Objects;
import javax.annotation.Nullable;
import com.google.common.annotations.Beta;
import xyz.zhouxy.plusone.commons.constant.PatternConsts;
/**
* StringTools
*
* <p>
* 字符串工具类。
* </p>
*
* @author <a href="http://zhouxy.xyz:3000/ZhouXY108">ZhouXY</a>
* @since 1.0.0
*/
@Beta
public class StringTools {
public static final String EMPTY_STRING = "";
/**
* 判断字符串是否非空白
*
* <pre>
* StringTools.isNotBlank(null); // false
* StringTools.isNotBlank(""); // false
* StringTools.isNotBlank(" "); // false
* StringTools.isNotBlank("Hello"); // true
* </pre>
*
* @param cs 检查的字符串
* @return 是否非空白
*/
public static boolean isNotBlank(@Nullable final String cs) {
public static boolean isNotBlank(final String cs) {
if (cs == null || cs.isEmpty()) {
return false;
}
@@ -65,161 +37,15 @@ public class StringTools {
return false;
}
/**
* 判断是否空白字符串
*
* <pre>
* StringTools.isBlank(null); // true
* StringTools.isBlank(""); // true
* StringTools.isBlank(" "); // true
* StringTools.isBlank("Hello"); // false
* </pre>
*
* @param cs 检查的字符串
* @return 是否空白
* @since 1.1.0
*/
public static boolean isBlank(@Nullable String cs) {
if (cs == null || cs.isEmpty()) {
return true;
}
for (int i = 0; i < cs.length(); i++) {
if (!Character.isWhitespace(cs.charAt(i))) {
return false;
}
}
return true;
}
/**
* 重复字符串
*
* @param str 要重复的字符串
* @param times 重复次数
* @return 结果
*/
public static String repeat(String str, int times) {
return repeat(str, times, Integer.MAX_VALUE);
}
/**
* 重复字符串
*
* @param str 要重复的字符串
* @param times 重复次数
* @param maxLength 最大长度
* @return 结果
*/
public static String repeat(final String str, int times, int maxLength) {
public static String repeat(String str, int times, int maxLength) {
AssertTools.checkArgument(Objects.nonNull(str));
return String.valueOf(ArrayTools.repeat(str.toCharArray(), times, maxLength));
}
/**
* 判断字符串是否非空
*
* <pre>
* StringTools.isNotEmpty(null); // false
* StringTools.isNotEmpty(""); // false
* StringTools.isNotEmpty(" "); // true
* StringTools.isNotEmpty("Hello"); // true
* </pre>
*
* @param cs 检查的字符串
* @return 是否非空
* @since 1.1.0
*/
public static boolean isNotEmpty(@Nullable final String cs) {
return cs != null && !cs.isEmpty();
}
/**
* 判断字符串是否为空字符串
*
* <pre>
* StringTools.isEmpty(null); // true
* StringTools.isEmpty(""); // true
* StringTools.isEmpty(" "); // false
* StringTools.isEmpty("Hello"); // false
* </pre>
*
* @param cs 检查的字符串
* @return 是否空字符串
* @since 1.1.0
*/
public static boolean isEmpty(@Nullable final String cs) {
return cs == null || cs.isEmpty();
}
/**
* 判断字符串是否为邮箱地址
*
* @param cs 检查的字符串
* @return 是否是邮箱地址
* @since 1.1.0
*
* @see PatternConsts#EMAIL
*/
@Beta
public static boolean isEmail(@Nullable final String cs) {
return RegexTools.matches(cs, PatternConsts.EMAIL);
}
/**
* 判断字符串是否为 URL 地址
*
* @param cs 检查的字符串
* @return 是否是 URL
* @since 1.1.0
*
* @see URL
*/
@Beta
public static boolean isURL(@Nullable final String cs) {
try {
new URL(cs);
} catch (MalformedURLException e) {
return false;
}
return true;
}
/**
* 脱敏
*
* @param src 原字符串
* @param front 前面保留的字符数
* @param end 后面保留的字符数
* @return 脱敏结果
* @since 1.1.0
*/
public static String desensitize(@Nullable final String src, int front, int end) {
return desensitize(src, '*', front, end);
}
/**
* 脱敏
*
* @param src 原字符串
* @param replacedChar 用于替换的字符
* @param front 前面保留的字符数
* @param end 后面保留的字符数
* @return 脱敏结果
* @since 1.1.0
*/
public static String desensitize(@Nullable final String src, char replacedChar, int front, int end) {
if (src == null || src.isEmpty()) {
return EMPTY_STRING;
}
AssertTools.checkArgument(front >= 0 && end >= 0);
AssertTools.checkArgument((front + end) <= src.length(), "需要截取的长度不能大于原字符串长度");
final char[] charArray = src.toCharArray();
for (int i = front; i < charArray.length - end; i++) {
charArray[i] = replacedChar;
}
return String.valueOf(charArray);
}
private StringTools() {
throw new IllegalStateException("Utility class");
}

View File

@@ -39,28 +39,13 @@ public class TreeBuilder<T, TSubTree extends T, TIdentity> {
private final BiConsumer<TSubTree, T> addChildMethod;
private final Comparator<? super T> defaultComparator;
/**
* 构造一个 {@code TreeBuilder}。不指定用于排序的 {@code Comparator}。
*
* @param identityGetter 从节点中获取其标识的逻辑
* @param parentIdentityGetter 获取父节点标识的逻辑
* @param addChild 添加子节点的逻辑
*/
public TreeBuilder(Function<T, TIdentity> identityGetter, Function<T, Optional<TIdentity>> parentIdentityGetter,
BiConsumer<TSubTree, T> addChild) {
this(identityGetter, parentIdentityGetter, addChild, null);
}
/**
* 构造一个 {@code TreeBuilder}。
*
* @param identityGetter 从节点中获取其标识的逻辑
* @param parentIdentityGetter 获取父节点标识的逻辑
* @param addChild 添加子节点的逻辑
* @param defaultComparator 默认的 {@code Comparator},用于排序
*/
public TreeBuilder(Function<T, TIdentity> identityGetter, Function<T, Optional<TIdentity>> parentIdentityGetter,
BiConsumer<TSubTree, T> addChild, @Nullable Comparator<? super T> defaultComparator) {
BiConsumer<TSubTree, T> addChild, Comparator<? super T> defaultComparator) {
this.identityGetter = identityGetter;
this.parentIdentityGetter = parentIdentityGetter;
this.addChildMethod = addChild;

View File

@@ -1,28 +0,0 @@
/*
* Copyright 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.
*/
/**
* <h2>工具类</h2>
* <p>
* 包含树构建器({@link TreeBuilder})、断言工具({@link AssertTools}、ID 生成器({@link IdGenerator})及其它实用工具类。
* </p>
*
* @author <a href="http://zhouxy.xyz:3000/ZhouXY108">ZhouXY</a>
*/
@ParametersAreNonnullByDefault
package xyz.zhouxy.plusone.commons.util;
import javax.annotation.ParametersAreNonnullByDefault;

View File

@@ -81,7 +81,7 @@ class IWithCodeTests {
assertThrows(NullPointerException.class, () -> WithLongCode.INSTANCE.isCodeEquals(longCode));
}
private enum WithCode implements IWithCode<String> {
private static enum WithCode implements IWithCode<String> {
INSTANCE("testCode"),
SAME_CODE_INSTANCE("testCode"),
WRONG_CODE_INSTANCE("wrongCode"),
@@ -102,7 +102,7 @@ class IWithCodeTests {
}
}
private enum WithIntCode implements IWithIntCode {
private static enum WithIntCode implements IWithIntCode {
INSTANCE(0),
SAME_CODE_INSTANCE(0),
WRONG_CODE_INSTANCE(1),
@@ -120,7 +120,7 @@ class IWithCodeTests {
}
}
private enum WithLongCode implements IWithLongCode {
private static enum WithLongCode implements IWithLongCode {
INSTANCE(0L),
SAME_CODE_INSTANCE(0L),
WRONG_CODE_INSTANCE(108L),

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2024-2025 the original author or authors.
* Copyright 2024 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.

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2024-2025 the original author or authors.
* Copyright 2024 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.

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2024-2025 the original author or authors.
* Copyright 2024 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.

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2023-2025 the original author or authors.
* Copyright 2023-2024 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.
@@ -39,7 +39,6 @@ import java.util.Comparator;
import java.util.List;
import java.util.function.Function;
@SuppressWarnings("deprecation")
class ValidatableStringRecordTests {
private static final Logger log = LoggerFactory.getLogger(ValidatableStringRecordTests.class);
@@ -84,7 +83,6 @@ class User {
}
}
@SuppressWarnings("deprecation")
@ValueObject
class Email extends ValidatableStringRecord<Email> {
private Email(String value) {
@@ -97,7 +95,6 @@ class Email extends ValidatableStringRecord<Email> {
}
}
@SuppressWarnings("deprecation")
@ValueObject
class Username extends ValidatableStringRecord<Username> {
private Username(String username) {

View File

@@ -34,7 +34,6 @@ import lombok.extern.slf4j.Slf4j;
import xyz.zhouxy.plusone.commons.exception.business.BizException;
@Slf4j
@SuppressWarnings("null")
public
class CustomUnifiedResponseFactoryTests {

View File

@@ -32,7 +32,6 @@ import lombok.extern.slf4j.Slf4j;
import xyz.zhouxy.plusone.commons.exception.business.BizException;
@Slf4j
@SuppressWarnings("null")
public
class UnifiedResponseTests {

View File

@@ -181,7 +181,7 @@ public class PagingAndSortingQueryParamsTests {
}
AccountQueryParams queryParams = jackson.readValue(WRONG_JSON_STR, AccountQueryParams.class);
assertThrows(IllegalArgumentException.class, queryParams::buildPagingParams);
assertThrows(IllegalArgumentException.class, () -> queryParams.buildPagingParams()); // NOSONAR
}
@Test

View File

@@ -38,7 +38,9 @@ class QuarterTests {
assertEquals(1, quarter.getValue());
assertEquals("Q1", quarter.name());
assertThrows(DateTimeException.class, () -> Quarter.of(0));
assertThrows(DateTimeException.class, () -> {
Quarter.of(0);
});
// ==========
@@ -83,7 +85,9 @@ class QuarterTests {
assertEquals(2, quarter.getValue());
assertEquals("Q2", quarter.name());
assertThrows(DateTimeException.class, () -> Quarter.of(5));
assertThrows(DateTimeException.class, () -> {
Quarter.of(5);
});
// ==========
@@ -128,7 +132,9 @@ class QuarterTests {
assertEquals(3, quarter.getValue());
assertEquals("Q3", quarter.name());
assertThrows(IllegalArgumentException.class, () -> Quarter.valueOf("Abc"));
assertThrows(IllegalArgumentException.class, () -> {
Quarter.valueOf("Abc");
});
// ==========
@@ -173,7 +179,9 @@ class QuarterTests {
assertEquals(4, quarter.getValue());
assertEquals("Q4", quarter.name());
assertThrows(IllegalArgumentException.class, () -> Quarter.valueOf("Q5"));
assertThrows(IllegalArgumentException.class, () -> {
Quarter.valueOf("Q5");
});
// ==========

View File

@@ -36,7 +36,6 @@ import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.junit.jupiter.api.Assertions.assertTrue;
@Slf4j
@SuppressWarnings("null")
public class YearQuarterTests {
// ================================
@@ -589,7 +588,7 @@ public class YearQuarterTests {
Year.MIN_VALUE,
Year.MAX_VALUE,
})
void of_ValidYearMonth_CreatesYearMonth_Q1(int year) {
void of_ValidYearMonth_CreatesYearMnoth_Q1(int year) {
{
YearMonth yearMonth = YearMonth.of(year, 1);
YearQuarter yearQuarter = YearQuarter.of(yearMonth);
@@ -620,7 +619,7 @@ public class YearQuarterTests {
Year.MIN_VALUE,
Year.MAX_VALUE,
})
void of_ValidYearMonth_CreatesYearMonth_Q2(int year) {
void of_ValidYearMonth_CreatesYearMnoth_Q2(int year) {
{
YearMonth yearMonth = YearMonth.of(year, 4);
YearQuarter yearQuarter = YearQuarter.of(yearMonth);
@@ -651,7 +650,7 @@ public class YearQuarterTests {
Year.MIN_VALUE,
Year.MAX_VALUE,
})
void of_ValidYearMonth_CreatesYearMonth_Q3(int year) {
void of_ValidYearMonth_CreatesYearMnoth_Q3(int year) {
{
YearMonth yearMonth = YearMonth.of(year, 7);
YearQuarter yearQuarter = YearQuarter.of(yearMonth);
@@ -682,7 +681,7 @@ public class YearQuarterTests {
Year.MIN_VALUE,
Year.MAX_VALUE,
})
void of_ValidYearMonth_CreatesYearMonth_Q4(int year) {
void of_ValidYearMonth_CreatesYearMnoth_Q4(int year) {
{
YearMonth yearMonth = YearMonth.of(year, 10);
YearQuarter yearQuarter = YearQuarter.of(yearMonth);
@@ -713,7 +712,7 @@ public class YearQuarterTests {
Year.MIN_VALUE,
Year.MAX_VALUE,
})
void of_NullYearMonth_CreatesYearMonth_Q4(int year) {
void of_NullYearMonth_CreatesYearMnoth_Q4(int year) {
YearMonth yearMonth = null;
assertThrows(NullPointerException.class,
() -> YearQuarter.of(yearMonth));

View File

@@ -57,53 +57,53 @@ public class ArrayToolsTests {
static final double[] EMPTY_DOUBLE_ARRAY = {};
// ================================
// #region - isEmpty
// #region - isNullOrEmpty
// ================================
@Test
void isEmpty_NullArray_ReturnsTrue() {
void isNullOrEmpty_NullArray_ReturnsTrue() {
assertAll(
() -> assertTrue(ArrayTools.isEmpty(NULL_STRING_ARRAY)),
() -> assertTrue(ArrayTools.isEmpty(NULL_INTEGER_ARRAY)),
() -> assertTrue(ArrayTools.isEmpty(NULL_CHAR_ARRAY)),
() -> assertTrue(ArrayTools.isEmpty(NULL_BYTE_ARRAY)),
() -> assertTrue(ArrayTools.isEmpty(NULL_SHORT_ARRAY)),
() -> assertTrue(ArrayTools.isEmpty(NULL_INT_ARRAY)),
() -> assertTrue(ArrayTools.isEmpty(NULL_LONG_ARRAY)),
() -> assertTrue(ArrayTools.isEmpty(NULL_FLOAT_ARRAY)),
() -> assertTrue(ArrayTools.isEmpty(NULL_DOUBLE_ARRAY)));
() -> assertTrue(ArrayTools.isNullOrEmpty(NULL_STRING_ARRAY)),
() -> assertTrue(ArrayTools.isNullOrEmpty(NULL_INTEGER_ARRAY)),
() -> assertTrue(ArrayTools.isNullOrEmpty(NULL_CHAR_ARRAY)),
() -> assertTrue(ArrayTools.isNullOrEmpty(NULL_BYTE_ARRAY)),
() -> assertTrue(ArrayTools.isNullOrEmpty(NULL_SHORT_ARRAY)),
() -> assertTrue(ArrayTools.isNullOrEmpty(NULL_INT_ARRAY)),
() -> assertTrue(ArrayTools.isNullOrEmpty(NULL_LONG_ARRAY)),
() -> assertTrue(ArrayTools.isNullOrEmpty(NULL_FLOAT_ARRAY)),
() -> assertTrue(ArrayTools.isNullOrEmpty(NULL_DOUBLE_ARRAY)));
}
@Test
void isEmpty_EmptyArray_ReturnsTrue() {
void isNullOrEmpty_EmptyArray_ReturnsTrue() {
assertAll(
() -> assertTrue(ArrayTools.isEmpty(EMPTY_STRING_ARRAY)),
() -> assertTrue(ArrayTools.isEmpty(EMPTY_INTEGER_ARRAY)),
() -> assertTrue(ArrayTools.isEmpty(EMPTY_CHAR_ARRAY)),
() -> assertTrue(ArrayTools.isEmpty(EMPTY_BYTE_ARRAY)),
() -> assertTrue(ArrayTools.isEmpty(EMPTY_SHORT_ARRAY)),
() -> assertTrue(ArrayTools.isEmpty(EMPTY_INT_ARRAY)),
() -> assertTrue(ArrayTools.isEmpty(EMPTY_LONG_ARRAY)),
() -> assertTrue(ArrayTools.isEmpty(EMPTY_FLOAT_ARRAY)),
() -> assertTrue(ArrayTools.isEmpty(EMPTY_DOUBLE_ARRAY)));
() -> assertTrue(ArrayTools.isNullOrEmpty(EMPTY_STRING_ARRAY)),
() -> assertTrue(ArrayTools.isNullOrEmpty(EMPTY_INTEGER_ARRAY)),
() -> assertTrue(ArrayTools.isNullOrEmpty(EMPTY_CHAR_ARRAY)),
() -> assertTrue(ArrayTools.isNullOrEmpty(EMPTY_BYTE_ARRAY)),
() -> assertTrue(ArrayTools.isNullOrEmpty(EMPTY_SHORT_ARRAY)),
() -> assertTrue(ArrayTools.isNullOrEmpty(EMPTY_INT_ARRAY)),
() -> assertTrue(ArrayTools.isNullOrEmpty(EMPTY_LONG_ARRAY)),
() -> assertTrue(ArrayTools.isNullOrEmpty(EMPTY_FLOAT_ARRAY)),
() -> assertTrue(ArrayTools.isNullOrEmpty(EMPTY_DOUBLE_ARRAY)));
}
@Test
void isEmpty_NonEmptyArray_ReturnsFalse() {
void isNullOrEmpty_NonEmptyArray_ReturnsFalse() {
assertAll(
() -> assertFalse(ArrayTools.isEmpty(new String[] { "a" })),
() -> assertFalse(ArrayTools.isEmpty(new Integer[] { 1 })),
() -> assertFalse(ArrayTools.isEmpty(new char[] { 'a' })),
() -> assertFalse(ArrayTools.isEmpty(new byte[] { 1 })),
() -> assertFalse(ArrayTools.isEmpty(new short[] { 1 })),
() -> assertFalse(ArrayTools.isEmpty(new int[] { 1 })),
() -> assertFalse(ArrayTools.isEmpty(new long[] { 1 })),
() -> assertFalse(ArrayTools.isEmpty(new float[] { 1 })),
() -> assertFalse(ArrayTools.isEmpty(new double[] { 1 })));
() -> assertFalse(ArrayTools.isNullOrEmpty(new String[] { "a" })),
() -> assertFalse(ArrayTools.isNullOrEmpty(new Integer[] { 1 })),
() -> assertFalse(ArrayTools.isNullOrEmpty(new char[] { 'a' })),
() -> assertFalse(ArrayTools.isNullOrEmpty(new byte[] { 1 })),
() -> assertFalse(ArrayTools.isNullOrEmpty(new short[] { 1 })),
() -> assertFalse(ArrayTools.isNullOrEmpty(new int[] { 1 })),
() -> assertFalse(ArrayTools.isNullOrEmpty(new long[] { 1 })),
() -> assertFalse(ArrayTools.isNullOrEmpty(new float[] { 1 })),
() -> assertFalse(ArrayTools.isNullOrEmpty(new double[] { 1 })));
}
// ================================
// #endregion - isEmpty
// #endregion - isNullOrEmpty
// ================================
// ================================
@@ -730,37 +730,37 @@ public class ArrayToolsTests {
// ================================
@Test
void indexOf_NullPredicate_ThrowsNullPointerException() {
assertThrows(NullPointerException.class, () -> ArrayTools.indexOf(new String[] {}, null));
void indexOfWithPredicate_NullPredicate_ThrowsNullPointerException() {
assertThrows(NullPointerException.class, () -> ArrayTools.indexOfWithPredicate(new String[] {}, null));
}
@Test
void indexOf_NullArray_ReturnsNotFoundIndex() {
void indexOfWithPredicate_NullArray_ReturnsNotFoundIndex() {
Predicate<String> predicate = s -> s.equals("test");
int result = ArrayTools.indexOf(null, predicate);
int result = ArrayTools.indexOfWithPredicate(null, predicate);
assertEquals(ArrayTools.NOT_FOUND_INDEX, result);
}
@Test
void indexOf_EmptyArray_ReturnsNotFoundIndex() {
void indexOfWithPredicate_EmptyArray_ReturnsNotFoundIndex() {
Predicate<String> predicate = s -> s.equals("test");
int result = ArrayTools.indexOf(new String[] {}, predicate);
int result = ArrayTools.indexOfWithPredicate(new String[] {}, predicate);
assertEquals(ArrayTools.NOT_FOUND_INDEX, result);
}
@Test
void indexOf_ArrayContainsMatchingElement_ReturnsIndex() {
void indexOfWithPredicate_ArrayContainsMatchingElement_ReturnsIndex() {
String[] array = { "apple", "banana", "cherry" };
Predicate<String> predicate = s -> s.equals("banana");
int result = ArrayTools.indexOf(array, predicate);
int result = ArrayTools.indexOfWithPredicate(array, predicate);
assertEquals(1, result);
}
@Test
void indexOf_ArrayDoesNotContainMatchingElement_ReturnsNotFoundIndex() {
void indexOfWithPredicate_ArrayDoesNotContainMatchingElement_ReturnsNotFoundIndex() {
String[] array = { "apple", "banana", "cherry" };
Predicate<String> predicate = s -> s.equals("orange");
int result = ArrayTools.indexOf(array, predicate);
int result = ArrayTools.indexOfWithPredicate(array, predicate);
assertEquals(ArrayTools.NOT_FOUND_INDEX, result);
}
@@ -847,12 +847,12 @@ public class ArrayToolsTests {
@Test
void indexOf_NullObject_ReturnsNotFound() {
assertEquals(ArrayTools.NOT_FOUND_INDEX, ArrayTools.indexOf(new String[] { "a", "b", "c" }, (String) null));
assertEquals(ArrayTools.NOT_FOUND_INDEX, ArrayTools.indexOf(new String[] { "a", "b", "c" }, null));
}
@Test
void indexOf_ArrayContainsNull_ReturnsIndex() {
assertEquals(1, ArrayTools.indexOf(new String[] { "a", null, "c" }, (String) null));
assertEquals(1, ArrayTools.indexOf(new String[] { "a", null, "c" }, null));
}
// ================================
@@ -864,37 +864,37 @@ public class ArrayToolsTests {
// ================================
@Test
void lastIndexOf_NullPredicate_ThrowsNullPointerException() {
assertThrows(NullPointerException.class, () -> ArrayTools.lastIndexOf(new String[] {}, null));
void lastIndexOfWithPredicate_NullPredicate_ThrowsNullPointerException() {
assertThrows(NullPointerException.class, () -> ArrayTools.lastIndexOfWithPredicate(new String[] {}, null));
}
@Test
void lastIndexOf_NullArray_ReturnsNotFoundIndex() {
void lastIndexOfWithPredicate_NullArray_ReturnsNotFoundIndex() {
Predicate<String> predicate = s -> s.equals("test");
int result = ArrayTools.lastIndexOf(null, predicate);
int result = ArrayTools.lastIndexOfWithPredicate(null, predicate);
assertEquals(ArrayTools.NOT_FOUND_INDEX, result);
}
@Test
void lastIndexOf_EmptyArray_ReturnsNotFoundIndex() {
void lastIndexOfWithPredicate_EmptyArray_ReturnsNotFoundIndex() {
Predicate<String> predicate = s -> s.equals("test");
int result = ArrayTools.lastIndexOf(new String[] {}, predicate);
int result = ArrayTools.lastIndexOfWithPredicate(new String[] {}, predicate);
assertEquals(ArrayTools.NOT_FOUND_INDEX, result);
}
@Test
void lastIndexOf_ArrayContainsMatchingElement_ReturnsIndex() {
void lastIndexOfWithPredicate_ArrayContainsMatchingElement_ReturnsIndex() {
String[] array = { "apple", "banana", "banana", "cherry" };
Predicate<String> predicate = s -> s.equals("banana");
int result = ArrayTools.lastIndexOf(array, predicate);
int result = ArrayTools.lastIndexOfWithPredicate(array, predicate);
assertEquals(2, result);
}
@Test
void lastIndexOf_ArrayDoesNotContainMatchingElement_ReturnsNotFoundIndex() {
void lastIndexOfWithPredicate_ArrayDoesNotContainMatchingElement_ReturnsNotFoundIndex() {
String[] array = { "apple", "banana", "cherry" };
Predicate<String> predicate = s -> s.equals("orange");
int result = ArrayTools.lastIndexOf(array, predicate);
int result = ArrayTools.lastIndexOfWithPredicate(array, predicate);
assertEquals(ArrayTools.NOT_FOUND_INDEX, result);
}
@@ -982,12 +982,12 @@ public class ArrayToolsTests {
@Test
void lastIndexOf_NullObject_ReturnsNotFound() {
assertEquals(ArrayTools.NOT_FOUND_INDEX, ArrayTools.lastIndexOf(new String[] { "a", "b", "c" }, (String) null));
assertEquals(ArrayTools.NOT_FOUND_INDEX, ArrayTools.lastIndexOf(new String[] { "a", "b", "c" }, null));
}
@Test
void lastIndexOf_ArrayContainsNull_ReturnsIndex() {
assertEquals(3, ArrayTools.lastIndexOf(new String[] { "a", null, "c", null, "e" }, (String) null));
assertEquals(3, ArrayTools.lastIndexOf(new String[] { "a", null, "c", null, "e" }, null));
}
// ================================

View File

@@ -27,7 +27,6 @@ import org.junit.jupiter.api.Test;
import lombok.extern.slf4j.Slf4j;
@Slf4j
@SuppressWarnings("null")
public class BigDecimalsTests {
@Test

View File

@@ -435,117 +435,32 @@ class DateTimeToolsTests {
// ================================
// ================================
// #region - start & end
// #region - others
// ================================
@Test
void testStartAndEndDateOfYear() {
void startDateOfYear() {
assertEquals(LocalDate.of(2008, 1, 1), DateTimeTools.startDateOfYear(2008));
assertEquals(LocalDate.of(2008, 12, 31), DateTimeTools.endDateOfYear(2008));
}
@Test
void testStartOfNextDate() {
assertEquals(LocalDateTime.of(2024, 12, 30, 0, 0, 0),
DateTimeTools.startOfNextDate(LOCAL_DATE));
assertEquals(LocalDateTime.of(2024, 12, 30, 0, 0, 0).atZone(SYS_ZONE_ID),
DateTimeTools.startOfNextDate(LOCAL_DATE, SYS_ZONE_ID));
assertEquals(LocalDateTime.of(2024, 12, 30, 0, 0, 0).atZone(ZONE_ID),
DateTimeTools.startOfNextDate(LOCAL_DATE, ZONE_ID));
}
// ================================
// #endregion - start & end
// ================================
// ================================
// #region - isFuture & isPast
// ================================
@Test
void test_isFuture_And_isPast_WhenFuture() {
Date date = new Date(Instant.now().plusSeconds(10).toEpochMilli());
assertTrue(DateTimeTools.isFuture(date));
assertFalse(DateTimeTools.isPast(date));
Calendar calendar = Calendar.getInstance(TimeZone.getTimeZone("+01:00"));
calendar.add(Calendar.SECOND, 10);
assertTrue(DateTimeTools.isFuture(calendar));
assertFalse(DateTimeTools.isPast(calendar));
Instant instant = Instant.now().plusSeconds(10);
assertTrue(DateTimeTools.isFuture(instant));
assertFalse(DateTimeTools.isPast(instant));
long timeMillis = Instant.now().plusSeconds(10).toEpochMilli();
assertTrue(DateTimeTools.isFuture(timeMillis));
assertFalse(DateTimeTools.isPast(timeMillis));
LocalDate localDate = LocalDate.now().plusDays(1);
assertTrue(DateTimeTools.isFuture(localDate));
assertFalse(DateTimeTools.isPast(localDate));
LocalDateTime localDateTime = LocalDateTime.now().plusSeconds(10);
assertTrue(DateTimeTools.isFuture(localDateTime));
assertFalse(DateTimeTools.isPast(localDateTime));
ZonedDateTime zonedDateTime = Instant.now().plusSeconds(10).atZone(ZoneId.of("+01:00"));
assertTrue(DateTimeTools.isFuture(zonedDateTime));
assertFalse(DateTimeTools.isPast(zonedDateTime));
}
@Test
void test_isFuture_And_isPast_WhenPast() {
Date date = new Date(Instant.now().minusSeconds(10).toEpochMilli());
assertFalse(DateTimeTools.isFuture(date));
assertTrue(DateTimeTools.isPast(date));
Calendar calendar = Calendar.getInstance(TimeZone.getTimeZone("+01:00"));
calendar.add(Calendar.SECOND, -10);
assertFalse(DateTimeTools.isFuture(calendar));
assertTrue(DateTimeTools.isPast(calendar));
Instant instant = Instant.now().minusSeconds(10);
assertFalse(DateTimeTools.isFuture(instant));
assertTrue(DateTimeTools.isPast(instant));
long timeMillis = Instant.now().minusSeconds(10).toEpochMilli();
assertFalse(DateTimeTools.isFuture(timeMillis));
assertTrue(DateTimeTools.isPast(timeMillis));
LocalDate localDate = LocalDate.now().minusDays(1);
assertFalse(DateTimeTools.isFuture(localDate));
assertTrue(DateTimeTools.isPast(localDate));
LocalDateTime localDateTime = LocalDateTime.now().minusSeconds(10);
assertFalse(DateTimeTools.isFuture(localDateTime));
assertTrue(DateTimeTools.isPast(localDateTime));
ZonedDateTime zonedDateTime = Instant.now().minusSeconds(10).atZone(ZoneId.of("+01:00"));
assertFalse(DateTimeTools.isFuture(zonedDateTime));
assertTrue(DateTimeTools.isPast(zonedDateTime));
}
// ================================
// #endregion - isFuture & isPast
// ================================
// ================================
// #region - others
// ================================
@Test
void startDateTimeRange() {
Range<LocalDateTime> localDateTimeRange = DateTimeTools.toDateTimeRange(LOCAL_DATE);
assertEquals(LOCAL_DATE.atStartOfDay(), localDateTimeRange.lowerEndpoint());
assertEquals(LocalDate.of(2024, 12, 30).atStartOfDay(), localDateTimeRange.upperEndpoint());
assertTrue(localDateTimeRange.contains(LOCAL_DATE.atStartOfDay()));
assertEquals(LocalDate.of(2024, 12, 30).atStartOfDay(), localDateTimeRange.upperEndpoint());
assertEquals(LocalDate.of(2024, 12, 30).atStartOfDay(), localDateTimeRange.upperEndpoint());
assertFalse(localDateTimeRange.contains(LocalDate.of(2024, 12, 30).atStartOfDay()));
Range<ZonedDateTime> zonedDateTimeRange = DateTimeTools.toDateTimeRange(LOCAL_DATE, SYS_ZONE_ID);
assertEquals(LOCAL_DATE.atStartOfDay().atZone(SYS_ZONE_ID), zonedDateTimeRange.lowerEndpoint());
assertEquals(LocalDate.of(2024, 12, 30).atStartOfDay().atZone(SYS_ZONE_ID), zonedDateTimeRange.upperEndpoint());
assertTrue(zonedDateTimeRange.contains(LOCAL_DATE.atStartOfDay().atZone(SYS_ZONE_ID)));
assertEquals(ZonedDateTime.of(LocalDate.of(2024, 12, 30).atStartOfDay(), SYS_ZONE_ID), zonedDateTimeRange.upperEndpoint());
assertEquals(ZonedDateTime.of(LocalDate.of(2024, 12, 30).atStartOfDay(), SYS_ZONE_ID), zonedDateTimeRange.upperEndpoint());
assertFalse(zonedDateTimeRange.contains(LocalDate.of(2024, 12, 30).atStartOfDay().atZone(SYS_ZONE_ID)));
}
@@ -565,10 +480,6 @@ class DateTimeToolsTests {
assertThrows(DateTimeException.class, () -> DateTimeTools.toYearString(Year.MIN_VALUE - 1));
assertThrows(DateTimeException.class, () -> DateTimeTools.toYearString(Year.MAX_VALUE + 1));
assertEquals("2024", DateTimeTools.toYearString(Year.of(2024)));
assertEquals("999999999", DateTimeTools.toYearString(Year.of(Year.MAX_VALUE)));
assertEquals("-999999999", DateTimeTools.toYearString(Year.of(Year.MIN_VALUE)));
assertEquals("01", DateTimeTools.toMonthStringMM(1));
assertEquals("02", DateTimeTools.toMonthStringMM(2));
assertEquals("3", DateTimeTools.toMonthStringM(3));

View File

@@ -30,7 +30,7 @@ import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.ValueSource;
@SuppressWarnings({"deprecation", "null"})
@SuppressWarnings("deprecation")
public
class EnumToolsTests {

View File

@@ -37,8 +37,9 @@ import cn.hutool.core.collection.ConcurrentHashSet;
public class IdGeneratorTests {
final ThreadPoolExecutor executor = new ThreadPoolExecutor(10, 10, 0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<>());
final ThreadPoolExecutor executor = new ThreadPoolExecutor(10, 10,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>());
@Test
void testSnowflakeIdGenerator() { // NOSONAR
@@ -47,7 +48,7 @@ public class IdGeneratorTests {
for (int i = 0; i < 10000; i++) {
executor.execute(() -> {
for (int j = 0; j < 50000; j++) {
if (!ids.add(snowflake.nextId())) {
if (false == ids.add(snowflake.nextId())) {
throw new RuntimeException("重复ID");
}
}

View File

@@ -35,7 +35,6 @@ import org.junit.jupiter.api.Test;
/**
* {@link OptionalTools} 单元测试
*/
@SuppressWarnings("null")
public
class OptionalToolsTests {

View File

@@ -28,7 +28,6 @@ import org.junit.jupiter.api.Test;
import lombok.extern.slf4j.Slf4j;
@Slf4j
@SuppressWarnings("null")
public
class RegexToolsTests {
@@ -75,7 +74,7 @@ class RegexToolsTests {
@Test
void getPatterns_NullPatterns_ThrowsException() {
assertThrows(IllegalArgumentException.class, () -> {
assertThrows(NullPointerException.class, () -> {
RegexTools.getPatterns(null, true);
});
}

View File

@@ -26,17 +26,11 @@ import static org.junit.jupiter.api.Assertions.assertTrue;
import java.lang.reflect.Constructor;
import java.util.Arrays;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
@SuppressWarnings("null")
public
class StringToolsTests {
// ================================
// #region - isNotBlank
// ================================
@Test
void isNotBlank_NullString_ReturnsFalse() {
assertFalse(StringTools.isNotBlank(null));
@@ -57,206 +51,6 @@ class StringToolsTests {
assertTrue(StringTools.isNotBlank("Hello"));
}
// ================================
// #endregion - isNotBlank
// ================================
// ================================
// #region - isBlank
// ================================
@Test
void isBlank_NullString_ReturnsTrue() {
assertTrue(StringTools.isBlank(null));
}
@Test
void isBlank_EmptyString_ReturnsTrue() {
assertTrue(StringTools.isBlank(""));
}
@Test
void isBlank_WhitespaceString_ReturnsTrue() {
assertTrue(StringTools.isBlank(" "));
}
@Test
void isBlank_NonWhitespaceString_ReturnsFalse() {
assertFalse(StringTools.isBlank("Hello"));
}
// ================================
// #endregion - isBlank
// ================================
// ================================
// #region - isNotEmpty
// ================================
@Test
void isNotEmpty_NullString_ReturnsFalse() {
assertFalse(StringTools.isNotEmpty(null));
}
@Test
void isNotEmpty_EmptyString_ReturnsFalse() {
assertFalse(StringTools.isNotEmpty(""));
}
@Test
void isNotEmpty_WhitespaceString_ReturnsTrue() {
assertTrue(StringTools.isNotEmpty(" "));
}
@Test
void isNotEmpty_NonWhitespaceString_ReturnsTrue() {
assertTrue(StringTools.isNotEmpty("Hello"));
}
// ================================
// #endregion - isNotEmpty
// ================================
// ================================
// #region - isEmpty
// ================================
@Test
void isEmpty_NullString_ReturnsTrue() {
assertTrue(StringTools.isEmpty(null));
}
@Test
void isEmpty_EmptyString_ReturnsTrue() {
assertTrue(StringTools.isEmpty(""));
}
@Test
void isEmpty_WhitespaceString_ReturnsFalse() {
assertFalse(StringTools.isEmpty(" "));
}
@Test
void isEmpty_NonWhitespaceString_ReturnsFalse() {
assertFalse(StringTools.isEmpty("Hello"));
}
// ================================
// #endregion - isEmpty
// ================================
// ================================
// #region - EMAIL
// ================================
@Test
public void testValidEmails() {
assertTrue(StringTools.isEmail("test@example.com"));
assertTrue(StringTools.isEmail("user.name+tag+sorting@example.com"));
assertTrue(StringTools.isEmail("user@sub.example.com"));
assertTrue(StringTools.isEmail("user@123.123.123.123"));
}
@Test
public void testInvalidEmails() {
assertFalse(StringTools.isEmail(".username@example.com"));
assertFalse(StringTools.isEmail("@missingusername.com"));
assertFalse(StringTools.isEmail("plainaddress"));
assertFalse(StringTools.isEmail("username..username@example.com"));
assertFalse(StringTools.isEmail("username.@example.com"));
assertFalse(StringTools.isEmail("username@-example.com"));
assertFalse(StringTools.isEmail("username@-example.com"));
assertFalse(StringTools.isEmail("username@.com.com"));
assertFalse(StringTools.isEmail("username@.com.my"));
assertFalse(StringTools.isEmail("username@.com"));
assertFalse(StringTools.isEmail("username@com."));
assertFalse(StringTools.isEmail("username@com"));
assertFalse(StringTools.isEmail("username@example..com"));
assertFalse(StringTools.isEmail("username@example.com-"));
assertFalse(StringTools.isEmail("username@example.com."));
assertFalse(StringTools.isEmail("username@example"));
}
// ================================
// #endregion - EMAIL
// ================================
// ================================
// #region - isURL
// ================================
/**
* TC1: 验证标准HTTP协议URL
*/
@Test
void isURL_ValidHttpURL_ReturnsTrue() {
assertTrue(StringTools.isURL("http://example.com"));
}
/**
* TC2: 验证带路径参数的HTTPS复杂URL
*/
@Test
void isURL_ValidHttpsComplexURL_ReturnsTrue() {
assertTrue(StringTools.isURL("https://test.com:8080/api/v1?param=value#anchor"));
}
/**
* TC3: 验证FTP协议URL
*/
@Test
void isURL_ValidFtpURL_ReturnsTrue() {
assertTrue(StringTools.isURL("ftp://files.example.com/directory/"));
}
/**
* TC4: 验证非法协议处理
*/
@Test
void isURL_InvalidProtocol_ReturnsFalse() {
assertFalse(StringTools.isURL("httpx://invalid.com"));
}
/**
* TC5: 验证null输入处理
*/
@Test
void isURL_NullInput_ReturnsFalse() {
assertFalse(StringTools.isURL(null));
}
/**
* TC6: 验证空字符串处理
*/
@Test
void isURL_EmptyString_ReturnsFalse() {
assertFalse(StringTools.isURL(StringTools.EMPTY_STRING));
}
/**
* TC7: 验证缺失协议头处理
*/
@Test
void isURL_MissingProtocol_ReturnsFalse() {
assertFalse(StringTools.isURL("www.example.com/path"));
}
/**
* TC8: 验证未编码特殊字符处理
*/
@Test
void isURL_UnencodedSpecialChars_ReturnsTrue() {
assertTrue(StringTools.isURL("http://example.com/测试"));
}
// ================================
// #endregion - isURL
// ================================
// ================================
// #region - repeat
// ================================
@Test
void repeat_NullString_ThrowsException() {
IllegalArgumentException exception = assertThrows(IllegalArgumentException.class, () -> {
@@ -295,81 +89,6 @@ class StringToolsTests {
assertEquals("", StringTools.repeat("Hello", 0));
}
// ================================
// #endregion - repeat
// ================================
// ================================
// #region - desensitize
// ================================
@Test
public void desensitize_NullInput_ReturnsEmptyString() {
String result = StringTools.desensitize(null, '#', 2, 2);
Assertions.assertEquals("", result);
}
@Test
public void desensitize_EmptyString_ReturnsEmptyString() {
String result = StringTools.desensitize("", '#', 2, 2);
Assertions.assertEquals("", result);
}
@Test
public void desensitize_NegativeFront_ThrowsException() {
Assertions.assertThrows(IllegalArgumentException.class, () -> {
StringTools.desensitize("123456", '#', -1, 2);
});
}
@Test
public void desensitize_NegativeEnd_ThrowsException() {
Assertions.assertThrows(IllegalArgumentException.class, () -> {
StringTools.desensitize("123456", '#', 2, -1);
});
}
@Test
public void desensitize_FrontPlusEndExceedsLength_ThrowsException() {
Assertions.assertThrows(IllegalArgumentException.class, () -> {
StringTools.desensitize("123456", '#', 3, 4);
});
}
@Test
public void desensitize_ValidInput_ReturnsDesensitizedString() {
String result = StringTools.desensitize("123456", '#', 2, 2);
Assertions.assertEquals("12##56", result);
}
@Test
public void desensitize_FrontZero_ReturnsDesensitizedString() {
String result = StringTools.desensitize("123456", '#', 0, 2);
Assertions.assertEquals("####56", result);
}
@Test
public void desensitize_EndZero_ReturnsDesensitizedString() {
String result = StringTools.desensitize("123456", '#', 2, 0);
Assertions.assertEquals("12####", result);
}
@Test
public void desensitize_FrontAndEndZero_ReturnsDesensitizedString() {
String result = StringTools.desensitize("123456", '#', 0, 0);
Assertions.assertEquals("######", result);
}
@Test
public void desensitize_ValidInput_DefaultReplacedChar_ReturnsDesensitizedString() {
String result = StringTools.desensitize("123456", 2, 2);
Assertions.assertEquals("12**56", result);
}
// ================================
// #endregion - desensitize
// ================================
@Test
void test_constructor_isNotAccessible_ThrowsIllegalStateException() {
Constructor<?>[] constructors = StringTools.class.getDeclaredConstructors();

View File

@@ -39,7 +39,6 @@ import cn.hutool.core.util.ObjectUtil;
import lombok.EqualsAndHashCode;
import lombok.ToString;
@SuppressWarnings("null")
class TreeBuilderTests {
private static final Logger log = LoggerFactory.getLogger(TreeBuilderTests.class);
@@ -87,19 +86,19 @@ class TreeBuilderTests {
Arrays.stream(new Menu[] { B001, B002, B003, B004 })
.sorted(Comparator.comparing(Menu::getOrderNum))
.collect(Collectors.toList()),
((MenuList) menuMap.get("B")).children);
MenuList.class.cast(menuMap.get("B")).children);
assertEquals(
Arrays.stream(new Menu[] { C1, C2, C3 })
.sorted(Comparator.comparing(Menu::getOrderNum))
.collect(Collectors.toList()),
((MenuList) menuMap.get("C")).children);
MenuList.class.cast(menuMap.get("C")).children);
assertEquals(
Arrays.stream(new Menu[] { C1001, C1002 })
.sorted(Comparator.comparing(Menu::getOrderNum))
.collect(Collectors.toList()),
((MenuList) menuMap.get("C1")).children);
MenuList.class.cast(menuMap.get("C1")).children);
}
@@ -125,18 +124,18 @@ class TreeBuilderTests {
}
assertEquals(ImmutableList.of(B001, B002, B003, B004),
((MenuList) menuMap.get("B")).children);
MenuList.class.cast(menuMap.get("B")).children);
assertEquals(ImmutableList.of(C1, C2, C3),
((MenuList) menuMap.get("C")).children);
MenuList.class.cast(menuMap.get("C")).children);
assertEquals(ImmutableList.of(C1001, C1002),
((MenuList) menuMap.get("C1")).children);
MenuList.class.cast(menuMap.get("C1")).children);
}
@ToString
@EqualsAndHashCode
private abstract static class Menu implements Serializable {
private static abstract class Menu implements Serializable { // NOSONAR
protected final String parentMenuCode;
protected final String menuCode;
protected final String title;