26 Commits

Author SHA1 Message Date
8afe83e8df test: 完善 MapModifier 的单元测试 2025-10-21 14:40:53 +08:00
ddaacf1576 docs: 补充 MapModifier 的 javadoc 2025-10-21 14:39:46 +08:00
3155964b7c docs: 修改 MapModifier javadoc 中的错误 2025-10-20 16:36:04 +08:00
e0e33766e9 feat: 新增 MapModifier 类,支持链式操作修改 Map 数据
- 添加 MapModifier 类,提供了一系列对 Map 数据的修改操作方法,如 put、putIfAbsent、putAll、remove、clear 等
- 支持直接修改指定的 Map
- 支持从 Supplier 中获取 Map 并进行修改
- 提供了创建不可修改 Map 的方法
- 增加了相应的单元测试用例
2025-10-20 16:23:12 +08:00
3b441e4575 docs: update README.md
Signed-off-by: ZhouXY108 <luquanlion@outlook.com>
2025-10-17 10:25:54 +00:00
4ed6edd9b6 feat(model): 添加 SemVer 表示语义版本号 (!4 @Gitee)
- feat: 添加 `SemVer` 表示语义版本号
- test: 完善 `SemVer` 的单元测试
2025-10-17 18:13:29 +08:00
3d297331c4 test: 减少 ZipTools 测试时处理的数据量 2025-10-17 10:16:52 +08:00
665de3cdf0 docs: 更新 IdWorker 的文档注释 (!3 @Gitee) 2025-10-14 13:12:04 +00:00
c2862203d4 feat: 新增 RandomTools#randomInt 用于生成指定区间内的随机整数 (!2 @Gitee) 2025-10-14 02:00:04 +00:00
fafb26fcf7 test: 减少测试时处理的数据量 2025-10-14 02:37:03 +08:00
a65af967cb feat: 新增 ZipTools 工具类 (#70 #Gitea)
新增 `ZipTools` 工具类提供最基础的数据压缩/解压方法
2025-10-13 21:21:15 +08:00
27e5650482 perf: 优化 AssertTools 代码 2025-10-13 17:36:47 +08:00
8106126e79 refactor!: 重构 PagingAndSortingQueryParams (#69 @Gitea)
将构建 `PagingParams` 的过程放在 `PagingParamsBuilder` 中。
当定义一个 `PagingAndSortingQueryParams` 的子类时,
该类应包含一个静态的 `PagingParamsBuilder` 单例对象,
使对 `sortableProperties` 的校验逻辑在类加载时执行。

原来实现方式是将校验 `sortableProperties` 的逻辑放在
`PagingAndSortingQueryParams` 的构造方法中,
每创建一个对象就得执行一次,造成不必要的浪费。
2025-10-12 01:33:18 +08:00
99d8f210c5 feat(dependencies): 添加 okhttp 和 okio 依赖 2025-10-09 10:41:24 +08:00
08432353fe refactor(exception)!: 修改 SysExceptionBizException
- 将 `SysException` 和 `BizException` 的构造方法设为 `protected`,供子类的构造方法调用
- 创建对应的工厂方法用于直接创建 `SysException` 和 `BizException` 实例
2025-10-01 22:56:56 +08:00
a7b1067ebb test: 完善 Numbers#sum(BigInteger...) 的单元测试 2025-09-03 21:40:51 +08:00
1dbfb36146 feat: Numbers 类新增 parseXxx 方法 (#65 @Gitea)
`Numbers` 类新增 `parseXxx` 方法用于将字符串转为对应类型的数字,当转换失败时返回默认值。默认值允许为 `null`。
2025-09-03 21:37:42 +08:00
1fc0b198c9 feat: 新增将日期区间转为日期时间区间的方法 (#64 @Gitea)
- 重载 `DateTimeTools#toDateTimeRange`
- 对新增的方法进行单元测试
- 简化单元测试代码
2025-09-03 21:08:51 +08:00
15e07901e6 perf: 简单优化代码 (#63 @Gitea) 2025-09-03 21:01:50 +08:00
8f451e7eb9 refactor(exception)!: 重构多场景异常相关代码 (#62 @Gitea)
- `IExceptionType` 不继承自 `IExceptionFactory`,具体表示异常场景的枚举,可按需实现这两个接口
- 简化 `IMultiTypesException` 接口定义,不与 `IExceptionType` 强制绑定
- 修改相关文档与描述

通过以上修改,使表示异常场景的枚举可以与异常类分开定义,使不同的异常可以复用同一套场景枚举。不强制作为单一异常的工厂,在被不同的异常复用时,可以更灵活地定义不同的工厂方法。
2025-09-03 20:41:23 +08:00
2b6f946759 perf: 优化 JodaTimeTools (#61 @Gitea)
- 优化 `JodaTimeTools`
- 完善 javadoc
2025-09-03 19:49:12 +08:00
ce9f3edfbc build: 保持开发分支的版本号为 SNAPSHOT 2025-08-01 11:31:55 +08:00
0f90756f44 release: 1.1.0-RC2 2025-07-31 11:14:10 +08:00
34a49d30ca chore: 更新代码仓库地址 (plusone/plusone-commons#60 @Gitea)
Co-authored-by: ZhouXY108 <luquanlion@outlook.com>
Co-committed-by: ZhouXY108 <luquanlion@outlook.com>
2025-07-25 10:27:10 +08:00
f4c3793aab chore: 修改 @author 信息 (#59@Gitea)
Co-authored-by: ZhouXY108 <luquanlion@outlook.com>
Co-committed-by: ZhouXY108 <luquanlion@outlook.com>
2025-07-25 10:23:26 +08:00
6556a53163 refactor!: 重构 MultiTypesException (#58@Gitea)
- 将 `MultiTypesException` 重命名为 `IMultiTypesException`
- 将 `ExceptionType` 重命名为 `IExceptionType`
- 将 `IExceptionType` 中的工厂方法抽取到 `IExceptionFactory` 中
- 在 `IMultiTypesException` 接口中添加泛型参数 `TCode`,用于指定异常类型代码的类型
- 在 `IExceptionType` 接口中添加 `getDescription` 方法,用于获取异常类型的描述信息

Co-authored-by: ZhouXY108 <luquanlion@outlook.com>
Co-committed-by: ZhouXY108 <luquanlion@outlook.com>
2025-07-25 09:28:59 +08:00
95 changed files with 2380 additions and 416 deletions

View File

@@ -68,16 +68,16 @@ System.out.println(result); // Output: Return string
`RegexConsts` 包含常见正则表达式;`PatternConsts` 包含对应的 `Pattern` 对象
## 五、exception - 异常
### 1. MultiTypesException - 多类型异常
### 1. IMultiTypesException - 多类型异常
异常在不同场景下被抛出,可以用不同的枚举值,表示不同的场景类型。
异常实现 `MultiTypesException` 的 `MultiTypesException#getType` 方法,返回对应的场景类型。
异常实现 `IMultiTypesException` 的 `IMultiTypesException#getType` 方法,返回对应的场景类型。
表示场景类型的枚举实现 `MultiTypesException.ExceptionType`,其中的工厂方法用于创建对应类型的异常。
表示场景类型的枚举实现 `IMultiTypesException.IExceptionType`,其中的工厂方法用于创建对应类型的异常。
```java
public final class LoginException
extends RuntimeException
implements MultiTypesException<LoginException, LoginException.Type> {
implements IMultiTypesException<LoginException.Type> {
private static final long serialVersionUID = 881293090625085616L;
private final Type type;
private LoginException(@Nonnull Type type, @Nonnull String message) {
@@ -104,7 +104,7 @@ public final class LoginException
// ...
public enum Type implements ExceptionType {
public enum Type implements IExceptionType<String>, IExceptionFactory<LoginException> {
DEFAULT("00", "当前会话未登录"),
NOT_TOKEN("10", "未提供token"),
INVALID_TOKEN("20", "token无效"),
@@ -211,7 +211,7 @@ throw LoginException.Type.TOKEN_TIMEOUT.create();
#### 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)。
可使用 `UnifiedResponses` 快速构建 `UnifiedResponse` 对象。 `UnifiedResponses` 默认的成功代码为 "2000000" 用户按测试类 `CustomUnifiedResponseFactoryTests` 中所示范的,继承 `UnifiedResponses` 实现自己的工厂类, 自定义 `SUCCESS_CODE` 和 `DEFAULT_SUCCESS_MSG` 和工厂方法。 见 [issue#22 @Gitea](http://gitea.zhouxy.xyz/plusone/plusone-commons/issues/22)。
## 八、time - 时间 API
### 1. 季度

View File

@@ -5,11 +5,11 @@
],
"dictionaryDefinitions": [],
"dictionaries": [],
"words": [],
"ignoreWords": [
"words": [
"aliyun",
"baomidou",
"Batis",
"buildmetadata",
"Consolas",
"cspell",
"databind",
@@ -33,6 +33,7 @@
"Nonnull",
"NOSONAR",
"okhttp",
"okio",
"ooxml",
"overriden",
"plusone",

View File

@@ -7,7 +7,7 @@
<parent>
<groupId>xyz.zhouxy.plusone</groupId>
<artifactId>plusone-parent</artifactId>
<version>1.1.0-RC1</version>
<version>1.1.0-SNAPSHOT</version>
</parent>
<artifactId>plusone-commons</artifactId>
@@ -17,7 +17,6 @@
</description>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<java.version>1.8</java.version>
<maven.compiler.source>1.8</maven.compiler.source>
<maven.compiler.target>1.8</maven.compiler.target>

View File

@@ -27,7 +27,7 @@ import java.lang.annotation.Target;
* <p>
* 标识方法是读方法,如 getter。
*
* @author <a href="http://zhouxy.xyz:3000/ZhouXY108">ZhouXY</a>
* @author ZhouXY108 <luquanlion@outlook.com>
* @since 1.0.0
* @see WriterMethod
*/

View File

@@ -26,7 +26,7 @@ import java.lang.annotation.Target;
*
* <p>标识方法为静态工厂方法
*
* @author <a href="http://zhouxy.xyz:3000/ZhouXY108">ZhouXY</a>
* @author ZhouXY108 <luquanlion@outlook.com>
* @since 1.0.0
*/
@Target(ElementType.METHOD)

View File

@@ -27,7 +27,7 @@ import java.lang.annotation.Documented;
*
* <p>标识方法为不支持的操作。该方法将抛出 {@link UnsupportedOperationException}。
*
* @author <a href="http://zhouxy.xyz:3000/ZhouXY108">ZhouXY</a>
* @author ZhouXY108 <luquanlion@outlook.com>
* @version 1.0
* @since 1.0.0
* @see UnsupportedOperationException

View File

@@ -25,7 +25,7 @@ import java.lang.annotation.Target;
/**
* ValueObject - 值对象
*
* @author <a href="http://zhouxy.xyz:3000/ZhouXY108">ZhouXY</a>
* @author ZhouXY108 <luquanlion@outlook.com>
* @since 1.0.0
*/
@Inherited

View File

@@ -25,7 +25,7 @@ import java.lang.annotation.Target;
* 标识该方法是虚方法。
* <p>该注解用于提醒、强调父类虽然有默认实现,但子类可以根据自己的需要覆写。</p>
*
* @author <a href="http://zhouxy.xyz:3000/ZhouXY108">ZhouXY</a>
* @author ZhouXY108 <luquanlion@outlook.com>
* @since 1.0.0
*/
@Target(ElementType.METHOD)

View File

@@ -27,7 +27,7 @@ import java.lang.annotation.Target;
* <p>
* 标识方法是写方法,如 setter。
*
* @author <a href="http://zhouxy.xyz:3000/ZhouXY108">ZhouXY</a>
* @author ZhouXY108 <luquanlion@outlook.com>
* @since 1.0.0
* @see ReaderMethod
*/

View File

@@ -54,6 +54,6 @@
* <p>
* 标记一个类,表示其作为值对象,区别于 Entity。
*
* @author <a href="http://zhouxy.xyz:3000/ZhouXY108">ZhouXY</a>
* @author ZhouXY108 <luquanlion@outlook.com>
*/
package xyz.zhouxy.plusone.commons.annotation;

View File

@@ -26,7 +26,7 @@ import javax.annotation.Nullable;
* 用于像自定义异常等需要带有 {@code code} 字段的类,
* 方便其它地方的程序判断该类的是否实现了此接口,以此获取其实例的 {@code code} 字段的值。
*
* @author <a href="http://zhouxy.xyz:3000/ZhouXY108">ZhouXY</a>
* @author ZhouXY108 <luquanlion@outlook.com>
*/
public interface IWithCode<T> {

View File

@@ -25,7 +25,7 @@ import javax.annotation.Nullable;
* 用于像自定义异常等需要带有 {@code code} 字段的类,
* 方便其它地方的程序判断该类的是否实现了此接口,以此获取其实例的 {@code code} 字段的值。
*
* @author <a href="http://zhouxy.xyz:3000/ZhouXY108">ZhouXY</a>
* @author ZhouXY108 <luquanlion@outlook.com>
*/
public interface IWithIntCode {

View File

@@ -25,7 +25,7 @@ import javax.annotation.Nullable;
* 用于像自定义异常等需要带有 {@code code} 字段的类,
* 方便其它地方的程序判断该类的是否实现了此接口,以此获取其实例的 {@code code} 字段的值。
*
* @author <a href="http://zhouxy.xyz:3000/ZhouXY108">ZhouXY</a>
* @author ZhouXY108 <luquanlion@outlook.com>
*/
public interface IWithLongCode {

View File

@@ -67,7 +67,7 @@ import javax.annotation.Nullable;
* System.out.println(result); // Output: Return string
* </pre>
*
* @author <a href="http://zhouxy.xyz:3000/ZhouXY108">ZhouXY</a>
* @author ZhouXY108 <luquanlion@outlook.com>
* @since 1.0.0
*/
public final class Ref<T> {

View File

@@ -66,7 +66,7 @@
* 类似于枚举这样的类型,通常需要设置固定的码值表示对应的含义。
* 可实现 {@link IWithCode}、{@link IWithIntCode}、{@link IWithLongCode},便于在需要的地方对这些接口的实现进行处理。
*
* @author <a href="http://zhouxy.xyz:3000/ZhouXY108">ZhouXY</a>
* @author ZhouXY108 <luquanlion@outlook.com>
*/
@CheckReturnValue
@ParametersAreNonnullByDefault

View File

@@ -33,7 +33,7 @@ import com.google.common.collect.Table;
/**
* 集合工具类
*
* @author <a href="http://zhouxy.xyz:3000/ZhouXY108">ZhouXY</a>
* @author ZhouXY108 <luquanlion@outlook.com>
* @since 1.0.0
*/
public class CollectionTools {

View File

@@ -0,0 +1,254 @@
/*
* 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.
*/
package xyz.zhouxy.plusone.commons.collection;
import static xyz.zhouxy.plusone.commons.util.AssertTools.checkArgumentNotNull;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.function.BiFunction;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.function.Supplier;
import javax.annotation.CheckForNull;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import com.google.common.annotations.Beta;
/**
* Map 修改器
*
* <p>
* 封装一系列对 Map 数据的修改操作,修改 Map 的数据。可以用于 Map 的数据初始化等操作。
*
* <pre>
* // MapModifier
* MapModifier&lt;String, Object&gt; modifier = new MapModifier&lt;String, Object&gt;()
* .putAll(commonProperties)
* .put("username", "Ben")
* .put("accountStatus", LOCKED);
*
* // 从 Supplier 中获取 Map并修改数据
* Map&lt;String, Object&gt; map = modifier.getAndModify(HashMap::new);
*
* // 可以灵活使用不同 Map 类型的不同构造器
* Map&lt;String, Object&gt; map = modifier.getAndModify(() -&gt; new HashMap&lt;&gt;(8));
* Map&lt;String, Object&gt; map = modifier.getAndModify(() -&gt; new HashMap&lt;&gt;(anotherMap));
* Map&lt;String, Object&gt; map = modifier.getAndModify(TreeMap::new);
* Map&lt;String, Object&gt; map = modifier.getAndModify(ConcurrentHashMap::new);
*
* // 修改已有的 Map
* modifier.modify(map);
*
* // 创建一个有初始化数据的不可变的 Map
* Map&lt;String, Object&gt; map = modifier.getUnmodifiableMap();
*
* // 链式调用创建并初始化数据
* Map&lt;String, Object&gt; map = new MapModifier&lt;String, Object&gt;()
* .putAll(commonProperties)
* .put("username", "Ben")
* .put("accountStatus", LOCKED)
* .getAndModify(HashMap::new);
* </pre>
*
* @author ZhouXY108 <luquanlion@outlook.com>
* @since 1.1.0
*/
@Beta
public class MapModifier<K, V> {
@Nonnull
private Consumer<Map<K, V>> operators;
/**
* 创建一个空的 MapModifier
*/
public MapModifier() {
this.operators = m -> {
// do nothing
};
}
/**
* 添加一个键值对。
*
* <p>
* <b>注意:键值对是否允许为 {@code null},由最终作用的 {@link Map} 类型决定。</b>
*
* @param key 要添加的 {@code key}
* @param value 要添加的 {@code value}
* @return MapModifier
*/
public MapModifier<K, V> put(@Nullable K key, @Nullable V value) {
return addOperationInternal(map -> map.put(key, value));
}
/**
* 添加一个键值对,如果 key 已经存在,则不添加。
*
* <p>
* <b>注意:键值对是否允许为 {@code null},由最终作用的 {@link Map} 类型决定。</b>
*
* @param key 要添加的 {@code key}
* @param value 要添加的 {@code value}
* @return MapModifier
*/
public MapModifier<K, V> putIfAbsent(@Nullable K key, @Nullable V value) {
return addOperationInternal(map -> map.putIfAbsent(key, value));
}
/**
* 添加多个键值对。
*
* <p>
* <b>注意:键值对是否允许为 {@code null},由最终作用的 {@link Map} 类型决定。</b>
*
* @param otherMap 要添加的键值对集合。
* 如果为 {@code null},则什么都不做。
*
* @return MapModifier
*/
public MapModifier<K, V> putAll(@Nullable Map<? extends K, ? extends V> otherMap) {
if (otherMap == null || otherMap.isEmpty()) {
return this;
}
return addOperationInternal(map -> map.putAll(otherMap));
}
/**
* 添加多个键值对。
*
* <p>
* <b>注意:键值对是否允许为 {@code null},由最终作用的 {@link Map} 类型决定。</b>
*
* @param entries 要添加的键值对集合
* @return MapModifier
*/
@SafeVarargs
public final MapModifier<K, V> putAll(Map.Entry<? extends K, ? extends V>... entries) {
if (entries.length == 0) {
return this;
}
return addOperationInternal(map -> {
for (Map.Entry<? extends K, ? extends V> entry : entries) {
map.put(entry.getKey(), entry.getValue());
}
});
}
/**
* 当 {@code key} 不存在时,计算对应的值,并添加到 {@code map} 中。
*
* <p>
* 调用 {@link Map#computeIfAbsent(Object, Function)}。
*
* <p>
* <b>注意:键值对是否允许为 {@code null},由最终作用的 {@link Map} 类型决定。</b>
*
* @param key 要添加的 {@code key}
* @param mappingFunction 计算 {@code key} 对应的值
* @return MapModifier
*/
public MapModifier<K, V> computeIfAbsent(K key,
Function<? super K, ? extends V> mappingFunction) {
return addOperationInternal(map -> map.computeIfAbsent(key, mappingFunction));
}
/**
* 当 {@code key} 存在时,计算对应的值,并添加到 {@code map} 中。
*
* <p>
* 调用 {@link Map#computeIfPresent(Object, BiFunction)}。
*
* <p>
* <b>注意:键值对是否允许为 {@code null},由最终作用的 {@link Map} 类型决定。</b>
*
* @param key 要添加的 {@code key}
* @param remappingFunction 计算 {@code key} 对应的值
* @return MapModifier
*/
public MapModifier<K, V> computeIfPresent(K key,
BiFunction<? super K, ? super V, ? extends V> remappingFunction) {
return addOperationInternal(map -> map.computeIfPresent(key, remappingFunction));
}
/**
* 删除 {@code key}。
*
* <p>
* <b>注意key 是否允许为 {@code null},由最终作用的 {@link Map} 类型决定。</b>
*
* @param key 要删除的 {@code key}
* @return MapModifier
*/
public MapModifier<K, V> remove(K key) {
return addOperationInternal(map -> map.remove(key));
}
/**
* 清空 {@code map}
*
* @return MapModifier
*/
public MapModifier<K, V> clear() {
return addOperationInternal(Map::clear);
}
/**
* 修改 {@code map}
*
* @param map 要修改的 {@code map}
* @return 修改后的 {@code map}。当入参是 {@code null} 时,返回 {@code null}。
*/
public <T extends Map<K, V>> void modify(@Nullable T map) {
if (map != null) {
this.operators.accept(map);
}
}
/**
* 修改 {@code map}
*
* @param mapSupplier {@code map} 的 {@link Supplier}
* @return 修改后的 {@code map}。
* 当从 {@code mapSupplier} 获取的 {@code map} 为 {@code null} 时,返回 {@code null}。
*/
@CheckForNull
public <T extends Map<K, V>> T getAndModify(Supplier<T> mapSupplier) {
checkArgumentNotNull(mapSupplier, "The map supplier cannot be null.");
T map = mapSupplier.get();
modify(map);
return map;
}
/**
* 创建一个有初始化数据的不可变的 {@code Map}
*
* @return 不可变的 {@code Map}
*/
public Map<K, V> getUnmodifiableMap() {
return Collections.unmodifiableMap(getAndModify(HashMap::new));
}
private MapModifier<K, V> addOperationInternal(Consumer<Map<K, V>> operator) {
this.operators = this.operators.andThen(operator);
return this;
}
}

View File

@@ -22,7 +22,7 @@
* </h3>
* 集合工具类
*
* @author <a href="http://zhouxy.xyz:3000/ZhouXY108">ZhouXY</a>
* @author ZhouXY108 <luquanlion@outlook.com>
*/
@ParametersAreNonnullByDefault
package xyz.zhouxy.plusone.commons.collection;

View File

@@ -21,7 +21,7 @@ import java.util.regex.Pattern;
/**
* 正则表达式常量
*
* @author <a href="http://zhouxy.xyz:3000/ZhouXY108">ZhouXY</a>
* @author ZhouXY108 <luquanlion@outlook.com>
* @see RegexConsts
* @see xyz.zhouxy.plusone.commons.util.RegexTools
*/

View File

@@ -19,7 +19,7 @@ package xyz.zhouxy.plusone.commons.constant;
/**
* 正则表达式常量
*
* @author <a href="http://zhouxy.xyz:3000/ZhouXY108">ZhouXY</a>
* @author ZhouXY108 <luquanlion@outlook.com>
* @see PatternConsts
*/
public final class RegexConsts {

View File

@@ -22,7 +22,7 @@
* </h3>
* {@link RegexConsts} 包含常见正则表达式;{@link PatternConsts} 包含对应的 {@link Pattern} 对象。
*
* @author <a href="http://zhouxy.xyz:3000/ZhouXY108">ZhouXY</a>
* @author ZhouXY108 <luquanlion@outlook.com>
*/
package xyz.zhouxy.plusone.commons.constant;

View File

@@ -19,7 +19,7 @@ package xyz.zhouxy.plusone.commons.exception;
/**
* 数据不存在异常
*
* @author <a href="http://zhouxy.xyz:3000/ZhouXY108">ZhouXY</a>
* @author ZhouXY108 <luquanlion@outlook.com>
* @since 1.0.0
*/
public final class DataNotExistsException extends Exception {

View File

@@ -0,0 +1,62 @@
/*
* 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.
*/
package xyz.zhouxy.plusone.commons.exception;
import javax.annotation.Nonnull;
/**
* 异常工厂
*
* @param <X> 异常类型
* @author ZhouXY108 <luquanlion@outlook.com>
*/
public interface IExceptionFactory<X extends Exception> {
/**
* 创建异常
*
* @return 异常对象
*/
@Nonnull
X create();
/**
* 使用指定 {@code message} 创建异常
*
* @param message 异常信息
* @return 异常对象
*/
@Nonnull
X create(String message);
/**
* 使用指定 {@code cause} 创建异常
*
* @param cause 包装的异常
* @return 异常对象
*/
@Nonnull
X create(Throwable cause);
/**
* 使用指定 {@code message} 和 {@code cause} 创建异常
*
* @param message 异常信息
* @param cause 包装的异常
* @return 异常对象
*/
@Nonnull
X create(String message, Throwable cause);
}

View File

@@ -0,0 +1,39 @@
/*
* 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.
*/
package xyz.zhouxy.plusone.commons.exception;
import xyz.zhouxy.plusone.commons.annotation.Virtual;
import xyz.zhouxy.plusone.commons.base.IWithCode;
/**
* 异常场景
*
* @param <TCode> 场景编码
* @author ZhouXY108 <luquanlion@outlook.com>
*/
public interface IExceptionType<TCode> extends IWithCode<TCode> {
/**
* 默认异常信息
*/
String getDefaultMessage();
@Virtual
default String getDescription() {
return getDefaultMessage();
}
}

View File

@@ -17,24 +17,23 @@ package xyz.zhouxy.plusone.commons.exception;
import javax.annotation.Nonnull;
import xyz.zhouxy.plusone.commons.base.IWithCode;
/**
* MultiTypesException
* IMultiTypesException
*
* <p>
* 异常在不同场景下被抛出可以用不同的枚举值表示不同的场景类型
*
* <p>
* 异常实现 {@link MultiTypesException} {@link #getType} 方法返回对应的场景类型
* 异常实现 {@link IMultiTypesException} {@link #getType} 方法返回对应的场景类型
*
* <p>
* 表示场景类型的枚举实现 {@link ExceptionType}其中的工厂方法用于创建对应类型的异常
* 表示场景类型的枚举实现 {@link IExceptionType}各个枚举值本身就是该场景的异常的工厂实例
* 使用其中的工厂方法用于创建对应类型的异常
*
* <pre>
* public final class LoginException
* extends RuntimeException
* implements MultiTypesException&lt;LoginException, LoginException.Type&gt; {
* implements IMultiTypesException&lt;LoginException.Type&gt; {
* private static final long serialVersionUID = 881293090625085616L;
* private final Type type;
* private LoginException(&#64;Nonnull Type type, &#64;Nonnull String message) {
@@ -61,7 +60,7 @@ import xyz.zhouxy.plusone.commons.base.IWithCode;
*
* // ...
*
* public enum Type implements ExceptionType&lt;LoginException&gt; {
* public enum Type implements IExceptionType&lt;String&gt;, IExceptionFactory&lt;LoginException&gt; {
* DEFAULT("00", "当前会话未登录"),
* NOT_TOKEN("10", "未提供token"),
* INVALID_TOKEN("20", "token无效"),
@@ -118,73 +117,17 @@ import xyz.zhouxy.plusone.commons.base.IWithCode;
* throw LoginException.Type.TOKEN_TIMEOUT.create();
* </pre>
*
* @author <a href="http://zhouxy.xyz:3000/ZhouXY108">ZhouXY</a>
* @param <T> 异常场景
* @author ZhouXY108 <luquanlion@outlook.com>
* @since 1.0.0
*/
public interface MultiTypesException<E extends Exception, T extends MultiTypesException.ExceptionType<E>> {
public interface IMultiTypesException<T extends IExceptionType<?>> {
/**
* 异常类型
*
* @return 异常类型通常是实现了 {@link ExceptionType} 的枚举
* @return 异常类型通常是实现了 {@link IExceptionType} 的枚举
*/
@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

@@ -21,7 +21,6 @@ import java.time.format.DateTimeParseException;
import javax.annotation.Nonnull;
import xyz.zhouxy.plusone.commons.exception.business.RequestParamsException;
import xyz.zhouxy.plusone.commons.exception.MultiTypesException.ExceptionType;
/**
* 解析失败异常
@@ -34,12 +33,12 @@ import xyz.zhouxy.plusone.commons.exception.MultiTypesException.ExceptionType;
* throw new RequestParamsException(ParsingFailureException.Type.NUMBER_PARSING_FAILURE.create());
* </pre>
*
* @author <a href="http://zhouxy.xyz:3000/ZhouXY108">ZhouXY</a>
* @author ZhouXY108 <luquanlion@outlook.com>
* @since 1.0.0
*/
public final class ParsingFailureException
extends Exception
implements MultiTypesException<ParsingFailureException, ParsingFailureException.Type> {
implements IMultiTypesException<ParsingFailureException.Type> {
private static final long serialVersionUID = 795996090625132616L;
private final Type type;
@@ -171,7 +170,7 @@ public final class ParsingFailureException
/** XML 解析失败 */
public static final Type XML_PARSING_FAILURE = Type.XML_PARSING_FAILURE;
public enum Type implements ExceptionType<ParsingFailureException> {
public enum Type implements IExceptionType<String>, IExceptionFactory<ParsingFailureException> {
DEFAULT("00", "解析失败"),
NUMBER_PARSING_FAILURE("10", "数字转换失败"),
DATE_TIME_PARSING_FAILURE("20", "时间解析失败"),

View File

@@ -25,7 +25,7 @@ package xyz.zhouxy.plusone.commons.exception.business;
* <p>
* <b>NOTE: 通常表示业务中的意外情况。如:用户错误输入、缺失必填字段、用户余额不足等。</b>
*
* @author <a href="http://zhouxy.xyz:3000/ZhouXY108">ZhouXY</a>
* @author ZhouXY108 <luquanlion@outlook.com>
* @since 1.0.0
*/
public class BizException extends RuntimeException {
@@ -33,21 +33,13 @@ 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) {
protected BizException(String message) {
super(message);
}
@@ -57,7 +49,7 @@ public class BizException extends RuntimeException {
*
* @param cause 包装的异常
*/
public BizException(Throwable cause) {
protected BizException(Throwable cause) {
super(cause);
}
@@ -67,8 +59,27 @@ public class BizException extends RuntimeException {
* @param message 异常信息
* @param cause 包装的异常
*/
public BizException(String message, Throwable cause) {
protected BizException(String message, Throwable cause) {
super(message, cause);
}
public static BizException of() {
return new BizException(DEFAULT_MSG);
}
public static BizException of(String message) {
return new BizException(message);
}
public static BizException of(String errorMessageFormat, Object... errorMessageArgs) {
return new BizException(String.format(errorMessageFormat, errorMessageArgs));
}
public static BizException of(Throwable cause) {
return new BizException(cause);
}
public static BizException of(String message, Throwable cause) {
return new BizException(message, cause);
}
}

View File

@@ -18,8 +18,9 @@ package xyz.zhouxy.plusone.commons.exception.business;
import javax.annotation.Nonnull;
import xyz.zhouxy.plusone.commons.exception.MultiTypesException.ExceptionType;
import xyz.zhouxy.plusone.commons.exception.MultiTypesException;
import xyz.zhouxy.plusone.commons.exception.IExceptionFactory;
import xyz.zhouxy.plusone.commons.exception.IExceptionType;
import xyz.zhouxy.plusone.commons.exception.IMultiTypesException;
/**
* InvalidInputException
@@ -30,12 +31,12 @@ import xyz.zhouxy.plusone.commons.exception.MultiTypesException;
* <p>
* <b>NOTE: 属业务异常</b>
*
* @author <a href="http://zhouxy.xyz:3000/ZhouXY108">ZhouXY</a>
* @author ZhouXY108 <luquanlion@outlook.com>
* @since 1.0.0
*/
public final class InvalidInputException
extends RequestParamsException
implements MultiTypesException<InvalidInputException, InvalidInputException.Type> {
implements IMultiTypesException<InvalidInputException.Type> {
private static final long serialVersionUID = -28994090625082516L;
private final Type type;
@@ -109,7 +110,7 @@ public final class InvalidInputException
return this.type;
}
public enum Type implements ExceptionType<InvalidInputException> {
public enum Type implements IExceptionType<String>, IExceptionFactory<InvalidInputException> {
DEFAULT("00", "用户输入内容非法"),
CONTAINS_ILLEGAL_AND_MALICIOUS_LINKS("01", "包含非法恶意跳转链接"),
CONTAINS_ILLEGAL_WORDS("02", "包含违禁敏感词"),

View File

@@ -22,7 +22,7 @@ package xyz.zhouxy.plusone.commons.exception.business;
* <p>
* 用户请求参数错误
*
* @author <a href="http://zhouxy.xyz:3000/ZhouXY108">ZhouXY</a>
* @author ZhouXY108 <luquanlion@outlook.com>
* @since 1.0.0
*/
public class RequestParamsException extends BizException {

View File

@@ -17,6 +17,6 @@
/**
* 业务异常
*
* @author <a href="http://zhouxy.xyz:3000/ZhouXY108">ZhouXY</a>
* @author ZhouXY108 <luquanlion@outlook.com>
*/
package xyz.zhouxy.plusone.commons.exception.business;

View File

@@ -17,20 +17,21 @@
/**
* <h2>异常</h2>
*
* <h3>1. {@link MultiTypesException} - 多类型异常</h3>
* <h3>1. {@link IMultiTypesException} - 多类型异常</h3>
* <p>
* 异常在不同场景下被抛出,可以用不同的枚举值,表示不同的场景类型。
*
* <p>
* 异常实现 {@link MultiTypesException} 的 {@link MultiTypesException#getType} 方法,返回对应的场景类型。
* 异常实现 {@link IMultiTypesException} 的 {@link IMultiTypesException#getType} 方法,返回对应的场景类型。
*
* <p>
* 表示场景类型的枚举实现 {@link MultiTypesException.ExceptionType}其中的工厂方法用于创建对应类型的异常。
* 表示场景类型的枚举实现 {@link IMultiTypesException.IExceptionType}各个枚举值本身就是该场景的异常的工厂实例,
* 使用其中的工厂方法用于创建对应类型的异常。
*
* <pre>
* public final class LoginException
* extends RuntimeException
* implements MultiTypesException&lt;LoginException, LoginException.Type&gt; {
* implements IMultiTypesException&lt;LoginException, LoginException.Type, String&gt; {
* private static final long serialVersionUID = 881293090625085616L;
* private final Type type;
* private LoginException(&#64;Nonnull Type type, &#64;Nonnull String message) {
@@ -57,7 +58,7 @@
*
* // ...
*
* public enum Type implements ExceptionType&lt;LoginException&gt; {
* public enum Type implements IExceptionType&lt;LoginException, String&gt; {
* DEFAULT("00", "当前会话未登录"),
* NOT_TOKEN("10", "未提供token"),
* INVALID_TOKEN("20", "token无效"),
@@ -120,7 +121,7 @@
* <h3>3. 系统异常</h3>
* 预设常见的系统异常。可继承 {@link SysException} 自定义系统异常。
*
* @author <a href="http://zhouxy.xyz:3000/ZhouXY108">ZhouXY</a>
* @author ZhouXY108 <luquanlion@outlook.com>
*/
package xyz.zhouxy.plusone.commons.exception;

View File

@@ -27,7 +27,7 @@ package xyz.zhouxy.plusone.commons.exception.system;
* 当出现这种始料未及的诡异情况时,抛出 {@link DataOperationResultException} 并回滚事务。
* 后续需要排查原因。
*
* @author <a href="http://zhouxy.xyz:3000/ZhouXY108">ZhouXY</a>
* @author ZhouXY108 <luquanlion@outlook.com>
* @since 1.0.0
*/
public final class DataOperationResultException extends SysException {

View File

@@ -22,7 +22,7 @@ package xyz.zhouxy.plusone.commons.exception.system;
* <p>
* 在无法找到可访问的 Mac 地址时抛出
*
* @author <a href="http://zhouxy.xyz:3000/ZhouXY108">ZhouXY</a>
* @author ZhouXY108 <luquanlion@outlook.com>
* @since 1.0.0
*/
public class NoAvailableMacFoundException extends SysException {
@@ -33,7 +33,7 @@ public class NoAvailableMacFoundException extends SysException {
* {@code cause} 未初始化,后面可能会通过调用 {@link #initCause} 进行初始化。
*/
public NoAvailableMacFoundException() {
super();
super("无法找到可访问的 Mac 地址");
}
/**

View File

@@ -22,7 +22,7 @@ package xyz.zhouxy.plusone.commons.exception.system;
* <p>
* 通常表示应用代码存在问题,或因环境问题,引发异常。
*
* @author <a href="http://zhouxy.xyz:3000/ZhouXY108">ZhouXY</a>
* @author ZhouXY108 <luquanlion@outlook.com>
* @since 1.0.0
*/
public class SysException extends RuntimeException {
@@ -30,21 +30,13 @@ 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) {
protected SysException(String message) {
super(message);
}
@@ -54,7 +46,7 @@ public class SysException extends RuntimeException {
*
* @param cause 包装的异常
*/
public SysException(Throwable cause) {
protected SysException(Throwable cause) {
super(cause);
}
@@ -64,7 +56,27 @@ public class SysException extends RuntimeException {
* @param message 异常信息
* @param cause 包装的异常
*/
public SysException(String message, Throwable cause) {
protected SysException(String message, Throwable cause) {
super(message, cause);
}
public static SysException of() {
return new SysException(DEFAULT_MSG);
}
public static SysException of(String message) {
return new SysException(message);
}
public static SysException of(String errorMessageFormat, Object... errorMessageArgs) {
return new SysException(String.format(errorMessageFormat, errorMessageArgs));
}
public static SysException of(Throwable cause) {
return new SysException(cause);
}
public static SysException of(String message, Throwable cause) {
return new SysException(message, cause);
}
}

View File

@@ -17,6 +17,6 @@
/**
* 系统异常
*
* @author <a href="http://zhouxy.xyz:3000/ZhouXY108">ZhouXY</a>
* @author ZhouXY108 <luquanlion@outlook.com>
*/
package xyz.zhouxy.plusone.commons.exception.system;

View File

@@ -25,7 +25,7 @@ import com.google.common.annotations.Beta;
* 一个特殊的 {@link java.util.function.UnaryOperator}。
* 表示对 {@code boolean} 值的一元操作。
*
* @author <a href="http://zhouxy.xyz:3000/ZhouXY108">ZhouXY</a>
* @author ZhouXY108 <luquanlion@outlook.com>
* @since 1.0.0
* @see java.util.function.UnaryOperator
*/

View File

@@ -25,7 +25,7 @@ import com.google.common.annotations.Beta;
* 一个特殊的 {@link java.util.function.UnaryOperator}。
* 表示对 {@code char} 的一元操作。
*
* @author <a href="http://zhouxy.xyz:3000/ZhouXY108">ZhouXY</a>
* @author ZhouXY108 <luquanlion@outlook.com>
* @since 1.0.0
* @see java.util.function.UnaryOperator
*/

View File

@@ -24,7 +24,7 @@ package xyz.zhouxy.plusone.commons.function;
*
* @param <E> 可抛出的异常类型
*
* @author <a href="http://zhouxy.xyz:3000/ZhouXY108">ZhouXY</a>
* @author ZhouXY108 <luquanlion@outlook.com>
* @since 1.0.0
*/
@FunctionalInterface

View File

@@ -25,7 +25,7 @@ import java.util.function.Supplier;
* <p>
* 返回 {@code Optional&lt;T&gt;} 对象。
*
* @author <a href="http://zhouxy.xyz:3000/ZhouXY108">ZhouXY</a>
* @author ZhouXY108 <luquanlion@outlook.com>
* @since 1.0.0
* @see Optional
* @see Supplier

View File

@@ -24,7 +24,7 @@ import java.util.function.Predicate;
* <p>
* {@link Predicate} 相关操作。
*
* @author <a href="http://zhouxy.xyz:3000/ZhouXY108">ZhouXY</a>
* @author ZhouXY108 <luquanlion@outlook.com>
* @since 1.0.0
* @see Predicate
*/

View File

@@ -22,7 +22,7 @@ package xyz.zhouxy.plusone.commons.function;
* <p>
* 允许抛出异常的消费操作。是一个特殊的 {@link java.util.function.Consumer}。
*
* @author <a href="http://zhouxy.xyz:3000/ZhouXY108">ZhouXY</a>
* @author ZhouXY108 <luquanlion@outlook.com>
* @since 1.0.0
* @see java.util.function.Consumer
*/

View File

@@ -25,7 +25,7 @@ package xyz.zhouxy.plusone.commons.function;
* @param <R> 返回结果类型
* @param <E> 异常类型
*
* @author <a href="http://zhouxy.xyz:3000/ZhouXY108">ZhouXY</a>
* @author ZhouXY108 <luquanlion@outlook.com>
* @since 1.0
* @see java.util.function.Function
*/

View File

@@ -22,7 +22,7 @@ package xyz.zhouxy.plusone.commons.function;
* <p>
* 接收一个参数,返回一个布尔值,可抛出异常。
*
* @author <a href="http://zhouxy.xyz:3000/ZhouXY108">ZhouXY</a>
* @author ZhouXY108 <luquanlion@outlook.com>
* @since 1.0.0
* @see java.util.function.Predicate
*/

View File

@@ -25,7 +25,7 @@ package xyz.zhouxy.plusone.commons.function;
* @param <T> 结果类型
* @param <E> 异常类型
*
* @author <a href="http://zhouxy.xyz:3000/ZhouXY108">ZhouXY</a>
* @author ZhouXY108 <luquanlion@outlook.com>
* @since 1.0.0
* @see java.util.function.Supplier
*/

View File

@@ -25,7 +25,7 @@ import java.util.function.BiFunction;
* <p>
* 接受类型为 T 和 U 的两个参数,返回 {@code Optional&lt;R&gt;} 对象。
*
* @author <a href="http://zhouxy.xyz:3000/ZhouXY108">ZhouXY</a>
* @author ZhouXY108 <luquanlion@outlook.com>
* @since 1.0.0
* @see Optional
* @see BiFunction

View File

@@ -25,7 +25,7 @@ import java.util.function.Function;
* <p>
* 接受类型为 T 的参数,返回 {@code Optional&lt;R&gt;} 对象。
*
* @author <a href="http://zhouxy.xyz:3000/ZhouXY108">ZhouXY</a>
* @author ZhouXY108 <luquanlion@outlook.com>
* @since 1.0.0
* @see Optional
* @see Function

View File

@@ -39,6 +39,6 @@
* | Optional | ToOptionalFunction | Optional&lt;R&gt; apply(T) |
* </pre>
*
* @author <a href="http://zhouxy.xyz:3000/ZhouXY108">ZhouXY</a>
* @author ZhouXY108 <luquanlion@outlook.com>
*/
package xyz.zhouxy.plusone.commons.function;

View File

@@ -33,7 +33,7 @@ import com.google.gson.stream.JsonWriter;
/**
* 包含 JSR-310 相关数据类型的 {@code TypeAdapter}
*
* @author <a href="http://zhouxy.xyz:3000/ZhouXY108">ZhouXY</a>
* @author ZhouXY108 <luquanlion@outlook.com>
* @since 1.1.0
* @see TypeAdapter
* @see com.google.gson.GsonBuilder

View File

@@ -42,7 +42,7 @@ import xyz.zhouxy.plusone.commons.util.StringTools;
* <p>
* 中国第二代居民身份证号
*
* @author <a href="http://zhouxy.xyz:3000/ZhouXY108">ZhouXY</a>
* @author ZhouXY108 <luquanlion@outlook.com>
* @since 1.0.0
* @see xyz.zhouxy.plusone.commons.constant.PatternConsts#CHINESE_2ND_ID_CARD_NUMBER
*/

View File

@@ -23,7 +23,7 @@ import xyz.zhouxy.plusone.commons.base.IWithIntCode;
/**
* 性别
*
* @author <a href="http://zhouxy.xyz:3000/ZhouXY108">ZhouXY</a>
* @author ZhouXY108 <luquanlion@outlook.com>
*/
public enum Gender implements IWithIntCode {
UNKNOWN(0, "Unknown", "未知"),

View File

@@ -24,7 +24,7 @@ import xyz.zhouxy.plusone.commons.util.StringTools;
/**
* 身份证号
*
* @author <a href="http://zhouxy.xyz:3000/ZhouXY108">ZhouXY</a>
* @author ZhouXY108 <luquanlion@outlook.com>
*/
public interface IDCardNumber {

View File

@@ -0,0 +1,275 @@
/*
* 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.
*/
package xyz.zhouxy.plusone.commons.model;
import static xyz.zhouxy.plusone.commons.util.AssertTools.checkArgument;
import java.io.Serializable;
import java.util.Objects;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import javax.annotation.Nullable;
import com.google.common.base.Splitter;
import xyz.zhouxy.plusone.commons.util.StringTools;
/**
* SemVer 语义版本号
*
* @author ZhouXY108 <luquanlion@outlook.com>
* @since 1.1.0
*
* @see <a href="https://semver.org/">Semantic Versioning 2.0.0</a>
*/
public class SemVer implements Comparable<SemVer>, Serializable {
private static final long serialVersionUID = 458265121025514002L;
private final String value;
private final int[] versionNumbers;
@Nullable
private final String preReleaseVersion;
@Nullable
private final String buildMetadata;
private static final String VERSION_NUMBERS = "(?<numbers>(?<major>0|[1-9]\\d*)\\.(?<minor>0|[1-9]\\d*)\\.(?<patch>0|[1-9]\\d*)(\\.(0|[1-9]\\d*)){0,2})";
private static final String PRE_RELEASE_VERSION = "(?:-(?<prerelease>(?:0|[1-9]\\d{0,41}|\\d{0,18}[a-zA-Z-][0-9a-zA-Z-]{0,18})(?:\\.(?:0|[1-9]\\d{0,41}|\\d{0,18}[a-zA-Z-][0-9a-zA-Z-]{0,18})){0,18}))?";
private static final String BUILD_METADATA = "(?:\\+(?<buildmetadata>[0-9a-zA-Z-]{1,18}(?:\\.[0-9a-zA-Z-]{1,18}){0,18}))?";
private static final Pattern PATTERN = Pattern.compile(
"^" + VERSION_NUMBERS + PRE_RELEASE_VERSION + BUILD_METADATA + "$");
/**
* 创建语义化版本号的值对象
*
* @param value 字符串值
* @param versionNumbers 主版本号、次版本号、修订号
* @param preReleaseVersion 先行版本号
* @param buildMetadata 版本编译信息
*/
private SemVer(String value,
int[] versionNumbers,
@Nullable String preReleaseVersion,
@Nullable String buildMetadata) {
this.value = value;
this.versionNumbers = versionNumbers;
this.preReleaseVersion = preReleaseVersion;
this.buildMetadata = buildMetadata;
}
/**
* 创建 SemVer 对象
*
* @param value 语义化版本号
* @return SemVer 对象
*/
public static SemVer of(final String value) {
checkArgument(StringTools.isNotBlank(value), "版本号不能为空");
final Matcher matcher = PATTERN.matcher(value);
checkArgument(matcher.matches(), "版本号格式错误");
// 数字版本部分
final String versionNumbersPart = matcher.group("numbers");
// 先行版本号部分
final String preReleaseVersionPart = matcher.group("prerelease");
// 版本编译信息部分
final String buildMetadataPart = matcher.group("buildmetadata");
final int[] versionNumbers = Splitter.on('.')
.splitToStream(versionNumbersPart)
// 必须都是数字
.mapToInt(Integer::parseInt)
.toArray();
return new SemVer(value, versionNumbers, preReleaseVersionPart, buildMetadataPart);
}
/**
* 获取主版本号
*
* @return 主版本号
*/
public int getMajor() {
return this.versionNumbers[0];
}
/**
* 获取次版本号
*
* @return 次版本号
*/
public int getMinor() {
return this.versionNumbers[1];
}
/**
* 获取修订号
*
* @return 修订号
*/
public int getPatch() {
return this.versionNumbers[2];
}
/**
* 获取先行版本号
*
* @return 先行版本号
*/
@Nullable
public String getPreReleaseVersion() {
return this.preReleaseVersion;
}
/**
* 获取版本编译信息
*
* @return 版本编译信息
*/
@Nullable
public String getBuildMetadata() {
return buildMetadata;
}
/** {@inheritDoc} */
@Override
public int compareTo(@SuppressWarnings("null") SemVer that) {
if (this == that) {
return 0;
}
int result = compareVersionNumbers(that);
if (result != 0) {
return result;
}
return comparePreReleaseVersion(that);
}
/**
* 获取字符串值
*
* @return 版本字符串
*/
public String getValue() {
return value;
}
/** {@inheritDoc} */
@Override
public int hashCode() {
return Objects.hash(value);
}
/** {@inheritDoc} */
@Override
public boolean equals(@Nullable Object obj) {
if (this == obj)
return true;
if (!(obj instanceof SemVer))
return false;
SemVer other = (SemVer) obj;
return Objects.equals(value, other.value);
}
/**
* 获取 SemVer 的字符串表示。如 {@code v1.2.3-alpha.1+build.1234}
*/
@Override
public String toString() {
return 'v' + value;
}
/**
* 比较主版本号、次版本号、修订号
*/
private int compareVersionNumbers(SemVer that) {
final int minLength = Integer.min(this.versionNumbers.length, that.versionNumbers.length);
for (int i = 0; i < minLength; i++) {
final int currentVersionNumberOfThis = this.versionNumbers[i];
final int currentVersionNumberOfThat = that.versionNumbers[i];
if (currentVersionNumberOfThis != currentVersionNumberOfThat) {
return currentVersionNumberOfThis - currentVersionNumberOfThat;
}
}
return this.versionNumbers.length - that.versionNumbers.length;
}
/**
* 比较先行版本号
*/
private int comparePreReleaseVersion(SemVer that) {
int thisWithoutPreReleaseVersionFlag = bool2Int(this.preReleaseVersion == null);
int thatWithoutPreReleaseVersionFlag = bool2Int(that.preReleaseVersion == null);
if (isTrue(thisWithoutPreReleaseVersionFlag | thatWithoutPreReleaseVersionFlag)) {
return thisWithoutPreReleaseVersionFlag - thatWithoutPreReleaseVersionFlag;
}
Splitter splitter = Splitter.on('.');
final String[] preReleaseVersionOfThis = splitter
.splitToStream(this.preReleaseVersion) // NOSONAR
.toArray(String[]::new);
final String[] preReleaseVersionOfThat = splitter
.splitToStream(that.preReleaseVersion) // NOSONAR
.toArray(String[]::new);
final int minLength = Integer.min(preReleaseVersionOfThis.length, preReleaseVersionOfThat.length);
for (int i = 0; i < minLength; i++) {
int r = comparePartOfPreReleaseVersion(preReleaseVersionOfThis[i], preReleaseVersionOfThat[i]);
if (r != 0) {
return r;
}
}
return preReleaseVersionOfThis.length - preReleaseVersionOfThat.length;
}
/**
* 比较先行版本号的组成部分
*/
private static int comparePartOfPreReleaseVersion(String p1, String p2) {
boolean p1IsNumber = isAllDigits(p1);
boolean p2IsNumber = isAllDigits(p2);
if (p1IsNumber) {
return p2IsNumber
? Integer.parseInt(p1) - Integer.parseInt(p2) // 都是数字
: -1; // p1 是数字p2 是字符串
}
// 如果 p1 是字符串p2 是数字,则返回 1字符串优先于纯数字
return p2IsNumber ? 1 : p1.compareTo(p2);
}
/**
* 判断字符串是否全为数字
*/
private static boolean isAllDigits(String str) {
for (int i = 0; i < str.length(); i++) {
char c = str.charAt(i);
if (c < '0' || c > '9') {
return false;
}
}
return true;
}
private static int bool2Int(boolean expression) {
return expression ? 1 : 0;
}
private static boolean isTrue(int b) {
return b != 0;
}
}

View File

@@ -17,6 +17,7 @@
package xyz.zhouxy.plusone.commons.model;
import static xyz.zhouxy.plusone.commons.util.AssertTools.checkArgument;
import static xyz.zhouxy.plusone.commons.util.AssertTools.checkArgumentNotNull;
import java.util.Objects;
import java.util.function.Supplier;
@@ -31,7 +32,7 @@ import xyz.zhouxy.plusone.commons.annotation.ReaderMethod;
/**
* 带校验的字符串值对象
*
* @author <a href="http://zhouxy.xyz:3000/ZhouXY108">ZhouXY</a>
* @author ZhouXY108 <luquanlion@outlook.com>
* @since 1.0.0
*
* @deprecated 弃用。使用工厂方法创建对象,并在其中进行校验即可。
@@ -75,8 +76,8 @@ public abstract class ValidatableStringRecord<T extends ValidatableStringRecord<
* @param errorMessage 正则不匹配时的错误信息
*/
protected ValidatableStringRecord(String value, Pattern pattern, String errorMessage) {
checkArgument(Objects.nonNull(value), "The value cannot be null.");
checkArgument(Objects.nonNull(pattern), "The pattern cannot be null.");
checkArgumentNotNull(value, "The value cannot be null.");
checkArgumentNotNull(pattern, "The pattern cannot be null.");
this.matcher = pattern.matcher(value);
checkArgument(this.matcher.matches(), errorMessage);
this.value = value;

View File

@@ -29,7 +29,7 @@ import xyz.zhouxy.plusone.commons.collection.CollectionTools;
*
* @param <T> 内容列表的元素类型
*
* @author <a href="http://zhouxy.xyz:3000/ZhouXY108">ZhouXY</a>
* @author ZhouXY108 <luquanlion@outlook.com>
* @see PagingAndSortingQueryParams
*/
public class PageResult<T> {

View File

@@ -27,7 +27,6 @@ import javax.annotation.Nullable;
import com.google.common.collect.ImmutableMap;
import xyz.zhouxy.plusone.commons.annotation.Virtual;
import xyz.zhouxy.plusone.commons.collection.CollectionTools;
import xyz.zhouxy.plusone.commons.util.RegexTools;
import xyz.zhouxy.plusone.commons.util.StringTools;
@@ -39,36 +38,16 @@ import xyz.zhouxy.plusone.commons.util.StringTools;
* 根据传入的 {@code size} 和 {@code pageNum}
* 提供 {@code getOffset} 方法计算 SQL 语句中 {@code offset} 的值。
*
* @author <a href="http://zhouxy.xyz:3000/ZhouXY108">ZhouXY</a>
* @author ZhouXY108 <luquanlion@outlook.com>
* @see PagingParams
* @see PageResult
*/
public class PagingAndSortingQueryParams {
private static final int DEFAULT_PAGE_SIZE = 15;
private Integer size;
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 final Map<String, String> sortableProperties;
/**
* 构造分页排序查询参数
*
* @param sortableProperties 可排序的属性。不可为空。
*/
public PagingAndSortingQueryParams(Map<String, String> sortableProperties) {
checkArgument(CollectionTools.isNotEmpty(sortableProperties),
"Sortable properties can not be empty.");
sortableProperties.forEach((k, v) ->
checkArgument(StringTools.isNotBlank(k) && StringTools.isNotBlank(v),
"Property name must not be blank."));
this.sortableProperties = ImmutableMap.copyOf(sortableProperties);
}
// Setters
/**
@@ -100,56 +79,18 @@ public class PagingAndSortingQueryParams {
// 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;
checkArgument(CollectionTools.isNotEmpty(this.orderBy),
"The 'orderBy' cannot be empty");
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;
}
@Override
public String toString() {
return "PagingAndSortingQueryParams ["
+ "size=" + size
+ ", pageNum=" + pageNum
+ ", orderBy=" + orderBy
+ ", sortableProperties=" + sortableProperties
+ "]";
}
private SortableProperty generateSortableProperty(String orderByStr) {
checkArgument(StringTools.isNotBlank(orderByStr));
checkArgument(RegexTools.matches(orderByStr, SORT_STR_PATTERN));
String[] propertyNameAndOrderType = orderByStr.split("-");
checkArgument(propertyNameAndOrderType.length == 2);
String propertyName = propertyNameAndOrderType[0];
checkArgument(sortableProperties.containsKey(propertyName),
"The property name must be in the set of sortable properties.");
String columnName = sortableProperties.get(propertyName);
String orderType = propertyNameAndOrderType[1];
return new SortableProperty(propertyName, columnName, orderType);
protected static PagingParamsBuilder pagingParamsBuilder(
int defaultSize, int maxSize, Map<String, String> sortableProperties) {
return new PagingParamsBuilder(defaultSize, maxSize, sortableProperties);
}
/**
@@ -217,4 +158,47 @@ public class PagingAndSortingQueryParams {
}
}
protected static final class PagingParamsBuilder {
private static final Pattern SORT_STR_PATTERN = Pattern.compile("^[a-zA-Z][\\w-]{0,63}-(desc|asc|DESC|ASC)$");
private final Map<String, String> sortableProperties;
protected final int defaultSize;
protected final int maxSize;
private PagingParamsBuilder(int defaultSize, int maxSize, Map<String, String> sortableProperties) {
this.defaultSize = defaultSize;
this.maxSize = maxSize;
checkArgument(CollectionTools.isNotEmpty(sortableProperties),
"Sortable properties can not be empty.");
sortableProperties.forEach((k, v) ->
checkArgument(StringTools.isNotBlank(k) && StringTools.isNotBlank(v),
"Property name must not be blank."));
this.sortableProperties = ImmutableMap.copyOf(sortableProperties);
}
public PagingParams buildPagingParams(PagingAndSortingQueryParams params) {
final int sizeValue = params.size != null ? params.size : this.defaultSize;
final long pageNumValue = params.pageNum != null ? params.pageNum : 1L;
checkArgument(CollectionTools.isNotEmpty(params.orderBy),
"The 'orderBy' cannot be empty");
final List<SortableProperty> propertiesToSort = params.orderBy.stream()
.map(this::generateSortableProperty)
.collect(Collectors.toList());
return new PagingParams(sizeValue, pageNumValue, propertiesToSort);
}
private SortableProperty generateSortableProperty(String orderByStr) {
checkArgument(StringTools.isNotBlank(orderByStr));
checkArgument(RegexTools.matches(orderByStr, SORT_STR_PATTERN));
String[] propertyNameAndOrderType = orderByStr.split("-");
checkArgument(propertyNameAndOrderType.length == 2);
String propertyName = propertyNameAndOrderType[0];
checkArgument(sortableProperties.containsKey(propertyName),
"The property name must be in the set of sortable properties.");
String columnName = sortableProperties.get(propertyName);
String orderType = propertyNameAndOrderType[1];
return new SortableProperty(propertyName, columnName, orderType);
}
}
}

View File

@@ -24,7 +24,7 @@ import xyz.zhouxy.plusone.commons.model.dto.PagingAndSortingQueryParams.Sortable
/**
* 分页参数
*
* @author <a href="http://zhouxy.xyz:3000/ZhouXY108">ZhouXY</a>
* @author ZhouXY108 <luquanlion@outlook.com>
* @see PagingAndSortingQueryParams
*/
public class PagingParams {

View File

@@ -22,7 +22,7 @@ import javax.annotation.Nullable;
/**
* 统一结果,对返回给前端的数据进行封装。
*
* @author <a href="http://zhouxy.xyz:3000/ZhouXY108">ZhouXY</a>
* @author ZhouXY108 <luquanlion@outlook.com>
*/
public class UnifiedResponse<T> {

View File

@@ -21,7 +21,7 @@ import javax.annotation.Nullable;
/**
* UnifiedResponse 工厂
*
* @author <a href="http://zhouxy.xyz:3000/ZhouXY108">ZhouXY</a>
* @author ZhouXY108 <luquanlion@outlook.com>
* @since 1.0.0
* @see UnifiedResponse
*/

View File

@@ -60,7 +60,7 @@
* 自定义 SUCCESS_CODE 和 DEFAULT_SUCCESS_MSG 和工厂方法。
* 见 <a href="http://zhouxy.xyz:3000/plusone/plusone-commons/issues/22">issue#22</a>。
*
* @author <a href="http://zhouxy.xyz:3000/ZhouXY108">ZhouXY</a>
* @author ZhouXY108 <luquanlion@outlook.com>
*/
@ParametersAreNonnullByDefault
package xyz.zhouxy.plusone.commons.model.dto;

View File

@@ -19,7 +19,7 @@
* <p>
* 包含业务建模可能用到的性别、身份证等元素,也包含 DTO 相关类,如分页查询参数,响应结果,分页结果等。
*
* @author <a href="http://zhouxy.xyz:3000/ZhouXY108">ZhouXY</a>
* @author ZhouXY108 <luquanlion@outlook.com>
*/
@ParametersAreNonnullByDefault
package xyz.zhouxy.plusone.commons.model;

View File

@@ -32,7 +32,7 @@ import xyz.zhouxy.plusone.commons.base.IWithIntCode;
/**
* 季度
*
* @author <a href="http://zhouxy.xyz:3000/ZhouXY108">ZhouXY</a>
* @author ZhouXY108 <luquanlion@outlook.com>
*/
public enum Quarter implements IWithIntCode {
/** 第一季度 */

View File

@@ -37,7 +37,7 @@ import xyz.zhouxy.plusone.commons.annotation.StaticFactoryMethod;
/**
* 表示年份与季度
*
* @author <a href="http://zhouxy.xyz:3000/ZhouXY108">ZhouXY</a>
* @author ZhouXY108 <luquanlion@outlook.com>
*/
@Immutable
public final class YearQuarter implements Comparable<YearQuarter>, Serializable {

View File

@@ -22,7 +22,7 @@
* 模仿 JDK 的 {@link java.time.Month} 和 {@link java.time.YearMonth}
* 实现 {@link Quarter}{@link YearQuarter},对季度进行建模。
*
* @author <a href="http://zhouxy.xyz:3000/ZhouXY108">ZhouXY</a>
* @author ZhouXY108 <luquanlion@outlook.com>
*/
@ParametersAreNonnullByDefault
package xyz.zhouxy.plusone.commons.time;

View File

@@ -17,6 +17,7 @@
package xyz.zhouxy.plusone.commons.util;
import static xyz.zhouxy.plusone.commons.util.AssertTools.checkArgument;
import static xyz.zhouxy.plusone.commons.util.AssertTools.checkArgumentNotNull;
import static xyz.zhouxy.plusone.commons.util.AssertTools.checkNotNull;
import java.math.BigDecimal;
@@ -37,7 +38,7 @@ import javax.annotation.Nullable;
* <p>
* 数组工具类
*
* @author <a href="http://zhouxy.xyz:3000/ZhouXY108">ZhouXY</a>
* @author ZhouXY108 <luquanlion@outlook.com>
* @since 1.0.0
*/
public class ArrayTools {
@@ -491,7 +492,7 @@ public class ArrayTools {
* @return 重复后的数组
*/
public static char[] repeat(char[] arr, int times, int maxLength) {
checkArgument(Objects.nonNull(arr));
checkArgumentNotNull(arr);
checkArgument(times >= 0,
"The number of times must be greater than or equal to zero");
checkArgument(maxLength >= 0,
@@ -526,7 +527,7 @@ public class ArrayTools {
* @return 重复后的数组
*/
public static byte[] repeat(byte[] arr, int times, int maxLength) {
checkArgument(Objects.nonNull(arr));
checkArgumentNotNull(arr);
checkArgument(times >= 0,
"The number of times must be greater than or equal to zero");
checkArgument(maxLength >= 0,
@@ -561,7 +562,7 @@ public class ArrayTools {
* @return 重复后的数组
*/
public static short[] repeat(short[] arr, int times, int maxLength) {
checkArgument(Objects.nonNull(arr));
checkArgumentNotNull(arr);
checkArgument(times >= 0,
"The number of times must be greater than or equal to zero");
checkArgument(maxLength >= 0,
@@ -596,7 +597,7 @@ public class ArrayTools {
* @return 重复后的数组
*/
public static int[] repeat(int[] arr, int times, int maxLength) {
checkArgument(Objects.nonNull(arr));
checkArgumentNotNull(arr);
checkArgument(times >= 0,
"The number of times must be greater than or equal to zero");
checkArgument(maxLength >= 0,
@@ -631,7 +632,7 @@ public class ArrayTools {
* @return 重复后的数组
*/
public static long[] repeat(long[] arr, int times, int maxLength) {
checkArgument(Objects.nonNull(arr));
checkArgumentNotNull(arr);
checkArgument(times >= 0,
"The number of times must be greater than or equal to zero");
checkArgument(maxLength >= 0,
@@ -666,7 +667,7 @@ public class ArrayTools {
* @return 重复后的数组
*/
public static float[] repeat(float[] arr, int times, int maxLength) {
checkArgument(Objects.nonNull(arr));
checkArgumentNotNull(arr);
checkArgument(times >= 0,
"The number of times must be greater than or equal to zero");
checkArgument(maxLength >= 0,
@@ -701,7 +702,7 @@ public class ArrayTools {
* @return 重复后的数组
*/
public static double[] repeat(double[] arr, int times, int maxLength) {
checkArgument(Objects.nonNull(arr));
checkArgumentNotNull(arr);
checkArgument(times >= 0,
"The number of times must be greater than or equal to zero");
checkArgument(maxLength >= 0,
@@ -750,7 +751,7 @@ public class ArrayTools {
* @param values 填充内容
*/
public static void fill(char[] a, int fromIndex, int toIndex, @Nullable char[] values) {
checkArgument(Objects.nonNull(a));
checkArgumentNotNull(a);
if (values == null || values.length == 0) {
return;
}
@@ -793,7 +794,7 @@ public class ArrayTools {
* @param values 填充内容
*/
public static void fill(byte[] a, int fromIndex, int toIndex, @Nullable byte[] values) {
checkArgument(Objects.nonNull(a));
checkArgumentNotNull(a);
if (values == null || values.length == 0) {
return;
}
@@ -836,7 +837,7 @@ public class ArrayTools {
* @param values 填充内容
*/
public static void fill(short[] a, int fromIndex, int toIndex, @Nullable short[] values) {
checkArgument(Objects.nonNull(a));
checkArgumentNotNull(a);
if (values == null || values.length == 0) {
return;
}
@@ -879,7 +880,7 @@ public class ArrayTools {
* @param values 填充内容
*/
public static void fill(int[] a, int fromIndex, int toIndex, @Nullable int[] values) {
checkArgument(Objects.nonNull(a));
checkArgumentNotNull(a);
if (values == null || values.length == 0) {
return;
}
@@ -922,7 +923,7 @@ public class ArrayTools {
* @param values 填充内容
*/
public static void fill(long[] a, int fromIndex, int toIndex, @Nullable long[] values) {
checkArgument(Objects.nonNull(a));
checkArgumentNotNull(a);
if (values == null || values.length == 0) {
return;
}
@@ -965,7 +966,7 @@ public class ArrayTools {
* @param values 填充内容
*/
public static void fill(float[] a, int fromIndex, int toIndex, @Nullable float[] values) {
checkArgument(Objects.nonNull(a));
checkArgumentNotNull(a);
if (values == null || values.length == 0) {
return;
}
@@ -1008,7 +1009,7 @@ public class ArrayTools {
* @param values 填充内容
*/
public static void fill(double[] a, int fromIndex, int toIndex, @Nullable double[] values) {
checkArgument(Objects.nonNull(a));
checkArgumentNotNull(a);
if (values == null || values.length == 0) {
return;
}
@@ -1063,7 +1064,7 @@ public class ArrayTools {
* @param values 填充内容
*/
private static <T> void fillInternal(T[] a, int fromIndex, int toIndex, @Nullable T[] values) {
checkArgument(Objects.nonNull(a));
checkArgumentNotNull(a);
if (values == null || values.length == 0) {
return;
}

View File

@@ -39,7 +39,7 @@ import xyz.zhouxy.plusone.commons.exception.system.DataOperationResultException;
* "must be a well-formed email address");
* </pre>
*
* @author <a href="http://zhouxy.xyz:3000/ZhouXY108">ZhouXY</a>
* @author ZhouXY108 <luquanlion@outlook.com>
*/
public class AssertTools {
@@ -54,7 +54,9 @@ public class AssertTools {
* @throws IllegalArgumentException 当条件不满足时抛出
*/
public static void checkArgument(boolean condition) {
checkCondition(condition, IllegalArgumentException::new);
if (!condition) {
throw new IllegalArgumentException();
}
}
/**
@@ -65,7 +67,9 @@ public class AssertTools {
* @throws IllegalArgumentException 当条件不满足时抛出
*/
public static void checkArgument(boolean condition, @Nullable String errorMessage) {
checkCondition(condition, () -> new IllegalArgumentException(errorMessage));
if (!condition) {
throw new IllegalArgumentException(errorMessage);
}
}
/**
@@ -76,7 +80,9 @@ public class AssertTools {
* @throws IllegalArgumentException 当条件不满足时抛出
*/
public static void checkArgument(boolean condition, Supplier<String> errorMessageSupplier) {
checkCondition(condition, () -> new IllegalArgumentException(errorMessageSupplier.get()));
if (!condition) {
throw new IllegalArgumentException(errorMessageSupplier.get());
}
}
/**
@@ -89,8 +95,9 @@ public class AssertTools {
*/
public static void checkArgument(boolean condition,
String errorMessageTemplate, Object... errorMessageArgs) {
checkCondition(condition,
() -> new IllegalArgumentException(String.format(errorMessageTemplate, errorMessageArgs)));
if (!condition) {
throw new IllegalArgumentException(String.format(errorMessageTemplate, errorMessageArgs));
}
}
// ================================
@@ -109,7 +116,9 @@ public class AssertTools {
* @throws IllegalArgumentException 当 {@code obj} 为 {@code null} 时抛出
*/
public static <T> T checkArgumentNotNull(@Nullable T obj) {
checkCondition(obj != null, IllegalArgumentException::new);
if (obj == null) {
throw new IllegalArgumentException();
}
return obj;
}
@@ -122,7 +131,9 @@ public class AssertTools {
* @throws IllegalArgumentException 当 {@code obj} 为 {@code null} 时抛出
*/
public static <T> T checkArgumentNotNull(@Nullable T obj, String errorMessage) {
checkCondition(obj != null, () -> new IllegalArgumentException(errorMessage));
if (obj == null) {
throw new IllegalArgumentException(errorMessage);
}
return obj;
}
@@ -135,7 +146,9 @@ public class AssertTools {
* @throws IllegalArgumentException 当 {@code obj} 为 {@code null} 时抛出
*/
public static <T> T checkArgumentNotNull(@Nullable T obj, Supplier<String> errorMessageSupplier) {
checkCondition(obj != null, () -> new IllegalArgumentException(errorMessageSupplier.get()));
if (obj == null) {
throw new IllegalArgumentException(errorMessageSupplier.get());
}
return obj;
}
@@ -150,8 +163,9 @@ public class AssertTools {
*/
public static <T> T checkArgumentNotNull(@Nullable T obj,
String errorMessageTemplate, Object... errorMessageArgs) {
checkCondition(obj != null,
() -> new IllegalArgumentException(String.format(errorMessageTemplate, errorMessageArgs)));
if (obj == null) {
throw new IllegalArgumentException(String.format(errorMessageTemplate, errorMessageArgs));
}
return obj;
}
@@ -170,7 +184,9 @@ public class AssertTools {
* @throws IllegalStateException 当条件不满足时抛出
*/
public static void checkState(boolean condition) {
checkCondition(condition, IllegalStateException::new);
if (!condition) {
throw new IllegalStateException();
}
}
/**
@@ -181,7 +197,9 @@ public class AssertTools {
* @throws IllegalStateException 当条件不满足时抛出
*/
public static void checkState(boolean condition, @Nullable String errorMessage) {
checkCondition(condition, () -> new IllegalStateException(errorMessage));
if (!condition) {
throw new IllegalStateException(errorMessage);
}
}
/**
@@ -192,7 +210,9 @@ public class AssertTools {
* @throws IllegalStateException 当条件不满足时抛出
*/
public static void checkState(boolean condition, Supplier<String> errorMessageSupplier) {
checkCondition(condition, () -> new IllegalStateException(errorMessageSupplier.get()));
if (!condition) {
throw new IllegalStateException(errorMessageSupplier.get());
}
}
/**
@@ -205,8 +225,9 @@ public class AssertTools {
*/
public static void checkState(boolean condition,
String errorMessageTemplate, Object... errorMessageArgs) {
checkCondition(condition,
() -> new IllegalStateException(String.format(errorMessageTemplate, errorMessageArgs)));
if (!condition) {
throw new IllegalStateException(String.format(errorMessageTemplate, errorMessageArgs));
}
}
// ================================
@@ -225,7 +246,9 @@ public class AssertTools {
* @throws NullPointerException 当 {@code obj} 为 {@code null} 时抛出
*/
public static <T> void checkNotNull(@Nullable T obj) {
checkCondition(obj != null, NullPointerException::new);
if (obj == null) {
throw new NullPointerException();
}
}
/**
@@ -237,7 +260,9 @@ public class AssertTools {
* @throws NullPointerException 当 {@code obj} 为 {@code null} 时抛出
*/
public static <T> void checkNotNull(@Nullable T obj, String errorMessage) {
checkCondition(obj != null, () -> new NullPointerException(errorMessage));
if (obj == null) {
throw new NullPointerException(errorMessage);
}
}
/**
@@ -249,7 +274,9 @@ public class AssertTools {
* @throws NullPointerException 当 {@code obj} 为 {@code null} 时抛出
*/
public static <T> void checkNotNull(@Nullable T obj, Supplier<String> errorMessageSupplier) {
checkCondition(obj != null, () -> new NullPointerException(errorMessageSupplier.get()));
if (obj == null) {
throw new NullPointerException(errorMessageSupplier.get());
}
}
/**
@@ -263,8 +290,9 @@ public class AssertTools {
*/
public static <T> void checkNotNull(@Nullable T obj,
String errorMessageTemplate, Object... errorMessageArgs) {
checkCondition(obj != null,
() -> new NullPointerException(String.format(errorMessageTemplate, errorMessageArgs)));
if (obj == null) {
throw new NullPointerException(String.format(errorMessageTemplate, errorMessageArgs));
}
}
// ================================
@@ -285,7 +313,9 @@ public class AssertTools {
*/
public static <T> T checkExists(@Nullable T obj)
throws DataNotExistsException {
checkCondition(obj != null, DataNotExistsException::new);
if (obj == null) {
throw new DataNotExistsException();
}
return obj;
}
@@ -300,7 +330,9 @@ public class AssertTools {
*/
public static <T> T checkExists(@Nullable T obj, String errorMessage)
throws DataNotExistsException {
checkCondition(obj != null, () -> new DataNotExistsException(errorMessage));
if (obj == null) {
throw new DataNotExistsException(errorMessage);
}
return obj;
}
@@ -315,7 +347,9 @@ public class AssertTools {
*/
public static <T> T checkExists(@Nullable T obj, Supplier<String> errorMessageSupplier)
throws DataNotExistsException {
checkCondition(obj != null, () -> new DataNotExistsException(errorMessageSupplier.get()));
if (obj == null) {
throw new DataNotExistsException(errorMessageSupplier.get());
}
return obj;
}
@@ -332,8 +366,9 @@ public class AssertTools {
public static <T> T checkExists(@Nullable T obj,
String errorMessageTemplate, Object... errorMessageArgs)
throws DataNotExistsException {
checkCondition(obj != null,
() -> new DataNotExistsException(String.format(errorMessageTemplate, errorMessageArgs)));
if (obj == null) {
throw new DataNotExistsException(String.format(errorMessageTemplate, errorMessageArgs));
}
return obj;
}
@@ -347,7 +382,9 @@ public class AssertTools {
*/
public static <T> T checkExists(Optional<T> optional)
throws DataNotExistsException {
checkCondition(optional.isPresent(), DataNotExistsException::new);
if (!optional.isPresent()) {
throw new DataNotExistsException();
}
return optional.get();
}
@@ -362,7 +399,9 @@ public class AssertTools {
*/
public static <T> T checkExists(Optional<T> optional, String errorMessage)
throws DataNotExistsException {
checkCondition(optional.isPresent(), () -> new DataNotExistsException(errorMessage));
if (!optional.isPresent()) {
throw new DataNotExistsException(errorMessage);
}
return optional.get();
}
@@ -377,7 +416,9 @@ public class AssertTools {
*/
public static <T> T checkExists(Optional<T> optional, Supplier<String> errorMessageSupplier)
throws DataNotExistsException {
checkCondition(optional.isPresent(), () -> new DataNotExistsException(errorMessageSupplier.get()));
if (!optional.isPresent()) {
throw new DataNotExistsException(errorMessageSupplier.get());
}
return optional.get();
}
@@ -394,8 +435,9 @@ public class AssertTools {
public static <T> T checkExists(Optional<T> optional,
String errorMessageTemplate, Object... errorMessageArgs)
throws DataNotExistsException {
checkCondition(optional.isPresent(),
() -> new DataNotExistsException(String.format(errorMessageTemplate, errorMessageArgs)));
if (!optional.isPresent()) {
throw new DataNotExistsException(String.format(errorMessageTemplate, errorMessageArgs));
}
return optional.get();
}

View File

@@ -31,7 +31,7 @@ import xyz.zhouxy.plusone.commons.annotation.StaticFactoryMethod;
* <p>
* BigDecimal 工具类
*
* @author <a href="http://zhouxy.xyz:3000/ZhouXY108">ZhouXY</a>
* @author ZhouXY108 <luquanlion@outlook.com>
* @since 1.0.0
*/
public class BigDecimals {

View File

@@ -27,11 +27,11 @@ import java.time.Year;
import java.time.YearMonth;
import java.time.ZoneId;
import java.time.ZonedDateTime;
import java.time.chrono.IsoChronology;
import java.util.Calendar;
import java.util.Date;
import java.util.TimeZone;
import com.google.common.collect.BoundType;
import com.google.common.collect.Range;
import xyz.zhouxy.plusone.commons.time.Quarter;
@@ -40,7 +40,7 @@ import xyz.zhouxy.plusone.commons.time.YearQuarter;
/**
* 日期时间工具类
*
* @author <a href="http://zhouxy.xyz:3000/ZhouXY108">ZhouXY</a>
* @author ZhouXY108 <luquanlion@outlook.com>
*/
public class DateTimeTools {
@@ -649,7 +649,7 @@ public class DateTimeTools {
// ================================
// ================================
// #region - others
// #region - range
// ================================
/**
@@ -673,6 +673,52 @@ public class DateTimeTools {
return Range.closedOpen(date.atStartOfDay(zone), startOfNextDate(date, zone));
}
/**
* 将指定日期范围转为日期时间范围
*
* @param dateRange 日期范围
* @return 对应的日期时间范围
*/
public static Range<LocalDateTime> toDateTimeRange(Range<LocalDate> dateRange) {
BoundType lowerBoundType = dateRange.lowerBoundType();
LocalDateTime lowerEndpoint = lowerBoundType == BoundType.CLOSED
? dateRange.lowerEndpoint().atStartOfDay()
: dateRange.lowerEndpoint().plusDays(1).atStartOfDay();
BoundType upperBoundType = dateRange.upperBoundType();
LocalDateTime upperEndpoint = upperBoundType == BoundType.CLOSED
? dateRange.upperEndpoint().plusDays(1).atStartOfDay()
: dateRange.upperEndpoint().atStartOfDay();
return Range.closedOpen(lowerEndpoint, upperEndpoint);
}
/**
* 将指定日期范围转为日期时间范围
*
* @param dateRange 日期范围
* @return 对应的日期时间范围
*/
public static Range<ZonedDateTime> toDateTimeRange(Range<LocalDate> dateRange, ZoneId zone) {
BoundType lowerBoundType = dateRange.lowerBoundType();
ZonedDateTime lowerEndpoint = lowerBoundType == BoundType.CLOSED
? dateRange.lowerEndpoint().atStartOfDay(zone)
: dateRange.lowerEndpoint().plusDays(1).atStartOfDay(zone);
BoundType upperBoundType = dateRange.upperBoundType();
ZonedDateTime upperEndpoint = upperBoundType == BoundType.CLOSED
? dateRange.upperEndpoint().plusDays(1).atStartOfDay(zone)
: dateRange.upperEndpoint().atStartOfDay(zone);
return Range.closedOpen(lowerEndpoint, upperEndpoint);
}
// ================================
// #endregion - range
// ================================
// ================================
// #region - others
// ================================
/**
* 判断指定年份是否为闰年
*
@@ -680,7 +726,7 @@ public class DateTimeTools {
* @return 指定年份是否为闰年
*/
public static boolean isLeapYear(int year) {
return IsoChronology.INSTANCE.isLeapYear(year);
return Year.isLeap(year);
}
// ================================

View File

@@ -26,7 +26,7 @@ import javax.annotation.Nullable;
/**
* 枚举工具类
*
* @author <a href="http://zhouxy.xyz:3000/ZhouXY108">ZhouXY</a>
* @author ZhouXY108 <luquanlion@outlook.com>
*/
public final class EnumTools {
@@ -154,7 +154,7 @@ public final class EnumTools {
*/
@Nullable
public static <E extends Enum<?>> Integer checkOrdinalNullable(Class<E> enumType, @Nullable Integer ordinal) {
return checkOrdinalOrDefault(enumType, ordinal, (Integer) null);
return checkOrdinalOrDefault(enumType, ordinal, null);
}
/**

View File

@@ -33,9 +33,10 @@ import xyz.zhouxy.plusone.commons.annotation.StaticFactoryMethod;
/**
* 枚举类
*
* <p>
* 参考 <a href="https://lostechies.com/jimmybogard/2008/08/12/enumeration-classes/">Enumeration classes</a>
*
* @author <a href="http://zhouxy.xyz:3000/ZhouXY108">ZhouXY</a>
* @author ZhouXY108 <luquanlion@outlook.com>
* @deprecated 设计 Enumeration 的灵感来自于 .net 社区,因为 C&num; 的枚举不带行为。
* 但 Java 的枚举可以带行为,故大多数情况下不需要这种设计。
*/

View File

@@ -30,7 +30,7 @@ import java.util.concurrent.ConcurrentHashMap;
*
* @see UUID
* @see IdWorker
* @author <a href="http://zhouxy.xyz:3000/ZhouXY108">ZhouXY</a>
* @author ZhouXY108 <luquanlion@outlook.com>
*/
public class IdGenerator {

View File

@@ -27,7 +27,11 @@ import javax.annotation.Nullable;
import xyz.zhouxy.plusone.commons.exception.system.NoAvailableMacFoundException;
/**
* Seata 提供的修改版雪花ID
* 修改版雪花 ID 生成器
*
* <p>
* 来自 Seata (https://seata.apache.org) 的 {@code org.apache.seata.common.util.IdWorker}
*
* <p>
* 大体思路为:
* <ol>
@@ -43,7 +47,6 @@ import xyz.zhouxy.plusone.commons.exception.system.NoAvailableMacFoundException;
* <li><a href="https://juejin.cn/post/7264387737276203065">在开源项目中看到一个改良版的雪花算法,现在它是你的了。</a></li>
* <li><a href="https://juejin.cn/post/7265516484029743138">关于若干读者,阅读“改良版雪花算法”后提出的几个共性问题的回复。</a></li>
* </ul>
*
*/
public class IdWorker {

View File

@@ -19,7 +19,7 @@ package xyz.zhouxy.plusone.commons.util;
/**
* Joda-Time 工具类
*
* @author <a href="http://zhouxy.xyz:3000/ZhouXY108">ZhouXY</a>
* @author ZhouXY108 <luquanlion@outlook.com>
*/
public class JodaTimeTools {
@@ -54,7 +54,9 @@ public class JodaTimeTools {
* @param zone 时区
* @return {@link org.joda.time.Instant} 对象
*/
public static org.joda.time.Instant toJodaInstant(java.time.LocalDateTime localDateTime, java.time.ZoneId zone) {
public static org.joda.time.Instant toJodaInstant(
java.time.LocalDateTime localDateTime,
java.time.ZoneId zone) {
return toJodaInstant(java.time.ZonedDateTime.of(localDateTime, zone));
}
@@ -92,9 +94,9 @@ public class JodaTimeTools {
* {@link org.joda.time.DateTimeZone} 对象
* 转换为 Java 中的 {@link java.time.Instant} 对象
*
* @param localDateTime
* @param zone
* @return
* @param localDateTime {@link org.joda.time.LocalDateTime} 对象
* @param zone {@link org.joda.time.DateTimeZone} 对象
* @return Java 表示时间戳的 {@link java.time.Instant} 对象
*/
public static java.time.Instant toJavaInstant(
org.joda.time.LocalDateTime localDateTime,
@@ -135,8 +137,9 @@ public class JodaTimeTools {
public static org.joda.time.DateTime toJodaDateTime(
java.time.LocalDateTime localDateTime,
java.time.ZoneId zone) {
org.joda.time.DateTimeZone dateTimeZone = toJodaZone(zone);
return toJodaInstant(java.time.ZonedDateTime.of(localDateTime, zone).toInstant()).toDateTime(dateTimeZone);
org.joda.time.LocalDateTime jodaLocalDateTime = toJodaLocalDateTime(localDateTime);
org.joda.time.DateTimeZone jodaZone = toJodaZone(zone);
return jodaLocalDateTime.toDateTime(jodaZone);
}
/**
@@ -218,9 +221,15 @@ public class JodaTimeTools {
* @return joda-time LocalDateTime
*/
public static org.joda.time.LocalDateTime toJodaLocalDateTime(java.time.LocalDateTime localDateTime) {
java.time.ZoneId javaZone = java.time.ZoneId.systemDefault();
org.joda.time.DateTimeZone jodaZone = toJodaZone(javaZone);
return toJodaInstant(localDateTime, javaZone).toDateTime(jodaZone).toLocalDateTime();
return new org.joda.time.LocalDateTime(
localDateTime.getYear(),
localDateTime.getMonthValue(),
localDateTime.getDayOfMonth(),
localDateTime.getHour(),
localDateTime.getMinute(),
localDateTime.getSecond(),
localDateTime.getNano() / 1_000_000 // 毫秒转纳秒
);
}
// ================================
@@ -238,9 +247,15 @@ public class JodaTimeTools {
* @return Java 8 LocalDateTime
*/
public static java.time.LocalDateTime toJavaLocalDateTime(org.joda.time.LocalDateTime localDateTime) {
org.joda.time.DateTimeZone jodaZone = org.joda.time.DateTimeZone.getDefault();
java.time.ZoneId javaZone = toJavaZone(jodaZone);
return toJavaInstant(localDateTime, jodaZone).atZone(javaZone).toLocalDateTime();
return java.time.LocalDateTime.of(
localDateTime.getYear(),
localDateTime.getMonthOfYear(),
localDateTime.getDayOfMonth(),
localDateTime.getHourOfDay(),
localDateTime.getMinuteOfHour(),
localDateTime.getSecondOfMinute(),
localDateTime.getMillisOfSecond() * 1_000_000 // 毫秒转纳秒
);
}
// ================================

View File

@@ -25,11 +25,13 @@ import javax.annotation.Nullable;
/**
* 数字工具类
*
* @author <a href="http://zhouxy.xyz:3000/ZhouXY108">ZhouXY</a>
* @author ZhouXY108 <luquanlion@outlook.com>
*/
public class Numbers {
// ================================
// #region - sum
// ================================
/**
* 求和
@@ -131,9 +133,13 @@ public class Numbers {
return BigDecimals.sum(numbers);
}
// ================================
// #endregion
// ================================
// ================================
// #region - nullToZero
// ================================
/**
* 将 {@code null} 转换为 {@code 0}
@@ -217,7 +223,122 @@ public class Numbers {
return BigDecimals.nullToZero(val);
}
// #endregion
// ================================
// #endregion - nullToZero
// ================================
// ================================
// #region - parse
// ================================
/**
* 将字符串转为对应 {@link Short},转换失败时返回 {@code defaultValue}(允许为 {@code null})。
*
* @param str 要转换的字符串
* @param defaultValue 默认值
* @return 转换结果
*/
@Nullable
public static Short parseShort(@Nullable String str, @Nullable Short defaultValue) {
if (StringTools.isBlank(str)) {
return defaultValue;
}
try {
return Short.parseShort(str);
}
catch (NumberFormatException ignore) {
// ignore
}
return defaultValue;
}
/**
* 将字符串转为 {@link Integer},转换失败时返回 {@code defaultValue}(允许为 {@code null})。
*
* @param str 要转换的字符串
* @param defaultValue 默认值
* @return 转换结果
*/
@Nullable
public static Integer parseInteger(@Nullable String str, @Nullable Integer defaultValue) {
if (StringTools.isBlank(str)) {
return defaultValue;
}
try {
return Integer.parseInt(str);
}
catch (NumberFormatException ignore) {
// ignore
}
return defaultValue;
}
/**
* 将字符串转为 {@link Long},转换失败时返回 {@code defaultValue}(允许为 {@code null})。
*
* @param str 要转换的字符串
* @param defaultValue 默认值
* @return 转换结果
*/
@Nullable
public static Long parseLong(@Nullable String str, @Nullable Long defaultValue) {
if (StringTools.isBlank(str)) {
return defaultValue;
}
try {
return Long.parseLong(str);
}
catch (NumberFormatException ignore) {
// ignore
}
return defaultValue;
}
/**
* 将字符串转为 {@link Float},转换失败时返回 {@code defaultValue}(允许为 {@code null})。
*
* @param str 要转换的字符串
* @param defaultValue 默认值
* @return 转换结果
*/
@Nullable
public static Float parseFloat(@Nullable String str, @Nullable Float defaultValue) {
if (StringTools.isBlank(str)) {
return defaultValue;
}
try {
return Float.parseFloat(str);
}
catch (NumberFormatException ignore) {
// ignore
}
return defaultValue;
}
/**
* 将字符串转为 {@link Double},转换失败时返回 {@code defaultValue}(允许为 {@code null})。
*
* @param str 要转换的字符串
* @param defaultValue 默认值
* @return 转换结果
*/
@Nullable
public static Double parseDouble(@Nullable String str, @Nullable Double defaultValue) {
if (StringTools.isBlank(str)) {
return defaultValue;
}
try {
return Double.parseDouble(str);
}
catch (NumberFormatException ignore) {
// ignore
}
return defaultValue;
}
// ================================
// #endregion - parse
// ================================
private Numbers() {
throw new IllegalStateException("Utility class");

View File

@@ -31,7 +31,7 @@ import com.google.common.annotations.Beta;
* <p>
* 提供一些 Optional 相关的方法
*
* @author <a href="http://zhouxy.xyz:3000/ZhouXY108">ZhouXY</a>
* @author ZhouXY108 <luquanlion@outlook.com>
* @since 1.0.0
* @see Optional
* @see OptionalInt

View File

@@ -17,26 +17,27 @@
package xyz.zhouxy.plusone.commons.util;
import static xyz.zhouxy.plusone.commons.util.AssertTools.checkArgument;
import static xyz.zhouxy.plusone.commons.util.AssertTools.checkArgumentNotNull;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
import java.util.Objects;
import java.util.Random;
import java.util.concurrent.ThreadLocalRandom;
import com.google.common.collect.BoundType;
import com.google.common.collect.Range;
/**
* 随机工具类
* <p>
* 建议调用方自行维护 Random 对象
*
* @author <a href="http://zhouxy.xyz:3000/ZhouXY108">ZhouXY</a>
* @author ZhouXY108 <luquanlion@outlook.com>
*/
public final class RandomTools {
private static final SecureRandom DEFAULT_SECURE_RANDOM;
static {
SecureRandom secureRandom = null;
SecureRandom secureRandom;
try {
secureRandom = SecureRandom.getInstanceStrong(); // 获取高强度安全随机数生成器
}
@@ -46,18 +47,41 @@ public final class RandomTools {
DEFAULT_SECURE_RANDOM = secureRandom;
}
/**
* 大写字母
*/
public static final String CAPITAL_LETTERS = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
/**
* 小写字母
*/
public static final String LOWERCASE_LETTERS = "abcdefghijklmnopqrstuvwxyz";
/**
* 数字
*/
public static final String NUMBERS = "0123456789";
/**
* 默认的 {@code SecureRandom}
*
* @return 默认的 {@code SecureRandom}
*/
public static SecureRandom defaultSecureRandom() {
return DEFAULT_SECURE_RANDOM;
}
/**
* 当前线程的 {@code ThreadLocalRandom}
*
* @return 当前线程的 {@code ThreadLocalRandom}
*/
public static ThreadLocalRandom currentThreadLocalRandom() {
return ThreadLocalRandom.current();
}
// ================================
// #region - randomStr
// ================================
/**
* 使用传入的随机数生成器,生成指定长度的字符串
*
@@ -69,20 +93,34 @@ public final class RandomTools {
* @return 随机字符串
*/
public static String randomStr(Random random, char[] sourceCharacters, int length) {
checkArgument(Objects.nonNull(random), "Random cannot be null.");
checkArgument(Objects.nonNull(sourceCharacters), "Source characters cannot be null.");
checkArgumentNotNull(random, "Random cannot be null.");
checkArgumentNotNull(sourceCharacters, "Source characters cannot be null.");
checkArgument(length >= 0, "The length should be greater than or equal to zero.");
return randomStrInternal(random, sourceCharacters, length);
}
/**
* 使用当前线程的 {@code ThreadLocalRandom},生成指定长度的字符串
*
* @param sourceCharacters 字符池。字符串的字符将在数组中选,不为空
* @param length 字符串长度
* @return 随机字符串
*/
public static String randomStr(char[] sourceCharacters, int length) {
checkArgument(Objects.nonNull(sourceCharacters), "Source characters cannot be null.");
checkArgumentNotNull(sourceCharacters, "Source characters cannot be null.");
checkArgument(length >= 0, "The length should be greater than or equal to zero.");
return randomStrInternal(ThreadLocalRandom.current(), sourceCharacters, length);
}
/**
* 使用默认的 {@code SecureRandom},生成指定长度的字符串
*
* @param sourceCharacters 字符池。字符串的字符将在数组中选,不为空
* @param length 字符串长度
* @return 随机字符串
*/
public static String secureRandomStr(char[] sourceCharacters, int length) {
checkArgument(Objects.nonNull(sourceCharacters), "Source characters cannot be null.");
checkArgumentNotNull(sourceCharacters, "Source characters cannot be null.");
checkArgument(length >= 0, "The length should be greater than or equal to zero.");
return randomStrInternal(DEFAULT_SECURE_RANDOM, sourceCharacters, length);
}
@@ -98,24 +136,137 @@ public final class RandomTools {
* @return 随机字符串
*/
public static String randomStr(Random random, String sourceCharacters, int length) {
checkArgument(Objects.nonNull(random), "Random cannot be null.");
checkArgument(Objects.nonNull(sourceCharacters), "Source characters cannot be null.");
checkArgumentNotNull(random, "Random cannot be null.");
checkArgumentNotNull(sourceCharacters, "Source characters cannot be null.");
checkArgument(length >= 0, "The length should be greater than or equal to zero.");
return randomStrInternal(random, sourceCharacters, length);
}
/**
* 使用当前线程的 {@code ThreadLocalRandom},生成指定长度的字符串
*
* @param sourceCharacters 字符池。字符串的字符将在数组中选,不为空
* @param length 字符串长度
* @return 随机字符串
*/
public static String randomStr(String sourceCharacters, int length) {
checkArgument(Objects.nonNull(sourceCharacters), "Source characters cannot be null.");
checkArgumentNotNull(sourceCharacters, "Source characters cannot be null.");
checkArgument(length >= 0, "The length should be greater than or equal to zero.");
return randomStrInternal(ThreadLocalRandom.current(), sourceCharacters, length);
}
/**
* 使用默认的 {@code SecureRandom},生成指定长度的字符串
*
* @param sourceCharacters 字符池。字符串的字符将在数组中选,不为空
* @param length 字符串长度
* @return 随机字符串
*/
public static String secureRandomStr(String sourceCharacters, int length) {
checkArgument(Objects.nonNull(sourceCharacters), "Source characters cannot be null.");
checkArgumentNotNull(sourceCharacters, "Source characters cannot be null.");
checkArgument(length >= 0, "The length should be greater than or equal to zero.");
return randomStrInternal(DEFAULT_SECURE_RANDOM, sourceCharacters, length);
}
// ================================
// #endregion - randomStr
// ================================
// ================================
// #region - randomInt
// ================================
/**
* 使用传入的随机数生成器,生成随机整数
*
* @param startInclusive 最小值(包含)
* @param endExclusive 最大值(不包含)
* @return 在区间 {@code [min, max)} 内的随机整数
*
* @since 1.1.0
*/
public static int randomInt(Random random, int startInclusive, int endExclusive) {
checkArgumentNotNull(random, "Random cannot be null.");
checkArgument(startInclusive < endExclusive, "Start value must be less than end value.");
return randomIntInternal(random, startInclusive, endExclusive);
}
/**
* 使用当前线程的 {@code ThreadLocalRandom},生成随机整数
*
* @param startInclusive 最小值(包含)
* @param endExclusive 最大值(不包含)
* @return 在区间 {@code [min, max)} 内的随机整数
*
* @since 1.1.0
*/
public static int randomInt(int startInclusive, int endExclusive) {
checkArgument(startInclusive < endExclusive, "Start value must be less than end value.");
return randomIntInternal(ThreadLocalRandom.current(), startInclusive, endExclusive);
}
/**
* 使用默认的 {@code SecureRandom},生成随机整数
*
* @param startInclusive 最小值(包含)
* @param endExclusive 最大值(不包含)
* @return 在区间 {@code [min, max)} 内的随机整数
*
* @since 1.1.0
*/
public static int secureRandomInt(int startInclusive, int endExclusive) {
checkArgument(startInclusive < endExclusive, "Start value must be less than end value.");
return randomIntInternal(DEFAULT_SECURE_RANDOM, startInclusive, endExclusive);
}
/**
* 使用传入的随机数生成器,生成随机整数
*
* @param range 整数区间
* @return 在指定区间内的随机整数
*
* @since 1.1.0
*/
public static int randomInt(Random random, Range<Integer> range) {
checkArgumentNotNull(random, "Random cannot be null.");
checkArgumentNotNull(range, "Range cannot be null.");
return randomIntInternal(random, range);
}
/**
* 使用当前线程的 {@code ThreadLocalRandom},生成随机整数
*
* @param range 整数区间
* @return 在指定区间内的随机整数
*
* @since 1.1.0
*/
public static int randomInt(Range<Integer> range) {
checkArgumentNotNull(range, "Range cannot be null.");
return randomIntInternal(ThreadLocalRandom.current(), range);
}
/**
* 使用默认的 {@code SecureRandom},生成随机整数
*
* @param range 整数区间
* @return 在指定区间内的随机整数
*
* @since 1.1.0
*/
public static int secureRandomInt(Range<Integer> range) {
checkArgumentNotNull(range, "Range cannot be null.");
return randomIntInternal(DEFAULT_SECURE_RANDOM, range);
}
// ================================
// #endregion - randomInt
// ================================
// ================================
// #region - private methods
// ================================
/**
* 使用传入的随机数生成器,生成指定长度的字符串
*
@@ -158,6 +309,35 @@ public final class RandomTools {
return String.valueOf(result);
}
/**
* 使用传入的随机数生成器,生成随机整数
*
* @param startInclusive 最小值(包含)
* @param endExclusive 最大值(不包含)
* @return 在区间 {@code [min, max)} 内的随机整数
*/
private static int randomIntInternal(Random random, int startInclusive, int endExclusive) {
return random.nextInt(endExclusive - startInclusive) + startInclusive;
}
/**
* 使用传入的随机数生成器,生成随机整数
*
* @param range 整数区间
* @return 在指定区间内的随机整数
*/
private static int randomIntInternal(Random random, Range<Integer> range) {
Integer lowerEndpoint = range.lowerEndpoint();
Integer upperEndpoint = range.upperEndpoint();
int min = range.lowerBoundType() == BoundType.CLOSED ? lowerEndpoint : lowerEndpoint + 1;
int max = range.upperBoundType() == BoundType.OPEN ? upperEndpoint : upperEndpoint + 1;
return random.nextInt(max - min) + min;
}
// ================================
// #endregion - private methods
// ================================
private RandomTools() {
throw new IllegalStateException("Utility class");
}

View File

@@ -35,7 +35,7 @@ import com.google.common.cache.LoadingCache;
/**
* 封装一些常用的正则操作,并可以缓存 {@link Pattern} 实例以复用。
*
* @author <a href="http://zhouxy.xyz:3000/ZhouXY108">ZhouXY</a>
* @author ZhouXY108 <luquanlion@outlook.com>
*/
public final class RegexTools {
@@ -363,7 +363,7 @@ public final class RegexTools {
this.flags = flags;
}
private final Pattern compilePattern() {
private Pattern compilePattern() {
return Pattern.compile(regex, flags);
}

View File

@@ -17,10 +17,10 @@
package xyz.zhouxy.plusone.commons.util;
import static xyz.zhouxy.plusone.commons.util.AssertTools.checkArgument;
import static xyz.zhouxy.plusone.commons.util.AssertTools.checkArgumentNotNull;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.Objects;
import javax.annotation.Nullable;
@@ -34,7 +34,7 @@ import xyz.zhouxy.plusone.commons.constant.PatternConsts;
* <p>
* 字符串工具类。
*
* @author <a href="http://zhouxy.xyz:3000/ZhouXY108">ZhouXY</a>
* @author ZhouXY108 <luquanlion@outlook.com>
* @since 1.0.0
*/
public class StringTools {
@@ -112,7 +112,7 @@ public class StringTools {
* @return 结果
*/
public static String repeat(final String str, int times, int maxLength) {
checkArgument(Objects.nonNull(str));
checkArgumentNotNull(str);
return String.valueOf(ArrayTools.repeat(str.toCharArray(), times, maxLength));
}
@@ -177,6 +177,9 @@ public class StringTools {
*/
@Beta
public static boolean isURL(@Nullable final String cs) {
if (cs == null) {
return false;
}
try {
new URL(cs);
} catch (MalformedURLException e) {

View File

@@ -32,7 +32,7 @@ import javax.annotation.Nullable;
/**
* TreeBuilder
*
* @author <a href="http://zhouxy.xyz:3000/ZhouXY108">ZhouXY</a>
* @author ZhouXY108 <luquanlion@outlook.com>
* @since 1.0.0
*/
public class TreeBuilder<T, TSubTree extends T, TIdentity> {

View File

@@ -0,0 +1,108 @@
/*
* 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.
*/
package xyz.zhouxy.plusone.commons.util;
import static xyz.zhouxy.plusone.commons.util.AssertTools.checkArgument;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.util.zip.Deflater;
import java.util.zip.DeflaterOutputStream;
import java.util.zip.Inflater;
import java.util.zip.InflaterOutputStream;
import javax.annotation.CheckForNull;
import javax.annotation.Nullable;
/**
* zip 工具类
*
* <p>
* 提供最基础的数据压缩/解压方法
*
* @author ZhouXY108 <luquanlion@outlook.com>
*
* @see Deflater
* @see Inflater
*/
public class ZipTools {
/**
* 使用默认压缩级别压缩数据
*
* @param input 输入
* @param level 压缩级别
* @return 压缩后的数据
*
* @throws IOException 发生 I/O 错误时抛出
*/
public static byte[] zip(@Nullable byte[] input) throws IOException {
return zipInternal(input, Deflater.DEFAULT_COMPRESSION);
}
/**
* 使用指定压缩级别压缩数据
*
* @param input 输入
* @param level 压缩级别
* @return 压缩后的数据
*
* @throws IOException 发生 I/O 错误时抛出
*/
public static byte[] zip(@Nullable byte[] input, int level) throws IOException {
checkArgument((level >= 0 && level <= 9) || level == Deflater.DEFAULT_COMPRESSION,
"invalid compression level");
return zipInternal(input, level);
}
@CheckForNull
private static byte[] zipInternal(@Nullable byte[] input, int level) throws IOException {
if (input == null) {
return null;
}
ByteArrayOutputStream out = new ByteArrayOutputStream();
try (DeflaterOutputStream dos = new DeflaterOutputStream(out, new Deflater(level))) {
dos.write(input);
dos.finish();
return out.toByteArray();
}
}
/**
* 解压数据
*
* @param input 输入
* @return 解压后的数据
*
* @throws IOException 发生 I/O 错误时抛出
*/
@CheckForNull
public static byte[] unzip(@Nullable byte[] input) throws IOException {
if (input == null) {
return null;
}
ByteArrayOutputStream out = new ByteArrayOutputStream();
try (InflaterOutputStream dos = new InflaterOutputStream(out, new Inflater())) {
dos.write(input);
dos.finish();
return out.toByteArray();
}
}
private ZipTools() {
throw new IllegalStateException("Utility class");
}
}

View File

@@ -19,7 +19,7 @@
* <p>
* 包含树构建器({@link TreeBuilder})、断言工具({@link AssertTools}、ID 生成器({@link IdGenerator})及其它实用工具类。
*
* @author <a href="http://zhouxy.xyz:3000/ZhouXY108">ZhouXY</a>
* @author ZhouXY108 <luquanlion@outlook.com>
*/
@ParametersAreNonnullByDefault
package xyz.zhouxy.plusone.commons.util;

View File

@@ -0,0 +1,316 @@
/*
* 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.
*/
package xyz.zhouxy.plusone.commons.collection;
import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNull;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.junit.jupiter.api.Assertions.assertTrue;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.TreeMap;
import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;
import javax.annotation.Nullable;
import org.junit.jupiter.api.Test;
import com.google.common.collect.ImmutableMap;
import lombok.Getter;
import lombok.extern.slf4j.Slf4j;
@Slf4j
public class MapModifierTests {
private static final String APP_START_ID = UUID.randomUUID().toString();
private static final String LOCKED = "LOCKED";
private static final Map<String, String> commonProperties = ImmutableMap.<String, String>builder()
.put("channel", "MOBILE")
.put("appStartId", APP_START_ID)
.build();
@Test
void demo() {
Map<String, String> expected = new HashMap<String, String>() {
{
put("channel", "MOBILE");
put("appStartId", APP_START_ID);
put("username", "Ben");
put("accountStatus", LOCKED);
}
};
// MapModifier
MapModifier<String, String> modifier = new MapModifier<String, String>()
.putAll(commonProperties)
.put("username", "Ben")
.put("accountStatus", LOCKED);
// 从 Supplier 中获取 Map并修改数据
HashMap<String, String> hashMap1 = modifier.getAndModify(HashMap::new);
assertEquals(expected, hashMap1);
// 可以灵活使用不同 Map 类型的不同构造器
HashMap<String, String> hashMap2 = modifier.getAndModify(() -> new HashMap<>(8));
assertEquals(expected, hashMap2);
// HashMap<String, String> hashMap3 = modifier.getAndModify(() -> new HashMap<>(anotherMap));
TreeMap<String, String> treeMap = modifier.getAndModify(TreeMap::new);
assertEquals(expected, treeMap);
ConcurrentHashMap<String, String> concurrentHashMap = modifier.getAndModify(ConcurrentHashMap::new);
assertEquals(expected, concurrentHashMap);
assertNull(modifier.getAndModify(() -> (Map<String, String>) null));
// 修改已有的 Map
Map<String, String> srcMap = new HashMap<>();
srcMap.put("srcKey1", "srcValue1");
srcMap.put("srcKey2", "srcValue2");
modifier.modify(srcMap);
assertEquals(new HashMap<String, String>() {
{
putAll(commonProperties);
put("username", "Ben");
put("accountStatus", LOCKED);
put("srcKey1", "srcValue1");
put("srcKey2", "srcValue2");
}
}, srcMap);
assertDoesNotThrow(() -> modifier.modify((Map<String, String>) null));
// 创建一个有初始化数据的不可变的 {@code Map}
Map<String, String> unmodifiableMap = modifier.getUnmodifiableMap();
assertEquals(expected, unmodifiableMap);
assertThrows(UnsupportedOperationException.class,
() -> unmodifiableMap.put("key", "value"));
}
@Test
void createAndInitData() {
// 链式调用创建并初始化数据
HashMap<String, String> map = new MapModifier<String, String>()
.putAll(commonProperties)
.put("username", "Ben")
.put("accountStatus", LOCKED)
.getAndModify(HashMap::new);
HashMap<String, String> expected = new HashMap<String, String>() {
{
put("channel", "MOBILE");
put("appStartId", APP_START_ID);
put("username", "Ben");
put("accountStatus", LOCKED);
}
};
assertEquals(expected, map);
}
@Test
void put() {
Map<String, String> map = new MapModifier<String, String>()
.put("key1", "value0")
.put("key1", "value1")
.getAndModify(HashMap::new);
assertEquals(new HashMap<String, String>() {
{
put("key1", "value0");
put("key1", "value1");
}
}, map);
new MapModifier<String, String>()
.put("key1", "newValue1")
.put("key2", null)
.modify(map);
assertEquals("newValue1", map.get("key1"));
assertTrue(map.containsKey("key2"));
assertNull(map.get("key2"));
}
@Test
void putIfAbsent() {
Map<String, String> map = new MapModifier<String, String>()
.putIfAbsent("key1", null)
.putIfAbsent("key1", "value1")
.putIfAbsent("key1", "value2")
.getAndModify(HashMap::new);
assertEquals(new HashMap<String, String>() {
{
putIfAbsent("key1", null);
putIfAbsent("key1", "value1");
putIfAbsent("key1", "value2");
}
}, map);
new MapModifier<String, String>()
.putIfAbsent("key1", "newValue1")
.modify(map);
assertTrue(map.containsKey("key1"));
assertEquals("value1", map.get("key1"));
}
@Test
void putAll_map() {
Map<String, String> entries = new HashMap<String, String>() {
{
put("key1", "value1");
put("key2", "value2");
}
};
Map<String, String> map = new MapModifier<String, String>()
.putAll((Map<String, String>) null)
.putAll(Collections.emptyMap())
.putAll(entries)
.getAndModify(HashMap::new);
assertEquals(entries, map);
new MapModifier<String, String>()
.putAll(new HashMap<String, String>() {
{
put("key2", "newValue2");
put("key3", "value3");
}
})
.modify(map);
assertEquals(new HashMap<String, String>() {
{
put("key1", "value1");
put("key2", "value2");
put("key2", "newValue2");
put("key3", "value3");
}
}, map);
}
@Test
void putAll_entries() {
Map<String, String> entries = new HashMap<String, String>() {
{
put("key1", "value1");
put("key2", "value2");
}
};
Map<String, String> map = new MapModifier<String, String>()
.putAll(new SimpleEntry<>("key1", "value1"),
new SimpleEntry<>("key2", "value2"))
.getAndModify(HashMap::new);
assertEquals(entries, map);
new MapModifier<String, String>()
.putAll()
.putAll(new SimpleEntry<>("key2", "newValue2"),
new SimpleEntry<>("key3", "value3"))
.modify(map);
assertEquals(new HashMap<String, String>() {
{
put("key1", "value1");
put("key2", "value2");
put("key2", "newValue2");
put("key3", "value3");
}
}, map);
}
@Test
void computeIfAbsent_keyAndFunction() {
Map<String, String> map = new MapModifier<String, String>()
.computeIfAbsent("key1", k -> null)
.computeIfAbsent("key1", k -> "value1")
.computeIfAbsent("key1", k -> "value2")
.getAndModify(HashMap::new);
assertEquals(new HashMap<String, String>() {
{
computeIfAbsent("key1", k -> null);
computeIfAbsent("key1", k -> "value1");
computeIfAbsent("key1", k -> "value2");
}
}, map);
new MapModifier<String, String>()
.computeIfAbsent("key1", k -> "newValue1")
.modify(map);
assertTrue(map.containsKey("key1"));
assertEquals("value1", map.get("key1"));
}
@Test
void computeIfPresent_keyAndBiFunction() {
Map<String, String> map = new HashMap<String, String>() {{
put("key1", "value1");
}};
new MapModifier<String, String>()
.computeIfPresent("key1", (k, v) -> k + v)
.computeIfPresent("key2", (k, v) -> k + v)
.modify(map);
assertEquals(new HashMap<String, String>() {{
put("key1", "key1value1");
}}, map);
}
@Test
void remove() {
Map<String, String> map = new HashMap<String, String>() {{
put("key1", "value1");
put("key2", "value2");
}};
new MapModifier<String, String>()
.remove("key2")
.modify(map);
assertEquals(new HashMap<String, String>() {{
put("key1", "value1");
}}, map);
}
@Test
void clear() {
Map<String, String> map = new HashMap<String, String>() {{
put("key1", "value1");
put("key2", "value2");
}};
new MapModifier<String, String>()
.clear()
.modify(map);
assertTrue(map.isEmpty());
}
@Getter
static class SimpleEntry<K, V> implements Map.Entry<K, V> {
private final K key;
private final V value;
public SimpleEntry(K key, V value) {
this.key = key;
this.value = value;
}
@Override
public V setValue(@Nullable V value) {
throw new UnsupportedOperationException("Unimplemented method 'setValue'");
}
}
}

View File

@@ -38,7 +38,6 @@ public class InvalidInputExceptionTests {
throw InvalidInputException.Type.CONTAINS_ILLEGAL_AND_MALICIOUS_LINKS.create();
});
assertSame(InvalidInputException.Type.CONTAINS_ILLEGAL_AND_MALICIOUS_LINKS, e.getType());
assertEquals(InvalidInputException.Type.CONTAINS_ILLEGAL_AND_MALICIOUS_LINKS.getCode(), e.getTypeCode());
assertEquals(InvalidInputException.Type.CONTAINS_ILLEGAL_AND_MALICIOUS_LINKS.getDefaultMessage(), e.getMessage());
assertNull(e.getCause());
}
@@ -50,7 +49,6 @@ public class InvalidInputExceptionTests {
throw InvalidInputException.Type.CONTAINS_ILLEGAL_WORDS.create(message);
});
assertSame(InvalidInputException.Type.CONTAINS_ILLEGAL_WORDS, e.getType());
assertEquals(InvalidInputException.Type.CONTAINS_ILLEGAL_WORDS.getCode(), e.getTypeCode());
assertEquals(message, e.getMessage());
assertNull(e.getCause());
}
@@ -62,7 +60,6 @@ public class InvalidInputExceptionTests {
throw InvalidInputException.Type.PICTURE_CONTAINS_ILLEGAL_INFORMATION.create(message);
});
assertSame(InvalidInputException.Type.PICTURE_CONTAINS_ILLEGAL_INFORMATION, e.getType());
assertEquals(InvalidInputException.Type.PICTURE_CONTAINS_ILLEGAL_INFORMATION.getCode(), e.getTypeCode());
assertNull(e.getMessage());
assertNull(e.getCause());
}
@@ -77,7 +74,6 @@ public class InvalidInputExceptionTests {
});
assertSame(InvalidInputException.Type.INFRINGE_COPYRIGHT, e.getType());
assertEquals(InvalidInputException.Type.INFRINGE_COPYRIGHT.getCode(), e.getTypeCode());
log.info("{}", e.getMessage());
assertEquals(nfe.toString(), e.getMessage());
assertSame(nfe, e.getCause());
@@ -92,7 +88,6 @@ public class InvalidInputExceptionTests {
});
assertSame(InvalidInputException.Type.DEFAULT, e.getType());
assertEquals(InvalidInputException.Type.DEFAULT.getCode(), e.getTypeCode());
assertNull(e.getMessage());
assertNull(e.getCause());
}
@@ -106,7 +101,6 @@ public class InvalidInputExceptionTests {
throw InvalidInputException.Type.CONTAINS_ILLEGAL_AND_MALICIOUS_LINKS.create(message, nfe);
});
assertSame(InvalidInputException.Type.CONTAINS_ILLEGAL_AND_MALICIOUS_LINKS, e.getType());
assertEquals(InvalidInputException.Type.CONTAINS_ILLEGAL_AND_MALICIOUS_LINKS.getCode(), e.getTypeCode());
assertEquals(message, e.getMessage());
assertSame(nfe, e.getCause());
}
@@ -120,7 +114,6 @@ public class InvalidInputExceptionTests {
throw InvalidInputException.Type.CONTAINS_ILLEGAL_WORDS.create(message, nfe);
});
assertSame(InvalidInputException.Type.CONTAINS_ILLEGAL_WORDS, e.getType());
assertEquals(InvalidInputException.Type.CONTAINS_ILLEGAL_WORDS.getCode(), e.getTypeCode());
assertNull(e.getMessage());
assertSame(nfe, e.getCause());
}
@@ -134,7 +127,6 @@ public class InvalidInputExceptionTests {
throw InvalidInputException.Type.CONTAINS_ILLEGAL_WORDS.create(message, npe);
});
assertSame(InvalidInputException.Type.CONTAINS_ILLEGAL_WORDS, e.getType());
assertEquals(InvalidInputException.Type.CONTAINS_ILLEGAL_WORDS.getCode(), e.getTypeCode());
assertEquals(message, e.getMessage());
assertNull(e.getCause());
}
@@ -148,7 +140,6 @@ public class InvalidInputExceptionTests {
throw InvalidInputException.Type.CONTAINS_ILLEGAL_WORDS.create(message, nfe);
});
assertSame(InvalidInputException.Type.CONTAINS_ILLEGAL_WORDS, e.getType());
assertEquals(InvalidInputException.Type.CONTAINS_ILLEGAL_WORDS.getCode(), e.getTypeCode());
assertNull(e.getMessage());
assertNull(e.getCause());
}
@@ -167,7 +158,6 @@ public class InvalidInputExceptionTests {
throw new InvalidInputException();
});
assertSame(InvalidInputException.Type.DEFAULT, e.getType());
assertEquals(InvalidInputException.Type.DEFAULT.getCode(), e.getTypeCode());
assertEquals(InvalidInputException.Type.DEFAULT.getDefaultMessage(), e.getMessage());
assertNull(e.getCause());
}
@@ -179,7 +169,6 @@ public class InvalidInputExceptionTests {
throw new InvalidInputException(message);
});
assertSame(InvalidInputException.Type.DEFAULT, e.getType());
assertEquals(InvalidInputException.Type.DEFAULT.getCode(), e.getTypeCode());
assertEquals(message, e.getMessage());
assertNull(e.getCause());
}
@@ -191,7 +180,6 @@ public class InvalidInputExceptionTests {
throw new InvalidInputException(message);
});
assertSame(InvalidInputException.Type.DEFAULT, e.getType());
assertEquals(InvalidInputException.Type.DEFAULT.getCode(), e.getTypeCode());
assertNull(e.getMessage());
assertNull(e.getCause());
}
@@ -206,7 +194,6 @@ public class InvalidInputExceptionTests {
});
assertSame(InvalidInputException.Type.DEFAULT, e.getType());
assertEquals(InvalidInputException.Type.DEFAULT.getCode(), e.getTypeCode());
log.info("{}", e.getMessage());
assertEquals(nfe.toString(), e.getMessage());
assertSame(nfe, e.getCause());
@@ -221,7 +208,6 @@ public class InvalidInputExceptionTests {
});
assertSame(InvalidInputException.Type.DEFAULT, e.getType());
assertEquals(InvalidInputException.Type.DEFAULT.getCode(), e.getTypeCode());
assertNull(e.getMessage());
assertNull(e.getCause());
}
@@ -235,7 +221,6 @@ public class InvalidInputExceptionTests {
throw new InvalidInputException(message, nfe);
});
assertSame(InvalidInputException.Type.DEFAULT, e.getType());
assertEquals(InvalidInputException.Type.DEFAULT.getCode(), e.getTypeCode());
assertEquals(message, e.getMessage());
assertSame(nfe, e.getCause());
}
@@ -249,7 +234,6 @@ public class InvalidInputExceptionTests {
throw new InvalidInputException(message, nfe);
});
assertSame(InvalidInputException.Type.DEFAULT, e.getType());
assertEquals(InvalidInputException.Type.DEFAULT.getCode(), e.getTypeCode());
assertNull(e.getMessage());
assertSame(nfe, e.getCause());
}
@@ -263,7 +247,6 @@ public class InvalidInputExceptionTests {
throw new InvalidInputException(message, npe);
});
assertSame(InvalidInputException.Type.DEFAULT, e.getType());
assertEquals(InvalidInputException.Type.DEFAULT.getCode(), e.getTypeCode());
assertEquals(message, e.getMessage());
assertNull(e.getCause());
}
@@ -277,7 +260,6 @@ public class InvalidInputExceptionTests {
throw new InvalidInputException(message, nfe);
});
assertSame(InvalidInputException.Type.DEFAULT, e.getType());
assertEquals(InvalidInputException.Type.DEFAULT.getCode(), e.getTypeCode());
assertNull(e.getMessage());
assertNull(e.getCause());
}

View File

@@ -41,7 +41,6 @@ public class ParsingFailureExceptionTests {
throw ParsingFailureException.DATE_TIME_PARSING_FAILURE.create();
});
assertSame(ParsingFailureException.DATE_TIME_PARSING_FAILURE, e.getType());
assertEquals(ParsingFailureException.DATE_TIME_PARSING_FAILURE.getCode(), e.getTypeCode());
assertEquals(ParsingFailureException.DATE_TIME_PARSING_FAILURE.getDefaultMessage(), e.getMessage());
assertNull(e.getCause());
}
@@ -53,7 +52,6 @@ public class ParsingFailureExceptionTests {
throw ParsingFailureException.JSON_PARSING_FAILURE.create(message);
});
assertSame(ParsingFailureException.Type.JSON_PARSING_FAILURE, e.getType());
assertEquals(ParsingFailureException.Type.JSON_PARSING_FAILURE.getCode(), e.getTypeCode());
assertEquals(message, e.getMessage());
assertNull(e.getCause());
}
@@ -65,7 +63,6 @@ public class ParsingFailureExceptionTests {
throw ParsingFailureException.XML_PARSING_FAILURE.create(message);
});
assertSame(ParsingFailureException.XML_PARSING_FAILURE, e.getType());
assertEquals(ParsingFailureException.XML_PARSING_FAILURE.getCode(), e.getTypeCode());
assertNull(e.getMessage());
assertNull(e.getCause());
}
@@ -80,7 +77,6 @@ public class ParsingFailureExceptionTests {
});
assertSame(ParsingFailureException.NUMBER_PARSING_FAILURE, e.getType());
assertEquals(ParsingFailureException.NUMBER_PARSING_FAILURE.getCode(), e.getTypeCode());
log.info("{}", e.getMessage());
assertEquals(nfe.toString(), e.getMessage());
assertSame(nfe, e.getCause());
@@ -95,7 +91,6 @@ public class ParsingFailureExceptionTests {
});
assertSame(ParsingFailureException.NUMBER_PARSING_FAILURE, e.getType());
assertEquals(ParsingFailureException.NUMBER_PARSING_FAILURE.getCode(), e.getTypeCode());
assertNull(e.getMessage());
assertNull(e.getCause());
}
@@ -109,7 +104,6 @@ public class ParsingFailureExceptionTests {
throw ParsingFailureException.NUMBER_PARSING_FAILURE.create(message, nfe);
});
assertSame(ParsingFailureException.NUMBER_PARSING_FAILURE, e.getType());
assertEquals(ParsingFailureException.NUMBER_PARSING_FAILURE.getCode(), e.getTypeCode());
assertEquals(message, e.getMessage());
assertSame(nfe, e.getCause());
}
@@ -123,7 +117,6 @@ public class ParsingFailureExceptionTests {
throw ParsingFailureException.DATE_TIME_PARSING_FAILURE.create(message, nfe);
});
assertSame(ParsingFailureException.DATE_TIME_PARSING_FAILURE, e.getType());
assertEquals(ParsingFailureException.DATE_TIME_PARSING_FAILURE.getCode(), e.getTypeCode());
assertNull(e.getMessage());
assertSame(nfe, e.getCause());
}
@@ -137,7 +130,6 @@ public class ParsingFailureExceptionTests {
throw ParsingFailureException.DATE_TIME_PARSING_FAILURE.create(message, npe);
});
assertSame(ParsingFailureException.DATE_TIME_PARSING_FAILURE, e.getType());
assertEquals(ParsingFailureException.DATE_TIME_PARSING_FAILURE.getCode(), e.getTypeCode());
assertEquals(message, e.getMessage());
assertNull(e.getCause());
}
@@ -151,7 +143,6 @@ public class ParsingFailureExceptionTests {
throw ParsingFailureException.DATE_TIME_PARSING_FAILURE.create(message, nfe);
});
assertSame(ParsingFailureException.DATE_TIME_PARSING_FAILURE, e.getType());
assertEquals(ParsingFailureException.DATE_TIME_PARSING_FAILURE.getCode(), e.getTypeCode());
assertNull(e.getMessage());
assertNull(e.getCause());
}
@@ -175,7 +166,6 @@ public class ParsingFailureExceptionTests {
});
assertSame(ParsingFailureException.DATE_TIME_PARSING_FAILURE, e.getType());
assertEquals(ParsingFailureException.DATE_TIME_PARSING_FAILURE.getCode(), e.getTypeCode());
assertEquals(dtpe.getMessage(), e.getMessage());
assertSame(dtpe, e.getCause());
}
@@ -189,7 +179,6 @@ public class ParsingFailureExceptionTests {
});
assertSame(ParsingFailureException.DATE_TIME_PARSING_FAILURE, e.getType());
assertEquals(ParsingFailureException.DATE_TIME_PARSING_FAILURE.getCode(), e.getTypeCode());
assertEquals(ParsingFailureException.DATE_TIME_PARSING_FAILURE.getDefaultMessage(), e.getMessage());
assertNull(e.getCause());
}
@@ -206,7 +195,6 @@ public class ParsingFailureExceptionTests {
});
assertSame(ParsingFailureException.DATE_TIME_PARSING_FAILURE, e.getType());
assertEquals(ParsingFailureException.DATE_TIME_PARSING_FAILURE.getCode(), e.getTypeCode());
assertEquals(message, e.getMessage());
assertSame(dtpe, e.getCause());
}
@@ -223,7 +211,6 @@ public class ParsingFailureExceptionTests {
});
assertSame(ParsingFailureException.DATE_TIME_PARSING_FAILURE, e.getType());
assertEquals(ParsingFailureException.DATE_TIME_PARSING_FAILURE.getCode(), e.getTypeCode());
assertNull(e.getMessage());
assertSame(dtpe, e.getCause());
}
@@ -238,7 +225,6 @@ public class ParsingFailureExceptionTests {
});
assertSame(ParsingFailureException.DATE_TIME_PARSING_FAILURE, e.getType());
assertEquals(ParsingFailureException.DATE_TIME_PARSING_FAILURE.getCode(), e.getTypeCode());
assertEquals(message, e.getMessage());
assertNull(e.getCause());
}
@@ -253,7 +239,6 @@ public class ParsingFailureExceptionTests {
});
assertSame(ParsingFailureException.DATE_TIME_PARSING_FAILURE, e.getType());
assertEquals(ParsingFailureException.DATE_TIME_PARSING_FAILURE.getCode(), e.getTypeCode());
assertNull(e.getMessage());
assertNull(e.getCause());
}
@@ -277,7 +262,6 @@ public class ParsingFailureExceptionTests {
});
assertSame(ParsingFailureException.NUMBER_PARSING_FAILURE, e.getType());
assertEquals(ParsingFailureException.NUMBER_PARSING_FAILURE.getCode(), e.getTypeCode());
assertEquals(dtpe.getMessage(), e.getMessage());
assertSame(dtpe, e.getCause());
}
@@ -291,7 +275,6 @@ public class ParsingFailureExceptionTests {
});
assertSame(ParsingFailureException.NUMBER_PARSING_FAILURE, e.getType());
assertEquals(ParsingFailureException.NUMBER_PARSING_FAILURE.getCode(), e.getTypeCode());
assertEquals(ParsingFailureException.NUMBER_PARSING_FAILURE.getDefaultMessage(), e.getMessage());
assertNull(e.getCause());
}
@@ -308,7 +291,6 @@ public class ParsingFailureExceptionTests {
});
assertSame(ParsingFailureException.NUMBER_PARSING_FAILURE, e.getType());
assertEquals(ParsingFailureException.NUMBER_PARSING_FAILURE.getCode(), e.getTypeCode());
assertEquals(message, e.getMessage());
assertSame(dtpe, e.getCause());
}
@@ -325,7 +307,6 @@ public class ParsingFailureExceptionTests {
});
assertSame(ParsingFailureException.NUMBER_PARSING_FAILURE, e.getType());
assertEquals(ParsingFailureException.NUMBER_PARSING_FAILURE.getCode(), e.getTypeCode());
assertNull(e.getMessage());
assertSame(dtpe, e.getCause());
}
@@ -340,7 +321,6 @@ public class ParsingFailureExceptionTests {
});
assertSame(ParsingFailureException.NUMBER_PARSING_FAILURE, e.getType());
assertEquals(ParsingFailureException.NUMBER_PARSING_FAILURE.getCode(), e.getTypeCode());
assertEquals(message, e.getMessage());
assertNull(e.getCause());
}
@@ -355,7 +335,6 @@ public class ParsingFailureExceptionTests {
});
assertSame(ParsingFailureException.NUMBER_PARSING_FAILURE, e.getType());
assertEquals(ParsingFailureException.NUMBER_PARSING_FAILURE.getCode(), e.getTypeCode());
assertNull(e.getMessage());
assertNull(e.getCause());
}

View File

@@ -0,0 +1,251 @@
/*
* 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.
*/
package xyz.zhouxy.plusone.commons.model;
import static org.apache.commons.lang3.ObjectUtils.compare;
import static org.junit.jupiter.api.Assertions.assertAll;
import static org.junit.jupiter.api.Assertions.assertArrayEquals;
import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertNotEquals;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.junit.jupiter.api.Assertions.assertTrue;
import java.util.Arrays;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.ValueSource;
import lombok.extern.slf4j.Slf4j;
@Slf4j
public class SemVerTests {
@ParameterizedTest
@ValueSource(strings = {
"25.10.17",
"25.10.17.11",
"25.10.17.11.38",
"25.10.17-RC1",
"25.10.17.11-RC1",
"25.10.17.11.38-RC1",
"25.10.17+build.a20251017.1",
"25.10.17.11+build.a20251017.1",
"25.10.17.11.38+build.a20251017.1",
"25.10.17-RC1+build.a20251017.1",
"25.10.17.11-RC1+build.a20251017.1",
"25.10.17.11.38-RC1+build.a20251017.1",
})
void test_of_success(String value) {
assertDoesNotThrow(() -> SemVer.of(value));
}
@ParameterizedTest
@ValueSource(strings = {
"25",
"25.10",
"x.10.17",
"25.x.17",
"25.10.x",
"25.10.17.11.38.20",
"025.10.17",
"25.010.17",
"25.10.017",
})
void test_of_wrongValue(String value) {
IllegalArgumentException e = assertThrows(IllegalArgumentException.class, () -> SemVer.of(value));
assertEquals("版本号格式错误", e.getMessage());
}
@ParameterizedTest
@ValueSource(strings = {
"25.1.1",
"25.1.1-RC1",
"25.1.1+build.a20250101.1",
"25.1.1-RC1+build.a20250101.1",
})
void compareTo_Major(String version_25_x_x) { // NOSONAR sonarqube(java:S117)
assertTrue(compare(SemVer.of(version_25_x_x), SemVer.of("24.12.30")) > 0);
assertTrue(compare(SemVer.of(version_25_x_x), SemVer.of("24.12.30.1")) > 0);
assertTrue(compare(SemVer.of(version_25_x_x), SemVer.of("24.12.30-RC2")) > 0);
assertTrue(compare(SemVer.of(version_25_x_x), SemVer.of("24.12.30+build.z20241230.1")) > 0);
assertTrue(compare(SemVer.of(version_25_x_x), SemVer.of("24.12.30-RC2+build.z20241230.1")) > 0);
}
@ParameterizedTest
@ValueSource(strings = {
"25.10.1",
"25.10.1-RC1",
"25.10.1+build.a20251001.1",
"25.10.1-RC1+build.a20251001.1",
})
void compareTo_Minor(String version_25_10_x) { // NOSONAR sonarqube(java:S117)
assertTrue(compare(SemVer.of(version_25_10_x), SemVer.of("25.9.30")) > 0);
assertTrue(compare(SemVer.of(version_25_10_x), SemVer.of("25.9.30.1")) > 0);
assertTrue(compare(SemVer.of(version_25_10_x), SemVer.of("25.9.30-RC2")) > 0);
assertTrue(compare(SemVer.of(version_25_10_x), SemVer.of("25.9.30+build.z20250930.1")) > 0);
assertTrue(compare(SemVer.of(version_25_10_x), SemVer.of("25.9.30-RC2+build.z20250930.1")) > 0);
}
@ParameterizedTest
@ValueSource(strings = {
"25.10.17",
"25.10.17-RC1",
"25.10.17+build.a20251017.1",
"25.10.17-RC1+build.a20251017.1",
})
void compareTo_Patch(String version_25_10_17) { // NOSONAR sonarqube(java:S117)
assertTrue(compare(SemVer.of(version_25_10_17), SemVer.of("25.10.16")) > 0);
assertTrue(compare(SemVer.of(version_25_10_17), SemVer.of("25.10.16.1")) > 0);
assertTrue(compare(SemVer.of(version_25_10_17), SemVer.of("25.10.16-RC2")) > 0);
assertTrue(compare(SemVer.of(version_25_10_17), SemVer.of("25.10.16+build.z20251016.2")) > 0);
assertTrue(compare(SemVer.of(version_25_10_17), SemVer.of("25.10.16-RC2+build.z20251016.2")) > 0);
}
@Test
void compareTo_MoreVersionNumber() {
assertTrue(compare(SemVer.of("25.10.17.1"), SemVer.of("25.10.17")) > 0);
assertTrue(compare(SemVer.of("25.10.17.11"), SemVer.of("25.10.17.1")) > 0);
assertEquals(0, compare(SemVer.of("25.10.17.11"), SemVer.of("25.10.17.11")));
assertTrue(compare(SemVer.of("25.10.17.11.1"), SemVer.of("25.10.17.11")) > 0);
assertTrue(compare(SemVer.of("25.10.17.11.38"), SemVer.of("25.10.17.11.1")) > 0);
assertEquals(0, compare(SemVer.of("25.10.17.11.38"), SemVer.of("25.10.17.11.38")));
}
@Test
void compareTo_PreReleaseVersion() {
// 先行版的优先级低于相关联的标准版本
assertTrue(compare(SemVer.of("25.10.17"), SemVer.of("25.10.17-0")) > 0);
assertTrue(compare(SemVer.of("25.10.17"), SemVer.of("25.10.17-RC1")) > 0);
// 只有数字的标识符以数值高低比较
assertAll(
() -> assertTrue(compare("25.10.17-RC.11", "25.10.17-RC.2") < 0),
() -> assertTrue(compare(SemVer.of("25.10.17-RC.11"), SemVer.of("25.10.17-RC.2")) > 0)
);
// 纯数字优先级低于非数字
assertAll(
() -> assertTrue(compare("25.10.17-999", "25.10.17-A") < 0),
() -> assertTrue(compare(SemVer.of("25.10.17-A.A"), SemVer.of("25.10.17-A.99")) > 0)
);
SemVer[] versions = {
SemVer.of("25.10.17-a"),
SemVer.of("25.10.17-aa"),
SemVer.of("25.10.17-A"),
SemVer.of("25.10.17-AA"),
SemVer.of("25.10.17--"),
SemVer.of("25.10.17-999"),
SemVer.of("25.10.17-z"),
SemVer.of("25.10.17-zz"),
SemVer.of("25.10.17-Z"),
SemVer.of("25.10.17-ZZ"),
};
assertArrayEquals(
new SemVer[] {
// 纯数字优先级低于非数字
SemVer.of("25.10.17-999"),
// 有字母或连接号时逐字符以 ASCII 的排序比较
SemVer.of("25.10.17--"),
SemVer.of("25.10.17-A"),
SemVer.of("25.10.17-AA"),
SemVer.of("25.10.17-Z"),
SemVer.of("25.10.17-ZZ"),
SemVer.of("25.10.17-a"),
SemVer.of("25.10.17-aa"),
SemVer.of("25.10.17-z"),
SemVer.of("25.10.17-zz"),
},
Arrays.stream(versions)
.sorted()
.toArray(SemVer[]::new));
// 若开头的标识符都相同时,栏位比较多的先行版本号优先级比较高
assertAll(
() -> assertTrue(compare(SemVer.of("25.10.17-ABC.DEF.1"), SemVer.of("25.10.17-ABC.DEF")) > 0),
() -> assertTrue(compare(SemVer.of("25.10.17-ABC.DEF.G"), SemVer.of("25.10.17-ABC.DEF")) > 0));
}
@Test
void compareTo_ignoreBuildMeta() {
assertNotEquals(SemVer.of("25.10.17"), SemVer.of("25.10.17+build.20251017.01"));
assertTrue(compare(SemVer.of("25.10.17"), SemVer.of("25.10.17+build.20251017.01")) == 0); // NOSONAR sonarqube(java:S5785)
assertNotEquals(SemVer.of("25.10.17+build.20251017.02"), SemVer.of("25.10.17+build.20251017.01"));
assertTrue(compare(SemVer.of("25.10.17+build.20251017.02"), SemVer.of("25.10.17+build.20251017.01")) == 0); // NOSONAR sonarqube(java:S5785)
assertNotEquals(SemVer.of("25.10.17-ABC.DEF"), SemVer.of("25.10.17-ABC.DEF+build.20251017.01"));
assertTrue(compare(SemVer.of("25.10.17-ABC.DEF"), SemVer.of("25.10.17-ABC.DEF+build.20251017.01")) == 0); // NOSONAR sonarqube(java:S5785)
assertNotEquals(SemVer.of("25.10.17-ABC.DEF+build.20251017.02"), SemVer.of("25.10.17-ABC.DEF+build.20251017.01"));
assertTrue(compare(SemVer.of("25.10.17-ABC.DEF+build.20251017.02"), SemVer.of("25.10.17-ABC.DEF+build.20251017.01")) == 0); // NOSONAR sonarqube(java:S5785)
}
@ParameterizedTest
@ValueSource(strings = {
"25.10.17",
"25.10.17-ABC.DEF.1",
"25.10.17+build.20251017.a2",
"25.10.17-ABC.DEF.1+build.20251017.a2",
})
void test_equals(String value) {
final SemVer v1 = SemVer.of(value);
final SemVer v2 = SemVer.of(value);
assertTrue(v1.compareTo(v1) == 0); // NOSONAR sonarqube(java:S5785)
assertTrue(v1.compareTo(v2) == 0); // NOSONAR sonarqube(java:S5785)
assertEquals(v1, v1);
assertEquals(v1.hashCode(), v2.hashCode());
assertEquals(v1, v2);
}
@Test
void test_equals_null() {
assertFalse(SemVer.of("25.10.17").equals(null)); // NOSONAR sonarqube(java:S5785)
}
@Test
void details() {
final String s = "25.10.17-ABC.DEF.1+build.20251017.02";
final SemVer v = SemVer.of(s);
assertEquals(25, v.getMajor());
assertEquals(10, v.getMinor());
assertEquals(17, v.getPatch());
assertEquals("ABC.DEF.1", v.getPreReleaseVersion());
assertEquals(s, v.getValue());
assertEquals("v" + s, v.toString());
assertEquals("build.20251017.02", v.getBuildMetadata());
}
}

View File

@@ -381,7 +381,7 @@ class CustomUnifiedResponseFactoryTests {
assertThrows(NullPointerException.class, () -> CustomUnifiedResponses.error(nullStatus, "查询失败", user));
// Throwable
BizException bizException = new BizException("业务异常");
BizException bizException = BizException.of("业务异常");
assertThrows(NullPointerException.class, () -> CustomUnifiedResponses.error(nullStatus, bizException));
assertThrows(NullPointerException.class, () -> CustomUnifiedResponses.error(nullStatus, (Throwable) null));
}

View File

@@ -379,7 +379,7 @@ class UnifiedResponseTests {
assertThrows(NullPointerException.class, () -> UnifiedResponses.error(nullStatus, "查询失败", user));
// Throwable
BizException bizException = new BizException("业务异常");
BizException bizException = BizException.of("业务异常");
assertThrows(NullPointerException.class, () -> UnifiedResponses.error(nullStatus, bizException));
assertThrows(NullPointerException.class, () -> UnifiedResponses.error(nullStatus, (Throwable) null));
}

View File

@@ -220,7 +220,7 @@ public class PagingAndSortingQueryParamsTests {
/**
* 账号信息查询参数
*
* @author <a href="http://zhouxy.xyz:3000/ZhouXY108">ZhouXY</a>
* @author ZhouXY108 <luquanlion@outlook.com>
*/
@ToString(callSuper = true)
class AccountQueryParams extends PagingAndSortingQueryParams {
@@ -231,9 +231,8 @@ class AccountQueryParams extends PagingAndSortingQueryParams {
.put("createTime", "create_time")
.build();
public AccountQueryParams() {
super(PROPERTY_COLUMN_MAP);
}
private static final PagingParamsBuilder PAGING_PARAMS_BUILDER = PagingAndSortingQueryParams
.pagingParamsBuilder(20, 100, PROPERTY_COLUMN_MAP);
private @Getter @Setter Long id;
private @Getter @Setter String username;
@@ -249,6 +248,10 @@ class AccountQueryParams extends PagingAndSortingQueryParams {
}
return this.createTimeEnd.plusDays(1);
}
public PagingParams buildPagingParams() {
return PAGING_PARAMS_BUILDER.buildPagingParams(this);
}
}
@Data

View File

@@ -43,6 +43,7 @@ import org.junit.jupiter.api.Test;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.google.common.collect.BoundType;
import com.google.common.collect.Range;
import xyz.zhouxy.plusone.commons.time.Quarter;
@@ -83,24 +84,6 @@ class DateTimeToolsTests {
CALENDAR.setTime(DATE);
}
// Joda
static final org.joda.time.LocalDateTime JODA_LOCAL_DATE_TIME
= new org.joda.time.LocalDateTime(2024, 12, 29, 12, 58, 30, 333);
static final org.joda.time.LocalDate JODA_LOCAL_DATE = JODA_LOCAL_DATE_TIME.toLocalDate();
static final org.joda.time.LocalTime JODA_LOCAL_TIME = JODA_LOCAL_DATE_TIME.toLocalTime();
// Joda - 2024-12-29 12:58:30.333 SystemDefaultZone
static final org.joda.time.DateTimeZone JODA_SYS_ZONE = org.joda.time.DateTimeZone.getDefault();
static final org.joda.time.DateTime JODA_DATE_TIME_WITH_SYS_ZONE = JODA_LOCAL_DATE_TIME.toDateTime(JODA_SYS_ZONE);
static final org.joda.time.Instant JODA_INSTANT_WITH_SYS_ZONE = JODA_DATE_TIME_WITH_SYS_ZONE.toInstant();
static final long JODA_INSTANT_MILLIS = JODA_INSTANT_WITH_SYS_ZONE.getMillis();
// Joda - 2024-12-29 12:58:30.333 GMT+04:00
static final org.joda.time.DateTimeZone JODA_ZONE = org.joda.time.DateTimeZone.forID("GMT+04:00");
static final org.joda.time.DateTime JODA_DATE_TIME = JODA_LOCAL_DATE_TIME.toDateTime(JODA_ZONE);
static final org.joda.time.Instant JODA_INSTANT = JODA_DATE_TIME.toInstant();
static final long JODA_MILLIS = JODA_INSTANT.getMillis();
// ================================
// #region - toDate
// ================================
@@ -109,9 +92,7 @@ class DateTimeToolsTests {
void toDate_timeMillis() {
assertNotEquals(SYS_DATE, DATE);
log.info("SYS_DATE: {}, DATE: {}", SYS_DATE, DATE);
assertEquals(SYS_DATE, JODA_DATE_TIME_WITH_SYS_ZONE.toDate());
assertEquals(SYS_DATE, DateTimeTools.toDate(INSTANT_MILLIS));
assertEquals(DATE, JODA_DATE_TIME.toDate());
assertEquals(DATE, DateTimeTools.toDate(MILLIS));
}
@@ -393,11 +374,11 @@ class DateTimeToolsTests {
// ================================
// ================================
// #region - others
// #region - range
// ================================
@Test
void startDateTimeRange() {
void toDateTimeRange_specifiedDate() {
Range<LocalDateTime> localDateTimeRange = DateTimeTools.toDateTimeRange(LOCAL_DATE);
assertEquals(LOCAL_DATE.atStartOfDay(), localDateTimeRange.lowerEndpoint());
assertEquals(LocalDate.of(2024, 12, 30).atStartOfDay(), localDateTimeRange.upperEndpoint());
@@ -411,8 +392,96 @@ class DateTimeToolsTests {
assertFalse(zonedDateTimeRange.contains(LocalDate.of(2024, 12, 30).atStartOfDay().atZone(SYS_ZONE_ID)));
}
@Test
void toDateTimeRange_dateRange_openRange() {
// (2000-01-01..2025-10-01) -> [2000-01-02T00:00..2025-10-01T00:00)
Range<LocalDate> dateRange = Range.open(
LocalDate.of(2000, 1, 1),
LocalDate.of(2025, 10, 1));
Range<LocalDateTime> localDateTimeRange = DateTimeTools.toDateTimeRange(dateRange);
assertEquals(BoundType.CLOSED, localDateTimeRange.lowerBoundType());
assertEquals(LocalDateTime.of(2000, 1, 2, 0, 0), localDateTimeRange.lowerEndpoint());
assertEquals(BoundType.OPEN, localDateTimeRange.upperBoundType());
assertEquals(LocalDateTime.of(2025, 10, 1, 0, 0), localDateTimeRange.upperEndpoint());
log.info(localDateTimeRange.toString());
Range<ZonedDateTime> zonedDateTimeRange = DateTimeTools.toDateTimeRange(dateRange, ZONE_ID);
assertEquals(BoundType.CLOSED, zonedDateTimeRange.lowerBoundType());
assertEquals(LocalDateTime.of(2000, 1, 2, 0, 0).atZone(ZONE_ID), zonedDateTimeRange.lowerEndpoint());
assertEquals(BoundType.OPEN, zonedDateTimeRange.upperBoundType());
assertEquals(LocalDateTime.of(2025, 10, 1, 0, 0).atZone(ZONE_ID), zonedDateTimeRange.upperEndpoint());
log.info(zonedDateTimeRange.toString());
}
@Test
void toDateTimeRange_dateRange_openClosedRange() {
// (2000-01-01..2025-10-01] -> [2025-01-02T00-00..2025-10-02 00:00)
Range<LocalDate> dateRange = Range.openClosed(
LocalDate.of(2000, 1, 1),
LocalDate.of(2025, 10, 1));
Range<LocalDateTime> localDateTimeRange = DateTimeTools.toDateTimeRange(dateRange);
assertEquals(BoundType.CLOSED, localDateTimeRange.lowerBoundType());
assertEquals(LocalDateTime.of(2000, 1, 2, 0, 0), localDateTimeRange.lowerEndpoint());
assertEquals(BoundType.OPEN, localDateTimeRange.upperBoundType());
assertEquals(LocalDateTime.of(2025, 10, 2, 0, 0), localDateTimeRange.upperEndpoint());
log.info(localDateTimeRange.toString());
Range<ZonedDateTime> zonedDateTimeRange = DateTimeTools.toDateTimeRange(dateRange, ZONE_ID);
assertEquals(BoundType.CLOSED, zonedDateTimeRange.lowerBoundType());
assertEquals(LocalDateTime.of(2000, 1, 2, 0, 0).atZone(ZONE_ID), zonedDateTimeRange.lowerEndpoint());
assertEquals(BoundType.OPEN, zonedDateTimeRange.upperBoundType());
assertEquals(LocalDateTime.of(2025, 10, 2, 0, 0).atZone(ZONE_ID), zonedDateTimeRange.upperEndpoint());
log.info(zonedDateTimeRange.toString());
}
@Test
void toDateTimeRange_dateRange_closedRange() {
// [2000-01-01..2025-10-01] -> [2025-01-01T00-00..2025-10-02 00:00)
Range<LocalDate> dateRange = Range.closed(
LocalDate.of(2000, 1, 1),
LocalDate.of(2025, 10, 1));
Range<LocalDateTime> localDateTimeRange = DateTimeTools.toDateTimeRange(dateRange);
assertEquals(BoundType.CLOSED, localDateTimeRange.lowerBoundType());
assertEquals(LocalDateTime.of(2000, 1, 1, 0, 0), localDateTimeRange.lowerEndpoint());
assertEquals(BoundType.OPEN, localDateTimeRange.upperBoundType());
assertEquals(LocalDateTime.of(2025, 10, 2, 0, 0), localDateTimeRange.upperEndpoint());
log.info(localDateTimeRange.toString());
Range<ZonedDateTime> zonedDateTimeRange = DateTimeTools.toDateTimeRange(dateRange, ZONE_ID);
assertEquals(BoundType.CLOSED, zonedDateTimeRange.lowerBoundType());
assertEquals(LocalDateTime.of(2000, 1, 1, 0, 0).atZone(ZONE_ID), zonedDateTimeRange.lowerEndpoint());
assertEquals(BoundType.OPEN, zonedDateTimeRange.upperBoundType());
assertEquals(LocalDateTime.of(2025, 10, 2, 0, 0).atZone(ZONE_ID), zonedDateTimeRange.upperEndpoint());
log.info(zonedDateTimeRange.toString());
}
@Test
void toDateTimeRange_dateRange_closedOpenRange() {
// [2025-01-01..2025-10-01) -> [2025-01-01T00-00..2025-10-01 00:00)
Range<LocalDate> dateRange = Range.closedOpen(
LocalDate.of(2000, 1, 1),
LocalDate.of(2025, 10, 1));
Range<LocalDateTime> localDateTimeRange = DateTimeTools.toDateTimeRange(dateRange);
assertEquals(BoundType.CLOSED, localDateTimeRange.lowerBoundType());
assertEquals(LocalDateTime.of(2000, 1, 1, 0, 0), localDateTimeRange.lowerEndpoint());
assertEquals(BoundType.OPEN, localDateTimeRange.upperBoundType());
assertEquals(LocalDateTime.of(2025, 10, 1, 0, 0), localDateTimeRange.upperEndpoint());
log.info(localDateTimeRange.toString());
Range<ZonedDateTime> zonedDateTimeRange = DateTimeTools.toDateTimeRange(dateRange, ZONE_ID);
assertEquals(BoundType.CLOSED, zonedDateTimeRange.lowerBoundType());
assertEquals(LocalDateTime.of(2000, 1, 1, 0, 0).atZone(ZONE_ID), zonedDateTimeRange.lowerEndpoint());
assertEquals(BoundType.OPEN, zonedDateTimeRange.upperBoundType());
assertEquals(LocalDateTime.of(2025, 10, 1, 0, 0).atZone(ZONE_ID), zonedDateTimeRange.upperEndpoint());
log.info(zonedDateTimeRange.toString());
}
// ================================
// #endregion - others
// #endregion - range
// ================================
// ================================

View File

@@ -33,7 +33,7 @@ import org.junit.jupiter.api.Test;
public class JodaTimeToolsTests {
// Java
static final LocalDateTime LOCAL_DATE_TIME = LocalDateTime.of(2024, 12, 29, 12, 58, 30, 333000000);
static final LocalDateTime LOCAL_DATE_TIME = LocalDateTime.of(2024, 12, 29, 12, 58, 30, 333 * 1_000_000);
static final LocalDate LOCAL_DATE = LOCAL_DATE_TIME.toLocalDate();
static final LocalTime LOCAL_TIME = LOCAL_DATE_TIME.toLocalTime();

View File

@@ -26,6 +26,7 @@ import java.util.Arrays;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertInstanceOf;
import static org.junit.jupiter.api.Assertions.assertNull;
import static org.junit.jupiter.api.Assertions.assertThrows;
public
@@ -68,9 +69,16 @@ class NumbersTests {
@Test
public void sum_BigIntegerArray_ReturnsCorrectSum() {
BigInteger[] numbers = {new BigInteger("1"), new BigInteger("2"), new BigInteger("3")};
BigInteger[] numbers = {
new BigInteger("1"),
new BigInteger("2"),
null,
new BigInteger("3")
};
BigInteger result = Numbers.sum(numbers);
assertEquals(new BigInteger("6"), result);
assertEquals(BigInteger.ZERO, Numbers.sum(new BigInteger[0]));
}
@Test
@@ -192,6 +200,100 @@ class NumbersTests {
assertEquals(BigDecimal.ZERO, result);
}
/**
* Test for {@link Numbers#parseShort(String, Short)}.
*/
@Test
public void parseShort() {
assertEquals((short) 12345, Numbers.parseShort("12345", (short) 5));
assertEquals((short) 5, Numbers.parseShort("1234.5", (short) 5));
assertEquals((short) 5, Numbers.parseShort("", (short) 5));
assertEquals((short) 5, Numbers.parseShort(null, (short) 5));
assertEquals((short) 12345, Numbers.parseShort("12345", null));
assertNull(Numbers.parseShort("1234.5", null));
assertNull(Numbers.parseShort("", null));
assertNull(Numbers.parseShort(null, null));
}
/**
* Test for {@link Numbers#parseInteger(String, Integer)}.
*/
@Test
public void parseInteger() {
assertEquals(12345, Numbers.parseInteger("12345", 5));
assertEquals(5, Numbers.parseInteger("1234.5", 5));
assertEquals(5, Numbers.parseInteger("", 5));
assertEquals(5, Numbers.parseInteger(null, 5));
assertEquals(12345, Numbers.parseInteger("12345", null));
assertNull(Numbers.parseInteger("1234.5", null));
assertNull(Numbers.parseInteger("", null));
assertNull(Numbers.parseInteger(null, null));
}
/**
* Test for {@link Numbers#parseLong(String, Long)}.
*/
@Test
public void parseLong() {
assertEquals(12345L, Numbers.parseLong("12345", 5L));
assertEquals(5L, Numbers.parseLong("1234.5", 5L));
assertEquals(5L, Numbers.parseLong("", 5L));
assertEquals(5L, Numbers.parseLong(null, 5L));
assertEquals(12345L, Numbers.parseLong("12345", null));
assertNull(Numbers.parseLong("1234.5", null));
assertNull(Numbers.parseLong("", null));
assertNull(Numbers.parseLong(null, null));
}
/**
* Test for {@link Numbers#parseFloat(String, Float)}.
*/
@Test
public void parseFloat() {
assertEquals(1.2345f, Numbers.parseFloat("1.2345", 5.1f));
assertEquals(5.0f, Numbers.parseFloat("a", 5.0f));
assertEquals(5.0f, Numbers.parseFloat("-001Z.2345", 5.0f));
assertEquals(5.0f, Numbers.parseFloat("+001AB.2345", 5.0f));
assertEquals(5.0f, Numbers.parseFloat("001Z.2345", 5.0f));
assertEquals(5.0f, Numbers.parseFloat("", 5.0f));
assertEquals(5.0f, Numbers.parseFloat(null, 5.0f));
assertEquals(1.2345f, Numbers.parseFloat("1.2345", null));
assertNull(Numbers.parseFloat("a", null));
assertNull(Numbers.parseFloat("-001Z.2345", null));
assertNull(Numbers.parseFloat("+001AB.2345", null));
assertNull(Numbers.parseFloat("001Z.2345", null));
assertNull(Numbers.parseFloat("", null));
assertNull(Numbers.parseFloat(null, null));
}
/**
* Test for {@link Numbers#parseDouble(String, Double)}.
*/
@Test
public void parseDouble() {
assertEquals(1.2345d, Numbers.parseDouble("1.2345", 5.1d));
assertEquals(5.0d, Numbers.parseDouble("a", 5.0d));
assertEquals(1.2345d, Numbers.parseDouble("001.2345", 5.1d));
assertEquals(-1.2345d, Numbers.parseDouble("-001.2345", 5.1d));
assertEquals(1.2345d, Numbers.parseDouble("+001.2345", 5.1d));
assertEquals(0d, Numbers.parseDouble("000.00", 5.1d));
assertEquals(5.1d, Numbers.parseDouble("", 5.1d));
assertEquals(5.1d, Numbers.parseDouble((String) null, 5.1d));
assertEquals(1.2345d, Numbers.parseDouble("1.2345", null));
assertEquals(null, Numbers.parseDouble("a", null));
assertEquals(1.2345d, Numbers.parseDouble("001.2345", null));
assertEquals(-1.2345d, Numbers.parseDouble("-001.2345", null));
assertEquals(1.2345d, Numbers.parseDouble("+001.2345", null));
assertEquals(0d, Numbers.parseDouble("000.00", null));
assertEquals(null, Numbers.parseDouble("", null));
assertEquals(null, Numbers.parseDouble((String) null, null));
}
@Test
void test_constructor_isNotAccessible_ThrowsIllegalStateException() {
Constructor<?>[] constructors = Numbers.class.getDeclaredConstructors();

View File

@@ -21,6 +21,7 @@ import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertInstanceOf;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.junit.jupiter.api.Assertions.assertTrue;
import java.lang.reflect.Constructor;
import java.security.SecureRandom;
@@ -30,6 +31,8 @@ import java.util.Random;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test;
import com.google.common.collect.Range;
@SuppressWarnings("null")
public class RandomToolsTests {
@@ -56,13 +59,16 @@ public class RandomToolsTests {
@Test
public void randomStr_NullSourceCharacters_ThrowsException() {
assertThrows(IllegalArgumentException.class, () -> RandomTools.randomStr(random, (char[]) null, 5));
assertThrows(IllegalArgumentException.class, () -> RandomTools.randomStr(random, (String) null, 5));
assertThrows(IllegalArgumentException.class,
() -> RandomTools.randomStr(random, (char[]) null, 5));
assertThrows(IllegalArgumentException.class,
() -> RandomTools.randomStr(random, (String) null, 5));
}
@Test
public void randomStr_NegativeLength_ThrowsException() {
assertThrows(IllegalArgumentException.class, () -> RandomTools.randomStr(random, sourceCharactersArray, -1));
assertThrows(IllegalArgumentException.class,
() -> RandomTools.randomStr(random, sourceCharactersArray, -1));
}
@Test
@@ -107,6 +113,50 @@ public class RandomToolsTests {
assertEquals(5, result.length());
}
@Test
public void randomInt_WithMinAndMax() {
for (int i = 0; i < 1000; i++) {
int r = RandomTools.randomInt(random, -2, 3);
assertTrue(r >= -2 && r < 3);
}
}
@Test
public void randomInt_WithClosedOpenRange() {
Range<Integer> co = Range.closedOpen(-2, 3);
for (int i = 0; i < 1000; i++) {
int rco = RandomTools.randomInt(random, co);
assertTrue(rco >= -2 && rco < 3);
}
}
@Test
public void randomInt_WithClosedRange() {
Range<Integer> cc = Range.closed(-2, 3);
for (int i = 0; i < 1000; i++) {
int rcc = RandomTools.randomInt(random, cc);
assertTrue(rcc >= -2 && rcc <= 3);
}
}
@Test
public void randomInt_WithOpenClosedRange() {
Range<Integer> oc = Range.openClosed(-2, 3);
for (int i = 0; i < 1000; i++) {
int roc = RandomTools.randomInt(random, oc);
assertTrue(roc > -2 && roc <= 3);
}
}
@Test
public void randomInt_WithOpenRange() {
Range<Integer> oo = Range.open(-2, 3);
for (int i = 0; i < 1000; i++) {
int roo = RandomTools.randomInt(random, oo);
assertTrue(roo > -2 && roo < 3);
}
}
@Test
void test_constructor_isNotAccessible_ThrowsIllegalStateException() {
Constructor<?>[] constructors = RandomTools.class.getDeclaredConstructors();

View File

@@ -0,0 +1,86 @@
package xyz.zhouxy.plusone.commons.util;
import static org.junit.jupiter.api.Assertions.assertArrayEquals;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertInstanceOf;
import static org.junit.jupiter.api.Assertions.assertNull;
import static org.junit.jupiter.api.Assertions.assertThrows;
import java.io.IOException;
import java.lang.reflect.Constructor;
import java.net.URL;
import java.util.Arrays;
import java.util.Random;
import java.util.zip.DataFormatException;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.ValueSource;
import com.google.common.base.Stopwatch;
import com.google.common.io.Resources;
import lombok.extern.slf4j.Slf4j;
@Slf4j
public class ZipToolsTests {
static byte[] bytes;
@BeforeAll
static void setup() throws IOException {
URL resource = Resources.getResource("xyz/zhouxy/plusone/commons/util/LoremIpsum.txt");
bytes = Resources.toByteArray(resource);
}
@Test
void zip_WithDefaultLevel() throws IOException, DataFormatException {
Stopwatch stopwatch = Stopwatch.createStarted();
byte[] zip = ZipTools.zip(bytes);
stopwatch.stop();
log.info("default level, size: {} ({})", zip.length, stopwatch);
assertArrayEquals(bytes, ZipTools.unzip(zip));
assertNull(ZipTools.zip(null));
assertNull(ZipTools.unzip(null));
}
@ParameterizedTest
@ValueSource(ints = { -1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 })
void zip_WithLevel(int level) throws IOException, DataFormatException {
Stopwatch stopwatch = Stopwatch.createStarted();
byte[] zip = ZipTools.zip(bytes, level);
stopwatch.stop();
log.info("level: {}, size: {} ({})", level, zip.length, stopwatch);
assertArrayEquals(bytes, ZipTools.unzip(zip));
assertNull(ZipTools.zip(null, level));
assertNull(ZipTools.unzip(null));
}
@Test
void zip_WithWrongLevel() {
Random random = new Random();
final int levelGtMax = random.nextInt() + 9;
assertThrows(IllegalArgumentException.class, () -> ZipTools.zip(bytes, levelGtMax));
final int levelLtMin = -1 - random.nextInt();
assertThrows(IllegalArgumentException.class, () -> ZipTools.zip(bytes, levelLtMin));
}
@Test
void test_constructor_isNotAccessible_ThrowsIllegalStateException() {
Constructor<?>[] constructors = ZipTools.class.getDeclaredConstructors();
Arrays.stream(constructors)
.forEach(constructor -> {
assertFalse(constructor.isAccessible());
constructor.setAccessible(true);
Throwable cause = assertThrows(Exception.class, constructor::newInstance)
.getCause();
assertInstanceOf(IllegalStateException.class, cause);
assertEquals("Utility class", cause.getMessage());
});
}
}

View File

@@ -0,0 +1,9 @@
Heaven much and fluttered sat the that lie chamber heart. Sure fantastic pallas in if chamber vainly i was by so pallas deep late the distant the stood. Now crest bust its and streaming but friends a on that still with master late the she this. Lent head soul this truly and presently surely i off. Grim and linking thy a is me my. Raven its is sir he his both with unmerciful a if evil vainly aptly this.
Truly mystery unseen that wished nevermore cushioned said bust lamplight. Home respite chamber its nevermore. Hesitating in upon said quoth broken sainted and leave soul floor. Home above still violet more fowl more much oer burning wandering maiden i. Desolate ghost still said from gently door before that rare the still till still only doubtless and the whether. That from pallas is entrance upon my when gilead. More its raven door bust then myself whose fiend swung that floor and quaint has i. Let whom ever followed on or of word name a the was and oer a surcease. Lamplight the sat my him tell darkness grew that distinctly. Some be as. Let chamber lattice of me caught i no me tufted raven songs ghastly quoth here stepped nights have. Heaven and door.
The angels relevancy radiant the undaunted me morrow though thy be mystery this my. Gently angels flown burning he tapping lining whispered i. Ungainly the broken stronger angels i tell separate what faintly and raven so. And take bird i.
Me ah was. The above floating madam cushioned lie had i. No as the. I horror with heard the above its said stayed. Me and what my me lenore the when the was. I croaking lie seat what cannot suddenly stood ghost. The of lenore terrors lifted for. Utters thy the desert grim that shrieked this fearing and form meant soon he friends louder. Filled more of of the raven. At above open engaged scarce never still vainly chamber the shore was still. Separate ancient dreary but his thy if nothing. Whose silence ominous at thy napping entrance off bust. Grew expressing on fowl. Decorum and token the door soul. Tapping kind of cushioned. Of nothing have thy evil above velvet a to doubtless memories chamber ungainly. I clasp rustling doubting more sitting for. Into core bird loneliness me lenore undaunted this raven the came mien bird i smiling and.
Sorrow engaged of what lenore i one agreeing quoth no. Stopped then i from me sculptured and and some in is respite. Lenore and lamplight and the at on the dream beguiling the angels said sad and on. By above the at. Lost above placid the it the of quoth and this in dreams ashore nothing. Wore shore came above raven stately. Of word it i from parting spoke heard. Was or lie prophet we. His into lamplight i. You more felt beating books shall word let above and mystery air.

View File

@@ -7,7 +7,7 @@
<parent>
<groupId>xyz.zhouxy.plusone</groupId>
<artifactId>plusone-parent</artifactId>
<version>1.1.0-RC1</version>
<version>1.1.0-SNAPSHOT</version>
</parent>
<artifactId>plusone-dependencies</artifactId>
@@ -18,11 +18,6 @@
</description>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<java.version>1.8</java.version>
<maven.compiler.source>1.8</maven.compiler.source>
<maven.compiler.target>1.8</maven.compiler.target>
<guava.version>33.4.8-jre</guava.version>
<joda-time.version>2.14.0</joda-time.version>
@@ -35,6 +30,9 @@
<commons-dbutils.version>1.8.1</commons-dbutils.version>
<commons-crypto.version>1.2.0</commons-crypto.version>
<okhttp.version>5.2.0</okhttp.version>
<okio.version>3.16.0</okio.version>
<logback.version>1.3.15</logback.version>
<jackson.version>2.18.3</jackson.version>
@@ -111,6 +109,18 @@
<version>${commons-crypto.version}</version>
</dependency>
<dependency>
<groupId>com.squareup.okhttp3</groupId>
<artifactId>okhttp</artifactId>
<version>${okhttp.version}</version>
</dependency>
<dependency>
<groupId>com.squareup.okio</groupId>
<artifactId>okio</artifactId>
<version>${okio.version}</version>
</dependency>
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
@@ -128,6 +138,7 @@
<artifactId>jackson-datatype-jsr310</artifactId>
<version>${jackson.version}</version>
</dependency>
<!-- Gson -->
<dependency>
<groupId>com.google.code.gson</groupId>
@@ -214,7 +225,7 @@
<version>${junit.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependency>
</dependencies>
</dependencyManagement>
@@ -223,7 +234,7 @@
<plugins>
<plugin>
<artifactId>maven-clean-plugin</artifactId>
<version>3.2.0</version>
<version>3.4.0</version>
</plugin>
<plugin>
<artifactId>maven-resources-plugin</artifactId>
@@ -231,31 +242,31 @@
</plugin>
<plugin>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.11.0</version>
<version>3.13.0</version>
</plugin>
<plugin>
<artifactId>maven-surefire-plugin</artifactId>
<version>3.0.0</version>
</plugin>
<plugin>
<artifactId>maven-jar-plugin</artifactId>
<version>3.3.0</version>
</plugin>
<plugin>
<artifactId>maven-jar-plugin</artifactId>
<version>3.4.2</version>
</plugin>
<plugin>
<artifactId>maven-install-plugin</artifactId>
<version>3.1.1</version>
<version>3.1.2</version>
</plugin>
<plugin>
<artifactId>maven-deploy-plugin</artifactId>
<version>3.1.1</version>
<version>3.1.2</version>
</plugin>
<plugin>
<artifactId>maven-site-plugin</artifactId>
<version>4.0.0-M7</version>
<version>3.12.1</version>
</plugin>
<plugin>
<artifactId>maven-project-info-reports-plugin</artifactId>
<version>3.4.2</version>
<version>3.6.1</version>
</plugin>
</plugins>
</pluginManagement>

View File

@@ -6,9 +6,13 @@
<groupId>xyz.zhouxy.plusone</groupId>
<artifactId>plusone-parent</artifactId>
<version>1.1.0-RC1</version>
<version>1.1.0-SNAPSHOT</version>
<packaging>pom</packaging>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<modules>
<module>plusone-commons</module>
<module>plusone-dependencies</module>