forked from plusone/plusone-commons
Compare commits
359 Commits
feature/ne
...
dev
| Author | SHA1 | Date | |
|---|---|---|---|
| cdd08d01d8 | |||
| cc6f599ddf | |||
| 1b7e0f5a40 | |||
| a6436cde85 | |||
| 620546ccda | |||
| d5cf06bee5 | |||
| 1e5b4b574b | |||
| 3462e5340f | |||
| b0685ae32f | |||
| 8cc11da121 | |||
| f2b9beb873 | |||
| 84112fcf45 | |||
| f7518063f8 | |||
| 4b7390447b | |||
| bed2f75da2 | |||
| c1e67af382 | |||
| 0ec65bf39d | |||
| c6f9cc0a80 | |||
| 829a7ed798 | |||
| f492d5d62e | |||
| 159a7769dc | |||
| 9ab92ce471 | |||
| 8dfb3ff694 | |||
| 264717eb62 | |||
| ba38175d93 | |||
| b8c666a023 | |||
| 255aaf182a | |||
| 468453781e | |||
| 3b241de08c | |||
| fb46def402 | |||
| 386ede6afd | |||
| 8ec61a84c9 | |||
| 726a94a1a8 | |||
| 3b441e4575 | |||
| 4ed6edd9b6 | |||
| 3d297331c4 | |||
| 665de3cdf0 | |||
| c2862203d4 | |||
| fafb26fcf7 | |||
| a65af967cb | |||
| 27e5650482 | |||
| 8106126e79 | |||
| 99d8f210c5 | |||
| 08432353fe | |||
| a7b1067ebb | |||
| 1dbfb36146 | |||
| 1fc0b198c9 | |||
| 15e07901e6 | |||
| 8f451e7eb9 | |||
| 2b6f946759 | |||
| ce9f3edfbc | |||
| 0f90756f44 | |||
| 34a49d30ca | |||
| f4c3793aab | |||
| 6556a53163 | |||
| 56079c29d8 | |||
| f111b02c21 | |||
| 56fd5f0a6a | |||
| e2e5f50162 | |||
| 8eac9054cd | |||
| a55c712349 | |||
| 0eda94a658 | |||
| c816696c55 | |||
| 8828b12c78 | |||
| 89acbecc5a | |||
| 336d99d4ba | |||
| 0731bf2c22 | |||
| 2827f69aef | |||
| 2e73ca5f6d | |||
| 1239a11cd7 | |||
| f8a2046d2d | |||
| fb2036c038 | |||
| f9b4c3c58c | |||
| 3ca2ec3be0 | |||
| f83bb55fd6 | |||
| e90e3dc1b4 | |||
| b774d8c477 | |||
| 2a18a47ffe | |||
| cb903a8cce | |||
| 030ed9ed3b | |||
| 5ce738bdfc | |||
| 97a4ae2279 | |||
| af66cd2380 | |||
| 3b519105bf | |||
| b70e526509 | |||
| a2781012be | |||
| 9e410029b1 | |||
| ee7213a687 | |||
| 45dc105dd0 | |||
| c779430e6f | |||
| 14b193418d | |||
| 56a4a606a6 | |||
| 7abd3a05ab | |||
| a05fc6cfe1 | |||
| bca4ce531a | |||
| 4b9c0de860 | |||
| 0f802db105 | |||
| 7606a4263c | |||
| f05e804795 | |||
| 36d05045cf | |||
| 05c30109ec | |||
| 6f26613f30 | |||
| 9ad82bdb57 | |||
| 7babf0953a | |||
| 09c6f41610 | |||
| eda835996e | |||
| 2396b78c4f | |||
| 6a498c301d | |||
| 40a44a939d | |||
| 7dfa93aca0 | |||
| 4e6028f217 | |||
| 57d85d05e9 | |||
| 78e44ac317 | |||
| 96a18c4f01 | |||
| 53d2c98461 | |||
| c82d1cf569 | |||
| a9024e37f1 | |||
| 4d0968a191 | |||
| 4df399bf54 | |||
| 4a620e5a2b | |||
| e989ad8f60 | |||
| 2f1df1b188 | |||
| 90da2b8eaa | |||
| 8d3bbbc56b | |||
| bdd6e61160 | |||
| f024a08dd2 | |||
| 15cea5fb4b | |||
| cb2eb0633f | |||
| ff3f80a447 | |||
| e5a57e03b4 | |||
| faab942e13 | |||
| 0f7ab8fed5 | |||
| 69377f3e67 | |||
| ec4efe5f0f | |||
| d92861284c | |||
| cd88892762 | |||
| e3ff5a2ab3 | |||
| d217e8b9ac | |||
| 669506e499 | |||
| 79b8b81220 | |||
| 6a54a8628b | |||
| 55027d91ef | |||
| ac0f73f7f0 | |||
| 0c4cfd3044 | |||
| 52a4011eb1 | |||
| bd992905f3 | |||
| 0bcc5b895a | |||
| 4c0f5095fa | |||
| 044320a2a9 | |||
| 1c940a9c7c | |||
| 4766f78411 | |||
| 0d1935bc8c | |||
| 8813923c86 | |||
| cda624c528 | |||
| 6e51302ba1 | |||
| 5c3923f8af | |||
| c2187a0823 | |||
| 0eee05d46a | |||
| 9978444120 | |||
| 25161044e9 | |||
| 6c225513ca | |||
| 9f96db1a0b | |||
| 8f588c25b5 | |||
| 8a60f4db66 | |||
| f1412d6eea | |||
| a2482419e0 | |||
| 979eedabb1 | |||
| 1727af5940 | |||
| 8411e75274 | |||
| 1f8036e170 | |||
| 37263c6933 | |||
| ab2fc54162 | |||
| 40302d83cf | |||
| 9ccaa2d1d6 | |||
| d72a5d3255 | |||
| f1491117de | |||
| 36823c1181 | |||
| 76f612f2cc | |||
| a887771565 | |||
| fb5ff43ed6 | |||
| cd9a9da7ba | |||
| 1b2978fb06 | |||
| 1a76f00b6a | |||
| 300a2436b1 | |||
| a6bb069f8a | |||
| dbb2ef8c50 | |||
| f7f7bed848 | |||
| c8e1d9ac59 | |||
| 6bc32ce379 | |||
| cee24b3d10 | |||
| 1fde8a9b8a | |||
| 7ad911fc1e | |||
| 6b82b49520 | |||
| 979293cb7a | |||
| 767217a143 | |||
| 3940f473ea | |||
| b37263f95c | |||
| 23ab9a68ea | |||
| 8c1db56d65 | |||
| 129ace6878 | |||
| 4559636e7d | |||
| 9f7eda47fe | |||
| 8ac446e228 | |||
| c9db3828a3 | |||
| 4b9b38eeb9 | |||
| ef35833dc0 | |||
| 102ce5185a | |||
| 672e180f43 | |||
| 488aaad452 | |||
| 1e4306005e | |||
| f93eec6d08 | |||
| 424857df6a | |||
| f6b2509b12 | |||
| c78a2ab380 | |||
| 33e1c14755 | |||
| fbef9bd005 | |||
| 275a156184 | |||
| ebacc622da | |||
| 9300f02a51 | |||
| ab82cfeea6 | |||
| 2cd0640a0b | |||
| cedf77d33c | |||
| 1e1c003751 | |||
| 0e90956147 | |||
| 75b39de99f | |||
| ab318136a8 | |||
| b4115aae95 | |||
| 10761e92ec | |||
| 65f0bb062c | |||
| cca3c8b1ee | |||
| 274f2d9874 | |||
| 2e4e32af45 | |||
| c06c486ab9 | |||
| d499433a34 | |||
| 1c98d05302 | |||
| 7af3b5ad7c | |||
| 6bbf55160b | |||
| cee89835e5 | |||
| 296e8995d6 | |||
| d2d824462b | |||
| 5dbaba3c5a | |||
| fe72ddb784 | |||
| 86d2716b8d | |||
| 3b82e27738 | |||
| fb9cb8ada9 | |||
| 8d24d8de23 | |||
| 3f0c14f2d9 | |||
| 7951172d68 | |||
| 476d3c5ac9 | |||
| 9068d409f5 | |||
| f2e12a4aab | |||
| 4a84941bd9 | |||
| 87c9273e43 | |||
| 72c55ec0e3 | |||
| f4b7005b92 | |||
| 443116a5a2 | |||
| 5932bbd53f | |||
| e5cd981508 | |||
| 66d0e811f7 | |||
| ab05b55b04 | |||
| 269f9d686e | |||
| 442374e53b | |||
| 85939e4fc4 | |||
| fa5b8aba7d | |||
| 0c3e3f77ce | |||
| 3c179f1488 | |||
| f2298e934c | |||
| cfc1ddc6b3 | |||
| 4ea3a3a6ab | |||
| 949c59fc1e | |||
| 1ade08783f | |||
| 1b0cf2bd42 | |||
| be0e21022e | |||
| c968b004e8 | |||
| ff472384fb | |||
| 538ad85124 | |||
| 979ff1760f | |||
| 6eb5ada623 | |||
| 88f96c7116 | |||
| de0a732616 | |||
| b72fd59b46 | |||
| 304dccc658 | |||
| 5e450a9bdb | |||
| 62fd1b900f | |||
| 424194d67b | |||
| 007e44c1d2 | |||
| e9c93a273c | |||
| 79aebe4fcc | |||
| 7a744c8953 | |||
| 322ecf4db8 | |||
| 1a9960dbf1 | |||
| 5e5202ff3a | |||
| 0b9635880e | |||
| 5c1f6046ed | |||
| f55ad5ae0c | |||
| 707712b2c0 | |||
| 6d76e9d524 | |||
| a59cadc537 | |||
| aebd66d059 | |||
| 0aaa9331dc | |||
| 43e01e5595 | |||
| fd1126be9b | |||
| c92f4fbd5c | |||
| 527b0f0980 | |||
| 91c30412de | |||
| 4ac281d65b | |||
| c58e799b1e | |||
| 71b3b193d1 | |||
| 516c9d9d79 | |||
| d580c4757e | |||
| 566202ce47 | |||
| 0850e765c8 | |||
| 92b4c5f3fc | |||
| c7ed383382 | |||
| b86bb6c15b | |||
| 245c31bdce | |||
| 33b271ddbe | |||
| 47eea5fc09 | |||
| 48bd4f1b31 | |||
| 2297536307 | |||
| 78ed0c8ed1 | |||
| 9ccb12f956 | |||
| d3b19c6cb0 | |||
| 22687605b2 | |||
| b9d4901b67 | |||
| d2d760e866 | |||
| b08562adaf | |||
| 292e982ab2 | |||
| 87c9d15751 | |||
| 3455dd1f32 | |||
| 9af12f35ca | |||
| b2ece2fab5 | |||
| cc34af04c2 | |||
| 09b5b1e0f3 | |||
| b547642e5e | |||
| 0b968e2c7b | |||
| f0cc49d06c | |||
| 39fe3d30d1 | |||
| 4fa4b41fae | |||
| 3d5a3ddbee | |||
| 9a0b6404cb | |||
| 7f80a411db | |||
| dc9e0d1b53 | |||
| 8d9ccdb08e | |||
| fba176864c | |||
| 725283c829 | |||
| 96fb846864 | |||
| fe190d8f43 | |||
| e5c2ba99c3 | |||
| c472050d00 | |||
| 1cf0b19ad0 | |||
| 5d0af2dad5 | |||
| 5cee71a342 | |||
| 81c5b6972a | |||
| 6a66b51b8e | |||
| cc10f11b37 | |||
| ce62bdcdc6 | |||
| 94d34faffd | |||
| eba31a93f3 |
14
.editorconfig
Normal file
14
.editorconfig
Normal file
@@ -0,0 +1,14 @@
|
||||
root = true
|
||||
|
||||
[*]
|
||||
# 缩进
|
||||
indent_style = space
|
||||
indent_size = 4
|
||||
# 行尾
|
||||
end_of_line = lf
|
||||
# 字符集
|
||||
charset = utf-8
|
||||
# 删除行尾空格
|
||||
trim_trailing_whitespace = true
|
||||
# 文件最后插入空行
|
||||
insert_final_newline = true
|
||||
2
.gitignore
vendored
2
.gitignore
vendored
@@ -3,6 +3,8 @@ target/
|
||||
!**/src/main/**/target/
|
||||
!**/src/test/**/target/
|
||||
|
||||
.flattened-pom.xml
|
||||
|
||||
### IntelliJ IDEA ###
|
||||
.idea
|
||||
*.iws
|
||||
|
||||
64
NOTICE
Normal file
64
NOTICE
Normal file
@@ -0,0 +1,64 @@
|
||||
Plusone Commons
|
||||
Copyright 2022-present Zhou Xingyi
|
||||
|
||||
This product includes software developed by
|
||||
Zhou Xingyi (周兴毅) (https://gitea.zhouxy.xyz/plusone).
|
||||
|
||||
This product is licensed to you under the Apache License, Version 2.0
|
||||
(the "License"). You may not use this product except in compliance with
|
||||
the License.
|
||||
|
||||
===========================================================================
|
||||
Third-party components and their licenses:
|
||||
===========================================================================
|
||||
|
||||
This software contains code from the following third-party projects:
|
||||
|
||||
1. Apache Seata
|
||||
- Component: IdWorker class implementation
|
||||
- Source: org.apache.seata.common.util.IdWorker
|
||||
- Origin: https://github.com/apache/incubator-seata/blob/2.x/common/src/main/java/org/apache/seata/common/util/IdWorker.java
|
||||
- License: Apache License 2.0
|
||||
- License URL: https://www.apache.org/licenses/LICENSE-2.0.txt
|
||||
- Copyright: The Apache Software Foundation
|
||||
|
||||
===========================================================================
|
||||
Dependencies and their licenses:
|
||||
===========================================================================
|
||||
|
||||
The following dependencies are used in this project:
|
||||
|
||||
Required Dependencies:
|
||||
- guava: Apache License 2.0 (https://www.apache.org/licenses/LICENSE-2.0.txt)
|
||||
|
||||
Optional Dependencies:
|
||||
- gson: Apache License 2.0 (https://www.apache.org/licenses/LICENSE-2.0.txt)
|
||||
- jsr305: Apache License 2.0 (https://www.apache.org/licenses/LICENSE-2.0.txt)
|
||||
- joda-time: Apache License 2.0 (https://www.apache.org/licenses/LICENSE-2.0.txt)
|
||||
|
||||
Test Dependencies:
|
||||
- commons-lang3: Apache License 2.0 (https://www.apache.org/licenses/LICENSE-2.0.txt)
|
||||
- Logback: Eclipse Public License 1.0 (https://www.eclipse.org/org/documents/epl-1.0/EPL-1.0.txt) / LGPL 2.1 (https://www.gnu.org/licenses/old-licenses/lgpl-2.1.html)
|
||||
- Slf4j: MIT License (https://mit-license.org/)
|
||||
- JUnit: Eclipse Public License 2.0 (https://www.eclipse.org/org/documents/epl-2.0/EPL-2.0.txt)
|
||||
- lombok: MIT License (https://mit-license.org/)
|
||||
- hutool: MulanPSL-2.0 (http://license.coscl.org.cn/MulanPSL2)
|
||||
- MyBatis: Apache License 2.0 (https://www.apache.org/licenses/LICENSE-2.0.txt)
|
||||
- h2: MPL 2.0 (https://www.mozilla.org/en-US/MPL/2.0/) / EPL 1.0 (https://www.eclipse.org/org/documents/epl-1.0/EPL-1.0.txt)
|
||||
- Jackson: Apache License 2.0 (https://www.apache.org/licenses/LICENSE-2.0.txt)
|
||||
|
||||
===========================================================================
|
||||
Apache License 2.0 Notice:
|
||||
===========================================================================
|
||||
|
||||
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
|
||||
|
||||
http://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.
|
||||
38
README.md
Normal file
38
README.md
Normal file
@@ -0,0 +1,38 @@
|
||||
# Plusone Commons
|
||||
## 1. 简介
|
||||
Plusone Commons 是一个 Java 工具类库,提供了一系列实用的类和方法,用于简化开发。
|
||||
|
||||
一开始是为了补充日常开发中,guava 认为不需要,而我又用得上的工具,所以需要结合 guava 使用。后面也包含了一些从日常工作与学习中抽离出来可以通用的东西。
|
||||
|
||||
Plusone Commons 的工具类不追求“大而全”,而是只提供相对需要的部分功能。
|
||||
|
||||
> 未来一些不够“通用”的组件会迁移到更合适的模块中。
|
||||
|
||||
## 2. 安装
|
||||
项目基于 OpenJDK 8 和 maven 构建。
|
||||
|
||||
使用 Maven 添加依赖:
|
||||
```xml
|
||||
<dependency>
|
||||
<groupId>xyz.zhouxy.plusone</groupId>
|
||||
<artifactId>plusone-commons</artifactId>
|
||||
<version>${plusone-commons.version}</version>
|
||||
</dependency>
|
||||
```
|
||||
|
||||
## 3. 功能
|
||||
详细功能说明请查阅文档:
|
||||
|
||||
+ [文档地址](/plusone-commons/docs)
|
||||
|
||||
## 4. 代码仓库
|
||||
项目仓库一共建了三个:
|
||||
|
||||
+ [GitHub](https://github.com/ZhouXY108/plusone-commons)
|
||||
+ [gitee](https://gitee.com/zhouxy108/plusone-commons)
|
||||
+ [自建 Gitea 仓库](https://gitea.zhouxy.xyz/plusone/plusone-commons)
|
||||
|
||||
欢迎在 GitHub 和 gitee 上通过 issue 反馈使用过程中发现的问题和建议,也接受善意的 PR。
|
||||
|
||||
## 5. 许可
|
||||
项目使用 [Apache License 2.0](https://www.apache.org/licenses/LICENSE-2.0) 开源,相关声明请参阅 `NOTICE` 文件。
|
||||
52
cspell.json
Normal file
52
cspell.json
Normal file
@@ -0,0 +1,52 @@
|
||||
{
|
||||
"version": "0.2",
|
||||
"ignorePaths": [
|
||||
"*/src/test"
|
||||
],
|
||||
"dictionaryDefinitions": [],
|
||||
"dictionaries": [],
|
||||
"words": [
|
||||
"aliyun",
|
||||
"baomidou",
|
||||
"Batis",
|
||||
"buildmetadata",
|
||||
"Consolas",
|
||||
"cspell",
|
||||
"databind",
|
||||
"datasource",
|
||||
"dbutils",
|
||||
"fasterxml",
|
||||
"findbugs",
|
||||
"gson",
|
||||
"Hikari",
|
||||
"hutool",
|
||||
"jasypt",
|
||||
"jbcrypt",
|
||||
"Jdbc",
|
||||
"joda",
|
||||
"logback",
|
||||
"mapstruct",
|
||||
"mindrot",
|
||||
"Multimap",
|
||||
"Multiset",
|
||||
"mybatis",
|
||||
"Nonnull",
|
||||
"NOSONAR",
|
||||
"okhttp",
|
||||
"okio",
|
||||
"ooxml",
|
||||
"overriden",
|
||||
"plusone",
|
||||
"println",
|
||||
"projectlombok",
|
||||
"querydsl",
|
||||
"regexs",
|
||||
"Seata",
|
||||
"sonarlint",
|
||||
"springframework",
|
||||
"TWEPOCH",
|
||||
"Validatable",
|
||||
"zhouxy"
|
||||
],
|
||||
"import": []
|
||||
}
|
||||
8
plusone-commons/docs/1_annotation.md
Normal file
8
plusone-commons/docs/1_annotation.md
Normal file
@@ -0,0 +1,8 @@
|
||||
## 1. 注解
|
||||
|注解|说明|
|
||||
|--|--|
|
||||
| **StaticFactoryMethod** | **标识静态工厂方法**。 *《Effective Java》* 的 **Item1** 建议考虑用静态工厂方法替换构造器, 因而考虑有一个注解可以标记一下静态工厂方法,以和其它方法进行区分。|
|
||||
| **ReaderMethod** / **WriterMethod** | **分别标识读方法(如 getter)或写方法(如 setter)**。<br>*最早是写了一个集合类,为了方便判断使用读写锁时,哪些情况下使用读锁,哪些情况下使用写锁。*|
|
||||
| **UnsupportedOperation** | **标识该方法不被支持或没有实现**,将抛出 `UnsupportedOperationException`。 为了方便在使用时,不需要点进源码,就能知道该方法没有实现。|
|
||||
| **Virtual** | Java 非 final 的实例方法,对应 C++/C# 中的虚方法,允许被子类覆写。 **Virtual 注解旨在设计父类时,强调该方法父类虽然有默认实现,但子类可以根据自己的需要覆写**。|
|
||||
| **ValueObject** | 标记一个类,表示其作为**值对象**,区别于 Entity。|
|
||||
39
plusone-commons/docs/2_collection.md
Normal file
39
plusone-commons/docs/2_collection.md
Normal file
@@ -0,0 +1,39 @@
|
||||
## 2. 集合
|
||||
|
||||
### 2.1. CollectionTools
|
||||
|
||||
简单的集合工具类,包含判空等常用方法。
|
||||
|
||||
### 2.2. MapModifier
|
||||
|
||||
Map 修改器。封装一系列对 Map 数据的修改操作,修改 Map 的数据。可以用于 Map 的数据初始化等操作。
|
||||
|
||||
```java
|
||||
// MapModifier
|
||||
MapModifier<String, Object> modifier = new MapModifier<String, Object>()
|
||||
.putAll(commonProperties)
|
||||
.put("username", "Ben")
|
||||
.put("accountStatus", LOCKED);
|
||||
|
||||
// 从 Supplier 中获取 Map,并修改数据
|
||||
Map<String, Object> map = modifier.getAndModify(HashMap::new);
|
||||
|
||||
// 可以灵活使用不同 Map 类型的不同构造器
|
||||
Map<String, Object> map = modifier.getAndModify(() -> new HashMap<>(8));
|
||||
Map<String, Object> map = modifier.getAndModify(() -> new HashMap<>(anotherMap));
|
||||
Map<String, Object> map = modifier.getAndModify(TreeMap::new);
|
||||
Map<String, Object> map = modifier.getAndModify(ConcurrentHashMap::new);
|
||||
|
||||
// 修改已有的 Map
|
||||
modifier.modify(map);
|
||||
|
||||
// 创建一个有初始化数据的不可变的 Map
|
||||
Map<String, Object> map = modifier.getUnmodifiableMap();
|
||||
|
||||
// 链式调用创建并初始化数据
|
||||
Map<String, Object> map = new MapModifier<String, Object>()
|
||||
.putAll(commonProperties)
|
||||
.put("username", "Ben")
|
||||
.put("accountStatus", LOCKED)
|
||||
.getAndModify(HashMap::new);
|
||||
```
|
||||
118
plusone-commons/docs/3_exception.md
Normal file
118
plusone-commons/docs/3_exception.md
Normal file
@@ -0,0 +1,118 @@
|
||||
## 3. 异常
|
||||
|
||||
### 3.1. 业务异常
|
||||
|异常|描述|
|
||||
|---|---|
|
||||
|`BizException`|**业务异常**<br>*用户可继承 `BizException` 自定义业务异常。*|
|
||||
|» `RequestParamsException`|**用户请求参数错误**|
|
||||
|» » `InvalidInputException`|**用户输入内容非法**<br>00 - **DEFAULT** (用户输入内容非法)<br>01 - **CONTAINS_ILLEGAL_AND_MALICIOUS_LINKS** (包含非法恶意跳转链接)<br>02 - **CONTAINS_ILLEGAL_WORDS** (包含违禁敏感词)<br>03 - **PICTURE_CONTAINS_ILLEGAL_INFORMATION** (图片包含违禁信息)<br>04 - **INFRINGE_COPYRIGHT** (文件侵犯版权)|
|
||||
|
||||
### 3.2. 系统异常
|
||||
|异常|描述|
|
||||
|---|---|
|
||||
|`SysException`|**系统异常**(表示技术异常)<br>*用户可继承 `SysException` 自定义系统异常。*|
|
||||
|» `DataOperationResultException`|**数据操作的结果不符合预期**|
|
||||
|» `NoAvailableMacFoundException`|**无法找到可访问的 Mac 地址**|
|
||||
|
||||
### 3.3. 其它异常
|
||||
|异常|描述|
|
||||
|---|---|
|
||||
|`DataNotExistsException`|**数据不存在异常**|
|
||||
|`ParsingFailureException`|**数据解析异常**<br>00 - **DEFAULT** (解析失败)<br>10 - **NUMBER_PARSING_FAILURE** (数字转换失败)<br>20 - **DATE_TIME_PARSING_FAILURE** (时间解析失败)<br>30 - **JSON_PARSING_FAILURE** (JSON 解析失败)<br>40 - **XML_PARSING_FAILURE** (XML 解析失败)|
|
||||
|
||||
### 3.4. 多类型异常
|
||||
|
||||
异常在不同场景下被抛出,可以用不同的枚举值,表示不同的场景类型。
|
||||
|
||||
异常实现 `IMultiTypesException` 的 `getType` 方法,返回对应的场景类型。
|
||||
|
||||
枚举实现 `IExceptionType` 接口,表示不同的异常场景。也可以实现 `IExceptionFactory`,用于创建对应场景的异常。
|
||||
|
||||
```java
|
||||
public final class LoginException
|
||||
extends RuntimeException
|
||||
implements IMultiTypesException<LoginException.Type> {
|
||||
private static final long serialVersionUID = 881293090625085616L;
|
||||
private final Type type;
|
||||
private LoginException(@Nonnull Type type, @Nonnull String message) {
|
||||
super(message);
|
||||
this.type = type;
|
||||
}
|
||||
|
||||
private LoginException(@Nonnull Type type, @Nonnull Throwable cause) {
|
||||
super(cause);
|
||||
this.type = type;
|
||||
}
|
||||
|
||||
private LoginException(@Nonnull Type type,
|
||||
@Nonnull String message,
|
||||
@Nonnull Throwable cause) {
|
||||
super(message, cause);
|
||||
this.type = type;
|
||||
}
|
||||
|
||||
@Override
|
||||
public @Nonnull Type getType() {
|
||||
return this.type;
|
||||
}
|
||||
|
||||
// ...
|
||||
|
||||
public enum Type implements IExceptionType<String>, IExceptionFactory<LoginException> {
|
||||
DEFAULT("00", "当前会话未登录"),
|
||||
NOT_TOKEN("10", "未提供token"),
|
||||
INVALID_TOKEN("20", "token无效"),
|
||||
TOKEN_TIMEOUT("30", "token已过期"),
|
||||
BE_REPLACED("40", "token已被顶下线"),
|
||||
KICK_OUT("50", "token已被踢下线"),
|
||||
;
|
||||
|
||||
@Nonnull
|
||||
private final String code;
|
||||
@Nonnull
|
||||
private final String defaultMessage;
|
||||
|
||||
Type(@Nonnull String code, @Nonnull String defaultMessage) {
|
||||
this.code = code;
|
||||
this.defaultMessage = defaultMessage;
|
||||
}
|
||||
|
||||
@Override
|
||||
public @Nonnull String getCode() {
|
||||
return code;
|
||||
}
|
||||
|
||||
@Override
|
||||
public @Nonnull String getDefaultMessage() {
|
||||
return defaultMessage;
|
||||
}
|
||||
|
||||
@Override
|
||||
public @Nonnull LoginException create() {
|
||||
return new LoginException(this, this.defaultMessage);
|
||||
}
|
||||
|
||||
@Override
|
||||
public @Nonnull LoginException create(@Nonnull String message) {
|
||||
return new LoginException(this, message);
|
||||
}
|
||||
|
||||
@Override
|
||||
public @Nonnull LoginException create(@Nonnull Throwable cause) {
|
||||
return new LoginException(this, cause);
|
||||
}
|
||||
|
||||
@Override
|
||||
public @Nonnull LoginException create(@Nonnull String message, @Nonnull Throwable cause) {
|
||||
return new LoginException(this, message, cause);
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
使用时,可以使用这种方式创建并抛出异常:
|
||||
```java
|
||||
throw LoginException.Type.TOKEN_TIMEOUT.create();
|
||||
```
|
||||
|
||||
|
||||
22
plusone-commons/docs/4_function.md
Normal file
22
plusone-commons/docs/4_function.md
Normal file
@@ -0,0 +1,22 @@
|
||||
## 4. - 函数式编程
|
||||
|
||||
### 4.1. PredicateTools
|
||||
|
||||
`PredicateTools` 用于 `Predicate` 的相关操作。
|
||||
|
||||
### 4.2. Functional interfaces
|
||||
|
||||
补充可能用得上的函数式接口:
|
||||
|
||||
| Group | FunctionalInterface | method |
|
||||
| --- | --- | --- |
|
||||
| UnaryOperator | **BoolUnaryOperator** | boolean applyAsBool (boolean) |
|
||||
| UnaryOperator | **CharUnaryOperator** | char applyAsChar(char) |
|
||||
| Throwing | **Executable** | void execute() throws E |
|
||||
| Throwing | **ThrowingConsumer** | void accept(T) throws E |
|
||||
| Throwing | **ThrowingFunction** | R apply(T) throws E |
|
||||
| Throwing | **ThrowingPredicate** | boolean test(T) throws E |
|
||||
| Throwing | **ThrowingSupplier** | T get() throws E |
|
||||
| Optional | **OptionalSupplier** | Optional<T> get() throws E |
|
||||
| Optional | **ToOptionalBiFunction** | Optional<R> apply(T,U) |
|
||||
| Optional | **ToOptionalFunction** | Optional<R> apply(T) |
|
||||
100
plusone-commons/docs/5_model.md
Normal file
100
plusone-commons/docs/5_model.md
Normal file
@@ -0,0 +1,100 @@
|
||||
## 5. 数据模型
|
||||
|
||||
### 5.1. 业务模型
|
||||
|
||||
| | 类型 | 描述 |
|
||||
| --- | --- | --- |
|
||||
| 接口 | `IDCardNumber` | 身份证号 |
|
||||
| 值对象 | » `Chinese2ndGenIDCardNumber` | 中国二代居民身份证号 |
|
||||
| 值对象 | `SemVer` | 语义化版本号 |
|
||||
| 值对象(枚举)| `Gender` | 性别 |
|
||||
| ~~值对象(抽象类)~~| ~~`ValidatableStringRecord`~~ | ~~带校验的字符串值对象~~ |
|
||||
|
||||
### 5.2. 数据传输对象
|
||||
|
||||
#### 5.2.1. 分页查询
|
||||
|
||||
`PagingAndSortingQueryParams` (分页排序查询参数)
|
||||
|
||||
`PagingAndSortingQueryParams` 包含三个主要的属性:
|
||||
- **size** - 每页显示的记录数
|
||||
- **pageNum** - 当前页码
|
||||
- **orderBy** - 排序条件
|
||||
|
||||
分页必须伴随着排序,不然可能出现同一个对象重复出现在不同页,有的对象不被查询到的情况。
|
||||
|
||||
其中 `orderBy` 是一个 `List<String>`,可以指定多个排序条件,每个排序条件是一个字符串, 格式为“**属性名-ASC**”或“**属性名-DESC**”,分别表示升序和降序。
|
||||
|
||||
例如当 `orderBy` 的值为 `["name-ASC","age-DESC"]`,意味着要按 `name` 进行升序排列,`name` 相同的情况下则按 `age` 进行降序排列。
|
||||
|
||||
用户可继承 `PagingAndSortingQueryParams` 构建自己的分页查询入参,子类需在构造器中调用 `PagingAndSortingQueryParams` 的构造器,传入一个 `PagingParamsBuilder` 用于构建分页参数。同一场景下,复用一个 {@link PagingParamsBuilder} 实例即可。
|
||||
|
||||
构建 `PagingParamsBuilder` 时,需传入一个 `Map` 作为可排序字段的白名单,`key` 是供前端指定用于排序的**属性名**,`value` 是对应数据库中的**字段名**,只有在白名单中指定的属性名才允许作为排序条件。
|
||||
|
||||
```java
|
||||
@ToString(callSuper = true)
|
||||
class AccountQueryParams extends PagingAndSortingQueryParams {
|
||||
|
||||
private static final Map<String, String> PROPERTY_COLUMN_MAP = ImmutableMap.<String, String>builder()
|
||||
.put("id", "id")
|
||||
.put("username", "username")
|
||||
.build();
|
||||
|
||||
private static final PagingParamsBuilder PAGING_PARAMS_BUILDER = PagingAndSortingQueryParams
|
||||
.pagingParamsBuilder(20, 100, PROPERTY_COLUMN_MAP);
|
||||
|
||||
public AccountQueryParams() {
|
||||
super(PAGING_PARAMS_BUILDER);
|
||||
}
|
||||
|
||||
private @Getter @Setter Long id;
|
||||
private @Getter @Setter String username;
|
||||
private @Getter @Setter String email;
|
||||
private @Getter @Setter Integer status;
|
||||
}
|
||||
```
|
||||
|
||||
使用时调用 `PagingAndSortingQueryParams#buildPagingParams()` 方法获取分页参数 `PagingParams`。分页结果可以存放到 `PageResult` 中,作为出参。
|
||||
|
||||
```java
|
||||
public PageResult<AccountVO> queryPage(AccountQueryParams params) {
|
||||
// 获取分页参数
|
||||
PagingParams pagingParams = params.buildPagingParams();
|
||||
// 从 params 获取字段查询条件,从 pagingParams 获取分页条件,查询一页数据
|
||||
List<AccountVO> list = accountQueries.queryAccountList(params, pagingParams);
|
||||
// 查询总记录数
|
||||
long count = accountQueries.countAccount(params);
|
||||
// 返回分页结果
|
||||
return PageResult.of(list, count);
|
||||
}
|
||||
```
|
||||
|
||||
#### 5.2.2. UnifiedResponse
|
||||
|
||||
`UnifiedResponse` 对返回给前端的数据进行封装,包含 `code`、`message`、`data`。
|
||||
|
||||
`UnifiedResponses` 是 `UnifiedResponse` 的工厂类。用于快速构建 `UnifiedResponse` 对象,默认的成功代码为 `2000000`。
|
||||
|
||||
用户可以继承 `UnifiedResponses` 实现自己的工厂类,自定义 SUCCESS_CODE 和 DEFAULT_SUCCESS_MSG,以及工厂方法。如下所示:
|
||||
```java
|
||||
// 自定义工厂类
|
||||
public static class CustomUnifiedResponses extends UnifiedResponses {
|
||||
public static final String SUCCESS_CODE = "000";
|
||||
public static final String DEFAULT_SUCCESS_MSG = "成功";
|
||||
public static <T> UnifiedResponse<T> success() {
|
||||
return of(SUCCESS_CODE, DEFAULT_SUCCESS_MSG);
|
||||
}
|
||||
public static <T> UnifiedResponse<T> success(@Nullable String message) {
|
||||
return of(SUCCESS_CODE, message);
|
||||
}
|
||||
public static <T> UnifiedResponse<T> success(@Nullable String message, @Nullable T data) {
|
||||
return of(SUCCESS_CODE, message, data);
|
||||
}
|
||||
private CustomUnifiedResponses() {
|
||||
super();
|
||||
}
|
||||
}
|
||||
// 使用自定义工厂类
|
||||
CustomUnifiedResponses.success("查询成功", userList); // 状态码为 000
|
||||
```
|
||||
> 见 [issue#22 @Gitea](http://gitea.zhouxy.xyz/plusone/plusone-commons/issues/22)
|
||||
24
plusone-commons/docs/6_time.md
Normal file
24
plusone-commons/docs/6_time.md
Normal file
@@ -0,0 +1,24 @@
|
||||
## 6. 时间 API
|
||||
|
||||
### 6.1. 季度
|
||||
|
||||
模仿 JDK 的 `java.time.Month` 和 `java.time.YearMonth`, 实现 `xyz.zhouxy.plusone.commons.time.Quarter`、`xyz.zhouxy.plusone.commons.timeYearQuarter`,对季度进行建模。
|
||||
|
||||
*这两个类的代码修改后,也提交给了 **hutool**。见 gitee
|
||||
上的 [pr#1324](https://gitee.com/chinabugotech/hutool/pulls/1324)*。
|
||||
|
||||
### 6.2. DateTimeTools
|
||||
|
||||
`xyz.zhouxy.plusone.commons.util.DateTimeTools` 提供了包含 Java 旧的时间 API 和 `java.time` API 在内的日期时间的常用操作。
|
||||
|
||||
### 6.3. JodaTimeTools
|
||||
|
||||
`xyz.zhouxy.plusone.commons.util.JodaTimeTools` 提供了 JodaTime 和 `java.time` API 相互转换的工具方法:
|
||||
- `toJodaInstant`
|
||||
- `toJavaInstant`
|
||||
- `toJodaDateTime`
|
||||
- `toZonedDateTime`
|
||||
- `toJodaLocalDateTime`
|
||||
- `toJavaLocalDateTime`
|
||||
- `toJavaZone`
|
||||
- `toJodaZone`
|
||||
275
plusone-commons/docs/7_tools.md
Normal file
275
plusone-commons/docs/7_tools.md
Normal file
@@ -0,0 +1,275 @@
|
||||
## 7. 工具类
|
||||
|
||||
### 7.1. 数组工具(ArrayTools)
|
||||
|
||||
| 方法 | 描述 |
|
||||
| --- | --- |
|
||||
| `isEmpty` | 判断数组是否为空 |
|
||||
| `isNotEmpty` | 判断数组是否不为空 |
|
||||
| `isAllElementsNotNull` | 判断数组中所有元素是否不为空 |
|
||||
| `concat` | 拼接数组 |
|
||||
| `repeat` | 重复数组中的元素 |
|
||||
| `fill` | 填充数组 |
|
||||
| `indexOf` | 获取元素在数组中的索引 |
|
||||
| `lastIndexOf` | 获取元素最后出现在数组中的索引 |
|
||||
| `contains` | 判断数组中是否包含某个元素 |
|
||||
|
||||
### 7.2. 断言工具(AssertTools)
|
||||
|
||||
`AssertTools` 不封装过多判断逻辑,鼓励充分使用项目中的工具类对数据进行判断:
|
||||
|
||||
```java
|
||||
AssertTools.checkArgument(StringUtils.hasText(str), "The argument cannot be blank.");
|
||||
AssertTools.checkState(ArrayUtils.isNotEmpty(result), "The result cannot be empty.");
|
||||
AssertTools.checkCondition(!CollectionUtils.isEmpty(roles),
|
||||
() -> new InvalidInputException("The roles cannot be empty."));
|
||||
AssertTools.checkCondition(RegexTools.matches(email, PatternConsts.EMAIL),
|
||||
"must be a well-formed email address");
|
||||
```
|
||||
|
||||
### 7.3. 枚举工具
|
||||
|
||||
#### ~~7.3.1 枚举类(Enumeration)(已废弃)~~
|
||||
|
||||
~~`Enumeration` 的实现来自于 .net 社区。因为 C# 本身的枚举不带行为,所以继承自 `Enumeration` 类,以实现带行为的枚举常量。~~
|
||||
|
||||
**~~但 Java 的枚举可以带行为,故大多数情况下不需要这种设计。~~**
|
||||
|
||||
#### 7.3.2 Enum 工具类(EnumTools)
|
||||
|
||||
用于枚举的 ordinal 和枚举值的转换等操作。
|
||||
|
||||
由于不推荐使用枚举的 ordinal,**故大多数方法已废弃**。更推荐的实现是枚举实现 `IWithCode` 之类的接口,在枚举中提供枚举值和枚举码的转换。
|
||||
|
||||
### 7.4. ID 生成器
|
||||
|
||||
#### 7.4.1. ID 生成器(IdGenerator)
|
||||
|
||||
- 提供了 `UUID` 相关的方法
|
||||
| 方法 | 描述 |
|
||||
| --- | --- |
|
||||
| newUuid | 获取新的 `UUID` |
|
||||
| uuidString | 获取新的 UUID 字符串 |
|
||||
| simpleUuidString | 获取新的 UUID 字符串(无连接符) |
|
||||
| toSimpleString | 将 `UUID` 转换为无连接符的字符串 |
|
||||
- 使用 `IdWorker` *(来自 **Seata** 的雪花算法的变种)* 生成分布式唯一 ID
|
||||
|
||||
#### 7.4.2. IdWorker
|
||||
|
||||
来自 [Apache Seata](https://seata.apache.org/) 的 [`org.apache.seata.common.util.IdWorker`](https://github.com/apache/incubator-seata/blob/2.x/common/src/main/java/org/apache/seata/common/util/IdWorker.java),是雪花算法的变种。
|
||||
|
||||
详细介绍参考以下文章:
|
||||
- [Seata基于改良版雪花算法的分布式UUID生成器分析](https://seata.apache.org/zh-cn/blog/seata-analysis-UUID-generator)
|
||||
- [关于新版雪花算法的答疑](https://seata.apache.org/zh-cn/blog/seata-snowflake-explain)
|
||||
- [在开源项目中看到一个改良版的雪花算法,现在它是你的了。](https://juejin.cn/post/7264387737276203065)
|
||||
- [关于若干读者,阅读“改良版雪花算法”后提出的几个共性问题的回复。](https://juejin.cn/post/7265516484029743138)
|
||||
|
||||
#### 7.4.3. SnowflakeIdGenerator
|
||||
|
||||
`SnowflakeIdGenerator` 是原版的雪花算法的实现
|
||||
|
||||
### 7.5. 树构建器(TreeBuilder)
|
||||
|
||||
`TreeBuilder` 是一个树构建器,用于将列表数据构建为树结构。
|
||||
|
||||
`TreeBuilder` 构造器的入参:
|
||||
- **identityGetter**: 从节点中获取其标识的逻辑
|
||||
- **parentIdentityGetter**: 获取父节点标识的逻辑
|
||||
- **addChild**: 添加子节点的逻辑
|
||||
- **defaultComparator**: 默认的 Comparator,用于排序
|
||||
|
||||
> **注意:`TreeBuilder` 的 `buildTree` 方法,会直接更改列表中的节点。设计初衷是将查询到的列表,构建成为树结构之后直接返回给前端,如果需要,请在调用之前做深拷贝,然后再将深拷贝的结果作为入参传入。**
|
||||
|
||||
以下示例演示 `TreeBuilder` 的使用:
|
||||
|
||||
#### 7.5.1. 处理相对复杂的 entity
|
||||
|
||||
假设有如下的实体类:
|
||||
```java
|
||||
@AllArgsConstructor(access = AccessLevel.PROTECTED)
|
||||
@EqualsAndHashCode
|
||||
@ToString
|
||||
class Menu implements Serializable {
|
||||
protected final @Getter String parentMenuCode;
|
||||
protected final @Getter String menuCode;
|
||||
protected final @Getter String title;
|
||||
protected final @Getter int orderNum;
|
||||
|
||||
private static final long serialVersionUID = 20240917181424L;
|
||||
}
|
||||
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
@ToString(callSuper = true)
|
||||
class MenuItem extends Menu {
|
||||
|
||||
private final @Getter String url;
|
||||
|
||||
private MenuItem(String parentMenuCode, String menuCode, String title, String url, int orderNum) {
|
||||
super(parentMenuCode, menuCode, title, orderNum);
|
||||
this.url = url;
|
||||
}
|
||||
|
||||
static MenuItem of(String parentMenuCode, String menuCode, String title, String url, int orderNum) {
|
||||
return new MenuItem(parentMenuCode, menuCode, title, url, orderNum);
|
||||
}
|
||||
|
||||
static MenuItem of(String menuCode, String title, String url, int orderNum) {
|
||||
return new MenuItem(null, menuCode, title, url, orderNum);
|
||||
}
|
||||
|
||||
private static final long serialVersionUID = 20240917181910L;
|
||||
}
|
||||
|
||||
@ToString(callSuper = true)
|
||||
class MenuList extends Menu {
|
||||
|
||||
private List<Menu> children;
|
||||
|
||||
private MenuList(String parentMenuCode, String menuCode, String title, int orderNum) {
|
||||
super(parentMenuCode, menuCode, title, orderNum);
|
||||
}
|
||||
|
||||
static MenuList of(String parentMenuCode, String menuCode, String title, int orderNum) {
|
||||
return new MenuList(parentMenuCode, menuCode, title, orderNum);
|
||||
}
|
||||
|
||||
static MenuList of(String menuCode, String title, int orderNum) {
|
||||
return new MenuList(null, menuCode, title, orderNum);
|
||||
}
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
static MenuList of(String menuCode, String title, Iterable<Menu> children, int orderNum) {
|
||||
return of(null, menuCode, title, children, orderNum);
|
||||
}
|
||||
|
||||
static MenuList of(String parentMenuCode, String menuCode, String title, Iterable<Menu> children,
|
||||
int orderNum) {
|
||||
final MenuList instance = of(parentMenuCode, menuCode, title, orderNum);
|
||||
children.forEach(instance::addChild);
|
||||
return instance;
|
||||
}
|
||||
|
||||
public void addChild(Menu child) {
|
||||
if (this.children == null) {
|
||||
this.children = Lists.newArrayList();
|
||||
}
|
||||
this.children.add(child);
|
||||
}
|
||||
|
||||
private static final long serialVersionUID = 20240917181917L;
|
||||
}
|
||||
```
|
||||
|
||||
其中,`Menu` 表示菜单节点,其子类 `MenuItem` 表示菜单项,在树中作为叶子节点,另一子类 `MenuList` 表示菜单列表,其子菜单放在 `children` 字段中。`MenuList` 提供了 `addChild` 方法用于将子菜单添加到 `children` 中。
|
||||
|
||||
使用以下方式构建并使用 `TreeBuilder`:
|
||||
```java
|
||||
// 创建 TreeBuilder
|
||||
TreeBuilder<Menu, MenuList, String> treeBuilder = new TreeBuilder<>(
|
||||
// getMenuCode 方法获取节点标识
|
||||
Menu::getMenuCode,
|
||||
// getParentMenuCode 方法获取父节点标识,如果父节点不存在,返回 Optional.empty()
|
||||
menu -> Optional.ofNullable(menu.getParentMenuCode()),
|
||||
// addChild 方法用于将子节点添加到父节点的 children 中
|
||||
MenuList::addChild,
|
||||
// 默认的 Comparator,使用 orderNum 进行排序
|
||||
Comparator.comparing(Menu::getOrderNum));
|
||||
|
||||
// 需要的话进行深拷贝
|
||||
List<Menu> clonedMenus = menus.stream().map(ObjectUtil::clone).collect(Collectors.toList());
|
||||
// 按照创建时设置的逻辑,构建树结构
|
||||
List<Menu> result = treeBuilder.buildTree(clonedMenus);
|
||||
```
|
||||
|
||||
#### 7.5.2. 处理 POJO
|
||||
|
||||
`TreeBuilder` 也可以处理 POJO,只需要自定义 `TreeBuilder` 所需的入参即可。
|
||||
|
||||
```java
|
||||
// POJO
|
||||
@Data
|
||||
class Menu implements Serializable {
|
||||
private final String parentMenuCode;
|
||||
private final String menuCode;
|
||||
private final String title;
|
||||
private final int orderNum;
|
||||
|
||||
private final String url;
|
||||
private List<Menu> children;
|
||||
|
||||
private static final long serialVersionUID = 1298482252210272617L;
|
||||
}
|
||||
```
|
||||
|
||||
使用以下方式构建并使用 `TreeBuilder`:
|
||||
```java
|
||||
// 创建 TreeBuilder
|
||||
TreeBuilder<Menu, MenuList, String> treeBuilder = new TreeBuilder<>(
|
||||
// getMenuCode 方法获取节点标识
|
||||
Menu::getMenuCode,
|
||||
// getParentMenuCode 方法获取父节点标识,如果父节点不存在,返回 Optional.empty()
|
||||
menu -> Optional.ofNullable(menu.getParentMenuCode()),
|
||||
// 自定义 addChild 逻辑
|
||||
(menuList, child) -> {
|
||||
List<Menu> children = menuList.getChildren();
|
||||
if (children == null) {
|
||||
children = Lists.newArrayList();
|
||||
menuList.setChildren(children);
|
||||
}
|
||||
children.add(child);
|
||||
},
|
||||
// 默认的 Comparator,使用 orderNum 进行排序
|
||||
Comparator.comparing(Menu::getOrderNum));
|
||||
|
||||
// 按照创建时设置的逻辑,构建树结构
|
||||
List<Menu> result = treeBuilder.buildTree(clonedMenus);
|
||||
```
|
||||
|
||||
### 7.6. Ref
|
||||
`Ref` 包装了一个值,表示对该值的应用。
|
||||
|
||||
C# 中允许通过 ref 参数修饰符,将值返回给调用端:
|
||||
```csharp
|
||||
void Method(ref int refArgument)
|
||||
{
|
||||
refArgument = refArgument + 44;
|
||||
}
|
||||
|
||||
int number = 1;
|
||||
Method(ref number);
|
||||
Console.WriteLine(number); // Output: 45
|
||||
```
|
||||
`Ref` 使 Java 可以达到类似的效果,如:
|
||||
```java
|
||||
void method(Ref<Integer> refArgument) {
|
||||
refArgument.transformValue(i -> i + 44);
|
||||
}
|
||||
|
||||
Ref<Integer> number = Ref.of(1);
|
||||
method(number);
|
||||
System.out.println(number.getValue()); // Output: 45
|
||||
```
|
||||
当一个方法需要产生多个结果时,无法有多个返回值,可以使用 `Ref` 作为参数传入,方法内部修改 `Ref` 的值。 调用方在调用方法之后,使用 `getValue()` 获取结果。
|
||||
```java
|
||||
String method(Ref<Integer> intRefArgument, Ref<String> strRefArgument) {
|
||||
intRefArgument.transformValue(i -> i + 44);
|
||||
strRefArgument.setValue("Hello " + strRefArgument.getValue());
|
||||
return "Return string";
|
||||
}
|
||||
|
||||
Ref<Integer> number = Ref.of(1);
|
||||
Ref<String> str = Ref.of("Java");
|
||||
String result = method(number, str);
|
||||
System.out.println(number.getValue()); // Output: 45
|
||||
System.out.println(str.getValue()); // Output: Hello Java
|
||||
System.out.println(result); // Output: Return string
|
||||
```
|
||||
|
||||
### 7.7 其它工具类
|
||||
- **`BigDecimals`**: BigDecimal 工具
|
||||
- **`Numbers`**: 数字工具
|
||||
- **`OptionalTools`**: Optional 工具
|
||||
- **`RandomTools`**: 随机工具
|
||||
- **`RegexTools`**: 正则工具
|
||||
- **`StringTools`**: 字符串工具
|
||||
- **`ZipTools`**: zip 工具
|
||||
9
plusone-commons/docs/8_others.md
Normal file
9
plusone-commons/docs/8_others.md
Normal file
@@ -0,0 +1,9 @@
|
||||
## 8. 其它内容
|
||||
|
||||
### 8.1. IWithCode
|
||||
|
||||
对于类似枚举这样的类型,通常需要设置固定的码值表示对应的含义。 可实现 `IWithCode`、`IWithIntCode`、`IWithLongCode`,便于在需要的地方对这些接口的实现进行处理。
|
||||
|
||||
### 8.2. 正则常量
|
||||
|
||||
`RegexConsts` 包含常见正则表达式;`PatternConsts` 包含对应的 `Pattern` 对象
|
||||
135
plusone-commons/pom.xml
Normal file
135
plusone-commons/pom.xml
Normal file
@@ -0,0 +1,135 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<parent>
|
||||
<groupId>xyz.zhouxy.plusone</groupId>
|
||||
<artifactId>plusone-parent</artifactId>
|
||||
<version>1.1.0-SNAPSHOT</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>plusone-commons</artifactId>
|
||||
|
||||
<name>Plusone Commons</name>
|
||||
|
||||
<description>
|
||||
Plusone Commons 是一个 Java 工具类库,提供了一系列实用的类和方法,用于简化开发。结合 guava 使用。
|
||||
</description>
|
||||
|
||||
<dependencyManagement>
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>xyz.zhouxy.plusone</groupId>
|
||||
<artifactId>plusone-dependencies</artifactId>
|
||||
<version>${project.version}</version>
|
||||
<type>pom</type>
|
||||
<scope>import</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</dependencyManagement>
|
||||
|
||||
<dependencies>
|
||||
|
||||
<!-- ========== Compile Dependencies ========== -->
|
||||
|
||||
<dependency>
|
||||
<groupId>com.google.code.findbugs</groupId>
|
||||
<artifactId>jsr305</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>com.google.guava</groupId>
|
||||
<artifactId>guava</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>joda-time</groupId>
|
||||
<artifactId>joda-time</artifactId>
|
||||
<optional>true</optional>
|
||||
</dependency>
|
||||
|
||||
<!-- Gson -->
|
||||
<dependency>
|
||||
<groupId>com.google.code.gson</groupId>
|
||||
<artifactId>gson</artifactId>
|
||||
<optional>true</optional>
|
||||
</dependency>
|
||||
|
||||
<!-- ========== Test Dependencies ========== -->
|
||||
|
||||
<dependency>
|
||||
<groupId>org.apache.commons</groupId>
|
||||
<artifactId>commons-lang3</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>ch.qos.logback</groupId>
|
||||
<artifactId>logback-classic</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.junit.jupiter</groupId>
|
||||
<artifactId>junit-jupiter</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.projectlombok</groupId>
|
||||
<artifactId>lombok</artifactId>
|
||||
<optional>true</optional>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>cn.hutool</groupId>
|
||||
<artifactId>hutool-core</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.mybatis</groupId>
|
||||
<artifactId>mybatis</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>com.h2database</groupId>
|
||||
<artifactId>h2</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
|
||||
<!-- Jackson -->
|
||||
<dependency>
|
||||
<groupId>com.fasterxml.jackson.core</groupId>
|
||||
<artifactId>jackson-databind</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.fasterxml.jackson.datatype</groupId>
|
||||
<artifactId>jackson-datatype-jsr310</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
|
||||
</dependencies>
|
||||
|
||||
<build>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<artifactId>maven-compiler-plugin</artifactId>
|
||||
<configuration>
|
||||
<annotationProcessorPaths>
|
||||
<path>
|
||||
<groupId>org.projectlombok</groupId>
|
||||
<artifactId>lombok</artifactId>
|
||||
<version>${lombok.version}</version>
|
||||
</path>
|
||||
</annotationProcessorPaths>
|
||||
</configuration>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
</project>
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2022-2023 the original author or authors.
|
||||
* Copyright 2023-present ZhouXY
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -14,22 +14,24 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package xyz.zhouxy.plusone.commons.function;
|
||||
package xyz.zhouxy.plusone.commons.annotation;
|
||||
|
||||
import java.util.OptionalDouble;
|
||||
import java.util.function.Supplier;
|
||||
import java.lang.annotation.ElementType;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
/**
|
||||
* OptionalDoubleSupplier
|
||||
* ReaderMethod
|
||||
*
|
||||
* <p>
|
||||
* 返回 {@link OptionalDouble} 对象。
|
||||
*
|
||||
* @author <a href="https://gitee.com/zhouxy108">ZhouXY</a>
|
||||
* @since 0.1.0
|
||||
* @see OptionalDouble
|
||||
* @see Supplier
|
||||
* 标识方法是读方法,如 getter。
|
||||
*
|
||||
* @author ZhouXY
|
||||
* @since 1.0.0
|
||||
* @see WriterMethod
|
||||
*/
|
||||
@FunctionalInterface
|
||||
public interface OptionalDoubleSupplier extends Supplier<OptionalDouble> {
|
||||
@Target(ElementType.METHOD)
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
public @interface ReaderMethod {
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2022-2023 the original author or authors.
|
||||
* Copyright 2023-present ZhouXY
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -26,8 +26,8 @@ import java.lang.annotation.Target;
|
||||
*
|
||||
* <p>标识方法为静态工厂方法
|
||||
*
|
||||
* @author <a href="https://gitee.com/zhouxy108">ZhouXY</a>
|
||||
* @since 0.1.0
|
||||
* @author ZhouXY
|
||||
* @since 1.0.0
|
||||
*/
|
||||
@Target(ElementType.METHOD)
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@@ -0,0 +1,39 @@
|
||||
/*
|
||||
* Copyright 2023-present ZhouXY
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package xyz.zhouxy.plusone.commons.annotation;
|
||||
|
||||
import java.lang.annotation.ElementType;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
import java.lang.annotation.Documented;
|
||||
|
||||
/**
|
||||
* UnsupportedOperation
|
||||
*
|
||||
* <p>标识方法为不支持的操作。该方法将抛出 {@link UnsupportedOperationException}。
|
||||
*
|
||||
* @author ZhouXY
|
||||
* @version 1.0
|
||||
* @since 1.0.0
|
||||
* @see UnsupportedOperationException
|
||||
*/
|
||||
@Documented
|
||||
@Target({ ElementType.CONSTRUCTOR, ElementType.METHOD })
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
public @interface UnsupportedOperation {
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2022-2023 the original author or authors.
|
||||
* Copyright 2023-present ZhouXY
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -17,6 +17,7 @@
|
||||
package xyz.zhouxy.plusone.commons.annotation;
|
||||
|
||||
import java.lang.annotation.ElementType;
|
||||
import java.lang.annotation.Inherited;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
@@ -24,9 +25,10 @@ import java.lang.annotation.Target;
|
||||
/**
|
||||
* ValueObject - 值对象
|
||||
*
|
||||
* @author <a href="https://gitee.com/zhouxy108">ZhouXY</a>
|
||||
* @since 0.1.0
|
||||
* @author ZhouXY
|
||||
* @since 1.0.0
|
||||
*/
|
||||
@Inherited
|
||||
@Target(ElementType.TYPE)
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
public @interface ValueObject {
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2022-2023 the original author or authors.
|
||||
* Copyright 2023-present ZhouXY
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -22,13 +22,13 @@ import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
/**
|
||||
* 标识该方法是可覆写的。
|
||||
* 标识该方法是虚方法。
|
||||
* <p>该注解用于提醒、强调父类虽然有默认实现,但子类可以根据自己的需要覆写。</p>
|
||||
*
|
||||
* @author <a href="https://gitee.com/zhouxy108">ZhouXY</a>
|
||||
* @since 0.1.0
|
||||
*
|
||||
* @author ZhouXY
|
||||
* @since 1.0.0
|
||||
*/
|
||||
@Target(ElementType.METHOD)
|
||||
@Retention(RetentionPolicy.SOURCE)
|
||||
public @interface Overridable {
|
||||
public @interface Virtual {
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
/*
|
||||
* Copyright 2023-present ZhouXY
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package xyz.zhouxy.plusone.commons.annotation;
|
||||
|
||||
import java.lang.annotation.ElementType;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
/**
|
||||
* WriterMethod
|
||||
*
|
||||
* <p>
|
||||
* 标识方法是写方法,如 setter。
|
||||
*
|
||||
* @author ZhouXY
|
||||
* @since 1.0.0
|
||||
* @see ReaderMethod
|
||||
*/
|
||||
@Target(ElementType.METHOD)
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
public @interface WriterMethod {
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
/*
|
||||
* Copyright 2025-present ZhouXY
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
/**
|
||||
* 注解
|
||||
*
|
||||
* @author ZhouXY
|
||||
*/
|
||||
package xyz.zhouxy.plusone.commons.annotation;
|
||||
@@ -0,0 +1,79 @@
|
||||
/*
|
||||
* Copyright 2022-present ZhouXY
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package xyz.zhouxy.plusone.commons.base;
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
import javax.annotation.Nonnull;
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
/**
|
||||
* 规定实现类带有 {@code getCode} 方法。
|
||||
* 用于像自定义异常等需要带有 {@code code} 字段的类,
|
||||
* 方便其它地方的程序判断该类的是否实现了此接口,以此获取其实例的 {@code code} 字段的值。
|
||||
*
|
||||
* @author ZhouXY
|
||||
*/
|
||||
public interface IWithCode<T> {
|
||||
|
||||
/**
|
||||
* 获取码值
|
||||
* @return 码值
|
||||
*/
|
||||
@Nonnull
|
||||
T getCode();
|
||||
|
||||
/**
|
||||
* 判断 {@code code} 与给定的值是否相等
|
||||
*
|
||||
* @param code 用于判断的值
|
||||
* @return 判断结果
|
||||
*/
|
||||
default boolean isCodeEquals(@Nullable T code) {
|
||||
return Objects.equals(getCode(), code);
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断是否与给定的 {@link IWithCode} 有着相等的 {@code code}
|
||||
*
|
||||
* @param other 用于比较的对象
|
||||
* @return 判断结果
|
||||
*/
|
||||
default boolean isSameCodeAs(@Nullable IWithCode<?> other) {
|
||||
return other != null && Objects.equals(getCode(), other.getCode());
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断是否与给定的 {@link IWithIntCode} 有着相等的 {@code code}
|
||||
*
|
||||
* @param other 用于比较的对象
|
||||
* @return 判断结果
|
||||
*/
|
||||
default boolean isSameCodeAs(@Nullable IWithIntCode other) {
|
||||
return other != null && Objects.equals(getCode(), other.getCode());
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断是否与给定的 {@link IWithLongCode} 有着相等的 {@code code}
|
||||
*
|
||||
* @param other 用于比较的对象
|
||||
* @return 判断结果
|
||||
*/
|
||||
default boolean isSameCodeAs(@Nullable IWithLongCode other) {
|
||||
return other != null && Objects.equals(getCode(), other.getCode());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,77 @@
|
||||
/*
|
||||
* Copyright 2022-present ZhouXY
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package xyz.zhouxy.plusone.commons.base;
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
/**
|
||||
* 规定实现类带有 {@code getCode} 方法。
|
||||
* 用于像自定义异常等需要带有 {@code code} 字段的类,
|
||||
* 方便其它地方的程序判断该类的是否实现了此接口,以此获取其实例的 {@code code} 字段的值。
|
||||
*
|
||||
* @author ZhouXY
|
||||
*/
|
||||
public interface IWithIntCode {
|
||||
|
||||
/**
|
||||
* 获取码值
|
||||
* @return 码值
|
||||
*/
|
||||
int getCode();
|
||||
|
||||
/**
|
||||
* 判断 {@code code} 与给定的值是否相等
|
||||
*
|
||||
* @param code 用于判断的值
|
||||
* @return 判断结果
|
||||
*/
|
||||
default boolean isCodeEquals(int code) {
|
||||
return getCode() == code;
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断是否与给定的 {@link IWithCode} 有着相等的 {@code code}
|
||||
*
|
||||
* @param other 用于比较的对象
|
||||
* @return 判断结果
|
||||
*/
|
||||
default boolean isSameCodeAs(@Nullable IWithCode<?> other) {
|
||||
return other != null && Objects.equals(getCode(), other.getCode());
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断是否与给定的 {@link IWithIntCode} 有着相等的 {@code code}
|
||||
*
|
||||
* @param other 用于比较的对象
|
||||
* @return 判断结果
|
||||
*/
|
||||
default boolean isSameCodeAs(@Nullable IWithIntCode other) {
|
||||
return other != null && getCode() == other.getCode();
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断是否与给定的 {@link IWithLongCode} 有着相等的 {@code code}
|
||||
*
|
||||
* @param other 用于比较的对象
|
||||
* @return 判断结果
|
||||
*/
|
||||
default boolean isSameCodeAs(@Nullable IWithLongCode other) {
|
||||
return other != null && getCode() == other.getCode();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,77 @@
|
||||
/*
|
||||
* Copyright 2022-present ZhouXY
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package xyz.zhouxy.plusone.commons.base;
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
/**
|
||||
* 规定实现类带有 {@code getCode} 方法。
|
||||
* 用于像自定义异常等需要带有 {@code code} 字段的类,
|
||||
* 方便其它地方的程序判断该类的是否实现了此接口,以此获取其实例的 {@code code} 字段的值。
|
||||
*
|
||||
* @author ZhouXY
|
||||
*/
|
||||
public interface IWithLongCode {
|
||||
|
||||
/**
|
||||
* 获取码值
|
||||
* @return 码值
|
||||
*/
|
||||
long getCode();
|
||||
|
||||
/**
|
||||
* 判断 {@code code} 与给定的值是否相等
|
||||
*
|
||||
* @param code 用于判断的值
|
||||
* @return 判断结果
|
||||
*/
|
||||
default boolean isCodeEquals(long code) {
|
||||
return getCode() == code;
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断是否与给定的 {@link IWithCode} 有着相等的 {@code code}
|
||||
*
|
||||
* @param other 用于比较的对象
|
||||
* @return 判断结果
|
||||
*/
|
||||
default boolean isSameCodeAs(@Nullable IWithCode<?> other) {
|
||||
return other != null && Objects.equals(getCode(), other.getCode());
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断是否与给定的 {@link IWithIntCode} 有着相等的 {@code code}
|
||||
*
|
||||
* @param other 用于比较的对象
|
||||
* @return 判断结果
|
||||
*/
|
||||
default boolean isSameCodeAs(@Nullable IWithIntCode other) {
|
||||
return other != null && getCode() == other.getCode();
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断是否与给定的 {@link IWithLongCode} 有着相等的 {@code code}
|
||||
*
|
||||
* @param other 用于比较的对象
|
||||
* @return 判断结果
|
||||
*/
|
||||
default boolean isSameCodeAs(@Nullable IWithLongCode other) {
|
||||
return other != null && getCode() == other.getCode();
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2022-2023 the original author or authors.
|
||||
* Copyright 2025-present ZhouXY
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -14,15 +14,15 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
/**
|
||||
* 基础内容
|
||||
*
|
||||
* @author ZhouXY
|
||||
*/
|
||||
@CheckReturnValue
|
||||
@ParametersAreNonnullByDefault
|
||||
package xyz.zhouxy.plusone.commons.base;
|
||||
|
||||
/**
|
||||
* 规定实现类带有 {@code getCode} 方法。
|
||||
* 用于像自定义异常等需要带有 {@code code} 字段的类,
|
||||
* 方便其它地方的程序判断该类的是否实现了此接口,以此获取其实例的 {@code code} 字段的值。
|
||||
*
|
||||
* @author <a href="https://gitee.com/zhouxy108">ZhouXY</a>
|
||||
*/
|
||||
public interface IWithIntCode {
|
||||
int getCode();
|
||||
}
|
||||
import javax.annotation.ParametersAreNonnullByDefault;
|
||||
|
||||
import javax.annotation.CheckReturnValue;
|
||||
@@ -0,0 +1,228 @@
|
||||
/*
|
||||
* Copyright 2023-present ZhouXY
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package xyz.zhouxy.plusone.commons.collection;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
import javax.annotation.Nonnull;
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
import com.google.common.collect.Multimap;
|
||||
import com.google.common.collect.Multiset;
|
||||
import com.google.common.collect.RangeSet;
|
||||
import com.google.common.collect.Table;
|
||||
|
||||
/**
|
||||
* 集合工具类
|
||||
*
|
||||
* @author ZhouXY
|
||||
* @since 1.0.0
|
||||
*/
|
||||
public class CollectionTools {
|
||||
|
||||
// ================================
|
||||
// #region - isEmpty
|
||||
// ================================
|
||||
|
||||
/**
|
||||
* 判断集合是否为空
|
||||
*
|
||||
* @param collection 集合
|
||||
* @return 是否为空
|
||||
*/
|
||||
public static boolean isEmpty(@Nullable Collection<?> collection) {
|
||||
return collection == null || collection.isEmpty();
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断集合是否为空
|
||||
*
|
||||
* @param map 集合
|
||||
* @return 是否为空
|
||||
*/
|
||||
public static boolean isEmpty(@Nullable Map<?, ?> map) {
|
||||
return map == null || map.isEmpty();
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断集合是否为空
|
||||
*
|
||||
* @param table 集合
|
||||
* @return 是否为空
|
||||
*/
|
||||
public static boolean isEmpty(@Nullable Table<?, ?, ?> table) {
|
||||
return table == null || table.isEmpty();
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断集合是否为空
|
||||
*
|
||||
* @param map 集合
|
||||
* @return 是否为空
|
||||
*/
|
||||
public static boolean isEmpty(@Nullable Multimap<?, ?> map) {
|
||||
return map == null || map.isEmpty();
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断集合是否为空
|
||||
*
|
||||
* @param set 集合
|
||||
* @return 是否为空
|
||||
*/
|
||||
public static boolean isEmpty(@Nullable Multiset<?> set) {
|
||||
return set == null || set.isEmpty();
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断集合是否为空
|
||||
*
|
||||
* @param set 集合
|
||||
* @return 是否为空
|
||||
*/
|
||||
public static boolean isEmpty(@Nullable RangeSet<?> set) {
|
||||
return set == null || set.isEmpty();
|
||||
}
|
||||
|
||||
// ================================
|
||||
// #endregion - isEmpty
|
||||
// ================================
|
||||
|
||||
// ================================
|
||||
// #region - isNotEmpty
|
||||
// ================================
|
||||
|
||||
/**
|
||||
* 判断集合是否不为空
|
||||
*
|
||||
* @param collection 集合
|
||||
* @return 是否不为空
|
||||
*/
|
||||
public static boolean isNotEmpty(@Nullable Collection<?> collection) {
|
||||
return collection != null && !collection.isEmpty();
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断集合是否不为空
|
||||
*
|
||||
* @param map 集合
|
||||
* @return 是否不为空
|
||||
*/
|
||||
public static boolean isNotEmpty(@Nullable Map<?, ?> map) {
|
||||
return map != null && !map.isEmpty();
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断集合是否不为空
|
||||
*
|
||||
* @param table 集合
|
||||
* @return 是否不为空
|
||||
*/
|
||||
public static boolean isNotEmpty(@Nullable Table<?, ?, ?> table) {
|
||||
return table != null && !table.isEmpty();
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断集合是否不为空
|
||||
*
|
||||
* @param map 集合
|
||||
* @return 是否不为空
|
||||
*/
|
||||
public static boolean isNotEmpty(@Nullable Multimap<?, ?> map) {
|
||||
return map != null && !map.isEmpty();
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断集合是否不为空
|
||||
*
|
||||
* @param set 集合
|
||||
* @return 是否不为空
|
||||
*/
|
||||
public static boolean isNotEmpty(@Nullable Multiset<?> set) {
|
||||
return set != null && !set.isEmpty();
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断集合是否不为空
|
||||
*
|
||||
* @param set 集合
|
||||
* @return 是否不为空
|
||||
*/
|
||||
public static boolean isNotEmpty(@Nullable RangeSet<?> set) {
|
||||
return set != null && !set.isEmpty();
|
||||
}
|
||||
|
||||
// ================================
|
||||
// #endregion - isNotEmpty
|
||||
// ================================
|
||||
|
||||
// ================================
|
||||
// #region - nullToEmpty
|
||||
// ================================
|
||||
|
||||
/**
|
||||
* 将 {@code null} 转为空 {@code List}
|
||||
*
|
||||
* @param <T> List 元素的类型
|
||||
* @param list list
|
||||
* @return 如果 {@code list} 为 {@code null},返回空列表;
|
||||
* 如果 {@code list} 不为 {@code null},返回 {@code list} 本身
|
||||
*/
|
||||
@Nonnull
|
||||
public static <T> List<T> nullToEmptyList(@Nullable List<T> list) {
|
||||
return list == null ? Collections.emptyList() : list;
|
||||
}
|
||||
|
||||
/**
|
||||
* 将 {@code null} 转为空 {@code Set}
|
||||
*
|
||||
* @param <T> Set 元素的类型
|
||||
* @param set set
|
||||
* @return 如果 {@code set} 为 {@code null},返回空集合;
|
||||
* 如果 {@code set} 不为 {@code null},返回 {@code set} 本身
|
||||
*/
|
||||
@Nonnull
|
||||
public static <T> Set<T> nullToEmptySet(@Nullable Set<T> set) {
|
||||
return set == null ? Collections.emptySet() : set;
|
||||
}
|
||||
|
||||
/**
|
||||
* 将 {@code null} 转为空 {@code Map}
|
||||
*
|
||||
* @param <K> Map 的键的类型
|
||||
* @param <V> Map 的值的类型
|
||||
* @param map map
|
||||
* @return 如果 {@code map} 为 {@code null},返回空集合;
|
||||
* 如果 {@code map} 不为 {@code null},返回 {@code map} 本身
|
||||
*/
|
||||
@Nonnull
|
||||
public static <K, V> Map<K, V> nullToEmptyMap(@Nullable Map<K, V> map) {
|
||||
return map == null ? Collections.emptyMap() : map;
|
||||
}
|
||||
|
||||
// ================================
|
||||
// #endregion - nullToEmpty
|
||||
// ================================
|
||||
|
||||
private CollectionTools() {
|
||||
throw new IllegalStateException("Utility class");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,302 @@
|
||||
/*
|
||||
* Copyright 2025-present ZhouXY
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package xyz.zhouxy.plusone.commons.collection;
|
||||
|
||||
import static xyz.zhouxy.plusone.commons.util.AssertTools.checkArgumentNotNull;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.TreeMap;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
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.Nullable;
|
||||
|
||||
import com.google.common.annotations.Beta;
|
||||
|
||||
/**
|
||||
* Map 修改器
|
||||
*
|
||||
* <p>
|
||||
* 封装一系列对 Map 数据的修改操作,修改 Map 的数据。可以用于 Map 的数据初始化等操作。
|
||||
*
|
||||
* <pre>
|
||||
* // MapModifier
|
||||
* MapModifier<String, Object> modifier = new MapModifier<String, Object>()
|
||||
* .putAll(commonProperties)
|
||||
* .put("username", "Ben")
|
||||
* .put("accountStatus", LOCKED);
|
||||
*
|
||||
* // 从 Supplier 中获取 Map,并修改数据
|
||||
* Map<String, Object> map = modifier.getAndModify(HashMap::new);
|
||||
*
|
||||
* // 可以灵活使用不同 Map 类型的不同构造器
|
||||
* Map<String, Object> map = modifier.getAndModify(() -> new HashMap<>(8));
|
||||
* Map<String, Object> map = modifier.getAndModify(() -> new HashMap<>(anotherMap));
|
||||
* Map<String, Object> map = modifier.getAndModify(TreeMap::new);
|
||||
* Map<String, Object> map = modifier.getAndModify(ConcurrentHashMap::new);
|
||||
*
|
||||
* // 修改已有的 Map
|
||||
* modifier.modify(map);
|
||||
*
|
||||
* // 创建一个有初始化数据的不可变的 Map
|
||||
* Map<String, Object> map = modifier.getUnmodifiableMap();
|
||||
*
|
||||
* // 链式调用创建并初始化数据
|
||||
* Map<String, Object> map = new MapModifier<String, Object>()
|
||||
* .putAll(commonProperties)
|
||||
* .put("username", "Ben")
|
||||
* .put("accountStatus", LOCKED)
|
||||
* .getAndModify(HashMap::new);
|
||||
* </pre>
|
||||
*
|
||||
* @author ZhouXY
|
||||
* @since 1.1.0
|
||||
*/
|
||||
@Beta
|
||||
public class MapModifier<K, V> {
|
||||
|
||||
private final List<Consumer<Map<K, V>>> operations;
|
||||
|
||||
/**
|
||||
* 创建一个空的 MapModifier
|
||||
*/
|
||||
public MapModifier() {
|
||||
this.operations = new ArrayList<>();
|
||||
}
|
||||
|
||||
public MapModifier(int initialCapacity) {
|
||||
this.operations = new ArrayList<>(initialCapacity);
|
||||
}
|
||||
|
||||
/**
|
||||
* 添加一个键值对。
|
||||
*
|
||||
* <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));
|
||||
}
|
||||
|
||||
/**
|
||||
* 当 {@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) {
|
||||
checkArgumentNotNull(mappingFunction, "Mapping function cannot be null.");
|
||||
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) {
|
||||
checkArgumentNotNull(remappingFunction, "Remapping function cannot be null.");
|
||||
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}
|
||||
* @param <T> {@code map} 的类型
|
||||
*/
|
||||
public <T extends Map<K, V>> void modify(@Nullable T map) {
|
||||
if (map == null || this.operations.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
this.operations.forEach(operator -> operator.accept(map));
|
||||
}
|
||||
|
||||
/**
|
||||
* 修改 {@code map}
|
||||
*
|
||||
* @param mapSupplier {@code map} 的 {@link Supplier}
|
||||
* @param <T> {@code map} 的类型
|
||||
* @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 HashMap}
|
||||
*
|
||||
* @return {@code HashMap}
|
||||
*/
|
||||
public Map<K, V> getHashMap() {
|
||||
return getAndModify(HashMap::new);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取 {@code LinkedHashMap}
|
||||
*
|
||||
* @return {@code LinkedHashMap}
|
||||
*/
|
||||
public Map<K, V> getLinkedHashMap() {
|
||||
return getAndModify(LinkedHashMap::new);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取 {@code TreeMap}
|
||||
*
|
||||
* @return {@code TreeMap}
|
||||
*/
|
||||
public Map<K, V> getTreeMap() {
|
||||
return getAndModify(TreeMap::new);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取 {@code ConcurrentHashMap}
|
||||
*
|
||||
* @return {@code ConcurrentHashMap}
|
||||
*/
|
||||
public Map<K, V> getConcurrentHashMap() {
|
||||
return getAndModify(ConcurrentHashMap::new);
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建一个有初始化数据的不可变的 {@code Map}
|
||||
*
|
||||
* @return 不可变的 {@code Map}
|
||||
*/
|
||||
public Map<K, V> getUnmodifiableMap() {
|
||||
return Collections.unmodifiableMap(Objects.requireNonNull(getAndModify(HashMap::new)));
|
||||
}
|
||||
|
||||
private MapModifier<K, V> addOperationInternal(Consumer<Map<K, V>> operator) {
|
||||
this.operations.add(operator);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取操作数量
|
||||
*
|
||||
* @return 操作数量
|
||||
*/
|
||||
public int getOperatorCount() {
|
||||
return this.operations.size();
|
||||
}
|
||||
|
||||
/**
|
||||
* 是否有操作
|
||||
*
|
||||
* @return 如果有操作,则返回 {@code true},否则返回 {@code false}
|
||||
*/
|
||||
public boolean hasOperations() {
|
||||
return !this.operations.isEmpty();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "MapModifier [OperatorCount=" + this.operations.size() + "]";
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2022-2023 the original author or authors.
|
||||
* Copyright 2025-present ZhouXY
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -14,15 +14,12 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package xyz.zhouxy.plusone.commons.base;
|
||||
|
||||
/**
|
||||
* 规定实现类带有 {@code getCode} 方法。
|
||||
* 用于像自定义异常等需要带有 {@code code} 字段的类,
|
||||
* 方便其它地方的程序判断该类的是否实现了此接口,以此获取其实例的 {@code code} 字段的值。
|
||||
* 集合相关工具
|
||||
*
|
||||
* @author <a href="https://gitee.com/zhouxy108">ZhouXY</a>
|
||||
* @author ZhouXY
|
||||
*/
|
||||
public interface IWithLongCode {
|
||||
long getCode();
|
||||
}
|
||||
@ParametersAreNonnullByDefault
|
||||
package xyz.zhouxy.plusone.commons.collection;
|
||||
|
||||
import javax.annotation.ParametersAreNonnullByDefault;
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2022-2023 the original author or authors.
|
||||
* Copyright 2023-present ZhouXY
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -21,24 +21,77 @@ import java.util.regex.Pattern;
|
||||
/**
|
||||
* 正则表达式常量
|
||||
*
|
||||
* @author <a href="https://gitee.com/zhouxy108">ZhouXY</a>
|
||||
* @author ZhouXY
|
||||
* @see RegexConsts
|
||||
* @see xyz.zhouxy.plusone.commons.util.RegexTools
|
||||
*/
|
||||
public final class PatternConsts {
|
||||
|
||||
public static final Pattern DATE = Pattern.compile(RegexConsts.DATE);
|
||||
/**
|
||||
* yyyyMMdd
|
||||
*
|
||||
* @see RegexConsts#BASIC_ISO_DATE
|
||||
*
|
||||
*/
|
||||
public static final Pattern BASIC_ISO_DATE = Pattern.compile(RegexConsts.BASIC_ISO_DATE);
|
||||
|
||||
/**
|
||||
* yyyy-MM-dd
|
||||
*
|
||||
* @see RegexConsts#ISO_LOCAL_DATE
|
||||
*/
|
||||
public static final Pattern ISO_LOCAL_DATE = Pattern.compile(RegexConsts.ISO_LOCAL_DATE);
|
||||
|
||||
/**
|
||||
* 密码
|
||||
*
|
||||
* @see RegexConsts#PASSWORD
|
||||
*/
|
||||
public static final Pattern PASSWORD = Pattern.compile(RegexConsts.PASSWORD);
|
||||
|
||||
/**
|
||||
* 验证码
|
||||
*
|
||||
* @see RegexConsts#CAPTCHA
|
||||
*/
|
||||
public static final Pattern CAPTCHA = Pattern.compile(RegexConsts.CAPTCHA);
|
||||
|
||||
public static final Pattern EMAIL = Pattern.compile(RegexConsts.EMAIL);
|
||||
/**
|
||||
* 邮箱地址
|
||||
*
|
||||
* @see RegexConsts#EMAIL
|
||||
*/
|
||||
public static final Pattern EMAIL = Pattern.compile(RegexConsts.EMAIL, Pattern.CASE_INSENSITIVE);
|
||||
|
||||
/**
|
||||
* 中国大陆手机号
|
||||
*
|
||||
* @see RegexConsts#MOBILE_PHONE
|
||||
*/
|
||||
public static final Pattern MOBILE_PHONE = Pattern.compile(RegexConsts.MOBILE_PHONE);
|
||||
|
||||
/**
|
||||
* 用户名
|
||||
*
|
||||
* @see RegexConsts#USERNAME
|
||||
*/
|
||||
public static final Pattern USERNAME = Pattern.compile(RegexConsts.USERNAME);
|
||||
|
||||
/**
|
||||
* 昵称
|
||||
*
|
||||
* @see RegexConsts#NICKNAME
|
||||
*/
|
||||
public static final Pattern NICKNAME = Pattern.compile(RegexConsts.NICKNAME);
|
||||
|
||||
/**
|
||||
* 中国第二代居民身份证
|
||||
*
|
||||
* @see RegexConsts#CHINESE_2ND_ID_CARD_NUMBER
|
||||
*/
|
||||
public static final Pattern CHINESE_2ND_ID_CARD_NUMBER
|
||||
= Pattern.compile(RegexConsts.CHINESE_2ND_ID_CARD_NUMBER, Pattern.CASE_INSENSITIVE);
|
||||
|
||||
private PatternConsts() {
|
||||
throw new IllegalStateException("Utility class");
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
/*
|
||||
* Copyright 2022-present ZhouXY
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package xyz.zhouxy.plusone.commons.constant;
|
||||
|
||||
/**
|
||||
* 正则表达式常量
|
||||
*
|
||||
* @author ZhouXY
|
||||
* @see PatternConsts
|
||||
*/
|
||||
public final class RegexConsts {
|
||||
|
||||
public static final String BASIC_ISO_DATE = "^(?<yyyy>\\d{4,9})(?<MM>\\d{2})(?<dd>\\d{2})";
|
||||
|
||||
public static final String ISO_LOCAL_DATE = "^(?<yyyy>\\d{4,9})-(?<MM>\\d{2})-(?<dd>\\d{2})";
|
||||
|
||||
public static final String PASSWORD = "^(?=.*\\d)(?=.*[a-z])(?=.*[A-Z])[\\w\\\\!#$%&'*\\+\\-/=?^`{|}~@\\(\\)\\[\\]\",\\.;':><]{8,32}$";
|
||||
|
||||
public static final String CAPTCHA = "^\\w{4,6}$";
|
||||
|
||||
/**
|
||||
* from https://emailregex.com/
|
||||
*/
|
||||
public static final String EMAIL
|
||||
= "(?:[a-z0-9!#$%&'*+/=?^_`{|}~-]+(?:\\.[a-z0-9!#$%&'*+/=?^_`{|}~-]+)*|\"(?:[\\x01-\\x08\\x0b\\x0c\\x0e-\\x1f\\x21\\x23-\\x5b\\x5d-\\x7f]|\\\\[\\x01-\\x09\\x0b\\x0c\\x0e-\\x7f])*\")"
|
||||
+ "@(?:(?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\\.)+[a-z0-9](?:[a-z0-9-]*[a-z0-9])?|\\[(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?|[a-z0-9-]*[a-z0-9]:(?:[\\x01-\\x08\\x0b\\x0c\\x0e-\\x1f\\x21-\\x5a\\x53-\\x7f]|\\\\[\\x01-\\x09\\x0b\\x0c\\x0e-\\x7f])+)\\])";
|
||||
|
||||
public static final String MOBILE_PHONE = "^(13[0-9]|14[01456879]|15[0-35-9]|16[2567]|17[0-8]|18[0-9]|19[0-35-9])\\d{8}$";
|
||||
|
||||
public static final String USERNAME = "^[\\w-_.@]{4,36}$";
|
||||
|
||||
public static final String NICKNAME = "^[\\w-_.@]{4,36}$";
|
||||
|
||||
public static final String CHINESE_2ND_ID_CARD_NUMBER
|
||||
= "^(?<county>(?<city>(?<province>\\d{2})\\d{2})\\d{2})(?<birthDate>\\d{8})\\d{2}(?<gender>\\d)([\\dX])$";
|
||||
|
||||
private RegexConsts() {
|
||||
throw new IllegalStateException("Utility class");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
/*
|
||||
* Copyright 2025-present ZhouXY
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
/**
|
||||
* <h2>常量</h2>
|
||||
*
|
||||
* <h3>
|
||||
* 1. 正则常量
|
||||
* </h3>
|
||||
* {@link RegexConsts} 包含常见正则表达式;{@link PatternConsts} 包含对应的 {@link Pattern} 对象。
|
||||
*
|
||||
* @author ZhouXY
|
||||
*/
|
||||
package xyz.zhouxy.plusone.commons.constant;
|
||||
|
||||
import java.util.regex.Pattern;
|
||||
@@ -0,0 +1,66 @@
|
||||
/*
|
||||
* Copyright 2024-present ZhouXY
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package xyz.zhouxy.plusone.commons.exception;
|
||||
|
||||
/**
|
||||
* 数据不存在异常
|
||||
*
|
||||
* @author ZhouXY
|
||||
* @since 1.0.0
|
||||
*/
|
||||
public final class DataNotExistsException extends Exception {
|
||||
|
||||
private static final long serialVersionUID = 6536955800679703111L;
|
||||
|
||||
/**
|
||||
* 使用默认 message 构造新的 {@code DataNotExistsException}。
|
||||
* {@code cause} 未初始化,后面可能会通过调用 {@link #initCause} 进行初始化。
|
||||
*/
|
||||
public DataNotExistsException() {
|
||||
super();
|
||||
}
|
||||
|
||||
/**
|
||||
* 使用指定的 {@code message} 构造新的 {@code DataNotExistsException}。
|
||||
* {@code cause} 未初始化,后面可能会通过调用 {@link #initCause} 进行初始化。
|
||||
*
|
||||
* @param message 异常信息
|
||||
*/
|
||||
public DataNotExistsException(String message) {
|
||||
super(message);
|
||||
}
|
||||
|
||||
/**
|
||||
* 使用指定的 {@code cause} 构造新的 {@code DataNotExistsException}。
|
||||
* {@code message} 为 (cause==null ? null : cause.toString())。
|
||||
*
|
||||
* @param cause 包装的异常
|
||||
*/
|
||||
public DataNotExistsException(Throwable cause) {
|
||||
super(cause);
|
||||
}
|
||||
|
||||
/**
|
||||
* 使用指定的 {@code message} 和 {@code cause} 构造新的 {@code DataNotExistsException}。
|
||||
*
|
||||
* @param message 异常信息
|
||||
* @param cause 包装的异常
|
||||
*/
|
||||
public DataNotExistsException(String message, Throwable cause) {
|
||||
super(message, cause);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
/*
|
||||
* Copyright 2025-present ZhouXY
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package xyz.zhouxy.plusone.commons.exception;
|
||||
|
||||
import javax.annotation.Nonnull;
|
||||
|
||||
/**
|
||||
* 异常工厂
|
||||
*
|
||||
* @param <X> 异常类型
|
||||
* @author ZhouXY
|
||||
*/
|
||||
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);
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2022-2023 the original author or authors.
|
||||
* Copyright 2025-present ZhouXY
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -13,19 +13,29 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package xyz.zhouxy.plusone.commons.exception;
|
||||
|
||||
package xyz.zhouxy.plusone.commons.base;
|
||||
|
||||
import javax.annotation.Nonnull;
|
||||
import xyz.zhouxy.plusone.commons.annotation.Virtual;
|
||||
import xyz.zhouxy.plusone.commons.base.IWithCode;
|
||||
|
||||
/**
|
||||
* 规定实现类带有 {@code getCode} 方法。
|
||||
* 用于像自定义异常等需要带有 {@code code} 字段的类,
|
||||
* 方便其它地方的程序判断该类的是否实现了此接口,以此获取其实例的 {@code code} 字段的值。
|
||||
* 异常场景
|
||||
*
|
||||
* @author <a href="https://gitee.com/zhouxy108">ZhouXY</a>
|
||||
* @param <TCode> 场景编码
|
||||
* @author ZhouXY
|
||||
*/
|
||||
public interface IWithCode<T> {
|
||||
@Nonnull
|
||||
T getCode();
|
||||
public interface IExceptionType<TCode> extends IWithCode<TCode> {
|
||||
|
||||
/**
|
||||
* 默认异常信息
|
||||
*
|
||||
* @return 默认异常信息
|
||||
*/
|
||||
String getDefaultMessage();
|
||||
|
||||
@Virtual
|
||||
default String getDescription() {
|
||||
return getDefaultMessage();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,133 @@
|
||||
/*
|
||||
* Copyright 2025-present ZhouXY
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package xyz.zhouxy.plusone.commons.exception;
|
||||
|
||||
import javax.annotation.Nonnull;
|
||||
|
||||
/**
|
||||
* IMultiTypesException
|
||||
*
|
||||
* <p>
|
||||
* 异常在不同场景下被抛出,可以用不同的枚举值,表示不同的场景类型。
|
||||
*
|
||||
* <p>
|
||||
* 异常实现 {@link IMultiTypesException} 的 {@link #getType} 方法,返回对应的场景类型。
|
||||
*
|
||||
* <p>
|
||||
* 表示场景类型的枚举实现 {@link IExceptionType},各个枚举值本身就是该场景的异常的工厂实例,
|
||||
* 使用其中的工厂方法用于创建对应类型的异常。
|
||||
*
|
||||
* <pre>
|
||||
* public final class LoginException
|
||||
* extends RuntimeException
|
||||
* implements IMultiTypesException<LoginException.Type> {
|
||||
* private static final long serialVersionUID = 881293090625085616L;
|
||||
* private final Type type;
|
||||
* private LoginException(@Nonnull Type type, @Nonnull String message) {
|
||||
* super(message);
|
||||
* this.type = type;
|
||||
* }
|
||||
*
|
||||
* private LoginException(@Nonnull Type type, @Nonnull Throwable cause) {
|
||||
* super(cause);
|
||||
* this.type = type;
|
||||
* }
|
||||
*
|
||||
* private LoginException(@Nonnull Type type,
|
||||
* @Nonnull String message,
|
||||
* @Nonnull Throwable cause) {
|
||||
* super(message, cause);
|
||||
* this.type = type;
|
||||
* }
|
||||
*
|
||||
* @Override
|
||||
* public @Nonnull Type getType() {
|
||||
* return this.type;
|
||||
* }
|
||||
*
|
||||
* // ...
|
||||
*
|
||||
* public enum Type implements IExceptionType<String>, IExceptionFactory<LoginException> {
|
||||
* DEFAULT("00", "当前会话未登录"),
|
||||
* NOT_TOKEN("10", "未提供token"),
|
||||
* INVALID_TOKEN("20", "token无效"),
|
||||
* TOKEN_TIMEOUT("30", "token已过期"),
|
||||
* BE_REPLACED("40", "token已被顶下线"),
|
||||
* KICK_OUT("50", "token已被踢下线"),
|
||||
* ;
|
||||
*
|
||||
* @Nonnull
|
||||
* private final String code;
|
||||
* @Nonnull
|
||||
* private final String defaultMessage;
|
||||
*
|
||||
* Type(@Nonnull String code, @Nonnull String defaultMessage) {
|
||||
* this.code = code;
|
||||
* this.defaultMessage = defaultMessage;
|
||||
* }
|
||||
*
|
||||
* @Override
|
||||
* public @Nonnull String getCode() {
|
||||
* return code;
|
||||
* }
|
||||
*
|
||||
* @Override
|
||||
* public @Nonnull String getDefaultMessage() {
|
||||
* return defaultMessage;
|
||||
* }
|
||||
*
|
||||
* @Override
|
||||
* public @Nonnull LoginException create() {
|
||||
* return new LoginException(this, this.defaultMessage);
|
||||
* }
|
||||
*
|
||||
* @Override
|
||||
* public @Nonnull LoginException create(String message) {
|
||||
* return new LoginException(this, message);
|
||||
* }
|
||||
*
|
||||
* @Override
|
||||
* public @Nonnull LoginException create(Throwable cause) {
|
||||
* return new LoginException(this, cause);
|
||||
* }
|
||||
*
|
||||
* @Override
|
||||
* public @Nonnull LoginException create(String message, Throwable cause) {
|
||||
* return new LoginException(this, message, cause);
|
||||
* }
|
||||
* }
|
||||
* }
|
||||
* </pre>
|
||||
*
|
||||
* 使用时,可以使用这种方式创建并抛出异常:
|
||||
* <pre>
|
||||
* throw LoginException.Type.TOKEN_TIMEOUT.create();
|
||||
* </pre>
|
||||
*
|
||||
* @param <T> 异常场景
|
||||
* @author ZhouXY
|
||||
* @since 1.0.0
|
||||
*/
|
||||
public interface IMultiTypesException<T extends IExceptionType<?>> {
|
||||
|
||||
/**
|
||||
* 异常类型
|
||||
*
|
||||
* @return 异常类型。通常是实现了 {@link IExceptionType} 的枚举。
|
||||
*/
|
||||
@Nonnull
|
||||
T getType();
|
||||
}
|
||||
@@ -0,0 +1,221 @@
|
||||
/*
|
||||
* Copyright 2024-present ZhouXY
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package xyz.zhouxy.plusone.commons.exception;
|
||||
|
||||
import java.time.format.DateTimeParseException;
|
||||
|
||||
import javax.annotation.Nonnull;
|
||||
|
||||
import xyz.zhouxy.plusone.commons.exception.business.RequestParamsException;
|
||||
|
||||
/**
|
||||
* 解析失败异常
|
||||
*
|
||||
* <p>
|
||||
* 解析失败的不一定是客户传的参数,也可能是其它来源的数据解析失败。
|
||||
* 如果表示用户传参造成的解析失败,可使用 {@link RequestParamsException#RequestParamsException(Throwable)},
|
||||
* 将 ParsingFailureException 包装成 {@link RequestParamsException} 再抛出。
|
||||
* <pre>
|
||||
* throw new RequestParamsException(ParsingFailureException.Type.NUMBER_PARSING_FAILURE.create());
|
||||
* </pre>
|
||||
*
|
||||
* @author ZhouXY
|
||||
* @since 1.0.0
|
||||
*/
|
||||
public final class ParsingFailureException
|
||||
extends Exception
|
||||
implements IMultiTypesException<ParsingFailureException.Type> {
|
||||
private static final long serialVersionUID = 795996090625132616L;
|
||||
|
||||
private final Type type;
|
||||
|
||||
private ParsingFailureException(Type type, String message) {
|
||||
super(message);
|
||||
this.type = type;
|
||||
}
|
||||
|
||||
private ParsingFailureException(Type type, Throwable cause) {
|
||||
super(cause);
|
||||
this.type = type;
|
||||
}
|
||||
|
||||
private ParsingFailureException(Type type, String message, Throwable cause) {
|
||||
super(message, cause);
|
||||
this.type = type;
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建默认类型的 {@code ParsingFailureException}。
|
||||
* {@code type} 为 {@link Type#DEFAULT},
|
||||
* {@code message} 为 {@link Type#DEFAULT} 的默认信息。
|
||||
* {@code cause} 未初始化,后面可能会通过调用 {@link #initCause} 进行初始化。
|
||||
*/
|
||||
public ParsingFailureException() {
|
||||
this(Type.DEFAULT, Type.DEFAULT.getDefaultMessage());
|
||||
}
|
||||
|
||||
/**
|
||||
* 使用指定 {@code message} 创建默认类型的 {@code ParsingFailureException}。
|
||||
* {@code type} 为 {@link Type#DEFAULT},
|
||||
* {@code cause} 未初始化,后面可能会通过调用 {@link #initCause} 进行初始化。
|
||||
*
|
||||
* @param message 异常信息
|
||||
*/
|
||||
public ParsingFailureException(String message) {
|
||||
this(Type.DEFAULT, message);
|
||||
}
|
||||
|
||||
/**
|
||||
* 使用指定的 {@code cause} 创建默认类型的 {@code ParsingFailureException}。
|
||||
* {@code type} 为 {@link Type#DEFAULT},
|
||||
* {@code message} 为 (cause==null ? null : cause.toString())。
|
||||
*
|
||||
* @param cause 包装的异常
|
||||
*/
|
||||
public ParsingFailureException(Throwable cause) {
|
||||
this(Type.DEFAULT, cause);
|
||||
}
|
||||
|
||||
/**
|
||||
* 使用指定的 {@code message} 和 {@code cause} 创建默认类型的 {@code ParsingFailureException}。
|
||||
* {@code type} 为 {@link Type#DEFAULT}。
|
||||
*
|
||||
* @param message 异常信息
|
||||
* @param cause 包装的异常
|
||||
*/
|
||||
public ParsingFailureException(String message, Throwable cause) {
|
||||
this(Type.DEFAULT, message, cause);
|
||||
}
|
||||
|
||||
/**
|
||||
* 将 {@link DateTimeParseException} 包装为 {@link ParsingFailureException}。
|
||||
* {@code type} 为 {@link Type#DATE_TIME_PARSING_FAILURE}。
|
||||
*
|
||||
* @param cause 包装的 {@code DateTimeParseException}
|
||||
* @return ParsingFailureException
|
||||
*/
|
||||
public static ParsingFailureException of(DateTimeParseException cause) {
|
||||
if (cause == null) {
|
||||
return Type.DATE_TIME_PARSING_FAILURE.create();
|
||||
}
|
||||
return Type.DATE_TIME_PARSING_FAILURE.create(cause.getMessage(), cause);
|
||||
}
|
||||
|
||||
/**
|
||||
* 将 {@link DateTimeParseException} 包装为 {@link ParsingFailureException}。
|
||||
* {@code type} 为 {@link Type#DATE_TIME_PARSING_FAILURE}。
|
||||
*
|
||||
* @param message 异常信息
|
||||
* @param cause 包装的 {@code DateTimeParseException}
|
||||
* @return ParsingFailureException
|
||||
*/
|
||||
public static ParsingFailureException of(String message, DateTimeParseException cause) {
|
||||
return Type.DATE_TIME_PARSING_FAILURE.create(message, cause);
|
||||
}
|
||||
|
||||
/**
|
||||
* 将 {@link NumberFormatException} 包装为 {@link ParsingFailureException}。
|
||||
* {@code type} 为 {@link Type#NUMBER_PARSING_FAILURE}。
|
||||
*
|
||||
* @param cause 包装的 {@code NumberFormatException}
|
||||
* @return ParsingFailureException
|
||||
*/
|
||||
public static ParsingFailureException of(NumberFormatException cause) {
|
||||
if (cause == null) {
|
||||
return Type.NUMBER_PARSING_FAILURE.create();
|
||||
}
|
||||
return Type.NUMBER_PARSING_FAILURE.create(cause.getMessage(), cause);
|
||||
}
|
||||
|
||||
/**
|
||||
* 将 {@link NumberFormatException} 包装为 {@link ParsingFailureException}。
|
||||
* {@code type} 为 {@link Type#NUMBER_PARSING_FAILURE}。
|
||||
*
|
||||
* @param message 异常信息
|
||||
* @param cause {@code NumberFormatException}
|
||||
* @return ParsingFailureException
|
||||
*/
|
||||
public static ParsingFailureException of(String message, NumberFormatException cause) {
|
||||
return Type.NUMBER_PARSING_FAILURE.create(message, cause);
|
||||
}
|
||||
|
||||
@Override
|
||||
@Nonnull
|
||||
public Type getType() {
|
||||
return type;
|
||||
}
|
||||
|
||||
/** 默认类型 */
|
||||
public static final Type DEFAULT = Type.DEFAULT;
|
||||
/** 数字转换失败 */
|
||||
public static final Type NUMBER_PARSING_FAILURE = Type.NUMBER_PARSING_FAILURE;
|
||||
/** 时间解析失败 */
|
||||
public static final Type DATE_TIME_PARSING_FAILURE = Type.DATE_TIME_PARSING_FAILURE;
|
||||
/** JSON 解析失败 */
|
||||
public static final Type JSON_PARSING_FAILURE = Type.JSON_PARSING_FAILURE;
|
||||
/** XML 解析失败 */
|
||||
public static final Type XML_PARSING_FAILURE = Type.XML_PARSING_FAILURE;
|
||||
|
||||
public enum Type implements IExceptionType<String>, IExceptionFactory<ParsingFailureException> {
|
||||
DEFAULT("00", "解析失败"),
|
||||
NUMBER_PARSING_FAILURE("10", "数字转换失败"),
|
||||
DATE_TIME_PARSING_FAILURE("20", "时间解析失败"),
|
||||
JSON_PARSING_FAILURE("30", "JSON 解析失败"),
|
||||
XML_PARSING_FAILURE("40", "XML 解析失败"),
|
||||
;
|
||||
|
||||
@Nonnull
|
||||
private final String code;
|
||||
@Nonnull
|
||||
private final String defaultMessage;
|
||||
|
||||
Type(@Nonnull String code, @Nonnull String defaultMessage) {
|
||||
this.code = code;
|
||||
this.defaultMessage = defaultMessage;
|
||||
}
|
||||
|
||||
@Override
|
||||
public @Nonnull String getCode() {
|
||||
return code;
|
||||
}
|
||||
|
||||
@Override
|
||||
public @Nonnull String getDefaultMessage() {
|
||||
return defaultMessage;
|
||||
}
|
||||
|
||||
@Override
|
||||
public @Nonnull ParsingFailureException create() {
|
||||
return new ParsingFailureException(this, this.defaultMessage);
|
||||
}
|
||||
|
||||
@Override
|
||||
public @Nonnull ParsingFailureException create(String message) {
|
||||
return new ParsingFailureException(this, message);
|
||||
}
|
||||
|
||||
@Override
|
||||
public @Nonnull ParsingFailureException create(Throwable cause) {
|
||||
return new ParsingFailureException(this, cause);
|
||||
}
|
||||
|
||||
@Override
|
||||
public @Nonnull ParsingFailureException create(String message, Throwable cause) {
|
||||
return new ParsingFailureException(this, message, cause);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,85 @@
|
||||
/*
|
||||
* Copyright 2024-present ZhouXY
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package xyz.zhouxy.plusone.commons.exception.business;
|
||||
|
||||
/**
|
||||
* BizException
|
||||
*
|
||||
* <p>
|
||||
* 业务异常
|
||||
*
|
||||
* <p>
|
||||
* <b>NOTE: 通常表示业务中的意外情况。如:用户错误输入、缺失必填字段、用户余额不足等。</b>
|
||||
*
|
||||
* @author ZhouXY
|
||||
* @since 1.0.0
|
||||
*/
|
||||
public class BizException extends RuntimeException {
|
||||
private static final long serialVersionUID = 982585090625482416L;
|
||||
|
||||
private static final String DEFAULT_MSG = "业务异常";
|
||||
|
||||
/**
|
||||
* 使用指定的 {@code message} 构造新的业务异常。
|
||||
* {@code cause} 未初始化,后面可能会通过调用 {@link #initCause} 进行初始化。
|
||||
*
|
||||
* @param message 异常信息
|
||||
*/
|
||||
protected BizException(String message) {
|
||||
super(message);
|
||||
}
|
||||
|
||||
/**
|
||||
* 使用指定的 {@code cause} 构造新的业务异常。
|
||||
* {@code message} 为 (cause==null ? null : cause.toString())。
|
||||
*
|
||||
* @param cause 包装的异常
|
||||
*/
|
||||
protected BizException(Throwable cause) {
|
||||
super(cause);
|
||||
}
|
||||
|
||||
/**
|
||||
* 使用指定的 {@code message} 和 {@code cause} 构造新的业务异常。
|
||||
*
|
||||
* @param message 异常信息
|
||||
* @param 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);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,161 @@
|
||||
/*
|
||||
* Copyright 2024-present ZhouXY
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package xyz.zhouxy.plusone.commons.exception.business;
|
||||
|
||||
import javax.annotation.Nonnull;
|
||||
|
||||
import xyz.zhouxy.plusone.commons.exception.IExceptionFactory;
|
||||
import xyz.zhouxy.plusone.commons.exception.IExceptionType;
|
||||
import xyz.zhouxy.plusone.commons.exception.IMultiTypesException;
|
||||
|
||||
/**
|
||||
* InvalidInputException
|
||||
*
|
||||
* <p>
|
||||
* 用户输入内容非法
|
||||
*
|
||||
* <p>
|
||||
* <b>NOTE: 属业务异常</b>
|
||||
*
|
||||
* @author ZhouXY
|
||||
* @since 1.0.0
|
||||
*/
|
||||
public final class InvalidInputException
|
||||
extends RequestParamsException
|
||||
implements IMultiTypesException<InvalidInputException.Type> {
|
||||
private static final long serialVersionUID = -28994090625082516L;
|
||||
|
||||
private final Type type;
|
||||
|
||||
private InvalidInputException(Type type) {
|
||||
super(type.getDefaultMessage());
|
||||
this.type = type;
|
||||
}
|
||||
|
||||
private InvalidInputException(Type type, String message) {
|
||||
super(message);
|
||||
this.type = type;
|
||||
}
|
||||
|
||||
private InvalidInputException(Type type, Throwable cause) {
|
||||
super(cause);
|
||||
this.type = type;
|
||||
}
|
||||
|
||||
private InvalidInputException(Type type, String message, Throwable cause) {
|
||||
super(message, cause);
|
||||
this.type = type;
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建默认类型的 {@code InvalidInputException}。
|
||||
* {@code type} 为 {@link Type#DEFAULT},
|
||||
* {@code message} 为 {@link Type#DEFAULT} 的默认信息。
|
||||
* {@code cause} 未初始化,后面可能会通过调用 {@link #initCause} 进行初始化。
|
||||
*/
|
||||
public InvalidInputException() {
|
||||
this(Type.DEFAULT);
|
||||
}
|
||||
|
||||
/**
|
||||
* 使用指定 {@code message} 创建默认类型的 {@code InvalidInputException}。
|
||||
* {@code type} 为 {@link Type#DEFAULT},
|
||||
* {@code cause} 未初始化,后面可能会通过调用 {@link #initCause} 进行初始化。
|
||||
*
|
||||
* @param message 异常信息
|
||||
*/
|
||||
public InvalidInputException(String message) {
|
||||
this(Type.DEFAULT, message);
|
||||
}
|
||||
|
||||
/**
|
||||
* 使用指定的 {@code cause} 创建默认类型的 {@code InvalidInputException}。
|
||||
* {@code type} 为 {@link Type#DEFAULT},
|
||||
* {@code message} 为 (cause==null ? null : cause.toString())。
|
||||
*
|
||||
* @param cause 包装的异常
|
||||
*/
|
||||
public InvalidInputException(Throwable cause) {
|
||||
this(Type.DEFAULT, cause);
|
||||
}
|
||||
|
||||
/**
|
||||
* 使用指定的 {@code message} 和 {@code cause} 创建默认类型的 {@code InvalidInputException}。
|
||||
* {@code type} 为 {@link Type#DEFAULT}。
|
||||
*
|
||||
* @param message 异常信息
|
||||
* @param cause 包装的异常
|
||||
*/
|
||||
public InvalidInputException(String message, Throwable cause) {
|
||||
this(Type.DEFAULT, message, cause);
|
||||
}
|
||||
|
||||
@Override
|
||||
@Nonnull
|
||||
public Type getType() {
|
||||
return this.type;
|
||||
}
|
||||
|
||||
public enum Type implements IExceptionType<String>, IExceptionFactory<InvalidInputException> {
|
||||
DEFAULT("00", "用户输入内容非法"),
|
||||
CONTAINS_ILLEGAL_AND_MALICIOUS_LINKS("01", "包含非法恶意跳转链接"),
|
||||
CONTAINS_ILLEGAL_WORDS("02", "包含违禁敏感词"),
|
||||
PICTURE_CONTAINS_ILLEGAL_INFORMATION("03", "图片包含违禁信息"),
|
||||
INFRINGE_COPYRIGHT("04", "文件侵犯版权"),
|
||||
;
|
||||
|
||||
@Nonnull
|
||||
private final String code;
|
||||
@Nonnull
|
||||
private final String defaultMessage;
|
||||
|
||||
Type(@Nonnull String code, @Nonnull String defaultMsg) {
|
||||
this.code = code;
|
||||
this.defaultMessage = defaultMsg;
|
||||
}
|
||||
|
||||
@Override
|
||||
public @Nonnull String getCode() {
|
||||
return code;
|
||||
}
|
||||
|
||||
@Override
|
||||
public @Nonnull String getDefaultMessage() {
|
||||
return defaultMessage;
|
||||
}
|
||||
|
||||
@Override
|
||||
public @Nonnull InvalidInputException create() {
|
||||
return new InvalidInputException(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public @Nonnull InvalidInputException create(String message) {
|
||||
return new InvalidInputException(this, message);
|
||||
}
|
||||
|
||||
@Override
|
||||
public @Nonnull InvalidInputException create(Throwable cause) {
|
||||
return new InvalidInputException(this, cause);
|
||||
}
|
||||
|
||||
@Override
|
||||
public @Nonnull InvalidInputException create(String message, Throwable cause) {
|
||||
return new InvalidInputException(this, message, cause);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,71 @@
|
||||
/*
|
||||
* Copyright 2024-present ZhouXY
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package xyz.zhouxy.plusone.commons.exception.business;
|
||||
|
||||
/**
|
||||
* RequestParamsException
|
||||
*
|
||||
* <p>
|
||||
* 用户请求参数错误
|
||||
*
|
||||
* @author ZhouXY
|
||||
* @since 1.0.0
|
||||
*/
|
||||
public class RequestParamsException extends BizException {
|
||||
private static final long serialVersionUID = 448337090625192516L;
|
||||
|
||||
private static final String DEFAULT_MSG = "用户请求参数错误";
|
||||
|
||||
/**
|
||||
* 使用默认 message 构造新的 {@code RequestParamsException}。
|
||||
* {@code cause} 未初始化,后面可能会通过调用 {@link #initCause} 进行初始化。
|
||||
*/
|
||||
public RequestParamsException() {
|
||||
super(DEFAULT_MSG);
|
||||
}
|
||||
|
||||
/**
|
||||
* 使用指定的 {@code message} 构造新的 {@code RequestParamsException}。
|
||||
* {@code cause} 未初始化,后面可能会通过调用 {@link #initCause} 进行初始化。
|
||||
*
|
||||
* @param message 异常信息
|
||||
*/
|
||||
public RequestParamsException(String message) {
|
||||
super(message);
|
||||
}
|
||||
|
||||
/**
|
||||
* 使用指定的 {@code cause} 构造新的 {@code RequestParamsException}。
|
||||
* {@code message} 为 (cause==null ? null : cause.toString())。
|
||||
*
|
||||
* @param cause 包装的异常
|
||||
*/
|
||||
public RequestParamsException(Throwable cause) {
|
||||
super(cause);
|
||||
}
|
||||
|
||||
/**
|
||||
* 使用指定的 {@code message} 和 {@code cause} 构造新的 {@code RequestParamsException}。
|
||||
*
|
||||
* @param message 异常信息
|
||||
* @param cause 包装的异常
|
||||
*/
|
||||
public RequestParamsException(String message, Throwable cause) {
|
||||
super(message, cause);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
/*
|
||||
* Copyright 2025-present ZhouXY
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
/**
|
||||
* 业务异常
|
||||
*
|
||||
* @author ZhouXY
|
||||
*/
|
||||
package xyz.zhouxy.plusone.commons.exception.business;
|
||||
@@ -0,0 +1,22 @@
|
||||
/*
|
||||
* Copyright 2025-present ZhouXY
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
/**
|
||||
* 包含常见的业务异常与系统异常,以及异常相关的工具
|
||||
*
|
||||
* @author ZhouXY
|
||||
*/
|
||||
package xyz.zhouxy.plusone.commons.exception;
|
||||
@@ -0,0 +1,288 @@
|
||||
/*
|
||||
* Copyright 2024-present ZhouXY
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package xyz.zhouxy.plusone.commons.exception.system;
|
||||
|
||||
import java.util.function.Supplier;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
/**
|
||||
* JdbcUpdateAffectedIncorrectNumberOfRowsException
|
||||
*
|
||||
* <p>
|
||||
* 当数据操作的结果不符合预期时抛出。
|
||||
*
|
||||
* <p>
|
||||
* 比如当一个 insert 或 update 操作时,预计影响数据库中的一行数据,但结果却影响了零条数据或多条数据,
|
||||
* 当出现这种始料未及的诡异情况时,抛出 {@link JdbcUpdateAffectedIncorrectNumberOfRowsException} 并回滚事务。
|
||||
* 后续需要排查原因。
|
||||
*
|
||||
* @author ZhouXY
|
||||
* @since 1.0.0
|
||||
*/
|
||||
public final class JdbcUpdateAffectedIncorrectNumberOfRowsException extends SysException {
|
||||
private static final long serialVersionUID = 992754090625352516L;
|
||||
|
||||
private final long expected;
|
||||
private final long actual;
|
||||
|
||||
/**
|
||||
* 创建一个 {@code JdbcUpdateAffectedIncorrectNumberOfRowsException} 对象
|
||||
*
|
||||
* @param expected 预期影响的行数
|
||||
* @param actual 实际影响的行数
|
||||
*/
|
||||
public JdbcUpdateAffectedIncorrectNumberOfRowsException(long expected, long actual) {
|
||||
super(String.format("The number of rows affected is expected to be %d, but is: %d", expected, actual));
|
||||
this.expected = expected;
|
||||
this.actual = actual;
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建一个 {@code JdbcUpdateAffectedIncorrectNumberOfRowsException} 对象
|
||||
*
|
||||
* @param expected 预期影响的行数
|
||||
* @param actual 实际影响的行数
|
||||
* @param message 错误信息
|
||||
*/
|
||||
public JdbcUpdateAffectedIncorrectNumberOfRowsException(long expected, long actual, String message) {
|
||||
super(message);
|
||||
this.expected = expected;
|
||||
this.actual = actual;
|
||||
}
|
||||
|
||||
/**
|
||||
* 预期影响的行数
|
||||
*
|
||||
* @return the expected
|
||||
*/
|
||||
public long getExpected() {
|
||||
return expected;
|
||||
}
|
||||
|
||||
/**
|
||||
* 实际影响的行数
|
||||
*
|
||||
* @return the actual
|
||||
*/
|
||||
public long getActual() {
|
||||
return actual;
|
||||
}
|
||||
|
||||
// ================================
|
||||
// #region - AffectedRows
|
||||
// ================================
|
||||
|
||||
/**
|
||||
* 当影响的数据量与预计不同时抛出 {@link JdbcUpdateAffectedIncorrectNumberOfRowsException}。
|
||||
*
|
||||
* @param expected 预期影响的行数
|
||||
* @param actualRowCount 实际影响的行数
|
||||
*/
|
||||
public static void checkAffectedRows(int expected, int actualRowCount) {
|
||||
if (expected != actualRowCount) {
|
||||
throw new JdbcUpdateAffectedIncorrectNumberOfRowsException(expected, actualRowCount);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 当影响的数据量与预计不同时抛出 {@link JdbcUpdateAffectedIncorrectNumberOfRowsException}。
|
||||
*
|
||||
* @param expected 预期影响的行数
|
||||
* @param actualRowCount 实际影响的行数
|
||||
* @param errorMessage 异常信息
|
||||
*/
|
||||
public static void checkAffectedRows(int expected, int actualRowCount,
|
||||
@Nullable String errorMessage) {
|
||||
if (expected != actualRowCount) {
|
||||
throw new JdbcUpdateAffectedIncorrectNumberOfRowsException(expected, actualRowCount, errorMessage);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 当影响的数据量与预计不同时抛出 {@link JdbcUpdateAffectedIncorrectNumberOfRowsException}。
|
||||
*
|
||||
* @param expected 预期影响的行数
|
||||
* @param actualRowCount 实际影响的行数
|
||||
* @param errorMessageSupplier 异常信息
|
||||
*/
|
||||
public static void checkAffectedRows(int expected, int actualRowCount,
|
||||
Supplier<String> errorMessageSupplier) {
|
||||
if (expected != actualRowCount) {
|
||||
throw new JdbcUpdateAffectedIncorrectNumberOfRowsException(expected, actualRowCount, errorMessageSupplier.get());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 当影响的数据量与预计不同时抛出 {@link JdbcUpdateAffectedIncorrectNumberOfRowsException}。
|
||||
*
|
||||
* @param expected 预期影响的行数
|
||||
* @param actualRowCount 实际影响的行数
|
||||
* @param errorMessageTemplate 异常信息模板
|
||||
* @param errorMessageArgs 异常信息参数
|
||||
*/
|
||||
public static void checkAffectedRows(int expected, int actualRowCount,
|
||||
String errorMessageTemplate, Object... errorMessageArgs) {
|
||||
if (expected != actualRowCount) {
|
||||
throw new JdbcUpdateAffectedIncorrectNumberOfRowsException(expected, actualRowCount,
|
||||
String.format(errorMessageTemplate, errorMessageArgs));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 当影响的数据量与预计不同时抛出 {@link JdbcUpdateAffectedIncorrectNumberOfRowsException}。
|
||||
*
|
||||
* @param expected 预期影响的行数
|
||||
* @param actualRowCount 实际影响的行数
|
||||
*/
|
||||
public static void checkAffectedRows(long expected, long actualRowCount) {
|
||||
if (expected != actualRowCount) {
|
||||
throw new JdbcUpdateAffectedIncorrectNumberOfRowsException(expected, actualRowCount);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 当影响的数据量与预计不同时抛出 {@link JdbcUpdateAffectedIncorrectNumberOfRowsException}。
|
||||
*
|
||||
* @param expected 预期影响的行数
|
||||
* @param actualRowCount 实际影响的行数
|
||||
* @param errorMessage 异常信息
|
||||
*/
|
||||
public static void checkAffectedRows(long expected, long actualRowCount,
|
||||
@Nullable String errorMessage) {
|
||||
if (expected != actualRowCount) {
|
||||
throw new JdbcUpdateAffectedIncorrectNumberOfRowsException(expected, actualRowCount, errorMessage);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 当影响的数据量与预计不同时抛出 {@link JdbcUpdateAffectedIncorrectNumberOfRowsException}。
|
||||
*
|
||||
* @param expected 预期影响的行数
|
||||
* @param actualRowCount 实际影响的行数
|
||||
* @param errorMessageSupplier 异常信息
|
||||
*/
|
||||
public static void checkAffectedRows(long expected, long actualRowCount,
|
||||
Supplier<String> errorMessageSupplier) {
|
||||
if (expected != actualRowCount) {
|
||||
throw new JdbcUpdateAffectedIncorrectNumberOfRowsException(expected, actualRowCount, errorMessageSupplier.get());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 当影响的数据量与预计不同时抛出 {@link JdbcUpdateAffectedIncorrectNumberOfRowsException}。
|
||||
*
|
||||
* @param expected 预期影响的行数
|
||||
* @param actualRowCount 实际影响的行数
|
||||
* @param errorMessageTemplate 异常信息模板
|
||||
* @param errorMessageArgs 异常信息参数
|
||||
*/
|
||||
public static void checkAffectedRows(long expected, long actualRowCount,
|
||||
String errorMessageTemplate, Object... errorMessageArgs) {
|
||||
if (expected != actualRowCount) {
|
||||
throw new JdbcUpdateAffectedIncorrectNumberOfRowsException(expected, actualRowCount,
|
||||
String.format(errorMessageTemplate, errorMessageArgs));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 当影响的数据量不为 1 时抛出 {@link JdbcUpdateAffectedIncorrectNumberOfRowsException}。
|
||||
*
|
||||
* @param actualRowCount 实际影响的行数
|
||||
*/
|
||||
public static void checkAffectedOneRow(int actualRowCount) {
|
||||
checkAffectedRows(1, actualRowCount);
|
||||
}
|
||||
|
||||
/**
|
||||
* 当影响的数据量不为 1 时抛出 {@link JdbcUpdateAffectedIncorrectNumberOfRowsException}。
|
||||
*
|
||||
* @param actualRowCount 实际影响的行数
|
||||
* @param errorMessage 异常信息
|
||||
*/
|
||||
public static void checkAffectedOneRow(int actualRowCount, String errorMessage) {
|
||||
checkAffectedRows(1, actualRowCount, errorMessage);
|
||||
}
|
||||
|
||||
/**
|
||||
* 当影响的数据量不为 1 时抛出 {@link JdbcUpdateAffectedIncorrectNumberOfRowsException}。
|
||||
*
|
||||
* @param actualRowCount 实际影响的行数
|
||||
* @param errorMessageSupplier 异常信息
|
||||
*/
|
||||
public static void checkAffectedOneRow(int actualRowCount, Supplier<String> errorMessageSupplier) {
|
||||
checkAffectedRows(1, actualRowCount, errorMessageSupplier);
|
||||
}
|
||||
|
||||
/**
|
||||
* 当影响的数据量不为 1 时抛出 {@link JdbcUpdateAffectedIncorrectNumberOfRowsException}。
|
||||
*
|
||||
* @param actualRowCount 实际影响的行数
|
||||
* @param errorMessageTemplate 异常信息模板
|
||||
* @param errorMessageArgs 异常信息参数
|
||||
*/
|
||||
public static void checkAffectedOneRow(int actualRowCount,
|
||||
String errorMessageTemplate, Object... errorMessageArgs) {
|
||||
checkAffectedRows(1, actualRowCount, errorMessageTemplate, errorMessageArgs);
|
||||
}
|
||||
|
||||
/**
|
||||
* 当影响的数据量不为 1 时抛出 {@link JdbcUpdateAffectedIncorrectNumberOfRowsException}。
|
||||
*
|
||||
* @param result 实际影响的数据量
|
||||
*/
|
||||
public static void checkAffectedOneRow(long result) {
|
||||
checkAffectedRows(1L, result);
|
||||
}
|
||||
|
||||
/**
|
||||
* 当影响的数据量不为 1 时抛出 {@link JdbcUpdateAffectedIncorrectNumberOfRowsException}。
|
||||
*
|
||||
* @param actualRowCount 实际影响的行数
|
||||
* @param errorMessage 异常信息
|
||||
*/
|
||||
public static void checkAffectedOneRow(long actualRowCount, String errorMessage) {
|
||||
checkAffectedRows(1L, actualRowCount, errorMessage);
|
||||
}
|
||||
|
||||
/**
|
||||
* 当影响的数据量不为 1 时抛出 {@link JdbcUpdateAffectedIncorrectNumberOfRowsException}。
|
||||
*
|
||||
* @param actualRowCount 实际影响的行数
|
||||
* @param errorMessageSupplier 异常信息
|
||||
*/
|
||||
public static void checkAffectedOneRow(long actualRowCount, Supplier<String> errorMessageSupplier) {
|
||||
checkAffectedRows(1L, actualRowCount, errorMessageSupplier);
|
||||
}
|
||||
|
||||
/**
|
||||
* 当影响的数据量不为 1 时抛出 {@link JdbcUpdateAffectedIncorrectNumberOfRowsException}。
|
||||
*
|
||||
* @param actualRowCount 实际影响的行数
|
||||
* @param errorMessageTemplate 异常信息模板
|
||||
* @param errorMessageArgs 异常信息参数
|
||||
*/
|
||||
public static void checkAffectedOneRow(long actualRowCount,
|
||||
String errorMessageTemplate, Object... errorMessageArgs) {
|
||||
checkAffectedRows(1L, actualRowCount, errorMessageTemplate, errorMessageArgs);
|
||||
}
|
||||
|
||||
// ================================
|
||||
// #endregion - AffectedRows
|
||||
// ================================
|
||||
|
||||
}
|
||||
@@ -0,0 +1,68 @@
|
||||
/*
|
||||
* Copyright 2023-present ZhouXY
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package xyz.zhouxy.plusone.commons.exception.system;
|
||||
|
||||
/**
|
||||
* NoAvailableMacFoundException
|
||||
*
|
||||
* <p>
|
||||
* 在无法找到可访问的 Mac 地址时抛出
|
||||
*
|
||||
* @author ZhouXY
|
||||
* @since 1.0.0
|
||||
*/
|
||||
public class NoAvailableMacFoundException extends SysException {
|
||||
private static final long serialVersionUID = 152827098461071551L;
|
||||
|
||||
/**
|
||||
* 使用默认 message 构造新的 {@code NoAvailableMacFoundException}。
|
||||
* {@code cause} 未初始化,后面可能会通过调用 {@link #initCause} 进行初始化。
|
||||
*/
|
||||
public NoAvailableMacFoundException() {
|
||||
super("无法找到可访问的 Mac 地址");
|
||||
}
|
||||
|
||||
/**
|
||||
* 使用指定的 {@code message} 构造新的 {@code NoAvailableMacFoundException}。
|
||||
* {@code cause} 未初始化,后面可能会通过调用 {@link #initCause} 进行初始化。
|
||||
*
|
||||
* @param message 异常信息
|
||||
*/
|
||||
public NoAvailableMacFoundException(String message) {
|
||||
super(message);
|
||||
}
|
||||
|
||||
/**
|
||||
* 使用指定的 {@code cause} 构造新的 {@code NoAvailableMacFoundException}。
|
||||
* {@code message} 为 (cause==null ? null : cause.toString())。
|
||||
*
|
||||
* @param cause 包装的异常
|
||||
*/
|
||||
public NoAvailableMacFoundException(Throwable cause) {
|
||||
super(cause);
|
||||
}
|
||||
|
||||
/**
|
||||
* 使用指定的 {@code message} 和 {@code cause} 构造新的 {@code NoAvailableMacFoundException}。
|
||||
*
|
||||
* @param message 异常信息
|
||||
* @param cause 包装的异常
|
||||
*/
|
||||
public NoAvailableMacFoundException(String message, Throwable cause) {
|
||||
super(message, cause);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,82 @@
|
||||
/*
|
||||
* Copyright 2024-present ZhouXY
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package xyz.zhouxy.plusone.commons.exception.system;
|
||||
|
||||
/**
|
||||
* 系统异常
|
||||
*
|
||||
* <p>
|
||||
* 通常表示应用代码存在问题,或因环境问题,引发异常。
|
||||
*
|
||||
* @author ZhouXY
|
||||
* @since 1.0.0
|
||||
*/
|
||||
public class SysException extends RuntimeException {
|
||||
private static final long serialVersionUID = -936435090625482516L;
|
||||
|
||||
private static final String DEFAULT_MSG = "系统异常";
|
||||
|
||||
/**
|
||||
* 使用指定的 {@code message} 构造新的系统异常。
|
||||
* {@code cause} 未初始化,后面可能会通过调用 {@link #initCause} 进行初始化。
|
||||
*
|
||||
* @param message 异常信息
|
||||
*/
|
||||
protected SysException(String message) {
|
||||
super(message);
|
||||
}
|
||||
|
||||
/**
|
||||
* 使用指定的 {@code cause} 构造新的系统异常。
|
||||
* {@code message} 为 (cause==null ? null : cause.toString())。
|
||||
*
|
||||
* @param cause 包装的异常
|
||||
*/
|
||||
protected SysException(Throwable cause) {
|
||||
super(cause);
|
||||
}
|
||||
|
||||
/**
|
||||
* 使用指定的 {@code message} 和 {@code cause} 构造新的系统异常。
|
||||
*
|
||||
* @param message 异常信息
|
||||
* @param 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);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
/*
|
||||
* Copyright 2025-present ZhouXY
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
/**
|
||||
* 系统异常
|
||||
*
|
||||
* @author ZhouXY
|
||||
*/
|
||||
package xyz.zhouxy.plusone.commons.exception.system;
|
||||
@@ -0,0 +1,52 @@
|
||||
/*
|
||||
* Copyright 2024-present ZhouXY
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package xyz.zhouxy.plusone.commons.function;
|
||||
|
||||
import com.google.common.annotations.Beta;
|
||||
|
||||
/**
|
||||
* BoolUnaryOperator
|
||||
*
|
||||
* <p>
|
||||
* 一个特殊的 {@link java.util.function.UnaryOperator}。
|
||||
* 表示对 {@code boolean} 值的一元操作。
|
||||
*
|
||||
* @author ZhouXY
|
||||
* @since 1.0.0
|
||||
* @see java.util.function.UnaryOperator
|
||||
*/
|
||||
@Beta
|
||||
@FunctionalInterface
|
||||
public interface BoolUnaryOperator {
|
||||
|
||||
/**
|
||||
* 将此函数应用于给定的 {@code boolean} 参数,返回一个 {@code boolean} 结果。
|
||||
*
|
||||
* @param operand 操作数
|
||||
* @return 结果
|
||||
*/
|
||||
boolean applyAsBool(boolean operand);
|
||||
|
||||
/**
|
||||
* 返回一个 {@code BoolUnaryOperator},该操作符将给定的操作数取反。
|
||||
*
|
||||
* @return {@code BoolUnaryOperator}
|
||||
*/
|
||||
static BoolUnaryOperator not() {
|
||||
return b -> !b;
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2022-2023 the original author or authors.
|
||||
* Copyright 2024-present ZhouXY
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -16,20 +16,28 @@
|
||||
|
||||
package xyz.zhouxy.plusone.commons.function;
|
||||
|
||||
import java.util.OptionalLong;
|
||||
import java.util.function.Function;
|
||||
import com.google.common.annotations.Beta;
|
||||
|
||||
/**
|
||||
* ToOptionalLongFunction
|
||||
* CharUnaryOperator
|
||||
*
|
||||
* <p>
|
||||
* 接受类型为 T 的参数,返回 {@link OptionalLong} 对象。
|
||||
* 一个特殊的 {@link java.util.function.UnaryOperator}。
|
||||
* 表示对 {@code char} 的一元操作。
|
||||
*
|
||||
* @author <a href="https://gitee.com/zhouxy108">ZhouXY</a>
|
||||
* @since 0.1.0
|
||||
* @see OptionalLong
|
||||
* @see Function
|
||||
* @author ZhouXY
|
||||
* @since 1.0.0
|
||||
* @see java.util.function.UnaryOperator
|
||||
*/
|
||||
@Beta
|
||||
@FunctionalInterface
|
||||
public interface ToOptionalLongFunction<T> extends Function<T, OptionalLong> {
|
||||
public interface CharUnaryOperator {
|
||||
|
||||
/**
|
||||
* 将此函数应用于给定的 {@code char} 参数,返回一个 {@code char} 结果。
|
||||
*
|
||||
* @param operand 操作数
|
||||
* @return 结果
|
||||
*/
|
||||
char applyAsChar(char operand);
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2022-2023 the original author or authors.
|
||||
* Copyright 2024-present ZhouXY
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -16,20 +16,25 @@
|
||||
|
||||
package xyz.zhouxy.plusone.commons.function;
|
||||
|
||||
import java.util.OptionalInt;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
/**
|
||||
* OptionalIntSupplier
|
||||
* Executable
|
||||
*
|
||||
* <p>
|
||||
* 返回 {@link OptionalInt} 对象。
|
||||
*
|
||||
* @author <a href="https://gitee.com/zhouxy108">ZhouXY</a>
|
||||
* @since 0.1.0
|
||||
* @see OptionalInt
|
||||
* @see Supplier
|
||||
* 表示一个无入参无返回值的操作,可抛出异常。
|
||||
*
|
||||
* @param <E> 可抛出的异常类型
|
||||
*
|
||||
* @author ZhouXY
|
||||
* @since 1.0.0
|
||||
*/
|
||||
@FunctionalInterface
|
||||
public interface OptionalIntSupplier extends Supplier<OptionalInt> {
|
||||
public interface Executable<E extends Throwable> {
|
||||
|
||||
/**
|
||||
* 执行
|
||||
*
|
||||
* @throws E 可抛出的异常
|
||||
*/
|
||||
void execute() throws E;
|
||||
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2022-2023 the original author or authors.
|
||||
* Copyright 2023-present ZhouXY
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -24,9 +24,9 @@ import java.util.function.Supplier;
|
||||
*
|
||||
* <p>
|
||||
* 返回 {@code Optional<T>} 对象。
|
||||
*
|
||||
* @author <a href="https://gitee.com/zhouxy108">ZhouXY</a>
|
||||
* @since 0.1.0
|
||||
*
|
||||
* @author ZhouXY
|
||||
* @since 1.0.0
|
||||
* @see Optional
|
||||
* @see Supplier
|
||||
*/
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2022-2023 the original author or authors.
|
||||
* Copyright 2023-present ZhouXY
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -19,38 +19,37 @@ package xyz.zhouxy.plusone.commons.function;
|
||||
import java.util.function.Predicate;
|
||||
|
||||
/**
|
||||
* Predicates
|
||||
* PredicateTools
|
||||
*
|
||||
* <p>
|
||||
* {@link Predicate} 相关操作。
|
||||
* </p>
|
||||
*
|
||||
* @author <a href="https://gitee.com/zhouxy108">ZhouXY</a>
|
||||
* @since 0.1.0
|
||||
* @author ZhouXY
|
||||
* @since 1.0.0
|
||||
* @see Predicate
|
||||
*/
|
||||
public class Predicates {
|
||||
public class PredicateTools {
|
||||
|
||||
/**
|
||||
* 将 lambda 表达式或者方法引用指明为对应类型的 {@link Predicate} 对象。
|
||||
* 如将 {@code Objects::nonNull} 明确地指定为 {@code Predicate<String>},
|
||||
* 使之可以链式调用 {@link Predicate#and(Predicate)}、{@link Predicate#or(Predicate)}
|
||||
* 等方法,连接其它 {@code Predicate<? super T>} 对象。
|
||||
*
|
||||
*
|
||||
* <pre>
|
||||
* Predicate<String> predicate = Predicates.<String>of(Objects::nonNull)
|
||||
* Predicate<String> predicate = PredicateTools.<String>from(Objects::nonNull)
|
||||
* .and(StringUtils::isNotEmpty);
|
||||
* </pre>
|
||||
*
|
||||
*
|
||||
* @param <T> 目标类型
|
||||
* @param predicate 原 {@link Predicate} 实例
|
||||
* @return 包装的 {@link Predicate} 实例
|
||||
* @param predicate Lambda 表达式
|
||||
* @return 传入的表达式自动成为 {@link Predicate} 实例
|
||||
*/
|
||||
public static <T> Predicate<T> of(Predicate<? super T> predicate) {
|
||||
return predicate::test;
|
||||
public static <T> Predicate<T> from(Predicate<T> predicate) {
|
||||
return predicate;
|
||||
}
|
||||
|
||||
private Predicates() {
|
||||
private PredicateTools() {
|
||||
throw new IllegalStateException("Utility class");
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2022-2023 the original author or authors.
|
||||
* Copyright 2024-present ZhouXY
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -16,20 +16,25 @@
|
||||
|
||||
package xyz.zhouxy.plusone.commons.function;
|
||||
|
||||
import java.util.Optional;
|
||||
import java.util.function.DoubleFunction;
|
||||
|
||||
/**
|
||||
* DoubleToOptionalFunction
|
||||
* ThrowingConsumer
|
||||
*
|
||||
* <p>
|
||||
* 接受类型为 double 的参数,返回 {@code Optional<R>} 对象。
|
||||
* 允许抛出异常的消费操作。是一个特殊的 {@link java.util.function.Consumer}。
|
||||
*
|
||||
* @author <a href="https://gitee.com/zhouxy108">ZhouXY</a>
|
||||
* @since 0.1.0
|
||||
* @see Optional
|
||||
* @see DoubleFunction
|
||||
* @author ZhouXY
|
||||
* @since 1.0.0
|
||||
* @see java.util.function.Consumer
|
||||
*/
|
||||
@FunctionalInterface
|
||||
public interface DoubleToOptionalFunction<R> extends DoubleFunction<Optional<R>> {
|
||||
public interface ThrowingConsumer<T, E extends Throwable> {
|
||||
|
||||
/**
|
||||
* 消费给定的参数,允许抛出异常
|
||||
*
|
||||
* @param t 要消费的参数
|
||||
* @throws E 抛出的异常
|
||||
*/
|
||||
void accept(T t) throws E;
|
||||
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
/*
|
||||
* Copyright 2025-present ZhouXY
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package xyz.zhouxy.plusone.commons.function;
|
||||
|
||||
/**
|
||||
* ThrowingFunction
|
||||
*
|
||||
* <p>
|
||||
* 接收一个参数,并返回一个结果,可以抛出异常。
|
||||
*
|
||||
* @param <T> 入参类型
|
||||
* @param <R> 返回结果类型
|
||||
* @param <E> 异常类型
|
||||
*
|
||||
* @author ZhouXY
|
||||
* @since 1.0
|
||||
* @see java.util.function.Function
|
||||
*/
|
||||
@FunctionalInterface
|
||||
public interface ThrowingFunction<T, R, E extends Throwable> {
|
||||
|
||||
/**
|
||||
* 接收一个参数,并返回一个结果,可以抛出异常。
|
||||
*
|
||||
* @param t 入参
|
||||
* @return 函数结果
|
||||
* @throws E 抛出的异常
|
||||
*/
|
||||
R apply(T t) throws E;
|
||||
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2022-2023 the original author or authors.
|
||||
* Copyright 2024-present ZhouXY
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -16,20 +16,25 @@
|
||||
|
||||
package xyz.zhouxy.plusone.commons.function;
|
||||
|
||||
import java.util.OptionalInt;
|
||||
import java.util.function.Function;
|
||||
|
||||
/**
|
||||
* ToOptionalIntFunction
|
||||
* ThrowingPredicate
|
||||
*
|
||||
* <p>
|
||||
* 接受类型为 T 的参数,返回 {@link OptionalInt} 对象。
|
||||
* 接收一个参数,返回一个布尔值,可抛出异常。
|
||||
*
|
||||
* @author <a href="https://gitee.com/zhouxy108">ZhouXY</a>
|
||||
* @since 0.1.0
|
||||
* @see OptionalInt
|
||||
* @see Function
|
||||
* @author ZhouXY
|
||||
* @since 1.0.0
|
||||
* @see java.util.function.Predicate
|
||||
*/
|
||||
@FunctionalInterface
|
||||
public interface ToOptionalIntFunction<T> extends Function<T, OptionalInt> {
|
||||
public interface ThrowingPredicate<T, E extends Throwable> {
|
||||
|
||||
/**
|
||||
* 对给定的参数进行评估
|
||||
*
|
||||
* @param t 入参
|
||||
* @return 入参符合条件时返回 {@code true},否则返回 {@code false}
|
||||
* @throws E 抛出的异常
|
||||
*/
|
||||
boolean test(T t) throws E;
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2022-2023 the original author or authors.
|
||||
* Copyright 2024-present ZhouXY
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -16,20 +16,28 @@
|
||||
|
||||
package xyz.zhouxy.plusone.commons.function;
|
||||
|
||||
import java.util.OptionalDouble;
|
||||
import java.util.function.Function;
|
||||
|
||||
/**
|
||||
* ToOptionalDoubleFunction
|
||||
* ThrowingSupplier
|
||||
*
|
||||
* <p>
|
||||
* 接受类型为 T 的参数,返回 {@link OptionalDouble} 对象。
|
||||
* 允许抛出异常的 Supplier 接口。
|
||||
*
|
||||
* @author <a href="https://gitee.com/zhouxy108">ZhouXY</a>
|
||||
* @since 0.1.0
|
||||
* @see OptionalDouble
|
||||
* @see Function
|
||||
* @param <T> 结果类型
|
||||
* @param <E> 异常类型
|
||||
*
|
||||
* @author ZhouXY
|
||||
* @since 1.0.0
|
||||
* @see java.util.function.Supplier
|
||||
*/
|
||||
@FunctionalInterface
|
||||
public interface ToOptionalDoubleFunction<T> extends Function<T, OptionalDouble> {
|
||||
public interface ThrowingSupplier<T, E extends Throwable> {
|
||||
|
||||
/**
|
||||
* 获取一个结果,允许抛出异常。
|
||||
*
|
||||
* @return 结果
|
||||
* @throws E 允许抛出的异常
|
||||
*/
|
||||
T get() throws E;
|
||||
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2022-2023 the original author or authors.
|
||||
* Copyright 2023-present ZhouXY
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -25,8 +25,8 @@ import java.util.function.BiFunction;
|
||||
* <p>
|
||||
* 接受类型为 T 和 U 的两个参数,返回 {@code Optional<R>} 对象。
|
||||
*
|
||||
* @author <a href="https://gitee.com/zhouxy108">ZhouXY</a>
|
||||
* @since 0.1.0
|
||||
* @author ZhouXY
|
||||
* @since 1.0.0
|
||||
* @see Optional
|
||||
* @see BiFunction
|
||||
*/
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2022-2023 the original author or authors.
|
||||
* Copyright 2023-present ZhouXY
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -25,8 +25,8 @@ import java.util.function.Function;
|
||||
* <p>
|
||||
* 接受类型为 T 的参数,返回 {@code Optional<R>} 对象。
|
||||
*
|
||||
* @author <a href="https://gitee.com/zhouxy108">ZhouXY</a>
|
||||
* @since 0.1.0
|
||||
* @author ZhouXY
|
||||
* @since 1.0.0
|
||||
* @see Optional
|
||||
* @see Function
|
||||
*/
|
||||
@@ -0,0 +1,44 @@
|
||||
/*
|
||||
* Copyright 2025-present ZhouXY
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
/**
|
||||
* <h2>函数式编程</h2>
|
||||
*
|
||||
* <h3>1. PredicateTools</h3>
|
||||
* <p>
|
||||
* {@link PredicateTools} 用于 {@link java.util.function.Predicate} 的相关操作。
|
||||
*
|
||||
* <h3>2. Functional interfaces</h3>
|
||||
* <p>
|
||||
* 补充可能用得上的函数式接口:
|
||||
* <pre>
|
||||
* | Group | FunctionalInterface | method |
|
||||
* | ------------- | -------------------- | -------------------------------- |
|
||||
* | UnaryOperator | BoolUnaryOperator | boolean applyAsBool (boolean) |
|
||||
* | UnaryOperator | CharUnaryOperator | char applyAsChar(char) |
|
||||
* | Throwing | Executable | void execute() throws E |
|
||||
* | Throwing | ThrowingConsumer | void accept(T) throws E |
|
||||
* | Throwing | ThrowingFunction | R apply(T) throws E |
|
||||
* | Throwing | ThrowingPredicate | boolean test(T) throws E |
|
||||
* | Throwing | ThrowingSupplier | T get() throws E |
|
||||
* | Optional | OptionalSupplier | Optional<T> get() throws E |
|
||||
* | Optional | ToOptionalBiFunction | Optional<R> apply(T,U) |
|
||||
* | Optional | ToOptionalFunction | Optional<R> apply(T) |
|
||||
* </pre>
|
||||
*
|
||||
* @author ZhouXY
|
||||
*/
|
||||
package xyz.zhouxy.plusone.commons.function;
|
||||
@@ -0,0 +1,169 @@
|
||||
/*
|
||||
* Copyright 2025-present ZhouXY
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package xyz.zhouxy.plusone.commons.gson.adapter;
|
||||
|
||||
import static xyz.zhouxy.plusone.commons.util.AssertTools.checkArgumentNotNull;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.time.Instant;
|
||||
import java.time.LocalDate;
|
||||
import java.time.LocalDateTime;
|
||||
import java.time.ZonedDateTime;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
import java.time.temporal.TemporalAccessor;
|
||||
import java.time.temporal.TemporalQuery;
|
||||
|
||||
import com.google.gson.TypeAdapter;
|
||||
import com.google.gson.stream.JsonReader;
|
||||
import com.google.gson.stream.JsonWriter;
|
||||
|
||||
/**
|
||||
* 包含 JSR-310 相关数据类型的 {@code TypeAdapter}
|
||||
*
|
||||
* @author ZhouXY
|
||||
* @since 1.1.0
|
||||
* @see TypeAdapter
|
||||
* @see com.google.gson.GsonBuilder
|
||||
*/
|
||||
public class JSR310TypeAdapters {
|
||||
|
||||
/**
|
||||
* {@code LocalDate} 的 {@code TypeAdapter},
|
||||
* 用于 Gson 对 {@code LocalDate} 进行相互转换。
|
||||
*/
|
||||
public static final class LocalDateTypeAdapter
|
||||
extends TemporalAccessorTypeAdapter<LocalDate, LocalDateTypeAdapter> {
|
||||
|
||||
/**
|
||||
* 默认构造函数,
|
||||
* 使用 {@link DateTimeFormatter#ISO_LOCAL_DATE} 进行 {@link LocalDate} 的序列化与反序列化。
|
||||
*/
|
||||
public LocalDateTypeAdapter() {
|
||||
this(DateTimeFormatter.ISO_LOCAL_DATE);
|
||||
}
|
||||
|
||||
/**
|
||||
* 构造函数,
|
||||
* 使用传入的 {@link DateTimeFormatter} 进行 {@link LocalDate} 的序列化与反序列化。
|
||||
*
|
||||
* @param formatter 用于序列化 {@link LocalDate} 的格式化器,不可为 {@code null}。
|
||||
*/
|
||||
public LocalDateTypeAdapter(DateTimeFormatter formatter) {
|
||||
super(LocalDate::from, formatter);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@code LocalDateTime} 的 {@code TypeAdapter},
|
||||
* 用于 Gson 对 {@code LocalDateTime} 进行相互转换。
|
||||
*/
|
||||
public static final class LocalDateTimeTypeAdapter
|
||||
extends TemporalAccessorTypeAdapter<LocalDateTime, LocalDateTimeTypeAdapter> {
|
||||
|
||||
/**
|
||||
* 默认构造函数,
|
||||
* 使用 {@link DateTimeFormatter#ISO_LOCAL_DATE_TIME} 进行 {@link LocalDateTime} 的序列化与反序列化。
|
||||
*/
|
||||
public LocalDateTimeTypeAdapter() {
|
||||
this(DateTimeFormatter.ISO_LOCAL_DATE_TIME);
|
||||
}
|
||||
|
||||
/**
|
||||
* 构造函数,
|
||||
* 使用传入的 {@link DateTimeFormatter} 进行 {@link LocalDateTime} 的序列化与反序列化。
|
||||
*
|
||||
* @param formatter 用于序列化 {@link LocalDateTime} 的格式化器,不可为 {@code null}。
|
||||
*/
|
||||
public LocalDateTimeTypeAdapter(DateTimeFormatter formatter) {
|
||||
super(LocalDateTime::from, formatter);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@code ZonedDateTime} 的 {@code TypeAdapter},
|
||||
* 用于 Gson 对 {@code ZonedDateTime} 进行相互转换。
|
||||
*/
|
||||
public static final class ZonedDateTimeTypeAdapter
|
||||
extends TemporalAccessorTypeAdapter<ZonedDateTime, ZonedDateTimeTypeAdapter> {
|
||||
|
||||
/**
|
||||
* 默认构造函数,
|
||||
* 使用 {@link DateTimeFormatter#ISO_ZONED_DATE_TIME} 进行 {@link ZonedDateTime} 的序列化与反序列化。
|
||||
*/
|
||||
public ZonedDateTimeTypeAdapter() {
|
||||
this(DateTimeFormatter.ISO_ZONED_DATE_TIME);
|
||||
}
|
||||
|
||||
/**
|
||||
* 构造函数,
|
||||
* 使用传入的 {@link DateTimeFormatter} 进行 {@link ZonedDateTime} 的序列化与反序列化。
|
||||
*
|
||||
* @param formatter 用于序列化 {@link ZonedDateTime} 的格式化器,不可为 {@code null}。
|
||||
*/
|
||||
public ZonedDateTimeTypeAdapter(DateTimeFormatter formatter) {
|
||||
super(ZonedDateTime::from, formatter);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@code Instant} 的 {@code TypeAdapter},
|
||||
* 用于 Gson 对 {@code Instant} 进行相互转换。
|
||||
*
|
||||
* <p>
|
||||
* 使用 {@link DateTimeFormatter#ISO_INSTANT} 进行 {@link Instant} 的序列化与反序列化。
|
||||
*
|
||||
*/
|
||||
public static final class InstantTypeAdapter
|
||||
extends TemporalAccessorTypeAdapter<Instant, InstantTypeAdapter> {
|
||||
|
||||
public InstantTypeAdapter() {
|
||||
super(Instant::from, DateTimeFormatter.ISO_INSTANT);
|
||||
}
|
||||
}
|
||||
|
||||
private abstract static class TemporalAccessorTypeAdapter<
|
||||
T extends TemporalAccessor,
|
||||
TTypeAdapter extends TemporalAccessorTypeAdapter<T, TTypeAdapter>>
|
||||
extends TypeAdapter<T> {
|
||||
|
||||
private final TemporalQuery<T> temporalQuery;
|
||||
|
||||
private final DateTimeFormatter dateTimeFormatter;
|
||||
|
||||
protected TemporalAccessorTypeAdapter(
|
||||
TemporalQuery<T> temporalQuery, DateTimeFormatter dateTimeFormatter) {
|
||||
checkArgumentNotNull(dateTimeFormatter, "formatter must not be null.");
|
||||
this.temporalQuery = temporalQuery;
|
||||
this.dateTimeFormatter = dateTimeFormatter;
|
||||
}
|
||||
|
||||
/** {@inheritDoc} */
|
||||
@Override
|
||||
public void write(JsonWriter out, T value) throws IOException {
|
||||
out.value(dateTimeFormatter.format(value));
|
||||
}
|
||||
|
||||
/** {@inheritDoc} */
|
||||
@Override
|
||||
public T read(JsonReader in) throws IOException {
|
||||
return dateTimeFormatter.parse(in.nextString(), temporalQuery);
|
||||
}
|
||||
}
|
||||
|
||||
private JSR310TypeAdapters() {
|
||||
throw new IllegalStateException("Utility class");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
/*
|
||||
* Copyright 2025-present ZhouXY
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Gson 相关类型适配器
|
||||
*/
|
||||
package xyz.zhouxy.plusone.commons.gson.adapter;
|
||||
@@ -0,0 +1,20 @@
|
||||
/*
|
||||
* Copyright 2025-present ZhouXY
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Gson 相关辅助工具
|
||||
*/
|
||||
package xyz.zhouxy.plusone.commons.gson;
|
||||
@@ -0,0 +1,283 @@
|
||||
/*
|
||||
* Copyright 2024-present ZhouXY
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package xyz.zhouxy.plusone.commons.model;
|
||||
|
||||
import static xyz.zhouxy.plusone.commons.util.AssertTools.checkArgument;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.time.LocalDate;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.regex.Matcher;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
import com.google.common.base.Strings;
|
||||
import com.google.common.collect.ImmutableMap;
|
||||
import com.google.errorprone.annotations.Immutable;
|
||||
|
||||
import xyz.zhouxy.plusone.commons.annotation.ReaderMethod;
|
||||
import xyz.zhouxy.plusone.commons.annotation.ValueObject;
|
||||
import xyz.zhouxy.plusone.commons.constant.PatternConsts;
|
||||
import xyz.zhouxy.plusone.commons.util.StringTools;
|
||||
|
||||
/**
|
||||
* Chinese2ndGenIDCardNumber
|
||||
*
|
||||
* <p>
|
||||
* 中国第二代居民身份证号
|
||||
*
|
||||
* @author ZhouXY
|
||||
* @since 1.0.0
|
||||
* @see xyz.zhouxy.plusone.commons.constant.PatternConsts#CHINESE_2ND_ID_CARD_NUMBER
|
||||
*/
|
||||
@ValueObject
|
||||
@Immutable
|
||||
public class Chinese2ndGenIDCardNumber
|
||||
implements IDCardNumber, Comparable<Chinese2ndGenIDCardNumber>, Serializable {
|
||||
private static final long serialVersionUID = 5655592250204184210L;
|
||||
|
||||
/** 身份证号码 */
|
||||
private final String value;
|
||||
|
||||
/** 省份编码 */
|
||||
private final String provinceCode;
|
||||
/** 市级编码 */
|
||||
private final String cityCode;
|
||||
/** 县级编码 */
|
||||
private final String countyCode;
|
||||
/** 性别 */
|
||||
private final Gender gender;
|
||||
/** 出生日期 */
|
||||
private final LocalDate birthDate;
|
||||
|
||||
private static final DateTimeFormatter DATE_FORMATTER = DateTimeFormatter.ofPattern("yyyyMMdd");
|
||||
|
||||
private Chinese2ndGenIDCardNumber(String value,
|
||||
String provinceCode, String cityCode, String countyCode,
|
||||
Gender gender, LocalDate birthDate) {
|
||||
this.value = value;
|
||||
this.provinceCode = provinceCode;
|
||||
this.cityCode = cityCode;
|
||||
this.countyCode = countyCode;
|
||||
this.gender = gender;
|
||||
this.birthDate = birthDate;
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据身份证号码创建 {@code Chinese2ndGenIDCardNumber} 对象
|
||||
*
|
||||
* @param idCardNumber 身份证号码值
|
||||
* @return {@code Chinese2ndGenIDCardNumber} 对象
|
||||
*/
|
||||
public static Chinese2ndGenIDCardNumber of(final String idCardNumber) {
|
||||
try {
|
||||
checkArgument(StringTools.isNotBlank(idCardNumber), "二代居民身份证校验失败:号码为空");
|
||||
final String value = idCardNumber.toUpperCase();
|
||||
final Matcher matcher = PatternConsts.CHINESE_2ND_ID_CARD_NUMBER.matcher(value);
|
||||
checkArgument(matcher.matches(), () -> "二代居民身份证校验失败:" + value);
|
||||
|
||||
final String provinceCode = matcher.group("province");
|
||||
checkArgument(Chinese2ndGenIDCardNumber.PROVINCE_CODES.containsKey(provinceCode));
|
||||
|
||||
final String cityCode = matcher.group("city");
|
||||
final String countyCode = matcher.group("county");
|
||||
|
||||
// 出生日期
|
||||
final String birthDateStr = matcher.group("birthDate");
|
||||
final LocalDate birthDate = LocalDate.parse(birthDateStr, DATE_FORMATTER);
|
||||
|
||||
// 性别
|
||||
final int genderCode = Integer.parseInt(matcher.group("gender"));
|
||||
final Gender gender = genderCode % 2 == 0 ? Gender.FEMALE : Gender.MALE;
|
||||
|
||||
return new Chinese2ndGenIDCardNumber(value, provinceCode, cityCode, countyCode, gender, birthDate);
|
||||
}
|
||||
catch (IllegalArgumentException e) {
|
||||
throw e;
|
||||
}
|
||||
catch (Exception e) {
|
||||
throw new IllegalArgumentException(e);
|
||||
}
|
||||
}
|
||||
|
||||
// ================================
|
||||
// #region - reader methods
|
||||
// ================================
|
||||
|
||||
@Override
|
||||
@ReaderMethod
|
||||
public String value() {
|
||||
return value;
|
||||
}
|
||||
|
||||
/**
|
||||
* 所属省份代码
|
||||
*
|
||||
* @return 所属省份代码
|
||||
*/
|
||||
@ReaderMethod
|
||||
public String getProvinceCode() {
|
||||
return provinceCode;
|
||||
}
|
||||
|
||||
/**
|
||||
* 所属省份名称
|
||||
*
|
||||
* @return 所属省份名称
|
||||
*/
|
||||
@ReaderMethod
|
||||
public String getProvinceName() {
|
||||
return PROVINCE_CODES.get(this.provinceCode);
|
||||
}
|
||||
|
||||
/**
|
||||
* 所属省份完整行政区划代码
|
||||
*
|
||||
* @return 所属省份完整行政区划代码
|
||||
*/
|
||||
@ReaderMethod
|
||||
public String getFullProvinceCode() {
|
||||
return Strings.padEnd(this.provinceCode, 12, '0');
|
||||
}
|
||||
|
||||
/**
|
||||
* 所属市级代码
|
||||
*
|
||||
* @return 所属市级代码
|
||||
*/
|
||||
@ReaderMethod
|
||||
public String getCityCode() {
|
||||
return cityCode;
|
||||
}
|
||||
|
||||
/**
|
||||
* 所属市级完整行政区划代码
|
||||
*
|
||||
* @return 所属市级完整行政区划代码
|
||||
*/
|
||||
@ReaderMethod
|
||||
public String getFullCityCode() {
|
||||
return Strings.padEnd(this.cityCode, 12, '0');
|
||||
}
|
||||
|
||||
/**
|
||||
* 所属县级代码
|
||||
*
|
||||
* @return 所属县级代码
|
||||
*/
|
||||
@ReaderMethod
|
||||
public String getCountyCode() {
|
||||
return countyCode;
|
||||
}
|
||||
|
||||
/**
|
||||
* 所属县级完整行政区划代码
|
||||
*
|
||||
* @return 所属县级完整行政区划代码
|
||||
*/
|
||||
@ReaderMethod
|
||||
public String getFullCountyCode() {
|
||||
return Strings.padEnd(this.countyCode, 12, '0');
|
||||
}
|
||||
|
||||
@ReaderMethod
|
||||
@Override
|
||||
public Gender getGender() {
|
||||
return gender;
|
||||
}
|
||||
|
||||
@ReaderMethod
|
||||
@Override
|
||||
public LocalDate getBirthDate() {
|
||||
return birthDate;
|
||||
}
|
||||
|
||||
// ================================
|
||||
// #endregion - reader methods
|
||||
// ================================
|
||||
|
||||
/** 省份代码表 */
|
||||
public static final Map<String, String> PROVINCE_CODES = ImmutableMap.<String, String>builder()
|
||||
.put("11", "北京")
|
||||
.put("12", "天津")
|
||||
.put("13", "河北")
|
||||
.put("14", "山西")
|
||||
.put("15", "内蒙古")
|
||||
.put("21", "辽宁")
|
||||
.put("22", "吉林")
|
||||
.put("23", "黑龙江")
|
||||
.put("31", "上海")
|
||||
.put("32", "江苏")
|
||||
.put("33", "浙江")
|
||||
.put("34", "安徽")
|
||||
.put("35", "福建")
|
||||
.put("36", "江西")
|
||||
.put("37", "山东")
|
||||
.put("41", "河南")
|
||||
.put("42", "湖北")
|
||||
.put("43", "湖南")
|
||||
.put("44", "广东")
|
||||
.put("45", "广西")
|
||||
.put("46", "海南")
|
||||
.put("50", "重庆")
|
||||
.put("51", "四川")
|
||||
.put("52", "贵州")
|
||||
.put("53", "云南")
|
||||
.put("54", "西藏")
|
||||
.put("61", "陕西")
|
||||
.put("62", "甘肃")
|
||||
.put("63", "青海")
|
||||
.put("64", "宁夏")
|
||||
.put("65", "新疆")
|
||||
.put("71", "台湾")
|
||||
.put("81", "香港")
|
||||
.put("82", "澳门")
|
||||
.put("83", "台湾") // 台湾身份证号码以83开头,但是行政区划为71
|
||||
.put("91", "国外")
|
||||
.build();
|
||||
|
||||
@SuppressWarnings("null")
|
||||
@Override
|
||||
public int compareTo(Chinese2ndGenIDCardNumber o) {
|
||||
return value.compareTo(o.value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hash(value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(@Nullable Object obj) {
|
||||
if (this == obj) {
|
||||
return true;
|
||||
}
|
||||
if (!(obj instanceof Chinese2ndGenIDCardNumber)) {
|
||||
return false;
|
||||
}
|
||||
Chinese2ndGenIDCardNumber other = (Chinese2ndGenIDCardNumber) obj;
|
||||
return Objects.equals(value, other.value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return value();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,91 @@
|
||||
/*
|
||||
* Copyright 2024-present ZhouXY
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package xyz.zhouxy.plusone.commons.model;
|
||||
|
||||
import static xyz.zhouxy.plusone.commons.util.AssertTools.checkCondition;
|
||||
|
||||
import xyz.zhouxy.plusone.commons.base.IWithIntCode;
|
||||
|
||||
/**
|
||||
* 性别
|
||||
*
|
||||
* @author ZhouXY
|
||||
*/
|
||||
public enum Gender implements IWithIntCode {
|
||||
UNKNOWN(0, "Unknown", "未知"),
|
||||
MALE(1, "Male", "男"),
|
||||
FEMALE(2, "Female", "女"),
|
||||
|
||||
;
|
||||
|
||||
private static final Gender[] VALUES = new Gender[] { UNKNOWN, MALE, FEMALE };
|
||||
|
||||
private final int value;
|
||||
private final String displayName;
|
||||
private final String displayNameZh;
|
||||
|
||||
Gender(int value, String displayName, String displayNameZh) {
|
||||
this.value = value;
|
||||
this.displayName = displayName;
|
||||
this.displayNameZh = displayNameZh;
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据码值获取对应枚举
|
||||
*
|
||||
* @param value 码值
|
||||
* @return 枚举值
|
||||
*/
|
||||
public static Gender of(int value) {
|
||||
checkCondition(0 <= value && value < VALUES.length,
|
||||
() -> new EnumConstantNotPresentException(Gender.class, String.valueOf(value)));
|
||||
return VALUES[value];
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取枚举码值
|
||||
*
|
||||
* @return 码值
|
||||
*/
|
||||
public int getValue() {
|
||||
return value;
|
||||
}
|
||||
|
||||
/**
|
||||
* 枚举名称
|
||||
*
|
||||
* @return 枚举名称
|
||||
*/
|
||||
public String getDisplayName() {
|
||||
return displayName;
|
||||
}
|
||||
|
||||
/**
|
||||
* 枚举中文名称
|
||||
*
|
||||
* @return 枚举中文名称
|
||||
*/
|
||||
public String getDisplayNameZh() {
|
||||
return displayNameZh;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getCode() {
|
||||
return getValue();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,105 @@
|
||||
/*
|
||||
* Copyright 2024-present ZhouXY
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package xyz.zhouxy.plusone.commons.model;
|
||||
|
||||
import java.time.LocalDate;
|
||||
import java.time.Period;
|
||||
|
||||
import xyz.zhouxy.plusone.commons.util.StringTools;
|
||||
|
||||
/**
|
||||
* 身份证号
|
||||
*
|
||||
* @author ZhouXY
|
||||
*/
|
||||
public interface IDCardNumber {
|
||||
|
||||
char DEFAULT_REPLACED_CHAR = '*';
|
||||
int DEFAULT_DISPLAY_FRONT = 1;
|
||||
int DEFAULT_DISPLAY_END = 2;
|
||||
|
||||
/**
|
||||
* 身份证号
|
||||
*
|
||||
* @return 身份证号
|
||||
*/
|
||||
String value();
|
||||
|
||||
/**
|
||||
* 根据身份证号判断性别
|
||||
*
|
||||
* @return {@link Gender} 对象
|
||||
*/
|
||||
Gender getGender();
|
||||
|
||||
/**
|
||||
* 获取出生日期
|
||||
*
|
||||
* @return 出生日期
|
||||
*/
|
||||
LocalDate getBirthDate();
|
||||
|
||||
/**
|
||||
* 计算年龄
|
||||
*
|
||||
* @return 年龄
|
||||
*/
|
||||
default int getAge() {
|
||||
LocalDate now = LocalDate.now();
|
||||
return Period.between(getBirthDate(), now).getYears();
|
||||
}
|
||||
|
||||
// ================================
|
||||
// #region - toString
|
||||
// ================================
|
||||
|
||||
/**
|
||||
* 脱敏字符串(前面留 1 位,后面留 2 位)
|
||||
*
|
||||
* @return 脱敏字符串
|
||||
*/
|
||||
default String toDesensitizedString() {
|
||||
return StringTools.desensitize(value(), DEFAULT_REPLACED_CHAR, DEFAULT_DISPLAY_FRONT, DEFAULT_DISPLAY_END);
|
||||
}
|
||||
|
||||
/**
|
||||
* 脱敏字符串
|
||||
*
|
||||
* @param front 前面保留的字符数
|
||||
* @param end 后面保留的字符数
|
||||
* @return 脱敏字符串
|
||||
*/
|
||||
default String toDesensitizedString(int front, int end) {
|
||||
return StringTools.desensitize(value(), DEFAULT_REPLACED_CHAR, front, end);
|
||||
}
|
||||
|
||||
/**
|
||||
* 脱敏字符串
|
||||
*
|
||||
* @param replacedChar 替换字符
|
||||
* @param front 前面保留的字符数
|
||||
* @param end 后面保留的字符数
|
||||
* @return 脱敏字符串
|
||||
*/
|
||||
default String toDesensitizedString(char replacedChar, int front, int end) {
|
||||
return StringTools.desensitize(value(), replacedChar, front, end);
|
||||
}
|
||||
|
||||
// ================================
|
||||
// #endregion - toString
|
||||
// ================================
|
||||
}
|
||||
@@ -0,0 +1,275 @@
|
||||
/*
|
||||
* Copyright 2025-present ZhouXY
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package xyz.zhouxy.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 ZhouXY
|
||||
* @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;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,133 @@
|
||||
/*
|
||||
* Copyright 2023-present ZhouXY
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package xyz.zhouxy.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;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import javax.annotation.Nonnull;
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
import xyz.zhouxy.plusone.commons.annotation.ReaderMethod;
|
||||
|
||||
/**
|
||||
* 带校验的字符串值对象
|
||||
*
|
||||
* @author ZhouXY
|
||||
* @since 1.0.0
|
||||
*
|
||||
* @deprecated 弃用。使用工厂方法创建对象,并在其中进行校验即可。
|
||||
*/
|
||||
@Deprecated
|
||||
public abstract class ValidatableStringRecord<T extends ValidatableStringRecord<T>> // NOSONAR 暂不删除
|
||||
implements Comparable<T> {
|
||||
|
||||
@Nonnull
|
||||
private final String value;
|
||||
|
||||
private final Matcher matcher;
|
||||
|
||||
/**
|
||||
* 构造字符串值对象
|
||||
*
|
||||
* @param value 字符串值
|
||||
* @param pattern 正则
|
||||
*/
|
||||
protected ValidatableStringRecord(String value, Pattern pattern) {
|
||||
this(value, pattern, "Invalid value");
|
||||
}
|
||||
|
||||
/**
|
||||
* 构造字符串值对象
|
||||
*
|
||||
* @param value 字符串值
|
||||
* @param pattern 正则
|
||||
* @param errorMessageSupplier 正则不匹配时的错误信息
|
||||
*/
|
||||
protected ValidatableStringRecord(String value, Pattern pattern,
|
||||
Supplier<String> errorMessageSupplier) {
|
||||
this(value, pattern, errorMessageSupplier.get());
|
||||
}
|
||||
|
||||
/**
|
||||
* 构造字符串值对象
|
||||
*
|
||||
* @param value 字符串值
|
||||
* @param pattern 正则
|
||||
* @param errorMessage 正则不匹配时的错误信息
|
||||
*/
|
||||
protected ValidatableStringRecord(String value, Pattern pattern, String errorMessage) {
|
||||
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;
|
||||
}
|
||||
|
||||
/**
|
||||
* 值对象的字符串值。
|
||||
*
|
||||
* @return 字符串(不为空)
|
||||
*/
|
||||
@ReaderMethod
|
||||
public final String value() {
|
||||
return this.value;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int compareTo(@SuppressWarnings("null") T o) {
|
||||
return this.value.compareTo(o.value());
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hash(getClass(), value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(@Nullable Object obj) {
|
||||
if (this == obj)
|
||||
return true;
|
||||
if (obj == null)
|
||||
return false;
|
||||
if (getClass() != obj.getClass())
|
||||
return false;
|
||||
@SuppressWarnings("unchecked")
|
||||
T other = (T) obj;
|
||||
return Objects.equals(value, other.value());
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return this.value();
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取正则匹配结果
|
||||
*
|
||||
* @return {@code Matcher} 对象
|
||||
*/
|
||||
protected final Matcher getMatcher() {
|
||||
return matcher;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,92 @@
|
||||
/*
|
||||
* Copyright 2024-present ZhouXY
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package xyz.zhouxy.plusone.commons.model.dto;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
import xyz.zhouxy.plusone.commons.annotation.StaticFactoryMethod;
|
||||
import xyz.zhouxy.plusone.commons.collection.CollectionTools;
|
||||
|
||||
/**
|
||||
* 返回分页查询的结果
|
||||
*
|
||||
* @param <T> 内容列表的元素类型
|
||||
*
|
||||
* @author ZhouXY
|
||||
* @see PagingAndSortingQueryParams
|
||||
*/
|
||||
public class PageResult<T> {
|
||||
|
||||
private final long total;
|
||||
|
||||
private final List<T> content;
|
||||
|
||||
private PageResult(@Nullable final List<T> content, final long total) {
|
||||
this.content = CollectionTools.nullToEmptyList(content);
|
||||
this.total = total;
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建一个分页查询的结果
|
||||
*
|
||||
* @param <T> 内容类型
|
||||
* @param content 一页数据
|
||||
* @param total 总数据量
|
||||
* @return 分页查询的结果
|
||||
*/
|
||||
@StaticFactoryMethod(PageResult.class)
|
||||
public static <T> PageResult<T> of(@Nullable final List<T> content, final long total) {
|
||||
return new PageResult<>(content, total);
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建一个空的分页查询的结果
|
||||
*
|
||||
* @param <T> 内容类型
|
||||
* @return 空结果
|
||||
*/
|
||||
@StaticFactoryMethod(PageResult.class)
|
||||
public static <T> PageResult<T> empty() {
|
||||
return new PageResult<>(Collections.emptyList(), 0L);
|
||||
}
|
||||
|
||||
/**
|
||||
* 总数据量
|
||||
*
|
||||
* @return 总数据量
|
||||
*/
|
||||
public long getTotal() {
|
||||
return total;
|
||||
}
|
||||
|
||||
/**
|
||||
* 一页数据
|
||||
*
|
||||
* @return 一页数据
|
||||
*/
|
||||
public List<T> getContent() {
|
||||
return content;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "PageResult [total=" + total + ", content=" + content + "]";
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,293 @@
|
||||
/*
|
||||
* Copyright 2022-present ZhouXY
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package xyz.zhouxy.plusone.commons.model.dto;
|
||||
|
||||
import static com.google.common.base.Preconditions.checkArgument;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.regex.Pattern;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
import com.google.common.collect.ImmutableMap;
|
||||
|
||||
import xyz.zhouxy.plusone.commons.collection.CollectionTools;
|
||||
import xyz.zhouxy.plusone.commons.util.RegexTools;
|
||||
import xyz.zhouxy.plusone.commons.util.StringTools;
|
||||
|
||||
/**
|
||||
* 分页排序查询参数
|
||||
*
|
||||
* <p>
|
||||
* 包含三个主要的属性:
|
||||
* <ul>
|
||||
* <li>size - 每页显示的记录数</li>
|
||||
* <li>pageNum - 当前页码</li>
|
||||
* <li>orderBy - 排序条件</li>
|
||||
* </ul>
|
||||
*
|
||||
* <p>
|
||||
* 分页必须伴随着排序,不然可能出现同一个对象重复出现在不同页,有的对象不被查询到的情况。
|
||||
*
|
||||
* <p>
|
||||
* 其中 {@code orderBy} 是一个 {@code List<String>},可以指定多个排序条件。
|
||||
* 每个排序条件是一个字符串, 格式为“属性名-ASC”或“属性名-DESC”,分别表示升序和降序。
|
||||
* 例如,当 {@code orderBy} 的值为 {@code ["name-ASC","age-DESC"]},
|
||||
* 意味着要按 {@code name} 进行升序排列,{@code name} 相同的情况下则按 {@code age} 进行降序排列。
|
||||
*
|
||||
* <p>
|
||||
* 用户可继承 {@link PagingAndSortingQueryParams} 构建自己的分页查询入参,
|
||||
* 子类需在构造器中调用 {@link PagingAndSortingQueryParams} 的构造器,
|
||||
* 传入一个 {@link PagingParamsBuilder} 用于构建分页参数。
|
||||
* 同一场景下,复用一个 {@link PagingParamsBuilder} 实例即可。
|
||||
*
|
||||
* <p>
|
||||
* 构建 {@link PagingParamsBuilder} 时,需传入一个 {@code Map} 作为可排序字段的白名单,
|
||||
* {@code key} 是供前端指定用于排序的属性名,{@code value} 是对应数据库中的字段名。
|
||||
* 只有在此白名单中的属性名才允许用于排序。
|
||||
*
|
||||
* <pre>
|
||||
* class AccountQueryParams extends PagingAndSortingQueryParams {
|
||||
* private static final Map<String, String> PROPERTY_COLUMN_MAP = ImmutableMap.<String, String>builder()
|
||||
* .put("id", "id")
|
||||
* .put("username", "username")
|
||||
* .build();
|
||||
* private static final PagingParamsBuilder PAGING_PARAMS_BUILDER = PagingAndSortingQueryParams
|
||||
* .pagingParamsBuilder(20, 100, PROPERTY_COLUMN_MAP);
|
||||
*
|
||||
* public AccountQueryParams() {
|
||||
* // 所有的 AccountQueryParams 复用同一个 PagingParamsBuilder 实例
|
||||
* super(PAGING_PARAMS_BUILDER);
|
||||
* }
|
||||
*
|
||||
* private @Getter @Setter Long id;
|
||||
* private @Getter @Setter String username;
|
||||
* private @Getter @Setter String email;
|
||||
* private @Getter @Setter Integer status;
|
||||
* }
|
||||
*
|
||||
* public PageResult<AccountVO> queryPage(AccountQueryParams params) {
|
||||
* // 获取分页参数
|
||||
* PagingParams pagingParams = params.buildPagingParams();
|
||||
* // 从 params 获取字段查询条件,从 pagingParams 获取分页条件,查询一页数据
|
||||
* List<AccountVO> list = accountQueries.queryAccountList(params, pagingParams);
|
||||
* // 查询总记录数
|
||||
* long count = accountQueries.countAccount(params);
|
||||
* // 返回分页结果
|
||||
* return PageResult.of(list, count);
|
||||
* }
|
||||
* </pre>
|
||||
*
|
||||
* @author ZhouXY
|
||||
* @see PagingParams
|
||||
* @see PageResult
|
||||
*/
|
||||
public class PagingAndSortingQueryParams {
|
||||
|
||||
private final PagingParamsBuilder pagingParamsBuilder;
|
||||
|
||||
private Integer size;
|
||||
private Long pageNum;
|
||||
private List<String> orderBy;
|
||||
|
||||
/**
|
||||
* 创建一个 {@code PagingAndSortingQueryParams} 实例
|
||||
*
|
||||
* @param pagingParamsBuilder
|
||||
* 分页参数构造器。
|
||||
* 通过 {@link #pagingParamsBuilder(int, int, Map)} 创建,同一场景下只需要共享同一个实例。
|
||||
*/
|
||||
protected PagingAndSortingQueryParams(PagingParamsBuilder pagingParamsBuilder) {
|
||||
this.pagingParamsBuilder = pagingParamsBuilder;
|
||||
}
|
||||
|
||||
// Setters
|
||||
|
||||
/**
|
||||
* 设置排序规则
|
||||
*
|
||||
* @param orderBy 排序规则,不能为空
|
||||
*/
|
||||
public final void setOrderBy(List<String> orderBy) {
|
||||
this.orderBy = orderBy;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置每页大小
|
||||
*
|
||||
* @param size 每页大小
|
||||
*/
|
||||
public final void setSize(@Nullable Integer size) {
|
||||
this.size = size;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置页码
|
||||
*
|
||||
* @param pageNum 页码
|
||||
*/
|
||||
public final void setPageNum(@Nullable Long pageNum) {
|
||||
this.pageNum = pageNum;
|
||||
}
|
||||
|
||||
// Setters end
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "PagingAndSortingQueryParams ["
|
||||
+ "size=" + size
|
||||
+ ", pageNum=" + pageNum
|
||||
+ ", orderBy=" + orderBy
|
||||
+ "]";
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建一个分页参数构造器
|
||||
*
|
||||
* @param defaultSize 默认每页大小
|
||||
* @param maxSize 最大每页大小
|
||||
* @param sortableProperties
|
||||
* 可排序属性。
|
||||
* key 是供前端指定用于排序的属性名,value 是对应数据库中的字段名。
|
||||
* 只有在此白名单中的属性名才允许用于排序。
|
||||
* @return 分页参数构造器
|
||||
*/
|
||||
protected static PagingParamsBuilder pagingParamsBuilder(
|
||||
int defaultSize, int maxSize, Map<String, String> sortableProperties) {
|
||||
return new PagingParamsBuilder(defaultSize, maxSize, sortableProperties);
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据当前查询参数,构建分页参数
|
||||
*
|
||||
* @return 分页参数
|
||||
*/
|
||||
public PagingParams buildPagingParams() {
|
||||
return this.pagingParamsBuilder.buildPagingParams(this);
|
||||
}
|
||||
|
||||
/**
|
||||
* 可排序属性
|
||||
*/
|
||||
public static final class SortableProperty {
|
||||
private final String propertyName;
|
||||
private final String columnName;
|
||||
private final String orderType;
|
||||
|
||||
private final String sqlSnippet;
|
||||
|
||||
private SortableProperty(String propertyName, String columnName, String orderType) {
|
||||
this.propertyName = propertyName;
|
||||
this.columnName = columnName;
|
||||
checkArgument("ASC".equalsIgnoreCase(orderType) || "DESC".equalsIgnoreCase(orderType));
|
||||
this.orderType = orderType.toUpperCase();
|
||||
|
||||
this.sqlSnippet = this.propertyName + " " + this.orderType;
|
||||
}
|
||||
|
||||
/**
|
||||
* 属性名
|
||||
*
|
||||
* @return 属性名
|
||||
*/
|
||||
public String getPropertyName() {
|
||||
return propertyName;
|
||||
}
|
||||
|
||||
/**
|
||||
* 对应数据库中列名称
|
||||
*
|
||||
* @return 列名称
|
||||
*/
|
||||
public String getColumnName() {
|
||||
return columnName;
|
||||
}
|
||||
|
||||
/**
|
||||
* 排序方式
|
||||
*
|
||||
* @return 排序方式
|
||||
*/
|
||||
public String getOrderType() {
|
||||
return orderType;
|
||||
}
|
||||
|
||||
/**
|
||||
* SQL 片段
|
||||
*
|
||||
* @return SQL 片段
|
||||
*/
|
||||
public String getSqlSnippet() {
|
||||
return sqlSnippet;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "SortableProperty ["
|
||||
+ "propertyName=" + propertyName
|
||||
+ ", columnName=" + columnName
|
||||
+ ", orderType=" + orderType
|
||||
+ "]";
|
||||
}
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,93 @@
|
||||
/*
|
||||
* Copyright 2024-present ZhouXY
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package xyz.zhouxy.plusone.commons.model.dto;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
import xyz.zhouxy.plusone.commons.model.dto.PagingAndSortingQueryParams.SortableProperty;
|
||||
|
||||
/**
|
||||
* 分页参数
|
||||
*
|
||||
* @author ZhouXY
|
||||
* @see PagingAndSortingQueryParams
|
||||
*/
|
||||
public class PagingParams {
|
||||
|
||||
/** 每页大小 */
|
||||
private final int size;
|
||||
/** 当前页码 */
|
||||
private final long pageNum;
|
||||
/** 偏移量 */
|
||||
private final long offset;
|
||||
/** 排序 */
|
||||
private final List<SortableProperty> orderBy;
|
||||
|
||||
PagingParams(int size, long pageNum, List<SortableProperty> orderBy) {
|
||||
this.size = size;
|
||||
this.pageNum = pageNum;
|
||||
this.offset = (pageNum - 1) * size;
|
||||
this.orderBy = orderBy;
|
||||
}
|
||||
|
||||
// Getters
|
||||
|
||||
/**
|
||||
* 排序规则
|
||||
*
|
||||
* @return 排序规则
|
||||
*/
|
||||
public final List<SortableProperty> getOrderBy() {
|
||||
return Collections.unmodifiableList(this.orderBy);
|
||||
}
|
||||
|
||||
/**
|
||||
* 每页大小
|
||||
*
|
||||
* @return 每页大小
|
||||
*/
|
||||
public final int getSize() {
|
||||
return this.size;
|
||||
}
|
||||
|
||||
/**
|
||||
* 当前页码
|
||||
*
|
||||
* @return 当前页码
|
||||
*/
|
||||
public final long getPageNum() {
|
||||
return this.pageNum;
|
||||
}
|
||||
|
||||
/**
|
||||
* 偏移量
|
||||
*
|
||||
* @return 偏移量
|
||||
*/
|
||||
public final long getOffset() {
|
||||
return this.offset;
|
||||
}
|
||||
|
||||
// Getters end
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "PageInfo [size=" + size + ", pageNum=" + pageNum + ", orderBy=" + orderBy + ", offset="
|
||||
+ getOffset() + "]";
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,116 @@
|
||||
/*
|
||||
* Copyright 2023-present ZhouXY
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package xyz.zhouxy.plusone.commons.model.dto;
|
||||
|
||||
import java.util.Objects;
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
/**
|
||||
* 统一结果,对返回给前端的数据进行封装。
|
||||
*
|
||||
* @author ZhouXY
|
||||
*/
|
||||
public class UnifiedResponse<T> {
|
||||
|
||||
private final String code;
|
||||
private final String message;
|
||||
|
||||
private final @Nullable T data;
|
||||
|
||||
// ================================
|
||||
// #region - Constructors
|
||||
// ================================
|
||||
|
||||
/**
|
||||
* 构造 {@code UnifiedResponse}
|
||||
*
|
||||
* @param code 状态码
|
||||
* @param message 响应信息
|
||||
*/
|
||||
UnifiedResponse(String code, @Nullable String message) {
|
||||
this(code, message, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* 构造 {@code UnifiedResponse}
|
||||
*
|
||||
* @param code 状态码
|
||||
* @param message 响应信息
|
||||
* @param data 响应数据
|
||||
*/
|
||||
UnifiedResponse(String code, @Nullable String message, @Nullable T data) {
|
||||
this.code = Objects.requireNonNull(code);
|
||||
this.message = message == null ? "" : message;
|
||||
this.data = data;
|
||||
}
|
||||
|
||||
// ================================
|
||||
// #endregion - Constructors
|
||||
// ================================
|
||||
|
||||
// ================================
|
||||
// #region - Getters
|
||||
// ================================
|
||||
|
||||
/**
|
||||
* 状态码
|
||||
*
|
||||
* @return 状态码
|
||||
*/
|
||||
public String getCode() {
|
||||
return code;
|
||||
}
|
||||
|
||||
/**
|
||||
* 响应信息
|
||||
*
|
||||
* @return 响应信息
|
||||
*/
|
||||
public String getMessage() {
|
||||
return message;
|
||||
}
|
||||
|
||||
/**
|
||||
* 响应数据
|
||||
*
|
||||
* @return 响应数据
|
||||
*/
|
||||
@Nullable
|
||||
public T getData() {
|
||||
return data;
|
||||
}
|
||||
|
||||
// ================================
|
||||
// #endregion - Getters
|
||||
// ================================
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return String.format("{code: \"%s\", message: \"%s\", data: %s}",
|
||||
this.code, this.message, transValue(this.data));
|
||||
}
|
||||
|
||||
private static String transValue(@Nullable Object value) {
|
||||
if (value == null) {
|
||||
return null;
|
||||
}
|
||||
if (value instanceof String) {
|
||||
return "\"" + value + "\"";
|
||||
}
|
||||
return String.valueOf(value);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,191 @@
|
||||
/*
|
||||
* Copyright 2025-present ZhouXY
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package xyz.zhouxy.plusone.commons.model.dto;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
/**
|
||||
* {@link UnifiedResponse} 工厂类。
|
||||
* 用于快速构建 {@link UnifiedResponse} 对象,默认的成功代码为 {@code 2000000}。
|
||||
*
|
||||
* <p>
|
||||
* 用户可以继承 {@link UnifiedResponses} 实现自己的工厂类,
|
||||
* 自定义 SUCCESS_CODE 和 DEFAULT_SUCCESS_MSG,以及工厂方法。
|
||||
* 如下所示:
|
||||
* <pre>
|
||||
* // 自定义工厂类
|
||||
* public static class CustomUnifiedResponses extends UnifiedResponses {
|
||||
*
|
||||
* public static final String SUCCESS_CODE = "000";
|
||||
* public static final String DEFAULT_SUCCESS_MSG = "成功";
|
||||
*
|
||||
* public static <T> UnifiedResponse<T> success() {
|
||||
* return of(SUCCESS_CODE, DEFAULT_SUCCESS_MSG);
|
||||
* }
|
||||
*
|
||||
* public static <T> UnifiedResponse<T> success(@Nullable String message) {
|
||||
* return of(SUCCESS_CODE, message);
|
||||
* }
|
||||
*
|
||||
* public static <T> UnifiedResponse<T> success(@Nullable String message, @Nullable T data) {
|
||||
* return of(SUCCESS_CODE, message, data);
|
||||
* }
|
||||
*
|
||||
* private CustomUnifiedResponses() {
|
||||
* super();
|
||||
* }
|
||||
* }
|
||||
* // 使用自定义工厂类
|
||||
* CustomUnifiedResponses.success("查询成功", userList); // 状态码为 000
|
||||
* </pre>
|
||||
* 见 <a href="http://zhouxy.xyz:3000/plusone/plusone-commons/issues/22">issue#22</a>。
|
||||
*
|
||||
* @author ZhouXY
|
||||
* @since 1.0.0
|
||||
* @see UnifiedResponse
|
||||
*/
|
||||
public class UnifiedResponses {
|
||||
|
||||
public static final String SUCCESS_CODE = "2000000";
|
||||
public static final String DEFAULT_SUCCESS_MSG = "SUCCESS";
|
||||
|
||||
// ================================
|
||||
// #region - success
|
||||
// ================================
|
||||
|
||||
/**
|
||||
* 默认成功响应结果
|
||||
*
|
||||
* @param <T> data 类型
|
||||
* @return {@code UnifiedResponse} 对象。
|
||||
* {@code code} = "2000000", {@code message} = "SUCCESS", {@code data} = null
|
||||
*/
|
||||
public static <T> UnifiedResponse<T> success() {
|
||||
return new UnifiedResponse<>(SUCCESS_CODE, DEFAULT_SUCCESS_MSG);
|
||||
}
|
||||
|
||||
/**
|
||||
* 使用指定 {@code message} 创建成功响应结果
|
||||
*
|
||||
* @param message 成功信息
|
||||
* @param <T> data 类型
|
||||
* @return {@code UnifiedResponse} 对象。
|
||||
* {@code code} = "2000000", {@code data} = null
|
||||
*/
|
||||
public static <T> UnifiedResponse<T> success(@Nullable String message) {
|
||||
return new UnifiedResponse<>(SUCCESS_CODE, message);
|
||||
}
|
||||
|
||||
/**
|
||||
* 使用指定 {@code message} 和 {@code data} 创建成功响应结果
|
||||
*
|
||||
* @param <T> data 类型
|
||||
* @param message 成功信息
|
||||
* @param data 携带数据
|
||||
* @return {@code UnifiedResponse} 对象。
|
||||
* {@code code} = "2000000"
|
||||
*/
|
||||
public static <T> UnifiedResponse<T> success(@Nullable String message, @Nullable T data) {
|
||||
return new UnifiedResponse<>(SUCCESS_CODE, message, data);
|
||||
}
|
||||
|
||||
// ================================
|
||||
// #endregion - success
|
||||
// ================================
|
||||
|
||||
// ================================
|
||||
// #region - error
|
||||
// ================================
|
||||
|
||||
/**
|
||||
* 创建错误响应结果
|
||||
*
|
||||
* @param code 错误码
|
||||
* @param message 错误信息
|
||||
* @param <T> data 类型
|
||||
* @return {@code UnifiedResponse} 对象({@code data} 为 {@code null})
|
||||
*/
|
||||
public static <T> UnifiedResponse<T> error(String code, @Nullable String message) {
|
||||
return new UnifiedResponse<>(code, message);
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建错误响应结果
|
||||
*
|
||||
* @param <T> data 类型
|
||||
* @param code 错误码
|
||||
* @param message 错误信息
|
||||
* @param data 携带数据
|
||||
* @return {@code UnifiedResponse} 对象
|
||||
*/
|
||||
public static <T> UnifiedResponse<T> error(String code, @Nullable String message, @Nullable T data) {
|
||||
return new UnifiedResponse<>(code, message, data);
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建错误响应结果
|
||||
*
|
||||
* @param code 错误码
|
||||
* @param e 异常
|
||||
* @return {@code UnifiedResponse} 对象。
|
||||
* {@code message} 为异常的 {@code message},
|
||||
* {@code data} 为 {@code null}。
|
||||
*/
|
||||
public static <T> UnifiedResponse<T> error(String code, Throwable e) {
|
||||
return new UnifiedResponse<>(code, e.getMessage());
|
||||
}
|
||||
|
||||
// ================================
|
||||
// #endregion - error
|
||||
// ================================
|
||||
|
||||
// ================================
|
||||
// #region - of
|
||||
// ================================
|
||||
|
||||
/**
|
||||
* 创建响应结果
|
||||
*
|
||||
* @param code 状态码
|
||||
* @param message 响应信息
|
||||
* @param <T> data 类型
|
||||
* @return {@code UnifiedResponse} 对象({@code data} 为 {@code null})
|
||||
*/
|
||||
public static <T> UnifiedResponse<T> of(String code, @Nullable String message) {
|
||||
return new UnifiedResponse<>(code, message);
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建响应结果
|
||||
*
|
||||
* @param <T> data 类型
|
||||
* @param code 状态码
|
||||
* @param message 响应信息
|
||||
* @param data 携带数据
|
||||
* @return {@code UnifiedResponse} 对象
|
||||
*/
|
||||
public static <T> UnifiedResponse<T> of(String code, @Nullable String message, @Nullable T data) {
|
||||
return new UnifiedResponse<>(code, message, data);
|
||||
}
|
||||
|
||||
// ================================
|
||||
// #endregion - of
|
||||
// ================================
|
||||
|
||||
protected UnifiedResponses() {
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,66 @@
|
||||
/*
|
||||
* Copyright 2025-present ZhouXY
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
/**
|
||||
* <h2>数据传输对象</h2>
|
||||
*
|
||||
* <h3>1. 分页</h3>
|
||||
* <p>
|
||||
* 分页组件由 {@link PagingAndSortingQueryParams} 作为入参,
|
||||
* 因为分页必须伴随着排序,不然可能出现同一个对象重复出现在不同页,有的对象不被查询到的情况,
|
||||
* 所以分页查询的入参必须包含排序条件。
|
||||
*
|
||||
* <p>
|
||||
* 用户可继承 {@link PagingAndSortingQueryParams}
|
||||
* 构建自己的分页查询入参,需在构造器中调用 {@link PagingAndSortingQueryParams} 的构造器,传入一个 Map 作为白名单,
|
||||
* key 是供前端指定用于排序的属性名,value 是对应数据库中的字段名,只有在白名单中指定的属性名才允许作为排序条件。
|
||||
*
|
||||
* <p>
|
||||
* {@link PagingAndSortingQueryParams} 包含三个主要的属性:
|
||||
* <ul>
|
||||
* <li>size - 每页显示的记录数</li>
|
||||
* <li>pageNum - 当前页码</li>
|
||||
* <li>orderBy - 排序条件</li>
|
||||
* </ul>
|
||||
* 其中 orderBy 是一个 List,可以指定多个排序条件,每个排序条件是一个字符串,
|
||||
* 格式为“属性名-ASC”或“属性名-DESC”,分别表示升序和降序。
|
||||
*
|
||||
* <p>
|
||||
* 比如前端传入的 orderBy 为 ["name-ASC","age-DESC"],意味着要按 name 进行升序,name 相同的情况下则按 age 进行降序。
|
||||
*
|
||||
* <p>
|
||||
* 使用时调用 {@link PagingAndSortingQueryParams#buildPagingParams()} 方法获取分页参数 {@link PagingParams}。
|
||||
*
|
||||
* <p>
|
||||
* 分页结果可以存放到 {@link PageResult} 中,作为出参。
|
||||
*
|
||||
* <h3>2. {@link UnifiedResponse}</h3>
|
||||
* <p>
|
||||
* {@link UnifiedResponse} 对返回给前端的数据进行封装,包含 code、message、data。
|
||||
*
|
||||
* <p>
|
||||
* {@link UnifiedResponses} 用于快速构建 {@link UnifiedResponse} 对象,默认的成功代码为 {@code 2000000}。
|
||||
*
|
||||
* <p>
|
||||
* 用户可以继承 {@link UnifiedResponses} 实现自己的工厂类,
|
||||
* 自定义 SUCCESS_CODE 和 DEFAULT_SUCCESS_MSG,以及工厂方法。
|
||||
*
|
||||
* @author ZhouXY
|
||||
*/
|
||||
@ParametersAreNonnullByDefault
|
||||
package xyz.zhouxy.plusone.commons.model.dto;
|
||||
|
||||
import javax.annotation.ParametersAreNonnullByDefault;
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2022-2023 the original author or authors.
|
||||
* Copyright 2025-present ZhouXY
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -14,22 +14,14 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package xyz.zhouxy.plusone.commons.function;
|
||||
|
||||
import java.util.OptionalLong;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
/**
|
||||
* OptionalLongSupplier
|
||||
*
|
||||
* <h2>业务建模组件</h2>
|
||||
* <p>
|
||||
* 返回 {@link OptionalLong} 对象。
|
||||
*
|
||||
* @author <a href="https://gitee.com/zhouxy108">ZhouXY</a>
|
||||
* @since 0.1.0
|
||||
* @see OptionalLong
|
||||
* @see Supplier
|
||||
* 包含业务建模可能用到的性别、身份证等元素,也包含 DTO 相关类,如分页查询参数,响应结果,分页结果等。
|
||||
*
|
||||
* @author ZhouXY
|
||||
*/
|
||||
@FunctionalInterface
|
||||
public interface OptionalLongSupplier extends Supplier<OptionalLong> {
|
||||
}
|
||||
@ParametersAreNonnullByDefault
|
||||
package xyz.zhouxy.plusone.commons.model;
|
||||
|
||||
import javax.annotation.ParametersAreNonnullByDefault;
|
||||
@@ -0,0 +1,273 @@
|
||||
/*
|
||||
* Copyright 2024-present ZhouXY
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package xyz.zhouxy.plusone.commons.time;
|
||||
|
||||
import static xyz.zhouxy.plusone.commons.util.AssertTools.checkNotNull;
|
||||
import static xyz.zhouxy.plusone.commons.util.AssertTools.checkCondition;
|
||||
|
||||
import java.time.DateTimeException;
|
||||
import java.time.Month;
|
||||
import java.time.MonthDay;
|
||||
import java.time.temporal.ChronoField;
|
||||
|
||||
import com.google.common.collect.Range;
|
||||
|
||||
import xyz.zhouxy.plusone.commons.annotation.StaticFactoryMethod;
|
||||
import xyz.zhouxy.plusone.commons.base.IWithIntCode;
|
||||
|
||||
/**
|
||||
* 季度
|
||||
*
|
||||
* @author ZhouXY
|
||||
*/
|
||||
public enum Quarter implements IWithIntCode {
|
||||
/** 第一季度 */
|
||||
Q1(1),
|
||||
/** 第二季度 */
|
||||
Q2(2),
|
||||
/** 第三季度 */
|
||||
Q3(3),
|
||||
/** 第四季度 */
|
||||
Q4(4),
|
||||
;
|
||||
|
||||
/** 季度值 (1/2/3/4) */
|
||||
private final int value;
|
||||
|
||||
/** 月份范围 */
|
||||
private final Range<Integer> monthRange;
|
||||
|
||||
/** 常量值 */
|
||||
private static final Quarter[] ENUMS = Quarter.values();
|
||||
|
||||
/**
|
||||
* @param value 季度值 (1/2/3/4)
|
||||
*/
|
||||
Quarter(int value) {
|
||||
this.value = value;
|
||||
|
||||
final int lastMonth = value * 3;
|
||||
final int firstMonth = lastMonth - 2;
|
||||
|
||||
this.monthRange = Range.closed(firstMonth, lastMonth);
|
||||
}
|
||||
|
||||
// ================================
|
||||
// #region - StaticFactoryMethods
|
||||
// ================================
|
||||
|
||||
/**
|
||||
* 根据给定的月份值返回对应的季度
|
||||
*
|
||||
* @param monthValue 月份值,取值范围为1到12
|
||||
* @return 对应的季度
|
||||
* @throws IllegalArgumentException 如果月份值不在有效范围内(1到12),将抛出异常
|
||||
*/
|
||||
@StaticFactoryMethod(Quarter.class)
|
||||
public static Quarter fromMonth(int monthValue) {
|
||||
ChronoField.MONTH_OF_YEAR.checkValidValue(monthValue);
|
||||
return of(computeQuarterValueInternal(monthValue));
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据给定的月份返回对应的季度
|
||||
*
|
||||
* @param month 月份
|
||||
* @return 对应的季度
|
||||
*/
|
||||
@StaticFactoryMethod(Quarter.class)
|
||||
public static Quarter fromMonth(Month month) {
|
||||
checkNotNull(month);
|
||||
final int monthValue = month.getValue();
|
||||
return of(computeQuarterValueInternal(monthValue));
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据指定的年份,获取一个新的 YearQuarter 实例
|
||||
* 此方法允许在保持当前季度信息不变的情况下,更改年份
|
||||
*
|
||||
* @param year 指定的年份
|
||||
* @return 返回一个新的 YearQuarter 实例,年份更新为指定的年份
|
||||
*/
|
||||
public final YearQuarter atYear(int year) {
|
||||
return YearQuarter.of(year, this);
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据给定的季度值返回对应的季度
|
||||
*
|
||||
* @param value 季度值 (1/2/3/4)
|
||||
* @return 对应的季度
|
||||
* @throws IllegalArgumentException 如果季度值不在有效范围内(1到4),将抛出异常
|
||||
*/
|
||||
@StaticFactoryMethod(Quarter.class)
|
||||
public static Quarter of(int value) {
|
||||
return ENUMS[checkValidIntValue(value) - 1];
|
||||
}
|
||||
|
||||
// ================================
|
||||
// #endregion - StaticFactoryMethods
|
||||
// ================================
|
||||
|
||||
// ================================
|
||||
// #region - computes
|
||||
// ================================
|
||||
|
||||
/**
|
||||
* 加上指定数量的季度
|
||||
*
|
||||
* @param quarters 所添加的季度数量
|
||||
* @return 计算结果
|
||||
*/
|
||||
public Quarter plus(long quarters) {
|
||||
final int amount = (int) ((quarters % 4) + 4);
|
||||
return ENUMS[(ordinal() + amount) % 4];
|
||||
}
|
||||
|
||||
/**
|
||||
* 减去指定数量的季度
|
||||
*
|
||||
* @param quarters 所减去的季度数量
|
||||
* @return 计算结果
|
||||
*/
|
||||
public Quarter minus(long quarters) {
|
||||
return plus(-(quarters % 4));
|
||||
}
|
||||
|
||||
// ================================
|
||||
// #endregion - computes
|
||||
// ================================
|
||||
|
||||
// ================================
|
||||
// #region - Getters
|
||||
// ================================
|
||||
|
||||
/**
|
||||
* 获取季度值
|
||||
*
|
||||
* @return 季度值
|
||||
*/
|
||||
public int getValue() {
|
||||
return value;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getCode() {
|
||||
return getValue();
|
||||
}
|
||||
|
||||
/**
|
||||
* 该季度的第一个月
|
||||
*
|
||||
* @return {@code Month} 对象
|
||||
*/
|
||||
public Month firstMonth() {
|
||||
return Month.of(firstMonthValue());
|
||||
}
|
||||
|
||||
/**
|
||||
* 该季度的第一个月
|
||||
*
|
||||
* @return 月份值从 1 开始,1 表示 1月,以此类推。
|
||||
*/
|
||||
public int firstMonthValue() {
|
||||
return this.monthRange.lowerEndpoint();
|
||||
}
|
||||
|
||||
/**
|
||||
* 该季度的最后一个月
|
||||
*
|
||||
* @return {@code Month} 对象
|
||||
*/
|
||||
public Month lastMonth() {
|
||||
return Month.of(lastMonthValue());
|
||||
}
|
||||
|
||||
/**
|
||||
* 该季度的最后一个月
|
||||
*
|
||||
* @return 月份值从 1 开始,1 表示 1月,以此类推。
|
||||
*/
|
||||
public int lastMonthValue() {
|
||||
return this.monthRange.upperEndpoint();
|
||||
}
|
||||
|
||||
/**
|
||||
* 该季度的第一天
|
||||
*
|
||||
* @return {@code MonthDay} 对象
|
||||
*/
|
||||
public MonthDay firstMonthDay() {
|
||||
return MonthDay.of(firstMonth(), 1);
|
||||
}
|
||||
|
||||
/**
|
||||
* 该季度的最后一天
|
||||
*
|
||||
* @return {@code MonthDay} 对象
|
||||
*/
|
||||
public MonthDay lastMonthDay() {
|
||||
// 季度的最后一个月不可能是 2 月
|
||||
final Month month = lastMonth();
|
||||
return MonthDay.of(month, month.maxLength());
|
||||
}
|
||||
|
||||
/**
|
||||
* 计算该季度的第一天为当年的第几天
|
||||
*
|
||||
* @param leapYear 是否为闰年
|
||||
* @return day of year
|
||||
*/
|
||||
public int firstDayOfYear(boolean leapYear) {
|
||||
return firstMonth().firstDayOfYear(leapYear);
|
||||
}
|
||||
|
||||
// ================================
|
||||
// #endregion - Getters
|
||||
// ================================
|
||||
|
||||
/**
|
||||
* 检查给定的季度值是否有效
|
||||
*
|
||||
* @param value 季度值
|
||||
* @return 如果给定的季度值有效则返回该值
|
||||
* @throws DateTimeException 如果给定的季度值不在有效范围内(1到4),将抛出异常
|
||||
*/
|
||||
public static int checkValidIntValue(int value) {
|
||||
checkCondition(value >= 1 && value <= 4,
|
||||
() -> new DateTimeException("Invalid value for Quarter: " + value));
|
||||
return value;
|
||||
}
|
||||
|
||||
// ================================
|
||||
// #region - Internal
|
||||
// ================================
|
||||
|
||||
/**
|
||||
* 计算给定月份对应的季度值
|
||||
*
|
||||
* @param monthValue 月份值,取值范围为1到12
|
||||
* @return 对应的季度值
|
||||
*/
|
||||
private static int computeQuarterValueInternal(int monthValue) {
|
||||
return (monthValue - 1) / 3 + 1;
|
||||
}
|
||||
|
||||
// ================================
|
||||
// #endregion - Internal
|
||||
// ================================
|
||||
}
|
||||
@@ -0,0 +1,393 @@
|
||||
/*
|
||||
* Copyright 2024-present ZhouXY
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package xyz.zhouxy.plusone.commons.time;
|
||||
|
||||
import static java.time.temporal.ChronoField.YEAR;
|
||||
import static xyz.zhouxy.plusone.commons.util.AssertTools.checkNotNull;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.time.LocalDate;
|
||||
import java.time.Month;
|
||||
import java.time.YearMonth;
|
||||
import java.time.ZoneId;
|
||||
import java.time.temporal.ChronoField;
|
||||
import java.util.Calendar;
|
||||
import java.util.Date;
|
||||
import java.util.Objects;
|
||||
import java.util.TimeZone;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
import com.google.errorprone.annotations.Immutable;
|
||||
|
||||
import xyz.zhouxy.plusone.commons.annotation.StaticFactoryMethod;
|
||||
|
||||
/**
|
||||
* 表示年份与季度
|
||||
*
|
||||
* @author ZhouXY
|
||||
*/
|
||||
@Immutable
|
||||
public final class YearQuarter implements Comparable<YearQuarter>, Serializable {
|
||||
private static final long serialVersionUID = 3804145964419489753L;
|
||||
|
||||
/** 年份 */
|
||||
private final int year;
|
||||
/** 季度 */
|
||||
private final Quarter quarter;
|
||||
/** 季度开始日期 */
|
||||
private final LocalDate firstDate;
|
||||
/** 季度结束日期 */
|
||||
private final LocalDate lastDate;
|
||||
|
||||
private YearQuarter(int year, Quarter quarter) {
|
||||
this.year = year;
|
||||
this.quarter = quarter;
|
||||
this.firstDate = quarter.firstMonthDay().atYear(year);
|
||||
this.lastDate = quarter.lastMonthDay().atYear(year);
|
||||
}
|
||||
|
||||
// #region - StaticFactory
|
||||
|
||||
/**
|
||||
* 根据指定年份与季度,创建 {@link YearQuarter} 实例
|
||||
*
|
||||
* @param year 年份
|
||||
* @param quarter 季度
|
||||
* @return {@link YearQuarter} 实例
|
||||
*/
|
||||
@StaticFactoryMethod(YearQuarter.class)
|
||||
public static YearQuarter of(int year, int quarter) {
|
||||
return new YearQuarter(YEAR.checkValidIntValue(year), Quarter.of(quarter));
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据指定年份与季度,创建 {@link YearQuarter} 实例
|
||||
*
|
||||
* @param year 年份
|
||||
* @param quarter 季度
|
||||
* @return {@link YearQuarter} 实例
|
||||
*/
|
||||
@StaticFactoryMethod(YearQuarter.class)
|
||||
public static YearQuarter of(int year, Quarter quarter) {
|
||||
return new YearQuarter(YEAR.checkValidIntValue(year), Objects.requireNonNull(quarter));
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据指定日期,判断日期所在的年份与季度,创建 {@link YearQuarter} 实例
|
||||
*
|
||||
* @param date 日期
|
||||
* @return {@link YearQuarter} 实例
|
||||
*/
|
||||
@StaticFactoryMethod(YearQuarter.class)
|
||||
public static YearQuarter of(LocalDate date) {
|
||||
checkNotNull(date);
|
||||
return new YearQuarter(date.getYear(), Quarter.fromMonth(date.getMonth()));
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据指定日期,判断日期所在的年份与季度,创建 {@link YearQuarter} 实例
|
||||
*
|
||||
* @param date 日期
|
||||
* @return {@link YearQuarter} 实例
|
||||
*
|
||||
* @deprecated
|
||||
* 此方法使用系统默认时区,不建议使用。
|
||||
* 请使用 {@link #of(Date,ZoneId)}、{@link #of(Date,TimeZone)} 或其它工厂方法
|
||||
*/
|
||||
@Deprecated
|
||||
@StaticFactoryMethod(YearQuarter.class)
|
||||
public static YearQuarter of(Date date) {
|
||||
checkNotNull(date);
|
||||
final int yearValue = YEAR.checkValidIntValue(date.getYear() + 1900L);
|
||||
final int monthValue = date.getMonth() + 1;
|
||||
return new YearQuarter(yearValue, Quarter.fromMonth(monthValue));
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据指定日期,判断日期所在的年份与季度,创建 {@link YearQuarter} 实例
|
||||
*
|
||||
* @param date 日期
|
||||
* @param zoneId 时区
|
||||
* @return {@link YearQuarter} 实例
|
||||
*/
|
||||
@StaticFactoryMethod(YearQuarter.class)
|
||||
public static YearQuarter of(Date date, ZoneId zoneId) {
|
||||
checkNotNull(date);
|
||||
checkNotNull(zoneId);
|
||||
LocalDate localDate = date.toInstant().atZone(zoneId).toLocalDate();
|
||||
return YearQuarter.of(localDate);
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据指定日期,判断日期所在的年份与季度,创建 {@link YearQuarter} 实例
|
||||
*
|
||||
* @param date 日期
|
||||
* @param timeZone 时区
|
||||
* @return {@link YearQuarter} 实例
|
||||
*/
|
||||
@StaticFactoryMethod(YearQuarter.class)
|
||||
public static YearQuarter of(Date date, TimeZone timeZone) {
|
||||
checkNotNull(date);
|
||||
checkNotNull(timeZone);
|
||||
LocalDate localDate = date.toInstant().atZone(timeZone.toZoneId()).toLocalDate();
|
||||
return YearQuarter.of(localDate);
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据指定日期,判断日期所在的年份与季度,创建 {@link YearQuarter} 实例
|
||||
*
|
||||
* @param date 日期
|
||||
* @return {@link YearQuarter} 实例
|
||||
*/
|
||||
@StaticFactoryMethod(YearQuarter.class)
|
||||
public static YearQuarter of(Calendar date) {
|
||||
checkNotNull(date);
|
||||
final int yearValue = ChronoField.YEAR.checkValidIntValue(date.get(Calendar.YEAR));
|
||||
final int monthValue = date.get(Calendar.MONTH) + 1;
|
||||
return new YearQuarter(yearValue, Quarter.fromMonth(monthValue));
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据指定年月,判断其所在的年份与季度,创建 {@link YearQuarter} 实例
|
||||
*
|
||||
* @param yearMonth 年月
|
||||
* @return {@link YearQuarter} 实例
|
||||
*/
|
||||
@StaticFactoryMethod(YearQuarter.class)
|
||||
public static YearQuarter of(YearMonth yearMonth) {
|
||||
checkNotNull(yearMonth);
|
||||
return of(yearMonth.getYear(), Quarter.fromMonth(yearMonth.getMonth()));
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据现在的日期,判断所在的年份与季度,创建 {@link YearQuarter} 实例
|
||||
*
|
||||
* @return {@link YearQuarter} 实例
|
||||
*/
|
||||
@StaticFactoryMethod(YearQuarter.class)
|
||||
public static YearQuarter now() {
|
||||
return of(LocalDate.now());
|
||||
}
|
||||
|
||||
// #endregion
|
||||
|
||||
// #region - Getters
|
||||
|
||||
/**
|
||||
* 年份
|
||||
*
|
||||
* @return 年份
|
||||
*/
|
||||
public int getYear() {
|
||||
return this.year;
|
||||
}
|
||||
|
||||
/**
|
||||
* 季度
|
||||
*
|
||||
* @return 季度
|
||||
*/
|
||||
public Quarter getQuarter() {
|
||||
return this.quarter;
|
||||
}
|
||||
|
||||
/**
|
||||
* 季度值。从 1 开始。
|
||||
*
|
||||
* @return 季度值
|
||||
*/
|
||||
public int getQuarterValue() {
|
||||
return this.quarter.getValue();
|
||||
}
|
||||
|
||||
/**
|
||||
* 该季度第一个月
|
||||
*
|
||||
* @return {@link YearMonth} 对象
|
||||
*/
|
||||
public YearMonth firstYearMonth() {
|
||||
return YearMonth.of(this.year, this.quarter.firstMonth());
|
||||
}
|
||||
|
||||
/**
|
||||
* 该季度第一个月
|
||||
*
|
||||
* @return {@link Month} 对象
|
||||
*/
|
||||
public Month firstMonth() {
|
||||
return this.quarter.firstMonth();
|
||||
}
|
||||
|
||||
/**
|
||||
* 该季度的第一个月
|
||||
*
|
||||
* @return 结果。月份值从 1 开始,1 表示 1月,以此类推。
|
||||
*/
|
||||
public int firstMonthValue() {
|
||||
return this.quarter.firstMonthValue();
|
||||
}
|
||||
|
||||
/**
|
||||
* 该季度的最后一个月
|
||||
*
|
||||
* @return {@link YearMonth} 对象
|
||||
*/
|
||||
public YearMonth lastYearMonth() {
|
||||
return YearMonth.of(this.year, this.quarter.lastMonth());
|
||||
}
|
||||
|
||||
/**
|
||||
* 该季度的最后一个月
|
||||
*
|
||||
* @return {@link Month} 对象
|
||||
*/
|
||||
public Month lastMonth() {
|
||||
return this.quarter.lastMonth();
|
||||
}
|
||||
|
||||
/**
|
||||
* 该季度的最后一个月
|
||||
*
|
||||
* @return 结果。月份值从 1 开始,1 表示 1月,以此类推。
|
||||
*/
|
||||
public int lastMonthValue() {
|
||||
return this.quarter.lastMonthValue();
|
||||
}
|
||||
|
||||
/**
|
||||
* 该季度的第一天
|
||||
*
|
||||
* @return {@link LocalDate} 对象
|
||||
*/
|
||||
public LocalDate firstDate() {
|
||||
return firstDate;
|
||||
}
|
||||
|
||||
/**
|
||||
* 该季度的最后一天
|
||||
*
|
||||
* @return {@link LocalDate} 对象
|
||||
*/
|
||||
public LocalDate lastDate() {
|
||||
return lastDate;
|
||||
}
|
||||
|
||||
// #endregion
|
||||
|
||||
// #region - computes
|
||||
|
||||
public YearQuarter plusQuarters(long quartersToAdd) {
|
||||
if (quartersToAdd == 0L) {
|
||||
return this;
|
||||
}
|
||||
long quarterCount = (this.year - 1) * 4L + (this.quarter.getValue()) + quartersToAdd;
|
||||
int newYear = YEAR.checkValidIntValue(Math.floorDiv(quarterCount - 1, 4) + 1);
|
||||
return new YearQuarter(newYear, this.quarter.plus(quartersToAdd));
|
||||
}
|
||||
|
||||
public YearQuarter minusQuarters(long quartersToAdd) {
|
||||
return plusQuarters(-quartersToAdd);
|
||||
}
|
||||
|
||||
public YearQuarter nextQuarter() {
|
||||
return plusQuarters(1L);
|
||||
}
|
||||
|
||||
public YearQuarter lastQuarter() {
|
||||
return minusQuarters(1L);
|
||||
}
|
||||
|
||||
public YearQuarter plusYears(long yearsToAdd) {
|
||||
if (yearsToAdd == 0L) {
|
||||
return this;
|
||||
}
|
||||
int newYear = YEAR.checkValidIntValue(this.year + yearsToAdd); // safe overflow
|
||||
return new YearQuarter(newYear, this.quarter);
|
||||
}
|
||||
|
||||
public YearQuarter minusYears(long yearsToAdd) {
|
||||
return plusYears(-yearsToAdd);
|
||||
}
|
||||
|
||||
public YearQuarter nextYear() {
|
||||
return plusYears(1L);
|
||||
}
|
||||
|
||||
public YearQuarter lastYear() {
|
||||
return minusYears(1L);
|
||||
}
|
||||
|
||||
// #endregion
|
||||
|
||||
// #region - hashCode & equals
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hash(year, quarter);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(@Nullable Object obj) {
|
||||
if (this == obj)
|
||||
return true;
|
||||
if (obj == null)
|
||||
return false;
|
||||
if (getClass() != obj.getClass())
|
||||
return false;
|
||||
YearQuarter other = (YearQuarter) obj;
|
||||
return year == other.year && quarter == other.quarter;
|
||||
}
|
||||
|
||||
// #endregion
|
||||
|
||||
// #region - compare
|
||||
|
||||
@SuppressWarnings("null")
|
||||
@Override
|
||||
public int compareTo(YearQuarter other) {
|
||||
int cmp = (this.year - other.year);
|
||||
if (cmp == 0) {
|
||||
cmp = this.quarter.compareTo(other.quarter);
|
||||
}
|
||||
return cmp;
|
||||
}
|
||||
|
||||
public boolean isBefore(YearQuarter other) {
|
||||
return this.compareTo(other) < 0;
|
||||
}
|
||||
|
||||
public boolean isAfter(YearQuarter other) {
|
||||
return this.compareTo(other) > 0;
|
||||
}
|
||||
|
||||
// #endregion
|
||||
|
||||
// #region - toString
|
||||
|
||||
/**
|
||||
* 返回 {@link YearQuarter} 的字符串表示形式,如 "2024 Q3"
|
||||
*
|
||||
* @return {@link YearQuarter} 的字符串表示形式
|
||||
*/
|
||||
@Override
|
||||
public String toString() {
|
||||
return this.year + " " + this.quarter.name();
|
||||
}
|
||||
|
||||
// #endregion
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
/*
|
||||
* Copyright 2025-present ZhouXY
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
/**
|
||||
* <h2>时间 API</h2>
|
||||
*
|
||||
* <h3>1. 季度 API</h3>
|
||||
*
|
||||
* 模仿 JDK 的 {@link java.time.Month} 和 {@link java.time.YearMonth},
|
||||
* 实现 {@link Quarter},{@link YearQuarter},对季度进行建模。
|
||||
*
|
||||
* @author ZhouXY
|
||||
*/
|
||||
@ParametersAreNonnullByDefault
|
||||
package xyz.zhouxy.plusone.commons.time;
|
||||
|
||||
import javax.annotation.ParametersAreNonnullByDefault;
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,485 @@
|
||||
/*
|
||||
* Copyright 2024-present ZhouXY
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package xyz.zhouxy.plusone.commons.util;
|
||||
|
||||
import java.util.Optional;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
import xyz.zhouxy.plusone.commons.exception.DataNotExistsException;
|
||||
|
||||
/**
|
||||
* 断言工具
|
||||
*
|
||||
* <p>
|
||||
* 本工具类不封装过多判断逻辑,鼓励充分使用项目中的工具类进行逻辑判断。
|
||||
*
|
||||
* <pre>
|
||||
* checkArgument(StringUtils.hasText(str), "The argument cannot be blank.");
|
||||
* checkState(ArrayUtils.isNotEmpty(result), "The result cannot be empty.");
|
||||
* checkCondition(!CollectionUtils.isEmpty(roles),
|
||||
* () -> new InvalidInputException("The roles cannot be empty."));
|
||||
* checkCondition(RegexTools.matches(email, PatternConsts.EMAIL),
|
||||
* "must be a well-formed email address");
|
||||
* </pre>
|
||||
*
|
||||
* @author ZhouXY
|
||||
*/
|
||||
public class AssertTools {
|
||||
|
||||
// ================================
|
||||
// #region - Argument
|
||||
// ================================
|
||||
|
||||
/**
|
||||
* 检查实参
|
||||
*
|
||||
* @param condition 判断参数是否符合条件的结果
|
||||
* @throws IllegalArgumentException 当条件不满足时抛出
|
||||
*/
|
||||
public static void checkArgument(boolean condition) {
|
||||
if (!condition) {
|
||||
throw new IllegalArgumentException();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查实参
|
||||
*
|
||||
* @param condition 判断参数是否符合条件的结果
|
||||
* @param errorMessage 异常信息
|
||||
* @throws IllegalArgumentException 当条件不满足时抛出
|
||||
*/
|
||||
public static void checkArgument(boolean condition, @Nullable String errorMessage) {
|
||||
if (!condition) {
|
||||
throw new IllegalArgumentException(errorMessage);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查实参
|
||||
*
|
||||
* @param condition 判断参数是否符合条件的结果
|
||||
* @param errorMessageSupplier 异常信息
|
||||
* @throws IllegalArgumentException 当条件不满足时抛出
|
||||
*/
|
||||
public static void checkArgument(boolean condition, Supplier<String> errorMessageSupplier) {
|
||||
if (!condition) {
|
||||
throw new IllegalArgumentException(errorMessageSupplier.get());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查实参
|
||||
*
|
||||
* @param condition 判断参数是否符合条件的结果
|
||||
* @param errorMessageTemplate 异常信息模板
|
||||
* @param errorMessageArgs 异常信息参数
|
||||
* @throws IllegalArgumentException 当条件不满足时抛出
|
||||
*/
|
||||
public static void checkArgument(boolean condition,
|
||||
String errorMessageTemplate, Object... errorMessageArgs) {
|
||||
if (!condition) {
|
||||
throw new IllegalArgumentException(String.format(errorMessageTemplate, errorMessageArgs));
|
||||
}
|
||||
}
|
||||
|
||||
// ================================
|
||||
// #endregion - Argument
|
||||
// ================================
|
||||
|
||||
// ================================
|
||||
// #region - ArgumentNotNull
|
||||
// ================================
|
||||
|
||||
/**
|
||||
* 判断入参不为 {@code null}
|
||||
*
|
||||
* @param <T> 入参类型
|
||||
* @param obj 入参
|
||||
* @return 校验通过时返回入参
|
||||
* @throws IllegalArgumentException 当 {@code obj} 为 {@code null} 时抛出
|
||||
*/
|
||||
public static <T> T checkArgumentNotNull(@Nullable T obj) {
|
||||
if (obj == null) {
|
||||
throw new IllegalArgumentException();
|
||||
}
|
||||
return obj;
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断入参不为 {@code null}
|
||||
*
|
||||
* @param <T> 入参类型
|
||||
* @param obj 入参
|
||||
* @param errorMessage 异常信息
|
||||
* @return 校验通过时返回入参
|
||||
* @throws IllegalArgumentException 当 {@code obj} 为 {@code null} 时抛出
|
||||
*/
|
||||
public static <T> T checkArgumentNotNull(@Nullable T obj, String errorMessage) {
|
||||
if (obj == null) {
|
||||
throw new IllegalArgumentException(errorMessage);
|
||||
}
|
||||
return obj;
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断入参不为 {@code null}
|
||||
*
|
||||
* @param <T> 入参类型
|
||||
* @param obj 入参
|
||||
* @param errorMessageSupplier 异常信息
|
||||
* @return 校验通过时返回入参
|
||||
* @throws IllegalArgumentException 当 {@code obj} 为 {@code null} 时抛出
|
||||
*/
|
||||
public static <T> T checkArgumentNotNull(@Nullable T obj, Supplier<String> errorMessageSupplier) {
|
||||
if (obj == null) {
|
||||
throw new IllegalArgumentException(errorMessageSupplier.get());
|
||||
}
|
||||
return obj;
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断入参不为 {@code null}
|
||||
*
|
||||
* @param <T> 入参类型
|
||||
* @param obj 入参
|
||||
* @param errorMessageTemplate 异常信息模板
|
||||
* @param errorMessageArgs 异常信息参数
|
||||
* @return 校验通过时返回入参
|
||||
* @throws IllegalArgumentException 当 {@code obj} 为 {@code null} 时抛出
|
||||
*/
|
||||
public static <T> T checkArgumentNotNull(@Nullable T obj,
|
||||
String errorMessageTemplate, Object... errorMessageArgs) {
|
||||
if (obj == null) {
|
||||
throw new IllegalArgumentException(String.format(errorMessageTemplate, errorMessageArgs));
|
||||
}
|
||||
return obj;
|
||||
}
|
||||
|
||||
// ================================
|
||||
// #endregion - ArgumentNotNull
|
||||
// ================================
|
||||
|
||||
// ================================
|
||||
// #region - State
|
||||
// ================================
|
||||
|
||||
/**
|
||||
* 检查状态
|
||||
*
|
||||
* @param condition 判断状态是否符合条件的结果
|
||||
* @throws IllegalStateException 当条件不满足时抛出
|
||||
*/
|
||||
public static void checkState(boolean condition) {
|
||||
if (!condition) {
|
||||
throw new IllegalStateException();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查状态
|
||||
*
|
||||
* @param condition 判断状态是否符合条件的结果
|
||||
* @param errorMessage 异常信息
|
||||
* @throws IllegalStateException 当条件不满足时抛出
|
||||
*/
|
||||
public static void checkState(boolean condition, @Nullable String errorMessage) {
|
||||
if (!condition) {
|
||||
throw new IllegalStateException(errorMessage);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查状态
|
||||
*
|
||||
* @param condition 判断状态是否符合条件的结果
|
||||
* @param errorMessageSupplier 异常信息
|
||||
* @throws IllegalStateException 当条件不满足时抛出
|
||||
*/
|
||||
public static void checkState(boolean condition, Supplier<String> errorMessageSupplier) {
|
||||
if (!condition) {
|
||||
throw new IllegalStateException(errorMessageSupplier.get());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查状态
|
||||
*
|
||||
* @param condition 判断状态是否符合条件的结果
|
||||
* @param errorMessageTemplate 异常信息模板
|
||||
* @param errorMessageArgs 异常信息参数
|
||||
* @throws IllegalStateException 当条件不满足时抛出
|
||||
*/
|
||||
public static void checkState(boolean condition,
|
||||
String errorMessageTemplate, Object... errorMessageArgs) {
|
||||
if (!condition) {
|
||||
throw new IllegalStateException(String.format(errorMessageTemplate, errorMessageArgs));
|
||||
}
|
||||
}
|
||||
|
||||
// ================================
|
||||
// #endregion - State
|
||||
// ================================
|
||||
|
||||
// ================================
|
||||
// #region - NotNull
|
||||
// ================================
|
||||
|
||||
/**
|
||||
* 判空
|
||||
*
|
||||
* @param <T> 入参类型
|
||||
* @param obj 入参
|
||||
* @throws NullPointerException 当 {@code obj} 为 {@code null} 时抛出
|
||||
*/
|
||||
public static <T> void checkNotNull(@Nullable T obj) {
|
||||
if (obj == null) {
|
||||
throw new NullPointerException();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 判空
|
||||
*
|
||||
* @param <T> 入参类型
|
||||
* @param obj 入参
|
||||
* @param errorMessage 异常信息
|
||||
* @throws NullPointerException 当 {@code obj} 为 {@code null} 时抛出
|
||||
*/
|
||||
public static <T> void checkNotNull(@Nullable T obj, String errorMessage) {
|
||||
if (obj == null) {
|
||||
throw new NullPointerException(errorMessage);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 判空
|
||||
*
|
||||
* @param <T> 入参类型
|
||||
* @param obj 入参
|
||||
* @param errorMessageSupplier 异常信息
|
||||
* @throws NullPointerException 当 {@code obj} 为 {@code null} 时抛出
|
||||
*/
|
||||
public static <T> void checkNotNull(@Nullable T obj, Supplier<String> errorMessageSupplier) {
|
||||
if (obj == null) {
|
||||
throw new NullPointerException(errorMessageSupplier.get());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 判空
|
||||
*
|
||||
* @param <T> 入参类型
|
||||
* @param obj 入参
|
||||
* @param errorMessageTemplate 异常信息模板
|
||||
* @param errorMessageArgs 异常信息参数
|
||||
* @throws NullPointerException 当 {@code obj} 为 {@code null} 时抛出
|
||||
*/
|
||||
public static <T> void checkNotNull(@Nullable T obj,
|
||||
String errorMessageTemplate, Object... errorMessageArgs) {
|
||||
if (obj == null) {
|
||||
throw new NullPointerException(String.format(errorMessageTemplate, errorMessageArgs));
|
||||
}
|
||||
}
|
||||
|
||||
// ================================
|
||||
// #endregion - NotNull
|
||||
// ================================
|
||||
|
||||
// ================================
|
||||
// #region - Exists
|
||||
// ================================
|
||||
|
||||
/**
|
||||
* 检查数据是否存在
|
||||
*
|
||||
* @param <T> 入参类型
|
||||
* @param obj 入参
|
||||
* @return 如果 {@code obj} 存在,返回 {@code obj} 本身
|
||||
* @throws DataNotExistsException 当 {@code obj} 不存在时抛出
|
||||
*/
|
||||
public static <T> T checkExists(@Nullable T obj)
|
||||
throws DataNotExistsException {
|
||||
if (obj == null) {
|
||||
throw new DataNotExistsException();
|
||||
}
|
||||
return obj;
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查数据是否存在
|
||||
*
|
||||
* @param <T> 入参类型
|
||||
* @param obj 入参
|
||||
* @param errorMessage 异常信息
|
||||
* @return 如果 {@code obj} 存在,返回 {@code obj} 本身
|
||||
* @throws DataNotExistsException 当 {@code obj} 不存在时抛出
|
||||
*/
|
||||
public static <T> T checkExists(@Nullable T obj, String errorMessage)
|
||||
throws DataNotExistsException {
|
||||
if (obj == null) {
|
||||
throw new DataNotExistsException(errorMessage);
|
||||
}
|
||||
return obj;
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查数据是否存在
|
||||
*
|
||||
* @param <T> 入参类型
|
||||
* @param obj 入参
|
||||
* @param errorMessageSupplier 异常信息
|
||||
* @return 如果 {@code obj} 存在,返回 {@code obj} 本身
|
||||
* @throws DataNotExistsException 当 {@code obj} 不存在时抛出
|
||||
*/
|
||||
public static <T> T checkExists(@Nullable T obj, Supplier<String> errorMessageSupplier)
|
||||
throws DataNotExistsException {
|
||||
if (obj == null) {
|
||||
throw new DataNotExistsException(errorMessageSupplier.get());
|
||||
}
|
||||
return obj;
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查数据是否存在
|
||||
*
|
||||
* @param <T> 入参类型
|
||||
* @param obj 入参
|
||||
* @param errorMessageTemplate 异常信息模板
|
||||
* @param errorMessageArgs 异常信息参数
|
||||
* @return 如果 {@code obj} 存在,返回 {@code obj} 本身
|
||||
* @throws DataNotExistsException 当 {@code obj} 不存在时抛出
|
||||
*/
|
||||
public static <T> T checkExists(@Nullable T obj,
|
||||
String errorMessageTemplate, Object... errorMessageArgs)
|
||||
throws DataNotExistsException {
|
||||
if (obj == null) {
|
||||
throw new DataNotExistsException(String.format(errorMessageTemplate, errorMessageArgs));
|
||||
}
|
||||
return obj;
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查数据是否存在
|
||||
*
|
||||
* @param <T> 入参类型
|
||||
* @param optional 入参
|
||||
* @return 如果 {@code optional} 存在,返回 {@code optional} 包含的值
|
||||
* @throws DataNotExistsException 当 {@code optional} 的值不存在时抛出
|
||||
*/
|
||||
public static <T> T checkExists(Optional<T> optional)
|
||||
throws DataNotExistsException {
|
||||
if (!optional.isPresent()) {
|
||||
throw new DataNotExistsException();
|
||||
}
|
||||
return optional.get();
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查数据是否存在
|
||||
*
|
||||
* @param <T> 入参类型
|
||||
* @param optional 入参
|
||||
* @param errorMessage 异常信息
|
||||
* @return 如果 {@code optional} 存在,返回 {@code optional} 包含的值
|
||||
* @throws DataNotExistsException 当 {@code optional} 的值不存在时抛出
|
||||
*/
|
||||
public static <T> T checkExists(Optional<T> optional, String errorMessage)
|
||||
throws DataNotExistsException {
|
||||
if (!optional.isPresent()) {
|
||||
throw new DataNotExistsException(errorMessage);
|
||||
}
|
||||
return optional.get();
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查数据是否存在
|
||||
*
|
||||
* @param <T> 入参类型
|
||||
* @param optional 入参
|
||||
* @param errorMessageSupplier 异常信息
|
||||
* @return 如果 {@code optional} 存在,返回 {@code optional} 包含的值
|
||||
* @throws DataNotExistsException 当 {@code optional} 的值不存在时抛出
|
||||
*/
|
||||
public static <T> T checkExists(Optional<T> optional, Supplier<String> errorMessageSupplier)
|
||||
throws DataNotExistsException {
|
||||
if (!optional.isPresent()) {
|
||||
throw new DataNotExistsException(errorMessageSupplier.get());
|
||||
}
|
||||
return optional.get();
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查数据是否存在
|
||||
*
|
||||
* @param <T> 入参类型
|
||||
* @param optional 入参
|
||||
* @param errorMessageTemplate 异常信息模板
|
||||
* @param errorMessageArgs 异常信息参数
|
||||
* @return 如果 {@code optional} 存在,返回 {@code optional} 包含的值
|
||||
* @throws DataNotExistsException 当 {@code optional} 的值不存在时抛出
|
||||
*/
|
||||
public static <T> T checkExists(Optional<T> optional,
|
||||
String errorMessageTemplate, Object... errorMessageArgs)
|
||||
throws DataNotExistsException {
|
||||
if (!optional.isPresent()) {
|
||||
throw new DataNotExistsException(String.format(errorMessageTemplate, errorMessageArgs));
|
||||
}
|
||||
return optional.get();
|
||||
}
|
||||
|
||||
// ================================
|
||||
// #endregion - Exists
|
||||
// ================================
|
||||
|
||||
// ================================
|
||||
// #region - Condition
|
||||
// ================================
|
||||
|
||||
/**
|
||||
* 当条件不满足时抛出异常。
|
||||
*
|
||||
* @param <T> 异常类型
|
||||
* @param condition 条件
|
||||
* @param e 异常
|
||||
* @throws T 当条件不满足时抛出异常
|
||||
*/
|
||||
public static <T extends Exception> void checkCondition(boolean condition, Supplier<T> e)
|
||||
throws T {
|
||||
if (!condition) {
|
||||
throw e.get();
|
||||
}
|
||||
}
|
||||
|
||||
// ================================
|
||||
// #endregion
|
||||
// ================================
|
||||
|
||||
// ================================
|
||||
// #region - constructor
|
||||
// ================================
|
||||
|
||||
private AssertTools() {
|
||||
throw new IllegalStateException("Utility class");
|
||||
}
|
||||
|
||||
// ================================
|
||||
// #endregion
|
||||
// ================================
|
||||
}
|
||||
@@ -0,0 +1,147 @@
|
||||
/*
|
||||
* Copyright 2023-present ZhouXY
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package xyz.zhouxy.plusone.commons.util;
|
||||
|
||||
import static xyz.zhouxy.plusone.commons.util.AssertTools.checkNotNull;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
|
||||
import javax.annotation.Nonnull;
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
import xyz.zhouxy.plusone.commons.annotation.StaticFactoryMethod;
|
||||
|
||||
/**
|
||||
* BigDecimals
|
||||
*
|
||||
* <p>
|
||||
* BigDecimal 工具类
|
||||
*
|
||||
* @author ZhouXY
|
||||
* @since 1.0.0
|
||||
*/
|
||||
public class BigDecimals {
|
||||
|
||||
/**
|
||||
* 判断两个 {@code BigDecimal} 的值是否相等
|
||||
*
|
||||
* @param a 第一个 {@code BigDecimal}
|
||||
* @param b 第二个 {@code BigDecimal}
|
||||
* @return 当两个 {@code BigDecimal} 的值相等时返回 {@code true}
|
||||
*/
|
||||
public static boolean equalsValue(@Nullable BigDecimal a, @Nullable BigDecimal b) {
|
||||
return (a == b) || (a != null && b != null && a.compareTo(b) == 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断两个 {@code BigDecimal} 的值 - 大于
|
||||
*
|
||||
* @param a 第一个 {@code BigDecimal}
|
||||
* @param b 第二个 {@code BigDecimal}
|
||||
* @return 当 {@code a} 大于 {@code b} 时返回 {@code true}
|
||||
*/
|
||||
public static boolean gt(BigDecimal a, BigDecimal b) {
|
||||
checkNotNull(a, "Parameter could not be null.");
|
||||
checkNotNull(b, "Parameter could not be null.");
|
||||
return (a != b) && (a.compareTo(b) > 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断两个 {@code BigDecimal} 的值 - 大于等于
|
||||
*
|
||||
* @param a 第一个 {@code BigDecimal}
|
||||
* @param b 第二个 {@code BigDecimal}
|
||||
* @return 当 {@code a} 大于等于 {@code b} 时返回 {@code true}
|
||||
*/
|
||||
public static boolean ge(BigDecimal a, BigDecimal b) {
|
||||
checkNotNull(a, "Parameter could not be null.");
|
||||
checkNotNull(b, "Parameter could not be null.");
|
||||
return (a == b) || (a.compareTo(b) >= 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断两个 {@code BigDecimal} 的值 - 小于
|
||||
*
|
||||
* @param a 第一个 {@code BigDecimal}
|
||||
* @param b 第二个 {@code BigDecimal}
|
||||
* @return 当 {@code a} 小于 {@code b} 时返回 {@code true}
|
||||
*/
|
||||
public static boolean lt(BigDecimal a, BigDecimal b) {
|
||||
checkNotNull(a, "Parameter could not be null.");
|
||||
checkNotNull(b, "Parameter could not be null.");
|
||||
return (a != b) && (a.compareTo(b) < 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断两个 {@code BigDecimal} 的值 - 小于等于
|
||||
*
|
||||
* @param a 第一个 {@code BigDecimal}
|
||||
* @param b 第二个 {@code BigDecimal}
|
||||
* @return 当 {@code a} 小于等于 {@code b} 时返回 {@code true}
|
||||
*/
|
||||
public static boolean le(BigDecimal a, BigDecimal b) {
|
||||
checkNotNull(a, "Parameter could not be null.");
|
||||
checkNotNull(b, "Parameter could not be null.");
|
||||
return (a == b) || (a.compareTo(b) <= 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* 求和
|
||||
*
|
||||
* @param numbers {@code BigDecimal} 数组
|
||||
* @return 求和结果
|
||||
*/
|
||||
public static BigDecimal sum(final BigDecimal... numbers) {
|
||||
if (ArrayTools.isEmpty(numbers)) {
|
||||
return BigDecimal.ZERO;
|
||||
}
|
||||
BigDecimal result = BigDecimals.nullToZero(numbers[0]);
|
||||
for (int i = 1; i < numbers.length; i++) {
|
||||
BigDecimal value = numbers[i];
|
||||
if (value != null) {
|
||||
result = result.add(value);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* 将 {@code null} 转换为 {@link BigDecimal#ZERO}
|
||||
*
|
||||
* @param val BigDecimal 对象
|
||||
* @return 如果 {@code val} 为 {@code null},则返回 {@link BigDecimal#ZERO},否则返回 {@code val}
|
||||
*/
|
||||
@Nonnull
|
||||
public static BigDecimal nullToZero(@Nullable final BigDecimal val) {
|
||||
return val != null ? val : BigDecimal.ZERO;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取字符串所表示的数值转换为 {@code BigDecimal}
|
||||
*
|
||||
* @param val 表示数值的字符串
|
||||
* @return {@code BigDecimal} 对象
|
||||
*/
|
||||
@StaticFactoryMethod(BigDecimal.class)
|
||||
public static BigDecimal of(@Nullable final String val) {
|
||||
return (StringTools.isNotBlank(val)) ? new BigDecimal(val) : BigDecimal.ZERO;
|
||||
}
|
||||
|
||||
private BigDecimals() {
|
||||
throw new IllegalStateException("Utility class");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,778 @@
|
||||
/*
|
||||
* Copyright 2023-present ZhouXY
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package xyz.zhouxy.plusone.commons.util;
|
||||
|
||||
import static java.time.temporal.ChronoField.*;
|
||||
|
||||
import java.time.Instant;
|
||||
import java.time.LocalDate;
|
||||
import java.time.LocalDateTime;
|
||||
import java.time.LocalTime;
|
||||
import java.time.Month;
|
||||
import java.time.Year;
|
||||
import java.time.YearMonth;
|
||||
import java.time.ZoneId;
|
||||
import java.time.ZonedDateTime;
|
||||
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.annotation.StaticFactoryMethod;
|
||||
import xyz.zhouxy.plusone.commons.time.Quarter;
|
||||
import xyz.zhouxy.plusone.commons.time.YearQuarter;
|
||||
|
||||
/**
|
||||
* 日期时间工具类
|
||||
*
|
||||
* @author ZhouXY
|
||||
*/
|
||||
public class DateTimeTools {
|
||||
|
||||
// ================================
|
||||
// #region - toString
|
||||
// ================================
|
||||
|
||||
public static String toYearString(int year) {
|
||||
return Integer.toString(YEAR.checkValidIntValue(year));
|
||||
}
|
||||
|
||||
public static String toYearString(Year year) {
|
||||
return year.toString();
|
||||
}
|
||||
|
||||
public static String toMonthStringM(int monthValue) {
|
||||
return Integer.toString(MONTH_OF_YEAR.checkValidIntValue(monthValue));
|
||||
}
|
||||
|
||||
public static String toMonthStringMM(int monthValue) {
|
||||
return String.format("%02d", MONTH_OF_YEAR.checkValidIntValue(monthValue));
|
||||
}
|
||||
|
||||
public static String toMonthStringM(Month month) {
|
||||
return Integer.toString(month.getValue());
|
||||
}
|
||||
|
||||
public static String toMonthStringMM(Month month) {
|
||||
return String.format("%02d", month.getValue());
|
||||
}
|
||||
|
||||
// ================================
|
||||
// #endregion
|
||||
// ================================
|
||||
|
||||
// ================================
|
||||
// #region - toDate
|
||||
// ================================
|
||||
|
||||
/**
|
||||
* 将时间戳转换为 {@link Date} 对象
|
||||
*
|
||||
* @param timeMillis 时间戳
|
||||
* @return {@link Date} 对象
|
||||
*/
|
||||
public static Date toDate(long timeMillis) {
|
||||
return Date.from(Instant.ofEpochMilli(timeMillis));
|
||||
}
|
||||
|
||||
/**
|
||||
* 将 {@link Calendar} 对象转换为 {@link Date} 对象
|
||||
*
|
||||
* @param calendar {@link Calendar} 对象
|
||||
* @return {@link Date} 对象
|
||||
*/
|
||||
public static Date toDate(Calendar calendar) {
|
||||
return calendar.getTime();
|
||||
}
|
||||
|
||||
/**
|
||||
* 将 {@link Instant} 对象转换为 {@link Date} 对象
|
||||
*
|
||||
* @param instant {@link Instant} 对象
|
||||
* @return {@link Date} 对象
|
||||
*/
|
||||
public static Date toDate(Instant instant) {
|
||||
return Date.from(instant);
|
||||
}
|
||||
|
||||
/**
|
||||
* 将 {@link ZonedDateTime} 对象转换为 {@link Date} 对象
|
||||
*
|
||||
* @param zonedDateTime {@link ZonedDateTime} 对象
|
||||
* @return {@link Date} 对象
|
||||
*/
|
||||
public static Date toDate(ZonedDateTime zonedDateTime) {
|
||||
return Date.from(zonedDateTime.toInstant());
|
||||
}
|
||||
|
||||
/**
|
||||
* 使用指定时区,将 {@link LocalDateTime} 对象转换为 {@link Date} 对象
|
||||
*
|
||||
* @param localDateTime {@link LocalDateTime} 对象
|
||||
* @param zone 时区
|
||||
* @return {@link Date} 对象
|
||||
*/
|
||||
public static Date toDate(LocalDateTime localDateTime, ZoneId zone) {
|
||||
return Date.from(ZonedDateTime.of(localDateTime, zone).toInstant());
|
||||
}
|
||||
|
||||
/**
|
||||
* 使用指定时区,将 {@link LocalDate} 和 {@link LocalTime} 对象转换为 {@link Date} 对象
|
||||
*
|
||||
* @param localDate {@link LocalDate} 对象
|
||||
* @param localTime {@link LocalTime} 对象
|
||||
* @param zone 时区
|
||||
* @return {@link Date} 对象
|
||||
*/
|
||||
public static Date toDate(LocalDate localDate, LocalTime localTime, ZoneId zone) {
|
||||
return Date.from(ZonedDateTime.of(localDate, localTime, zone).toInstant());
|
||||
}
|
||||
|
||||
// ================================
|
||||
// #endregion
|
||||
// ================================
|
||||
|
||||
// ================================
|
||||
// #region - toInstant
|
||||
// ================================
|
||||
|
||||
/**
|
||||
* 将时间戳转换为 {@link Instant} 对象
|
||||
*
|
||||
* @param timeMillis 时间戳
|
||||
* @return {@link Instant} 对象
|
||||
*/
|
||||
public static Instant toInstant(long timeMillis) {
|
||||
return Instant.ofEpochMilli(timeMillis);
|
||||
}
|
||||
|
||||
/**
|
||||
* 将 {@link Date} 对象转换为 {@link Instant} 对象
|
||||
*
|
||||
* @param date {@link Date} 对象
|
||||
* @return {@link Instant} 对象
|
||||
*/
|
||||
public static Instant toInstant(Date date) {
|
||||
return date.toInstant();
|
||||
}
|
||||
|
||||
/**
|
||||
* 将 {@link Calendar} 对象转换为 {@link Instant} 对象
|
||||
*
|
||||
* @param calendar {@link Calendar} 对象
|
||||
* @return {@link Instant} 对象
|
||||
*/
|
||||
public static Instant toInstant(Calendar calendar) {
|
||||
return calendar.toInstant();
|
||||
}
|
||||
|
||||
/**
|
||||
* 将 {@link ZonedDateTime} 对象转换为 {@link Instant} 对象
|
||||
*
|
||||
* @param zonedDateTime {@link ZonedDateTime} 对象
|
||||
* @return {@link Instant} 对象
|
||||
*/
|
||||
public static Instant toInstant(ZonedDateTime zonedDateTime) { // NOSONAR
|
||||
return zonedDateTime.toInstant();
|
||||
}
|
||||
|
||||
/**
|
||||
* 使用指定时区,将 {@link LocalDateTime} 对象转换为 {@link Instant} 对象
|
||||
*
|
||||
* @param localDateTime {@link LocalDateTime} 对象
|
||||
* @param zone 时区
|
||||
* @return {@link Instant} 对象
|
||||
*/
|
||||
public static Instant toInstant(LocalDateTime localDateTime, ZoneId zone) {
|
||||
return ZonedDateTime.of(localDateTime, zone).toInstant();
|
||||
}
|
||||
|
||||
// ================================
|
||||
// #endregion
|
||||
// ================================
|
||||
|
||||
// ================================
|
||||
// #region - toZonedDateTime
|
||||
// ================================
|
||||
|
||||
/**
|
||||
* 获取时间戳在指定时区的地区时间。
|
||||
* <p>
|
||||
* 传入不同 {@link ZoneId},获取到的 {@link ZonedDateTime} 对象实际上还是同一时间戳,
|
||||
* 只是不同时区的表示。
|
||||
*
|
||||
* @param timeMillis 时间戳
|
||||
* @param zone 时区
|
||||
* @return 带时区信息的地区时间
|
||||
*/
|
||||
public static ZonedDateTime toZonedDateTime(long timeMillis, ZoneId zone) {
|
||||
return ZonedDateTime.ofInstant(Instant.ofEpochMilli(timeMillis), zone);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取 {@link Date} 所表示的时间戳,在指定时区的地区时间。
|
||||
* <p>
|
||||
* 传入不同 {@link ZoneId},获取到的 {@link ZonedDateTime} 对象实际上还是同一时间戳,
|
||||
* 只是不同时区的表示。
|
||||
*
|
||||
* @param dateTime {@link Date} 对象
|
||||
* @param zone 时区
|
||||
* @return 带时区信息的地区时间
|
||||
*/
|
||||
public static ZonedDateTime toZonedDateTime(Date dateTime, ZoneId zone) {
|
||||
return ZonedDateTime.ofInstant(dateTime.toInstant(), zone);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取 {@link Date} 所表示的时间戳,在指定时区的地区时间。
|
||||
* <p>
|
||||
* 传入不同 {@link ZoneId},获取到的 {@link ZonedDateTime} 对象实际上表示的还是还是同一时间戳的时间,
|
||||
* 只是不同时区的表示。
|
||||
*
|
||||
* @param dateTime {@link Date} 对象
|
||||
* @param timeZone 时区
|
||||
* @return 带时区信息的地区时间
|
||||
*/
|
||||
public static ZonedDateTime toZonedDateTime(Date dateTime, TimeZone timeZone) {
|
||||
return ZonedDateTime.ofInstant(dateTime.toInstant(), timeZone.toZoneId());
|
||||
}
|
||||
|
||||
/**
|
||||
* 使用 {@code calendar} 对象的时区信息,将 {@link Calendar} 对象转换为 {@link ZonedDateTime}
|
||||
* 对象。
|
||||
*
|
||||
* @param calendar{@link Calendar} 对象
|
||||
* @return {@link ZonedDateTime} 对象
|
||||
*/
|
||||
public static ZonedDateTime toZonedDateTime(Calendar calendar) {
|
||||
return calendar.toInstant().atZone(calendar.getTimeZone().toZoneId());
|
||||
}
|
||||
|
||||
/**
|
||||
* 使用指定的时区,将 {@link Calendar} 对象转换为 {@link ZonedDateTime} 对象。
|
||||
*
|
||||
* @param calendar {@link Calendar} 对象
|
||||
* @param zone 时区
|
||||
* @return {@link ZonedDateTime} 对象
|
||||
*/
|
||||
public static ZonedDateTime toZonedDateTime(Calendar calendar, ZoneId zone) {
|
||||
return calendar.toInstant().atZone(zone);
|
||||
}
|
||||
|
||||
/**
|
||||
* 使用指定的时区,将 {@link Calendar} 对象转换为 {@link ZonedDateTime} 对象。
|
||||
*
|
||||
* @param calendar {@link Calendar} 对象
|
||||
* @param zone 时区
|
||||
* @return {@link ZonedDateTime} 对象
|
||||
*/
|
||||
public static ZonedDateTime toZonedDateTime(Calendar calendar, TimeZone zone) {
|
||||
return calendar.toInstant().atZone(zone.toZoneId());
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建带时区的地区时间
|
||||
*
|
||||
* @param localDateTime 地区时间
|
||||
* @param zone 时区
|
||||
* @return 带时区的地区时间
|
||||
*/
|
||||
public static ZonedDateTime toZonedDateTime(LocalDateTime localDateTime, ZoneId zone) {
|
||||
return ZonedDateTime.of(localDateTime, zone);
|
||||
}
|
||||
|
||||
// ================================
|
||||
// #endregion
|
||||
// ================================
|
||||
|
||||
// ================================
|
||||
// #region - toLocalDateTime
|
||||
// ================================
|
||||
|
||||
/**
|
||||
* 获取时间戳在指定时区的地区时间。
|
||||
*
|
||||
* @param timeMillis 时间戳
|
||||
* @param zone 时区
|
||||
* @return 地区时间
|
||||
*/
|
||||
public static LocalDateTime toLocalDateTime(long timeMillis, ZoneId zone) {
|
||||
return LocalDateTime.ofInstant(Instant.ofEpochMilli(timeMillis), zone);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取 {@link Date} 所表示的时间戳,在指定时区的地区时间。
|
||||
*
|
||||
* @param dateTime {@link Date} 对象
|
||||
* @param zone 时区
|
||||
* @return 地区时间
|
||||
*/
|
||||
public static LocalDateTime toLocalDateTime(Date dateTime, ZoneId zone) {
|
||||
return LocalDateTime.ofInstant(dateTime.toInstant(), zone);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取 {@link Date} 所表示的时间戳,在指定时区的地区时间。
|
||||
*
|
||||
* @param dateTime {@link Date} 对象
|
||||
* @param timeZone 时区
|
||||
* @return 地区时间
|
||||
*/
|
||||
public static LocalDateTime toLocalDateTime(Date dateTime, TimeZone timeZone) {
|
||||
return LocalDateTime.ofInstant(dateTime.toInstant(), timeZone.toZoneId());
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取 {@link Calendar} 所表示的时间戳,在指定时区的地区时间。
|
||||
*
|
||||
* @param calendar {@link Calendar} 对象
|
||||
* @param zone 时区
|
||||
* @return 地区时间
|
||||
*/
|
||||
public static LocalDateTime toLocalDateTime(Calendar calendar, ZoneId zone) {
|
||||
return LocalDateTime.ofInstant(calendar.toInstant(), zone);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取 {@link Calendar} 所表示的时间戳,在指定时区的地区时间。
|
||||
*
|
||||
* @param calendar {@link Calendar} 对象
|
||||
* @param zone 时区
|
||||
* @return 地区时间
|
||||
*/
|
||||
public static LocalDateTime toLocalDateTime(Calendar calendar, TimeZone zone) {
|
||||
return LocalDateTime.ofInstant(calendar.toInstant(), zone.toZoneId());
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取 {@link ZonedDateTime} 所表示的时间戳,在指定时区的地区时间。
|
||||
*
|
||||
* @param zonedDateTime {@link ZonedDateTime} 对象
|
||||
* @param zone 时区
|
||||
* @return 地区时间
|
||||
*/
|
||||
public static LocalDateTime toLocalDateTime(ZonedDateTime zonedDateTime, ZoneId zone) {
|
||||
return LocalDateTime.ofInstant(zonedDateTime.toInstant(), zone);
|
||||
}
|
||||
|
||||
// ================================
|
||||
// #endregion
|
||||
// ================================
|
||||
|
||||
// ================================
|
||||
// #region - YearQuarter & Quarter
|
||||
// ================================
|
||||
|
||||
/**
|
||||
* 获取指定日期所在季度
|
||||
*
|
||||
* @param date 日期
|
||||
* @return 日期所在的季度
|
||||
*
|
||||
* @deprecated
|
||||
* 此方法使用系统默认时区,不建议使用。
|
||||
* 请使用 {@link #getQuarter(Date,ZoneId)}、{@link #getQuarter(Date,TimeZone)} 或其它工厂方法
|
||||
*/
|
||||
@Deprecated
|
||||
public static YearQuarter getQuarter(Date date) {
|
||||
return YearQuarter.of(date);
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据指定日期,判断日期所在的年份与季度,创建 {@link YearQuarter} 实例
|
||||
*
|
||||
* @param date 日期
|
||||
* @param timeZone 时区
|
||||
* @return {@link YearQuarter} 实例
|
||||
*/
|
||||
@StaticFactoryMethod(YearQuarter.class)
|
||||
public static YearQuarter getQuarter(Date date, ZoneId timeZone) {
|
||||
return YearQuarter.of(date, timeZone);
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据指定日期,判断日期所在的年份与季度,创建 {@link YearQuarter} 实例
|
||||
*
|
||||
* @param date 日期
|
||||
* @param timeZone 时区
|
||||
* @return {@link YearQuarter} 实例
|
||||
*/
|
||||
@StaticFactoryMethod(YearQuarter.class)
|
||||
public static YearQuarter getQuarter(Date date, TimeZone timeZone) {
|
||||
return YearQuarter.of(date, timeZone);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取指定日期所在季度
|
||||
*
|
||||
* @param date 日期
|
||||
* @return 日期所在的季度
|
||||
*/
|
||||
@StaticFactoryMethod(YearQuarter.class)
|
||||
public static YearQuarter getQuarter(Calendar date) {
|
||||
return YearQuarter.of(date);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取指定月份所在季度
|
||||
*
|
||||
* @param month 月份
|
||||
* @return 季度
|
||||
*/
|
||||
@StaticFactoryMethod(Quarter.class)
|
||||
public static Quarter getQuarter(Month month) {
|
||||
return Quarter.fromMonth(month);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取指定年月所在季度
|
||||
*
|
||||
* @param year 年
|
||||
* @param month 月
|
||||
* @return 季度
|
||||
*/
|
||||
@StaticFactoryMethod(YearQuarter.class)
|
||||
public static YearQuarter getQuarter(int year, Month month) {
|
||||
return YearQuarter.of(YearMonth.of(year, month));
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取指定年月所在季度
|
||||
*
|
||||
* @param yearMonth 年月
|
||||
* @return 季度
|
||||
*/
|
||||
@StaticFactoryMethod(YearQuarter.class)
|
||||
public static YearQuarter getQuarter(YearMonth yearMonth) {
|
||||
return YearQuarter.of(yearMonth);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取指定日期所在季度
|
||||
*
|
||||
* @param date 日期
|
||||
* @return 日期所在的季度
|
||||
*/
|
||||
@StaticFactoryMethod(YearQuarter.class)
|
||||
public static YearQuarter getQuarter(LocalDate date) {
|
||||
return YearQuarter.of(date);
|
||||
}
|
||||
|
||||
// ================================
|
||||
// #endregion
|
||||
// ================================
|
||||
|
||||
// ================================
|
||||
// #region - start & end
|
||||
// ================================
|
||||
|
||||
/**
|
||||
* 获取指定年份的开始日期
|
||||
*
|
||||
* @param year 年份
|
||||
* @return 指定年份的开始日期
|
||||
*/
|
||||
public static LocalDate startDateOfYear(int year) {
|
||||
return LocalDate.ofYearDay(year, 1);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取指定年份的结束日期
|
||||
*
|
||||
* @param year 年份
|
||||
* @return 指定年份的结束日期
|
||||
*/
|
||||
public static LocalDate endDateOfYear(int year) {
|
||||
return LocalDate.of(year, 12, 31);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取指定日期的第二天的开始时间
|
||||
*
|
||||
* @param date 日期
|
||||
* @return 指定日期的第二天的开始时间
|
||||
*/
|
||||
public static LocalDateTime startOfNextDate(LocalDate date) {
|
||||
return date.plusDays(1L).atStartOfDay();
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取指定日期的第二天的开始时间
|
||||
*
|
||||
* @param date 日期
|
||||
* @param zone 时区
|
||||
* @return 指定日期的第二天的开始时间
|
||||
*/
|
||||
public static ZonedDateTime startOfNextDate(LocalDate date, ZoneId zone) {
|
||||
return date.plusDays(1L).atStartOfDay(zone);
|
||||
}
|
||||
|
||||
// ================================
|
||||
// #endregion - start & end
|
||||
// ================================
|
||||
|
||||
// ================================
|
||||
// #region - isFuture
|
||||
// ================================
|
||||
|
||||
/**
|
||||
* 判断指定日期时间是否在将来
|
||||
*
|
||||
* @param date 日期时间
|
||||
* @return 指定日期时间是否在将来
|
||||
*/
|
||||
public static boolean isFuture(Date date) {
|
||||
return date.after(new Date());
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断指定日期时间是否在将来
|
||||
*
|
||||
* @param calendar 日期时间
|
||||
* @return 指定日期时间是否在将来
|
||||
*/
|
||||
public static boolean isFuture(Calendar calendar) {
|
||||
return calendar.after(Calendar.getInstance());
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断指定时刻是否在将来
|
||||
*
|
||||
* @param instant 时刻
|
||||
* @return 指定时刻是否在将来
|
||||
*/
|
||||
public static boolean isFuture(Instant instant) {
|
||||
return instant.isAfter(Instant.now());
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断指定时间戳是否在将来
|
||||
*
|
||||
* @param timeMillis 时间戳
|
||||
* @return 指定时间戳是否在将来
|
||||
*/
|
||||
public static boolean isFuture(long timeMillis) {
|
||||
return timeMillis > System.currentTimeMillis();
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断指定日期是否在将来
|
||||
*
|
||||
* @param date 日期
|
||||
* @return 指定日期是否在将来
|
||||
*/
|
||||
public static boolean isFuture(LocalDate date) {
|
||||
return date.isAfter(LocalDate.now());
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断指定日期时间是否在将来
|
||||
*
|
||||
* @param dateTime 日期时间
|
||||
* @return 指定日期时间是否在将来
|
||||
*/
|
||||
public static boolean isFuture(LocalDateTime dateTime) {
|
||||
return dateTime.isAfter(LocalDateTime.now());
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断指定日期时间是否在将来
|
||||
*
|
||||
* @param dateTime 日期时间
|
||||
* @return 指定日期时间是否在将来
|
||||
*/
|
||||
public static boolean isFuture(ZonedDateTime dateTime) {
|
||||
return dateTime.isAfter(ZonedDateTime.now());
|
||||
}
|
||||
|
||||
// ================================
|
||||
// #endregion - isFuture
|
||||
// ================================
|
||||
|
||||
// ================================
|
||||
// #region - isPast
|
||||
// ================================
|
||||
|
||||
/**
|
||||
* 判断指定日期时间是否在过去
|
||||
*
|
||||
* @param date 日期时间
|
||||
* @return 指定日期时间是否在过去
|
||||
*/
|
||||
public static boolean isPast(Date date) {
|
||||
return date.before(new Date());
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断指定日期时间是否在过去
|
||||
*
|
||||
* @param calendar 日期时间
|
||||
* @return 指定日期时间是否在过去
|
||||
*/
|
||||
public static boolean isPast(Calendar calendar) {
|
||||
return calendar.before(Calendar.getInstance());
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断指定时刻是否在过去
|
||||
*
|
||||
* @param instant 时刻
|
||||
* @return 指定时刻是否在过去
|
||||
*/
|
||||
public static boolean isPast(Instant instant) {
|
||||
return instant.isBefore(Instant.now());
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断指定时间戳是否在过去
|
||||
*
|
||||
* @param timeMillis 时间戳
|
||||
* @return 指定时间戳是否在过去
|
||||
*/
|
||||
public static boolean isPast(long timeMillis) {
|
||||
return timeMillis < System.currentTimeMillis();
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断指定日期是否在过去
|
||||
*
|
||||
* @param date 日期
|
||||
* @return 指定日期是否在过去
|
||||
*/
|
||||
public static boolean isPast(LocalDate date) {
|
||||
return date.isBefore(LocalDate.now());
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断指定日期时间是否在过去
|
||||
*
|
||||
* @param dateTime 日期时间
|
||||
* @return 指定日期时间是否在过去
|
||||
*/
|
||||
public static boolean isPast(LocalDateTime dateTime) {
|
||||
return dateTime.isBefore(LocalDateTime.now());
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断指定日期时间是否在过去
|
||||
*
|
||||
* @param dateTime 日期时间
|
||||
* @return 指定日期时间是否在过去
|
||||
*/
|
||||
public static boolean isPast(ZonedDateTime dateTime) {
|
||||
return dateTime.isBefore(ZonedDateTime.now());
|
||||
}
|
||||
|
||||
// ================================
|
||||
// #endregion - isPast
|
||||
// ================================
|
||||
|
||||
// ================================
|
||||
// #region - range
|
||||
// ================================
|
||||
|
||||
/**
|
||||
* 获取指定日期的时间范围
|
||||
*
|
||||
* @param date 日期
|
||||
* @return 指定日期的时间范围
|
||||
*/
|
||||
public static Range<LocalDateTime> toDateTimeRange(LocalDate date) {
|
||||
return Range.closedOpen(date.atStartOfDay(), startOfNextDate(date));
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取指定日期的时间范围
|
||||
*
|
||||
* @param date 日期
|
||||
* @param zone 时区
|
||||
* @return 指定日期的时间范围
|
||||
*/
|
||||
public static Range<ZonedDateTime> toDateTimeRange(LocalDate date, ZoneId zone) {
|
||||
return Range.closedOpen(date.atStartOfDay(zone), startOfNextDate(date, zone));
|
||||
}
|
||||
|
||||
/**
|
||||
* 将指定日期范围转为日期时间范围
|
||||
*
|
||||
* @param 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 日期范围
|
||||
* @param zone 时区
|
||||
* @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
|
||||
// ================================
|
||||
|
||||
/**
|
||||
* 判断指定年份是否为闰年
|
||||
*
|
||||
* @param year 年份
|
||||
* @return 指定年份是否为闰年
|
||||
*/
|
||||
public static boolean isLeapYear(int year) {
|
||||
return Year.isLeap(year);
|
||||
}
|
||||
|
||||
// ================================
|
||||
// #endregion - others
|
||||
// ================================
|
||||
|
||||
/**
|
||||
* 私有构造方法
|
||||
*/
|
||||
private DateTimeTools() {
|
||||
throw new IllegalStateException("Utility class");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,213 @@
|
||||
/*
|
||||
* Copyright 2022-present ZhouXY
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package xyz.zhouxy.plusone.commons.util;
|
||||
|
||||
import static xyz.zhouxy.plusone.commons.util.AssertTools.checkCondition;
|
||||
import static xyz.zhouxy.plusone.commons.util.AssertTools.checkNotNull;
|
||||
|
||||
import java.util.function.Supplier;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
/**
|
||||
* 枚举工具类
|
||||
*
|
||||
* @author ZhouXY
|
||||
*/
|
||||
public final class EnumTools {
|
||||
|
||||
private EnumTools() {
|
||||
throw new IllegalStateException("Utility class");
|
||||
}
|
||||
|
||||
/**
|
||||
* 通过 ordinal 获取枚举实例
|
||||
*
|
||||
* @param <E> 枚举的类型
|
||||
* @param enumType 枚举的类型信息
|
||||
* @param ordinal 序号
|
||||
* @return 枚举对象
|
||||
* @deprecated 不推荐使用枚举的 ordinal。
|
||||
*/
|
||||
@Deprecated
|
||||
private static <E extends Enum<?>> E valueOfInternal(Class<E> enumType, int ordinal) { // NOSONAR 该方法弃用,但不删掉
|
||||
E[] values = enumType.getEnumConstants();
|
||||
checkCondition((ordinal >= 0 && ordinal < values.length),
|
||||
() -> new EnumConstantNotPresentException(enumType, Integer.toString(ordinal)));
|
||||
return values[ordinal];
|
||||
}
|
||||
|
||||
/**
|
||||
* 通过 ordinal 获取枚举实例
|
||||
*
|
||||
* @param <E> 枚举的类型
|
||||
* @param enumType 枚举的类型信息
|
||||
* @param ordinal 序号
|
||||
* @return 枚举对象
|
||||
* @deprecated 不推荐使用枚举的 ordinal。
|
||||
*/
|
||||
@Deprecated
|
||||
public static <E extends Enum<?>> E valueOf(Class<E> enumType, int ordinal) { // NOSONAR 该方法弃用,但不删掉
|
||||
checkNotNull(enumType, "Enum type must not be null.");
|
||||
return valueOfInternal(enumType, ordinal);
|
||||
}
|
||||
|
||||
/**
|
||||
* 通过 ordinal 获取枚举实例
|
||||
*
|
||||
* @param <E> 枚举的类型
|
||||
* @param enumType 枚举的类型信息
|
||||
* @param ordinal 序号
|
||||
* @param defaultValue 默认值
|
||||
* @return 枚举对象
|
||||
* @deprecated 不推荐使用枚举的 ordinal。
|
||||
*/
|
||||
@Deprecated
|
||||
public static <E extends Enum<?>> E valueOf(Class<E> enumType, // NOSONAR 该方法弃用,但不删掉
|
||||
@Nullable Integer ordinal, @Nullable E defaultValue) {
|
||||
checkNotNull(enumType);
|
||||
return null == ordinal ? defaultValue : valueOfInternal(enumType, ordinal);
|
||||
}
|
||||
|
||||
/**
|
||||
* 通过 ordinal 获取枚举实例
|
||||
*
|
||||
* @param <E> 枚举的类型
|
||||
* @param enumType 枚举的类型信息
|
||||
* @param ordinal 序号
|
||||
* @param defaultValue 默认值
|
||||
* @return 枚举对象
|
||||
* @deprecated 不推荐使用枚举的 ordinal。
|
||||
*/
|
||||
@Deprecated
|
||||
public static <E extends Enum<?>> E getValueOrDefault( // NOSONAR 该方法弃用,但不删掉
|
||||
Class<E> enumType,
|
||||
@Nullable Integer ordinal,
|
||||
Supplier<E> defaultValue) {
|
||||
checkNotNull(enumType);
|
||||
checkNotNull(defaultValue);
|
||||
return null == ordinal ? defaultValue.get() : valueOfInternal(enumType, ordinal);
|
||||
}
|
||||
|
||||
/**
|
||||
* 通过 ordinal 获取枚举实例
|
||||
*
|
||||
* @param <E> 枚举的类型
|
||||
* @param enumType 枚举的类型信息
|
||||
* @param ordinal 序号
|
||||
* @return 枚举对象
|
||||
* @deprecated 不推荐使用枚举的 ordinal。
|
||||
*/
|
||||
@Deprecated
|
||||
public static <E extends Enum<?>> E getValueOrDefault(Class<E> enumType, @Nullable Integer ordinal) { // NOSONAR 该方法弃用,但不删掉
|
||||
return getValueOrDefault(enumType, ordinal, () -> {
|
||||
checkNotNull(enumType, "Enum type must not be null.");
|
||||
E[] values = enumType.getEnumConstants();
|
||||
return values[0];
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 通过 ordinal 获取枚举实例
|
||||
*
|
||||
* @param <E> 枚举的类型
|
||||
* @param enumType 枚举的类型信息
|
||||
* @param ordinal 序号
|
||||
* @return 枚举对象
|
||||
* @deprecated 不推荐使用枚举的 ordinal。
|
||||
*/
|
||||
@Deprecated
|
||||
public static <E extends Enum<?>> E getValueNullable(Class<E> enumType, @Nullable Integer ordinal) { // NOSONAR 该方法弃用,但不删掉
|
||||
return valueOf(enumType, ordinal, null);
|
||||
}
|
||||
|
||||
public static <E extends Enum<?>> Integer checkOrdinal(Class<E> enumType, Integer ordinal) {
|
||||
checkNotNull(enumType, "Enum type must not be null.");
|
||||
checkNotNull(ordinal, "Ordinal must not be null.");
|
||||
E[] values = enumType.getEnumConstants();
|
||||
checkCondition(ordinal >= 0 && ordinal < values.length,
|
||||
() -> new EnumConstantNotPresentException(enumType, Integer.toString(ordinal)));
|
||||
return ordinal;
|
||||
}
|
||||
|
||||
/**
|
||||
* 校验枚举的 ordinal。
|
||||
*
|
||||
* @param <E> 枚举类型
|
||||
* @param enumType 枚举类型
|
||||
* @param ordinal The ordinal
|
||||
* @return The ordinal
|
||||
*/
|
||||
@Nullable
|
||||
public static <E extends Enum<?>> Integer checkOrdinalNullable(Class<E> enumType, @Nullable Integer ordinal) {
|
||||
return checkOrdinalOrDefault(enumType, ordinal, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* 校验枚举的 ordinal,如果 ordinal 为 {@code null},则返回 {@code 0}。
|
||||
*
|
||||
* @param <E> 枚举类型
|
||||
* @param enumType 枚举类型
|
||||
* @param ordinal The ordinal
|
||||
* @return The ordinal
|
||||
*/
|
||||
@Nullable
|
||||
public static <E extends Enum<?>> Integer checkOrdinalOrDefault(Class<E> enumType, @Nullable Integer ordinal) {
|
||||
return checkOrdinalOrDefault(enumType, ordinal, 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* 校验枚举的 ordinal,如果 ordinal 为 {@code null},则返回 {@code defaultValue}。
|
||||
*
|
||||
* @param <E> 枚举类型
|
||||
* @param enumType 枚举类型
|
||||
* @param ordinal The ordinal
|
||||
* @param defaultValue 默认值
|
||||
* @return The ordinal
|
||||
*/
|
||||
@Nullable
|
||||
public static <E extends Enum<?>> Integer checkOrdinalOrDefault(
|
||||
Class<E> enumType,
|
||||
@Nullable Integer ordinal,
|
||||
@Nullable Integer defaultValue) {
|
||||
checkNotNull(enumType);
|
||||
return checkOrdinalOrGetInternal(enumType, ordinal, () -> checkOrdinalOrDefaultInternal(enumType, defaultValue, null));
|
||||
}
|
||||
|
||||
/**
|
||||
* 仅对 {@code ordinal} 进行判断,不对 {@code defaultValue} 进行判断
|
||||
*/
|
||||
@Nullable
|
||||
private static <E extends Enum<?>> Integer checkOrdinalOrDefaultInternal(
|
||||
Class<E> enumType,
|
||||
@Nullable Integer ordinal,
|
||||
@Nullable Integer defaultValue) {
|
||||
return ordinal != null
|
||||
? checkOrdinal(enumType, ordinal)
|
||||
: defaultValue;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private static <E extends Enum<?>> Integer checkOrdinalOrGetInternal(
|
||||
Class<E> enumType,
|
||||
@Nullable Integer ordinal,
|
||||
Supplier<Integer> defaultValueSupplier) {
|
||||
return ordinal != null
|
||||
? checkOrdinal(enumType, ordinal)
|
||||
: defaultValueSupplier.get();
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2022-2023 the original author or authors.
|
||||
* Copyright 2022-present ZhouXY
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -16,39 +16,61 @@
|
||||
|
||||
package xyz.zhouxy.plusone.commons.util;
|
||||
|
||||
import static xyz.zhouxy.plusone.commons.util.AssertTools.checkArgument;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.function.Function;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
|
||||
import com.google.common.base.Preconditions;
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
import xyz.zhouxy.plusone.commons.annotation.StaticFactoryMethod;
|
||||
|
||||
/**
|
||||
* 枚举类
|
||||
*
|
||||
* <p>
|
||||
* 参考 <a href="https://lostechies.com/jimmybogard/2008/08/12/enumeration-classes/">Enumeration classes</a>
|
||||
*
|
||||
* @author ZhouXY
|
||||
* @deprecated 设计 Enumeration 的灵感来自于 .net 社区,因为 C# 的枚举不带行为。
|
||||
* 但 Java 的枚举可以带行为,故大多数情况下不需要这种设计。
|
||||
*/
|
||||
public abstract class Enumeration<T extends Enumeration<T>> implements Comparable<T> {
|
||||
@Deprecated
|
||||
public abstract class Enumeration<T extends Enumeration<T>> // NOSONAR 暂不移除
|
||||
implements Comparable<T> {
|
||||
protected final int id;
|
||||
protected final String name;
|
||||
|
||||
protected Enumeration(final int id, final String name) {
|
||||
Preconditions.checkArgument(StringUtils.isNotBlank(name), "Name of enumeration must has text.");
|
||||
checkArgument(StringTools.isNotBlank(name), "Name of enumeration must has text.");
|
||||
this.id = id;
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
/**
|
||||
* 枚举整数码值
|
||||
*
|
||||
* @return 整数码值
|
||||
*/
|
||||
public final int getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
/**
|
||||
* 枚举名称
|
||||
*
|
||||
* @return 枚举名称
|
||||
*/
|
||||
public final String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
@SuppressWarnings("null")
|
||||
@Override
|
||||
public final int compareTo(final T o) {
|
||||
return Integer.compare(this.id, o.id);
|
||||
@@ -60,7 +82,7 @@ public abstract class Enumeration<T extends Enumeration<T>> implements Comparabl
|
||||
}
|
||||
|
||||
@Override
|
||||
public final boolean equals(final Object obj) {
|
||||
public final boolean equals(@Nullable final Object obj) {
|
||||
if (this == obj)
|
||||
return true;
|
||||
if (obj == null)
|
||||
@@ -76,6 +98,9 @@ public abstract class Enumeration<T extends Enumeration<T>> implements Comparabl
|
||||
return getClass().getSimpleName() + '(' + id + ":" + name + ')';
|
||||
}
|
||||
|
||||
/**
|
||||
* 枚举值集合
|
||||
*/
|
||||
protected static final class ValueSet<T extends Enumeration<T>> {
|
||||
private final Map<Integer, T> valueMap;
|
||||
|
||||
@@ -83,21 +108,36 @@ public abstract class Enumeration<T extends Enumeration<T>> implements Comparabl
|
||||
this.valueMap = valueMap;
|
||||
}
|
||||
|
||||
@SafeVarargs
|
||||
/**
|
||||
* 创建枚举值集合
|
||||
*
|
||||
* @param <T> 枚举类型
|
||||
* @param values 枚举值
|
||||
* @return 枚举值集合
|
||||
*/
|
||||
@StaticFactoryMethod(ValueSet.class)
|
||||
public static <T extends Enumeration<T>> ValueSet<T> of(T... values) {
|
||||
Map<Integer, T> temp = new HashMap<>();
|
||||
for (T value : values) {
|
||||
temp.put(value.getId(), value);
|
||||
}
|
||||
public static <T extends Enumeration<T>> ValueSet<T> of(T[] values) {
|
||||
Map<Integer, T> temp = Arrays.stream(values)
|
||||
.collect(Collectors.toMap(Enumeration::getId, Function.identity()));
|
||||
return new ValueSet<>(Collections.unmodifiableMap(temp));
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据整数码值获取枚举对象
|
||||
*
|
||||
* @param id 整数码
|
||||
* @return 枚举对象
|
||||
*/
|
||||
public T get(int id) {
|
||||
Preconditions.checkArgument(this.valueMap.containsKey(id), "%s 对应的值不存在", id);
|
||||
checkArgument(this.valueMap.containsKey(id), "[%s] 对应的值不存在", id);
|
||||
return this.valueMap.get(id);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取所有枚举对象
|
||||
*
|
||||
* @return 所有枚举对象
|
||||
*/
|
||||
public Collection<T> getValues() {
|
||||
return this.valueMap.values();
|
||||
}
|
||||
@@ -0,0 +1,115 @@
|
||||
/*
|
||||
* Copyright 2023-present ZhouXY
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package xyz.zhouxy.plusone.commons.util;
|
||||
|
||||
import static xyz.zhouxy.plusone.commons.util.AssertTools.checkArgumentNotNull;
|
||||
|
||||
import java.util.Map;
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
/**
|
||||
* ID 生成器
|
||||
*
|
||||
* <p>
|
||||
* 生成 UUID 和 修改版雪花ID(Seata 版本)
|
||||
*
|
||||
* @see UUID
|
||||
* @see IdWorker
|
||||
* @author ZhouXY
|
||||
*/
|
||||
public class IdGenerator {
|
||||
|
||||
// ===== UUID =====
|
||||
|
||||
/**
|
||||
* 生成 UUID
|
||||
*
|
||||
* @return UUID
|
||||
*/
|
||||
public static UUID newUuid() {
|
||||
return UUID.randomUUID();
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成 UUID 字符串
|
||||
*
|
||||
* @return UUID 字符串
|
||||
*/
|
||||
public static String uuidString() {
|
||||
return UUID.randomUUID().toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成 UUID 字符串(无分隔符)
|
||||
*
|
||||
* @return UUID 字符串
|
||||
*/
|
||||
public static String simpleUuidString() {
|
||||
return toSimpleString(UUID.randomUUID());
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成 UUID 字符串(无分隔符)
|
||||
*
|
||||
* @param uuid UUID
|
||||
* @return UUID 字符串
|
||||
*/
|
||||
public static String toSimpleString(UUID uuid) {
|
||||
checkArgumentNotNull(uuid);
|
||||
return (uuidDigits(uuid.getMostSignificantBits() >> 32, 8) +
|
||||
uuidDigits(uuid.getMostSignificantBits() >> 16, 4) +
|
||||
uuidDigits(uuid.getMostSignificantBits(), 4) +
|
||||
uuidDigits(uuid.getLeastSignificantBits() >> 48, 4) +
|
||||
uuidDigits(uuid.getLeastSignificantBits(), 12));
|
||||
}
|
||||
|
||||
/** Returns val represented by the specified number of hex digits. */
|
||||
private static String uuidDigits(long val, int digits) {
|
||||
long hi = 1L << (digits * 4);
|
||||
return Long.toHexString(hi | (val & (hi - 1))).substring(1);
|
||||
}
|
||||
|
||||
// ===== SnowflakeId =====
|
||||
|
||||
private static final Map<Long, IdWorker> snowflakePool = new ConcurrentHashMap<>();
|
||||
|
||||
/**
|
||||
* 生成雪花ID
|
||||
*
|
||||
* @param workerId 工作机器ID
|
||||
* @return 雪花ID
|
||||
*/
|
||||
public static long nextSnowflakeId(long workerId) {
|
||||
IdWorker generator = getSnowflakeIdGenerator(workerId);
|
||||
return generator.nextId();
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取雪花ID生成器
|
||||
*
|
||||
* @param workerId 工作机器ID
|
||||
* @return {@code IdWorker} 对象。来自 Seata 的修改版雪花ID生成器。
|
||||
*/
|
||||
public static IdWorker getSnowflakeIdGenerator(long workerId) {
|
||||
return snowflakePool.computeIfAbsent(workerId, wid -> new IdWorker(workerId));
|
||||
}
|
||||
|
||||
private IdGenerator() {
|
||||
throw new IllegalStateException("Utility class");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,211 @@
|
||||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership.
|
||||
* The ASF licenses this file to You 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
|
||||
*
|
||||
* http://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 java.net.NetworkInterface;
|
||||
import java.net.SocketException;
|
||||
import java.util.Enumeration;
|
||||
import java.util.concurrent.ThreadLocalRandom;
|
||||
import java.util.concurrent.atomic.AtomicLong;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
import xyz.zhouxy.plusone.commons.exception.system.NoAvailableMacFoundException;
|
||||
|
||||
/**
|
||||
* 修改版雪花 ID 生成器
|
||||
*
|
||||
* <p>
|
||||
* 来自 <a href="https://seata.apache.org">Seata</a> 的 {@code org.apache.seata.common.util.IdWorker}
|
||||
*
|
||||
* <p>
|
||||
* 大体思路为:
|
||||
* <ol>
|
||||
* <li>每个机器线程安全地生成序列,前面加上机器的id,这样就不会与其它机器的id相冲突。</li>
|
||||
* <li>时间戳作为序列的“预留位”,它更像是应用启动时最开始的序列的一部分,在一个时间戳里生成 4096 个 id 之后,直接生成下一个时间戳的 id。</li>
|
||||
* </ol>
|
||||
*
|
||||
* <p>
|
||||
* 详情见以下介绍:
|
||||
* <ul>
|
||||
* <li><a href="https://seata.apache.org/zh-cn/blog/seata-analysis-UUID-generator/">Seata基于改良版雪花算法的分布式UUID生成器分析</a></li>
|
||||
* <li><a href="https://seata.apache.org/zh-cn/blog/seata-snowflake-explain">关于新版雪花算法的答疑</a></li>
|
||||
* <li><a href="https://juejin.cn/post/7264387737276203065">在开源项目中看到一个改良版的雪花算法,现在它是你的了。</a></li>
|
||||
* <li><a href="https://juejin.cn/post/7265516484029743138">关于若干读者,阅读“改良版雪花算法”后提出的几个共性问题的回复。</a></li>
|
||||
* </ul>
|
||||
*/
|
||||
public class IdWorker {
|
||||
|
||||
/**
|
||||
* Start time cut (2020-05-03)
|
||||
*/
|
||||
private static final long TWEPOCH = 1588435200000L;
|
||||
|
||||
/**
|
||||
* The number of bits occupied by workerId
|
||||
*/
|
||||
private static final int WORKER_ID_BITS = 10;
|
||||
|
||||
/**
|
||||
* The number of bits occupied by timestamp
|
||||
*/
|
||||
private static final int TIMESTAMP_BITS = 41;
|
||||
|
||||
/**
|
||||
* The number of bits occupied by sequence
|
||||
*/
|
||||
private static final int SEQUENCE_BITS = 12;
|
||||
|
||||
/**
|
||||
* Maximum supported machine id, the result is 1023
|
||||
*/
|
||||
private static final int MAX_WORKER_ID = ~(-1 << WORKER_ID_BITS);
|
||||
|
||||
/**
|
||||
* business meaning: machine ID (0 ~ 1023)
|
||||
* actual layout in memory:
|
||||
* highest 1 bit: 0
|
||||
* middle 10 bit: workerId
|
||||
* lowest 53 bit: all 0
|
||||
*/
|
||||
private long workerId;
|
||||
|
||||
/**
|
||||
* timestamp and sequence mix in one Long
|
||||
* highest 11 bit: not used
|
||||
* middle 41 bit: timestamp
|
||||
* lowest 12 bit: sequence
|
||||
*/
|
||||
private AtomicLong timestampAndSequence;
|
||||
|
||||
/**
|
||||
* mask that help to extract timestamp and sequence from a long
|
||||
*/
|
||||
private static final long TIMESTAMP_AND_SEQUENCE_MASK = ~(-1L << (TIMESTAMP_BITS + SEQUENCE_BITS));
|
||||
|
||||
/**
|
||||
* instantiate an IdWorker using given workerId
|
||||
* @param workerId if null, then will auto assign one
|
||||
*/
|
||||
public IdWorker(Long workerId) {
|
||||
initTimestampAndSequence();
|
||||
initWorkerId(workerId);
|
||||
}
|
||||
|
||||
/**
|
||||
* init first timestamp and sequence immediately
|
||||
*/
|
||||
private void initTimestampAndSequence() {
|
||||
long timestamp = getNewestTimestamp();
|
||||
long timestampWithSequence = timestamp << SEQUENCE_BITS;
|
||||
this.timestampAndSequence = new AtomicLong(timestampWithSequence);
|
||||
}
|
||||
|
||||
/**
|
||||
* init workerId
|
||||
* @param workerId if null, then auto generate one
|
||||
*/
|
||||
private void initWorkerId(@Nullable Long workerId) {
|
||||
if (workerId == null) {
|
||||
workerId = generateWorkerId();
|
||||
}
|
||||
if (workerId > MAX_WORKER_ID || workerId < 0) {
|
||||
String message = String.format("worker Id can't be greater than %d or less than 0", MAX_WORKER_ID);
|
||||
throw new IllegalArgumentException(message);
|
||||
}
|
||||
this.workerId = workerId << (TIMESTAMP_BITS + SEQUENCE_BITS);
|
||||
}
|
||||
|
||||
/**
|
||||
* get next UUID(base on snowflake algorithm), which look like:
|
||||
* highest 1 bit: always 0
|
||||
* next 10 bit: workerId
|
||||
* next 41 bit: timestamp
|
||||
* lowest 12 bit: sequence
|
||||
* @return UUID
|
||||
*/
|
||||
public long nextId() {
|
||||
waitIfNecessary();
|
||||
long next = timestampAndSequence.incrementAndGet();
|
||||
long timestampWithSequence = next & TIMESTAMP_AND_SEQUENCE_MASK;
|
||||
return workerId | timestampWithSequence;
|
||||
}
|
||||
|
||||
/**
|
||||
* block current thread if the QPS of acquiring UUID is too high
|
||||
* that current sequence space is exhausted
|
||||
*/
|
||||
private void waitIfNecessary() {
|
||||
long currentWithSequence = timestampAndSequence.get();
|
||||
long current = currentWithSequence >>> SEQUENCE_BITS;
|
||||
long newest = getNewestTimestamp();
|
||||
if (current >= newest) {
|
||||
try {
|
||||
Thread.sleep(5);
|
||||
} catch (InterruptedException ignore) { // NOSONAR don't care
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* get newest timestamp relative to twepoch
|
||||
*/
|
||||
private long getNewestTimestamp() {
|
||||
return System.currentTimeMillis() - TWEPOCH;
|
||||
}
|
||||
|
||||
/**
|
||||
* auto generate workerId, try using mac first, if failed, then randomly generate one
|
||||
* @return workerId
|
||||
*/
|
||||
private static long generateWorkerId() {
|
||||
try {
|
||||
return generateWorkerIdBaseOnMac();
|
||||
} catch (Exception e) {
|
||||
return generateRandomWorkerId();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* use lowest 10 bit of available MAC as workerId
|
||||
* @return workerId
|
||||
* @throws SocketException if an I/O error occurs.
|
||||
* @throws NoAvailableMacFoundException when there is no available mac found
|
||||
*/
|
||||
private static long generateWorkerIdBaseOnMac() throws SocketException, NoAvailableMacFoundException {
|
||||
Enumeration<NetworkInterface> all = NetworkInterface.getNetworkInterfaces();
|
||||
while (all.hasMoreElements()) {
|
||||
NetworkInterface networkInterface = all.nextElement();
|
||||
boolean isLoopback = networkInterface.isLoopback();
|
||||
boolean isVirtual = networkInterface.isVirtual();
|
||||
byte[] mac = networkInterface.getHardwareAddress();
|
||||
if (isLoopback || isVirtual || mac == null) {
|
||||
continue;
|
||||
}
|
||||
return ((mac[4] & 0B11) << 8) | (mac[5] & 0xFF);
|
||||
}
|
||||
throw new NoAvailableMacFoundException();
|
||||
}
|
||||
|
||||
/**
|
||||
* randomly generate one as workerId
|
||||
* @return workerId
|
||||
*/
|
||||
private static long generateRandomWorkerId() {
|
||||
return ThreadLocalRandom.current().nextInt(MAX_WORKER_ID + 1);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,299 @@
|
||||
/*
|
||||
* Copyright 2025-present ZhouXY
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package xyz.zhouxy.plusone.commons.util;
|
||||
|
||||
/**
|
||||
* Joda-Time 工具类
|
||||
*
|
||||
* @author ZhouXY
|
||||
*/
|
||||
public class JodaTimeTools {
|
||||
|
||||
// ================================
|
||||
// #region - toJodaInstant
|
||||
// ================================
|
||||
|
||||
/**
|
||||
* 将 {@link java.time.Instant} 转换为 {@link org.joda.time.Instant}
|
||||
*
|
||||
* @param instant {@link java.time.Instant} 对象
|
||||
* @return {@link org.joda.time.Instant} 对象
|
||||
*/
|
||||
public static org.joda.time.Instant toJodaInstant(java.time.Instant instant) {
|
||||
return new org.joda.time.Instant(instant.toEpochMilli());
|
||||
}
|
||||
|
||||
/**
|
||||
* 将 {@link java.time.ZonedDateTime} 转换为 {@link org.joda.time.Instant}
|
||||
*
|
||||
* @param zonedDateTime {@link java.time.ZonedDateTime} 对象
|
||||
* @return {@link org.joda.time.Instant} 对象
|
||||
*/
|
||||
public static org.joda.time.Instant toJodaInstant(java.time.ZonedDateTime zonedDateTime) {
|
||||
return toJodaInstant(zonedDateTime.toInstant());
|
||||
}
|
||||
|
||||
/**
|
||||
* 计算指定时区的地区时间,对应的时间戳。结果为 {@link org.joda.time.Instant} 对象
|
||||
*
|
||||
* @param localDateTime {@link java.time.LocalDateTime} 对象
|
||||
* @param zone 时区
|
||||
* @return {@link org.joda.time.Instant} 对象
|
||||
*/
|
||||
public static org.joda.time.Instant toJodaInstant(
|
||||
java.time.LocalDateTime localDateTime,
|
||||
java.time.ZoneId zone) {
|
||||
return toJodaInstant(java.time.ZonedDateTime.of(localDateTime, zone));
|
||||
}
|
||||
|
||||
// ================================
|
||||
// #endregion
|
||||
// ================================
|
||||
|
||||
// ================================
|
||||
// #region - toJavaInstant
|
||||
// ================================
|
||||
|
||||
/**
|
||||
* 将 {@link org.joda.time.Instant} 对象转换为 {@link java.time.Instant} 对象
|
||||
*
|
||||
* @param instant {@link org.joda.time.Instant} 对象
|
||||
* @return {@link java.time.Instant} 对象
|
||||
*/
|
||||
public static java.time.Instant toJavaInstant(org.joda.time.Instant instant) {
|
||||
return DateTimeTools.toInstant(instant.getMillis());
|
||||
}
|
||||
|
||||
/**
|
||||
* 将 joda-time 中的 {@link org.joda.time.DateTime} 对象转换为 Java 的
|
||||
* {@link java.time.Instant} 对象
|
||||
*
|
||||
* @param dateTime joda-time 中表示日期时间的 {@link org.joda.time.DateTime} 对象
|
||||
* @return Java 表示时间戳的 {@link java.time.Instant} 对象
|
||||
*/
|
||||
public static java.time.Instant toJavaInstant(org.joda.time.DateTime dateTime) {
|
||||
return DateTimeTools.toInstant(dateTime.getMillis());
|
||||
}
|
||||
|
||||
/**
|
||||
* 将 joda-time 中的 {@link org.joda.time.LocalDateTime} 对象和
|
||||
* {@link org.joda.time.DateTimeZone} 对象
|
||||
* 转换为 Java 中的 {@link java.time.Instant} 对象
|
||||
*
|
||||
* @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,
|
||||
org.joda.time.DateTimeZone zone) {
|
||||
return toJavaInstant(localDateTime.toDateTime(zone));
|
||||
}
|
||||
|
||||
// ================================
|
||||
// #endregion
|
||||
// ================================
|
||||
|
||||
// ================================
|
||||
// #region - toJodaDateTime
|
||||
// ================================
|
||||
|
||||
/**
|
||||
* 将 Java 中表示日期时间的 {@link java.time.ZonedDateTime} 对象
|
||||
* 转换为 joda-time 的 {@link org.joda.time.DateTime} 对象
|
||||
*
|
||||
* @param zonedDateTime 日期时间
|
||||
* @return joda-time 中对应的 {@link org.joda.time.DateTime} 对象
|
||||
*/
|
||||
public static org.joda.time.DateTime toJodaDateTime(java.time.ZonedDateTime zonedDateTime) {
|
||||
org.joda.time.DateTimeZone zone = org.joda.time.DateTimeZone.forID(zonedDateTime.getZone().getId());
|
||||
return toJodaInstant(zonedDateTime.toInstant()).toDateTime(zone);
|
||||
}
|
||||
|
||||
/**
|
||||
* 将 java.time 中表示日期时间的 {@link java.time.LocalDateTime} 对象和表示时区的
|
||||
* {@link java.time.ZoneId} 对象转换为 joda-time 中对应的 {@link org.joda.time.DateTime}
|
||||
* 对象
|
||||
* 转换为 joda-time 中对应的 {@link org.joda.time.DateTime} 对象
|
||||
*
|
||||
* @param localDateTime 日期时间
|
||||
* @param zone 时区
|
||||
* @return joda-time 中对应的 {@link org.joda.time.DateTime} 对象
|
||||
*/
|
||||
public static org.joda.time.DateTime toJodaDateTime(
|
||||
java.time.LocalDateTime localDateTime,
|
||||
java.time.ZoneId zone) {
|
||||
org.joda.time.LocalDateTime jodaLocalDateTime = toJodaLocalDateTime(localDateTime);
|
||||
org.joda.time.DateTimeZone jodaZone = toJodaZone(zone);
|
||||
return jodaLocalDateTime.toDateTime(jodaZone);
|
||||
}
|
||||
|
||||
/**
|
||||
* 计算时间戳在指定时区对应的时间,结果使用 {@link org.joda.time.DateTime} 表示
|
||||
*
|
||||
* @param instant java.time 中的时间戳
|
||||
* @param zone java.time 中的时区
|
||||
* @return joda-time 中带时区的日期时间
|
||||
*/
|
||||
public static org.joda.time.DateTime toJodaDateTime(
|
||||
java.time.Instant instant,
|
||||
java.time.ZoneId zone) {
|
||||
org.joda.time.DateTimeZone dateTimeZone = toJodaZone(zone);
|
||||
return toJodaInstant(instant).toDateTime(dateTimeZone);
|
||||
}
|
||||
|
||||
// ================================
|
||||
// #endregion
|
||||
// ================================
|
||||
|
||||
// ================================
|
||||
// #region - toZonedDateTime
|
||||
// ================================
|
||||
|
||||
/**
|
||||
* 将 joda-time 中带时区的日期时间,转换为 java.time 中带时区的日期时间
|
||||
*
|
||||
* @param dateTime joda-time 中带时区的日期时间
|
||||
* @return java.time 中带时区的日期时间
|
||||
*/
|
||||
public static java.time.ZonedDateTime toZonedDateTime(org.joda.time.DateTime dateTime) {
|
||||
java.time.ZoneId zone = dateTime.getZone().toTimeZone().toZoneId();
|
||||
return toJavaInstant(dateTime.toInstant()).atZone(zone);
|
||||
}
|
||||
|
||||
/**
|
||||
* 将 joda-time 中的 {@link org.joda.time.LocalDateTime} 和
|
||||
* {@link org.joda.time.DateTimeZone}
|
||||
* 转换为 java.time 中的 {@link java.time.ZonedDateTime}
|
||||
*
|
||||
* @param localDateTime joda-time 中的地区时间
|
||||
* @param dateTimeZone joda-time 中的时区
|
||||
* @return java.time 中带时区的日期时间
|
||||
*/
|
||||
public static java.time.ZonedDateTime toZonedDateTime(
|
||||
org.joda.time.LocalDateTime localDateTime,
|
||||
org.joda.time.DateTimeZone dateTimeZone) {
|
||||
java.time.ZoneId zone = toJavaZone(dateTimeZone);
|
||||
return toJavaInstant(localDateTime, dateTimeZone).atZone(zone);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取 joda-time 中的 {@link org.joda.time.Instant} 在指定时区的时间,用 Java 8+ 的
|
||||
* {@link java.time.ZonedDateTime} 表示
|
||||
*
|
||||
* @param instant joda-time 中的时间戳
|
||||
* @param dateTimeZone joda-time 中的时区
|
||||
* @return java.time 中带时区的日期时间
|
||||
*/
|
||||
public static java.time.ZonedDateTime toZonedDateTime(
|
||||
org.joda.time.Instant instant,
|
||||
org.joda.time.DateTimeZone dateTimeZone) {
|
||||
java.time.ZoneId zone = toJavaZone(dateTimeZone);
|
||||
return toJavaInstant(instant).atZone(zone);
|
||||
}
|
||||
|
||||
// ================================
|
||||
// #endregion
|
||||
// ================================
|
||||
|
||||
// ================================
|
||||
// #region - toJodaLocalDateTime
|
||||
// ================================
|
||||
|
||||
/**
|
||||
* 将 {@link java.time.LocalDateTime} 转换为 {@link org.joda.time.LocalDateTime}
|
||||
*
|
||||
* @param localDateTime Java 8 LocalDateTime
|
||||
* @return joda-time LocalDateTime
|
||||
*/
|
||||
public static org.joda.time.LocalDateTime toJodaLocalDateTime(java.time.LocalDateTime localDateTime) {
|
||||
return new org.joda.time.LocalDateTime(
|
||||
localDateTime.getYear(),
|
||||
localDateTime.getMonthValue(),
|
||||
localDateTime.getDayOfMonth(),
|
||||
localDateTime.getHour(),
|
||||
localDateTime.getMinute(),
|
||||
localDateTime.getSecond(),
|
||||
localDateTime.getNano() / 1_000_000 // 毫秒转纳秒
|
||||
);
|
||||
}
|
||||
|
||||
// ================================
|
||||
// #endregion
|
||||
// ================================
|
||||
|
||||
// ================================
|
||||
// #region - toJavaLocalDateTime
|
||||
// ================================
|
||||
|
||||
/**
|
||||
* 将 {@link org.joda.time.LocalDateTime} 转换为 {@link java.time.LocalDateTime}
|
||||
*
|
||||
* @param localDateTime joda-time LocalDateTime
|
||||
* @return Java 8 LocalDateTime
|
||||
*/
|
||||
public static java.time.LocalDateTime toJavaLocalDateTime(org.joda.time.LocalDateTime localDateTime) {
|
||||
return java.time.LocalDateTime.of(
|
||||
localDateTime.getYear(),
|
||||
localDateTime.getMonthOfYear(),
|
||||
localDateTime.getDayOfMonth(),
|
||||
localDateTime.getHourOfDay(),
|
||||
localDateTime.getMinuteOfHour(),
|
||||
localDateTime.getSecondOfMinute(),
|
||||
localDateTime.getMillisOfSecond() * 1_000_000 // 毫秒转纳秒
|
||||
);
|
||||
}
|
||||
|
||||
// ================================
|
||||
// #endregion
|
||||
// ================================
|
||||
|
||||
// ================================
|
||||
// #region - ZoneId <--> DateTimeZone
|
||||
// ================================
|
||||
|
||||
/**
|
||||
* 转换 Java API 和 joda-time API 表示时区的对象
|
||||
*
|
||||
* @param jodaZone joda-time API 中表示时区的对象
|
||||
* @return Java API 中表示时区的对象
|
||||
*/
|
||||
public static java.time.ZoneId toJavaZone(org.joda.time.DateTimeZone jodaZone) {
|
||||
return jodaZone.toTimeZone().toZoneId();
|
||||
}
|
||||
|
||||
/**
|
||||
* 转换 Java API 和 joda-time API 表示时区的对象
|
||||
*
|
||||
* @param zone Java API 中表示时区的对象
|
||||
* @return joda-time API 中表示时区的对象
|
||||
*/
|
||||
public static org.joda.time.DateTimeZone toJodaZone(java.time.ZoneId zone) {
|
||||
return org.joda.time.DateTimeZone.forID(zone.getId());
|
||||
}
|
||||
|
||||
// ================================
|
||||
// #endregion
|
||||
// ================================
|
||||
|
||||
/**
|
||||
* 私有构造方法
|
||||
*/
|
||||
private JodaTimeTools() {
|
||||
throw new IllegalStateException("Utility class");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,346 @@
|
||||
/*
|
||||
* Copyright 2022-present ZhouXY
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package xyz.zhouxy.plusone.commons.util;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.math.BigInteger;
|
||||
|
||||
import javax.annotation.Nonnull;
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
/**
|
||||
* 数字工具类
|
||||
*
|
||||
* @author ZhouXY
|
||||
*/
|
||||
public class Numbers {
|
||||
|
||||
// ================================
|
||||
// #region - sum
|
||||
// ================================
|
||||
|
||||
/**
|
||||
* 求和
|
||||
*
|
||||
* @param numbers 数据数组
|
||||
* @return 求和结果
|
||||
*/
|
||||
public static int sum(final short... numbers) {
|
||||
int result = 0;
|
||||
for (short number : numbers) {
|
||||
result += number;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* 求和
|
||||
*
|
||||
* @param numbers 数据数组
|
||||
* @return 求和结果
|
||||
*/
|
||||
public static long sum(final int... numbers) {
|
||||
long result = 0L;
|
||||
for (int number : numbers) {
|
||||
result += number;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* 求和
|
||||
*
|
||||
* @param numbers 数据数组
|
||||
* @return 求和结果
|
||||
*/
|
||||
public static long sum(final long... numbers) {
|
||||
long result = 0L;
|
||||
for (long number : numbers) {
|
||||
result += number;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* 求和
|
||||
*
|
||||
* @param numbers 数据数组
|
||||
* @return 求和结果
|
||||
*/
|
||||
public static double sum(final float... numbers) {
|
||||
double result = 0.00;
|
||||
for (float number : numbers) {
|
||||
result += number;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* 求和
|
||||
*
|
||||
* @param numbers 数据数组
|
||||
* @return 求和结果
|
||||
*/
|
||||
public static double sum(final double... numbers) {
|
||||
double result = 0.00;
|
||||
for (double number : numbers) {
|
||||
result += number;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* 求和
|
||||
*
|
||||
* @param numbers 数据数组
|
||||
* @return 求和结果
|
||||
*/
|
||||
public static BigInteger sum(final BigInteger... numbers) {
|
||||
if (ArrayTools.isEmpty(numbers)) {
|
||||
return BigInteger.ZERO;
|
||||
}
|
||||
BigInteger result = Numbers.nullToZero(numbers[0]);
|
||||
for (int i = 1; i < numbers.length; i++) {
|
||||
BigInteger value = numbers[i];
|
||||
if (value != null) {
|
||||
result = result.add(value);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* 求和
|
||||
*
|
||||
* @param numbers 数据数组
|
||||
* @return 求和结果
|
||||
*/
|
||||
public static BigDecimal sum(final BigDecimal... numbers) {
|
||||
return BigDecimals.sum(numbers);
|
||||
}
|
||||
|
||||
// ================================
|
||||
// #endregion
|
||||
// ================================
|
||||
|
||||
// ================================
|
||||
// #region - nullToZero
|
||||
// ================================
|
||||
|
||||
/**
|
||||
* 将 {@code null} 转换为 {@code 0}
|
||||
*
|
||||
* @param val 待转换的值
|
||||
* @return 如果 {@code val} 不为 {@code null},则返回该值;如果值为 {@code null},则返回 {@code 0}
|
||||
*/
|
||||
public static byte nullToZero(@Nullable final Byte val) {
|
||||
return val != null ? val : 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* 将 {@code null} 转换为 {@code 0}
|
||||
*
|
||||
* @param val 待转换的值
|
||||
* @return 如果 {@code val} 不为 {@code null},则返回该值;如果值为 {@code null},则返回 {@code 0}
|
||||
*/
|
||||
public static short nullToZero(@Nullable final Short val) {
|
||||
return val != null ? val : 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* 将 {@code null} 转换为 {@code 0}
|
||||
*
|
||||
* @param val 待转换的值
|
||||
* @return 如果 {@code val} 不为 {@code null},则返回该值;如果值为 {@code null},则返回 {@code 0}
|
||||
*/
|
||||
public static int nullToZero(@Nullable final Integer val) {
|
||||
return val != null ? val : 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* 将 {@code null} 转换为 {@code 0}
|
||||
*
|
||||
* @param val 待转换的值
|
||||
* @return 如果 {@code val} 不为 {@code null},则返回该值;如果值为 {@code null},则返回 {@code 0}
|
||||
*/
|
||||
public static long nullToZero(@Nullable final Long val) {
|
||||
return val != null ? val : 0L;
|
||||
}
|
||||
|
||||
/**
|
||||
* 将 {@code null} 转换为 {@code 0}
|
||||
*
|
||||
* @param val 待转换的值
|
||||
* @return 如果 {@code val} 不为 {@code null},则返回该值;如果值为 {@code null},则返回 {@code 0}
|
||||
*/
|
||||
public static float nullToZero(@Nullable final Float val) {
|
||||
return val != null ? val : 0.0F;
|
||||
}
|
||||
|
||||
/**
|
||||
* 将 {@code null} 转换为 {@code 0}
|
||||
*
|
||||
* @param val 待转换的值
|
||||
* @return 如果 {@code val} 不为 {@code null},则返回该值;如果值为 {@code null},则返回 {@code 0}
|
||||
*/
|
||||
public static double nullToZero(@Nullable final Double val) {
|
||||
return val != null ? val : 0.0;
|
||||
}
|
||||
|
||||
/**
|
||||
* 将 {@code null} 转换为 {@code 0}
|
||||
*
|
||||
* @param val 待转换的值
|
||||
* @return 如果 {@code val} 不为 {@code null},则返回该值;如果值为 {@code null},则返回 {@code 0}
|
||||
*/
|
||||
@Nonnull
|
||||
public static BigInteger nullToZero(@Nullable final BigInteger val) {
|
||||
return val != null ? val : BigInteger.ZERO;
|
||||
}
|
||||
|
||||
/**
|
||||
* 将 {@code null} 转换为 {@code 0}
|
||||
*
|
||||
* @param val 待转换的值
|
||||
* @return 如果 {@code val} 不为 {@code null},则返回该值;如果值为 {@code null},则返回 {@code 0}
|
||||
*/
|
||||
@Nonnull
|
||||
public static BigDecimal nullToZero(@Nullable final BigDecimal val) {
|
||||
return BigDecimals.nullToZero(val);
|
||||
}
|
||||
|
||||
// ================================
|
||||
// #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");
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2022-2023 the original author or authors.
|
||||
* Copyright 2023-present ZhouXY
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -26,27 +26,26 @@ import javax.annotation.Nullable;
|
||||
import com.google.common.annotations.Beta;
|
||||
|
||||
/**
|
||||
* OptionalUtil
|
||||
* OptionalTools
|
||||
*
|
||||
* <p>
|
||||
* 提供一些 Optional 相关的方法
|
||||
*
|
||||
* @author <a href="https://gitee.com/zhouxy108">ZhouXY</a>
|
||||
* @since 0.1.0
|
||||
* @author ZhouXY
|
||||
* @since 1.0.0
|
||||
* @see Optional
|
||||
* @see OptionalInt
|
||||
* @see OptionalLong
|
||||
* @see OptionalDouble
|
||||
*/
|
||||
public class OptionalUtil {
|
||||
public class OptionalTools {
|
||||
|
||||
/**
|
||||
* 将包装类 {@link Integer} 转为 {@link OptionalInt}(not null)。
|
||||
* <p>
|
||||
* 包装类为 {@code null} 表示值的缺失,转为 {@link OptionalInt} 后,由
|
||||
* {@link OptionalInt#empty()} 表示值的缺失。
|
||||
* </p>
|
||||
*
|
||||
*
|
||||
* @param value 包装对象
|
||||
* @return {@link OptionalInt} 实例
|
||||
*/
|
||||
@@ -58,13 +57,12 @@ public class OptionalUtil {
|
||||
* 将 {@code Optional<Integer>} 对象转为 {@link OptionalInt} 对象。
|
||||
* <p>
|
||||
* {@code Optional<Integer>} 将整数包装了两次,改为使用 {@link OptionalInt} 包装其中的整数数据。
|
||||
* </p>
|
||||
*
|
||||
*
|
||||
* @param optionalObj {@code Optional<Integer>} 对象
|
||||
* @return {@link OptionalInt} 实例
|
||||
*/
|
||||
public static OptionalInt toOptionalInt(Optional<Integer> optionalObj) {
|
||||
return optionalOf(optionalObj.orElse(null));
|
||||
return optionalObj.map(OptionalInt::of).orElseGet(OptionalInt::empty);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -72,8 +70,7 @@ public class OptionalUtil {
|
||||
* <p>
|
||||
* 包装类为 {@code null} 表示值的缺失,转为 {@link OptionalLong} 后,由
|
||||
* {@link OptionalLong#empty()} 表示值的缺失。
|
||||
* </p>
|
||||
*
|
||||
*
|
||||
* @param value 包装对象
|
||||
* @return {@link OptionalLong} 实例
|
||||
*/
|
||||
@@ -85,13 +82,12 @@ public class OptionalUtil {
|
||||
* 将 {@code Optional<Long>} 转为 {@link OptionalLong}。
|
||||
* <p>
|
||||
* {@code Optional<Long>} 将整数包装了两次,改为使用 {@link OptionalLong} 包装其中的整数数据。
|
||||
* </p>
|
||||
*
|
||||
*
|
||||
* @param optionalObj 包装对象
|
||||
* @return {@link OptionalLong} 实例
|
||||
*/
|
||||
public static OptionalLong toOptionalLong(Optional<Long> optionalObj) {
|
||||
return optionalOf(optionalObj.orElse(null));
|
||||
return optionalObj.map(OptionalLong::of).orElseGet(OptionalLong::empty);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -99,8 +95,7 @@ public class OptionalUtil {
|
||||
* <p>
|
||||
* 包装类为 {@code null} 表示值的缺失,转为 {@link OptionalDouble} 后,由
|
||||
* {@link OptionalDouble#empty()} 表示值的缺失。
|
||||
* </p>
|
||||
*
|
||||
*
|
||||
* @param value 包装对象
|
||||
* @return {@link OptionalDouble} 实例
|
||||
*/
|
||||
@@ -112,44 +107,65 @@ public class OptionalUtil {
|
||||
* 将 {@code Optional<Double>} 转为 {@link OptionalDouble}。
|
||||
* <p>
|
||||
* {@code Optional<Double>} 将整数包装了两次,改为使用 {@link OptionalDouble} 包装其中的整数数据。
|
||||
* </p>
|
||||
*
|
||||
*
|
||||
* @param optionalObj 包装对象
|
||||
* @return {@link OptionalDouble} 实例
|
||||
*/
|
||||
public static OptionalDouble toOptionalDouble(Optional<Double> optionalObj) {
|
||||
return optionalOf(optionalObj.orElse(null));
|
||||
return optionalObj.map(OptionalDouble::of).orElseGet(OptionalDouble::empty);
|
||||
}
|
||||
|
||||
/**
|
||||
* return the value of the optional object if present,
|
||||
* otherwise {@code null}.
|
||||
*
|
||||
*
|
||||
* @param <T> the class of the value
|
||||
* @param optionalObj {@link Optional} object, which must be non-null.
|
||||
* @return the value of the optional object if present, otherwise {@code null}.
|
||||
*/
|
||||
@Beta
|
||||
@Nullable
|
||||
public static <T> T orElseNull(Optional<T> optionalObj) {
|
||||
return optionalObj.orElse(null);
|
||||
}
|
||||
|
||||
/**
|
||||
* 将 {@link OptionalInt} 转为 {@link Integer}
|
||||
*
|
||||
* @param optionalObj optional 对象
|
||||
* @return {@link Integer} 对象。如果 {@code OptionalInt} 的值缺失,返回 {@code null}。
|
||||
*/
|
||||
@Beta
|
||||
@Nullable
|
||||
public static Integer toInteger(OptionalInt optionalObj) {
|
||||
return optionalObj.isPresent() ? optionalObj.getAsInt() : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 将 {@link OptionalLong} 转为 {@link Long}
|
||||
*
|
||||
* @param optionalObj optional 对象
|
||||
* @return {@link Long} 对象。如果 {@code OptionalLong} 的值缺失,返回 {@code null}。
|
||||
*/
|
||||
@Beta
|
||||
@Nullable
|
||||
public static Long toLong(OptionalLong optionalObj) {
|
||||
return optionalObj.isPresent() ? optionalObj.getAsLong() : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 将 {@link OptionalDouble} 转为 {@link Double}
|
||||
*
|
||||
* @param optionalObj optional 对象
|
||||
* @return {@link Double} 对象。如果 {@code OptionalDouble} 的值缺失,返回 {@code null}。
|
||||
*/
|
||||
@Beta
|
||||
@Nullable
|
||||
public static Double toDouble(OptionalDouble optionalObj) {
|
||||
return optionalObj.isPresent() ? optionalObj.getAsDouble() : null;
|
||||
}
|
||||
|
||||
private OptionalUtil() {
|
||||
private OptionalTools() {
|
||||
throw new IllegalStateException("Utility class");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,346 @@
|
||||
/*
|
||||
* Copyright 2023-present ZhouXY
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package xyz.zhouxy.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.Random;
|
||||
import java.util.concurrent.ThreadLocalRandom;
|
||||
|
||||
import com.google.common.collect.BoundType;
|
||||
import com.google.common.collect.Range;
|
||||
|
||||
/**
|
||||
* 随机工具类
|
||||
*
|
||||
* @author ZhouXY
|
||||
*/
|
||||
public final class RandomTools {
|
||||
|
||||
private static final SecureRandom DEFAULT_SECURE_RANDOM;
|
||||
|
||||
static {
|
||||
SecureRandom secureRandom;
|
||||
try {
|
||||
secureRandom = SecureRandom.getInstanceStrong(); // 获取高强度安全随机数生成器
|
||||
}
|
||||
catch (NoSuchAlgorithmException e) {
|
||||
secureRandom = new SecureRandom(); // 获取普通的安全随机数生成器
|
||||
}
|
||||
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
|
||||
// ================================
|
||||
|
||||
/**
|
||||
* 使用传入的随机数生成器,生成指定长度的字符串
|
||||
*
|
||||
* @param random 随机数生成器。根据需要可以传入
|
||||
* {@link java.util.concurrent.ThreadLocalRandom}、{@link java.security.SecureRandom}
|
||||
* 等,不为空
|
||||
* @param sourceCharacters 字符池。字符串的字符将在数组中选,不为空
|
||||
* @param length 字符串长度
|
||||
* @return 随机字符串
|
||||
*/
|
||||
public static String randomStr(Random random, char[] sourceCharacters, int length) {
|
||||
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) {
|
||||
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) {
|
||||
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);
|
||||
}
|
||||
|
||||
/**
|
||||
* 使用传入的随机数生成器,生成指定长度的字符串
|
||||
*
|
||||
* @param random 随机数生成器。根据需要可以传入
|
||||
* {@link java.util.concurrent.ThreadLocalRandom}、{@link java.security.SecureRandom}
|
||||
* 等,不为空
|
||||
* @param sourceCharacters 字符池。字符串的字符将在数组中选,不为空
|
||||
* @param length 字符串长度
|
||||
* @return 随机字符串
|
||||
*/
|
||||
public static String randomStr(Random random, String sourceCharacters, int length) {
|
||||
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) {
|
||||
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) {
|
||||
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 random 随机数生成器。根据需要传入
|
||||
* @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 random 随机数生成器
|
||||
* @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
|
||||
// ================================
|
||||
|
||||
/**
|
||||
* 使用传入的随机数生成器,生成指定长度的字符串
|
||||
*
|
||||
* @param random 随机数生成器。根据需要可以传入
|
||||
* {@link java.util.concurrent.ThreadLocalRandom}、{@link java.security.SecureRandom}
|
||||
* 等,不为空
|
||||
* @param sourceCharacters 字符池。字符串的字符将在数组中选,不为空
|
||||
* @param length 字符串长度
|
||||
* @return 随机字符串
|
||||
*/
|
||||
private static String randomStrInternal(Random random, char[] sourceCharacters, int length) {
|
||||
if (length == 0) {
|
||||
return StringTools.EMPTY_STRING;
|
||||
}
|
||||
final char[] result = new char[length];
|
||||
for (int i = 0; i < length; i++) {
|
||||
result[i] = sourceCharacters[random.nextInt(sourceCharacters.length)];
|
||||
}
|
||||
return String.valueOf(result);
|
||||
}
|
||||
|
||||
/**
|
||||
* 使用传入的随机数生成器,生成指定长度的字符串
|
||||
*
|
||||
* @param random 随机数生成器。根据需要可以传入
|
||||
* {@link java.util.concurrent.ThreadLocalRandom}、{@link java.security.SecureRandom}
|
||||
* 等,不为空
|
||||
* @param sourceCharacters 字符池。字符串的字符将在数组中选,不为空
|
||||
* @param length 字符串长度
|
||||
* @return 随机字符串
|
||||
*/
|
||||
private static String randomStrInternal(Random random, String sourceCharacters, int length) {
|
||||
if (length == 0) {
|
||||
return StringTools.EMPTY_STRING;
|
||||
}
|
||||
final char[] result = new char[length];
|
||||
for (int i = 0; i < length; i++) {
|
||||
result[i] = sourceCharacters.charAt(random.nextInt(sourceCharacters.length()));
|
||||
}
|
||||
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");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,205 @@
|
||||
/*
|
||||
* Copyright 2024-present ZhouXY
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package xyz.zhouxy.plusone.commons.util;
|
||||
|
||||
import java.util.Objects;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.function.Function;
|
||||
import java.util.function.Predicate;
|
||||
import java.util.function.UnaryOperator;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
/**
|
||||
* {@link Ref} 包装了一个值,表示对该值的应用。
|
||||
*
|
||||
* <p>灵感来自于 C# 的 {@code ref} 参数修饰符。C# 允许通过以下方式,将值返回给调用端:</p>
|
||||
* <pre>
|
||||
* void Method(ref int refArgument)
|
||||
* {
|
||||
* refArgument = refArgument + 44;
|
||||
* }
|
||||
*
|
||||
* int number = 1;
|
||||
* Method(ref number);
|
||||
* Console.WriteLine(number); // Output: 45
|
||||
* </pre>
|
||||
* {@link Ref} 使 Java 可以达到类似的效果,如:
|
||||
* <pre>
|
||||
* void method(final Ref<Integer> refArgument) {
|
||||
* refArgument.transformValue(i -> i + 44);
|
||||
* }
|
||||
*
|
||||
* Ref<Integer> number = Ref.of(1);
|
||||
* method(number);
|
||||
* System.out.println(number.getValue()); // Output: 45
|
||||
* </pre>
|
||||
* <p>
|
||||
* 当一个方法需要产生多个结果时,无法有多个返回值,可以使用 {@link Ref} 作为参数传入,方法内部修改 {@link Ref} 的值。
|
||||
* 调用方在调用方法之后,使用 {@code getValue()} 获取结果。
|
||||
*
|
||||
* <pre>
|
||||
* String method(final Ref<Integer> intRefArgument, final Ref<String> strRefArgument) {
|
||||
* intRefArgument.transformValue(i -> i + 44);
|
||||
* strRefArgument.setValue("Hello " + strRefArgument.getValue());
|
||||
* return "Return string";
|
||||
* }
|
||||
*
|
||||
* Ref<Integer> number = Ref.of(1);
|
||||
* Ref<String> str = Ref.of("Java");
|
||||
* String result = method(number, str);
|
||||
* System.out.println(number.getValue()); // Output: 45
|
||||
* System.out.println(str.getValue()); // Output: Hello Java
|
||||
* System.out.println(result); // Output: Return string
|
||||
* </pre>
|
||||
*
|
||||
* @author ZhouXY
|
||||
* @since 1.0.0
|
||||
*/
|
||||
public final class Ref<T> {
|
||||
|
||||
@Nullable
|
||||
private T value;
|
||||
|
||||
private Ref(@Nullable T value) {
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建对象引用
|
||||
*
|
||||
* @param <T> 引用的类型
|
||||
* @param value 引用的对象
|
||||
* @return {@code Ref} 对象
|
||||
*/
|
||||
public static <T> Ref<T> of(@Nullable T value) {
|
||||
return new Ref<>(value);
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建空引用
|
||||
*
|
||||
* @param <T> 引用的类型
|
||||
* @return 空引用
|
||||
*/
|
||||
public static <T> Ref<T> empty() {
|
||||
return new Ref<>(null);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取引用的对象
|
||||
*
|
||||
* @return 引用的对象
|
||||
*/
|
||||
@Nullable
|
||||
public T getValue() {
|
||||
return value;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置引用的对象
|
||||
*
|
||||
* @param value 要引用的对象
|
||||
*/
|
||||
public void setValue(@Nullable T value) {
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
/**
|
||||
* 使用 {@link UnaryOperator} 修改 {@code Ref} 内部引用的对象
|
||||
*
|
||||
* @param operator 修改逻辑
|
||||
*/
|
||||
public void transformValue(UnaryOperator<T> operator) {
|
||||
this.value = operator.apply(this.value);
|
||||
}
|
||||
|
||||
/**
|
||||
* 使用 {@link Function} 修改所引用的对象,返回新的 {@code Ref}
|
||||
*
|
||||
* @param <R> 结果的引用类型
|
||||
* @param function 修改逻辑
|
||||
* @return 修改后的对象的引用
|
||||
*/
|
||||
public <R> Ref<R> transform(Function<? super T, R> function) {
|
||||
return Ref.of(function.apply(this.value));
|
||||
}
|
||||
|
||||
/**
|
||||
* 使用 {@link Predicate} 检查引用的对象
|
||||
*
|
||||
* @param predicate 判断逻辑
|
||||
* @return 判断结果
|
||||
*/
|
||||
public boolean checkValue(Predicate<? super T> predicate) {
|
||||
return predicate.test(this.value);
|
||||
}
|
||||
|
||||
/**
|
||||
* 将引用的对象作为入参,执行 {@link Consumer} 的逻辑
|
||||
*
|
||||
* @param consumer 要执行的逻辑
|
||||
*/
|
||||
public void execute(Consumer<? super T> consumer) {
|
||||
consumer.accept(value);
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断所引用的对象是否为 {@code null}
|
||||
*
|
||||
* @return 是否为 {@code null}
|
||||
*/
|
||||
public boolean isNull() {
|
||||
return this.value == null;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 判断所引用的对象是否不为 {@code null}
|
||||
*
|
||||
* @return 是否不为 {@code null}
|
||||
*/
|
||||
public boolean isNotNull() {
|
||||
return this.value != null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return String.format("Ref[%s]", value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
final int prime = 31;
|
||||
int result = 1;
|
||||
result = prime * result + ((value == null) ? 0 : value.hashCode());
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(@Nullable Object obj) {
|
||||
if (this == obj)
|
||||
return true;
|
||||
if (obj == null)
|
||||
return false;
|
||||
if (getClass() != obj.getClass())
|
||||
return false;
|
||||
Ref<?> other = (Ref<?>) obj;
|
||||
return Objects.equals(this.value, other.value);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,389 @@
|
||||
/*
|
||||
* Copyright 2023-present ZhouXY
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package xyz.zhouxy.plusone.commons.util;
|
||||
|
||||
import static xyz.zhouxy.plusone.commons.util.AssertTools.checkArgument;
|
||||
import static xyz.zhouxy.plusone.commons.util.AssertTools.checkNotNull;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Objects;
|
||||
import java.util.Optional;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import javax.annotation.Nonnull;
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
import com.google.common.cache.CacheBuilder;
|
||||
import com.google.common.cache.CacheLoader;
|
||||
import com.google.common.cache.LoadingCache;
|
||||
|
||||
/**
|
||||
* 封装一些常用的正则操作,并可以缓存 {@link Pattern} 实例以复用。
|
||||
*
|
||||
* @author ZhouXY
|
||||
*/
|
||||
public final class RegexTools {
|
||||
|
||||
private static final int MAX_CACHE_SIZE = 256;
|
||||
private static final int DEFAULT_FLAG = 0;
|
||||
private static final LoadingCache<RegexAndFlags, Pattern> PATTERN_CACHE = CacheBuilder
|
||||
.newBuilder()
|
||||
.maximumSize(MAX_CACHE_SIZE)
|
||||
.build(new CacheLoader<RegexAndFlags, Pattern>() {
|
||||
@SuppressWarnings("null")
|
||||
public Pattern load(RegexAndFlags regexAndFlags) {
|
||||
return regexAndFlags.compilePattern();
|
||||
}
|
||||
});
|
||||
|
||||
// ================================
|
||||
// #region - getPattern
|
||||
// ================================
|
||||
|
||||
/**
|
||||
* 获取 {@link Pattern} 实例。
|
||||
*
|
||||
* @param pattern 正则表达式
|
||||
* @param cachePattern 是否缓存 {@link Pattern} 实例
|
||||
* @return {@link Pattern} 实例
|
||||
*/
|
||||
public static Pattern getPattern(final String pattern, final boolean cachePattern) {
|
||||
return getPattern(pattern, DEFAULT_FLAG, cachePattern);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取 {@link Pattern} 实例。
|
||||
*
|
||||
* @param pattern 正则表达式
|
||||
* @param flags 正则表达式匹配标识
|
||||
* @param cachePattern 是否缓存 {@link Pattern} 实例
|
||||
* @return {@link Pattern} 实例
|
||||
*/
|
||||
public static Pattern getPattern(final String pattern, final int flags, final boolean cachePattern) {
|
||||
checkNotNull(pattern);
|
||||
return cachePattern ? cacheAndGetPatternInternal(pattern, flags) : getPatternInternal(pattern, flags);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取 {@link Pattern} 实例,不缓存。
|
||||
*
|
||||
* @param pattern 正则表达式
|
||||
* @return {@link Pattern} 实例
|
||||
*/
|
||||
public static Pattern getPattern(final String pattern) {
|
||||
return getPattern(pattern, DEFAULT_FLAG);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取 {@link Pattern} 实例,不缓存。
|
||||
*
|
||||
* @param pattern 正则表达式
|
||||
* @param flags 正则表达式匹配标识
|
||||
* @return {@link Pattern} 实例
|
||||
*/
|
||||
@Nonnull
|
||||
public static Pattern getPattern(final String pattern, final int flags) {
|
||||
checkNotNull(pattern);
|
||||
return getPatternInternal(pattern, flags);
|
||||
}
|
||||
|
||||
// ================================
|
||||
// #endregion - getPattern
|
||||
// ================================
|
||||
|
||||
// ================================
|
||||
// #region - matches
|
||||
// ================================
|
||||
|
||||
/**
|
||||
* 判断 {@code input} 是否匹配 {@code pattern}。
|
||||
*
|
||||
* @param input 输入
|
||||
* @param pattern 正则
|
||||
* @return 判断结果
|
||||
*/
|
||||
public static boolean matches(@Nullable final CharSequence input, final Pattern pattern) {
|
||||
checkNotNull(pattern);
|
||||
return matchesInternal(input, pattern);
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断 {@code input} 是否匹配 {@code patterns} 中的一个。
|
||||
*
|
||||
* @param input 输入
|
||||
* @param patterns 正则
|
||||
* @return 判断结果
|
||||
*/
|
||||
public static boolean matchesAny(@Nullable final CharSequence input, final Pattern[] patterns) {
|
||||
checkArgument(ArrayTools.isAllElementsNotNull(patterns));
|
||||
return matchesAnyInternal(input, patterns);
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断 {@code input} 是否匹配全部正则。
|
||||
*
|
||||
* @param input 输入
|
||||
* @param patterns 正则
|
||||
* @return 判断结果
|
||||
*/
|
||||
public static boolean matchesAll(@Nullable final CharSequence input, final Pattern[] patterns) {
|
||||
checkArgument(ArrayTools.isAllElementsNotNull(patterns));
|
||||
return matchesAllInternal(input, patterns);
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断 {@code input} 是否匹配 {@code pattern}。
|
||||
*
|
||||
* @param input 输入
|
||||
* @param pattern 正则表达式
|
||||
* @param cachePattern 是否缓存 {@link Pattern} 实例
|
||||
* @return 判断结果
|
||||
*/
|
||||
public static boolean matches(@Nullable final CharSequence input, final String pattern,
|
||||
final boolean cachePattern) {
|
||||
return matches(input, pattern, DEFAULT_FLAG, cachePattern);
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断 {@code input} 是否匹配 {@code pattern}。
|
||||
*
|
||||
* @param input 输入
|
||||
* @param pattern 正则表达式
|
||||
* @param flags 正则表达式匹配标识
|
||||
* @param cachePattern 是否缓存 {@link Pattern} 实例
|
||||
* @return 判断结果
|
||||
*/
|
||||
public static boolean matches(@Nullable final CharSequence input, final String pattern, final int flags,
|
||||
final boolean cachePattern) {
|
||||
return matchesInternal(input, getPattern(pattern, flags, cachePattern));
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断 {@code input} 是否匹配 {@code pattern}。不缓存 {@link Pattern} 实例。
|
||||
*
|
||||
* @param input 输入
|
||||
* @param pattern 正则表达式
|
||||
* @return 判断结果
|
||||
*/
|
||||
public static boolean matches(@Nullable final CharSequence input, final String pattern) {
|
||||
return matches(input, pattern, DEFAULT_FLAG);
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断 {@code input} 是否匹配 {@code pattern}。不缓存 {@link Pattern} 实例。
|
||||
*
|
||||
* @param input 输入
|
||||
* @param pattern 正则表达式
|
||||
* @param flags 正则表达式匹配标识
|
||||
* @return 判断结果
|
||||
*/
|
||||
public static boolean matches(@Nullable final CharSequence input,
|
||||
final String pattern, final int flags) {
|
||||
return matchesInternal(input, getPattern(pattern, flags));
|
||||
}
|
||||
|
||||
// ================================
|
||||
// #endregion - matches
|
||||
// ================================
|
||||
|
||||
// ================================
|
||||
// #region - getMatcher
|
||||
// ================================
|
||||
|
||||
/**
|
||||
* 生成 Matcher。
|
||||
*
|
||||
* @param input 输入
|
||||
* @param pattern 正则
|
||||
* @return 结果
|
||||
*/
|
||||
public static Matcher getMatcher(final CharSequence input, final Pattern pattern) {
|
||||
checkNotNull(input);
|
||||
checkNotNull(pattern);
|
||||
return pattern.matcher(input);
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成 Matcher。
|
||||
*
|
||||
* @param input 输入
|
||||
* @param pattern 正则表达式
|
||||
* @param cachePattern 是否缓存 {@link Pattern} 实例
|
||||
* @return 结果
|
||||
*/
|
||||
public static Matcher getMatcher(final CharSequence input, final String pattern, boolean cachePattern) {
|
||||
return getMatcher(input, pattern, DEFAULT_FLAG, cachePattern);
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成 Matcher。
|
||||
*
|
||||
* @param input 输入
|
||||
* @param pattern 正则表达式
|
||||
* @param flags 正则表达式匹配标识
|
||||
* @param cachePattern 是否缓存 {@link Pattern} 实例
|
||||
* @return 结果
|
||||
*/
|
||||
public static Matcher getMatcher(final CharSequence input,
|
||||
final String pattern, final int flags, boolean cachePattern) {
|
||||
return getMatcher(input, getPattern(pattern, flags, cachePattern));
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成 Matcher。不缓存 {@link Pattern} 实例。
|
||||
*
|
||||
* @param input 输入
|
||||
* @param pattern 正则表达式
|
||||
* @return 结果
|
||||
*/
|
||||
public static Matcher getMatcher(final CharSequence input, final String pattern) {
|
||||
return getMatcher(input, pattern, DEFAULT_FLAG);
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成 Matcher。不缓存 {@link Pattern} 实例。
|
||||
*
|
||||
* @param input 输入
|
||||
* @param pattern 正则表达式
|
||||
* @param flags 正则表达式匹配标识
|
||||
* @return 结果
|
||||
*/
|
||||
public static Matcher getMatcher(final CharSequence input, final String pattern, final int flags) {
|
||||
checkNotNull(input);
|
||||
checkNotNull(pattern);
|
||||
return getPatternInternal(pattern, flags).matcher(input);
|
||||
}
|
||||
|
||||
// ================================
|
||||
// #endregion - getMatcher
|
||||
// ================================
|
||||
|
||||
// ================================
|
||||
// #region - internal methods
|
||||
// ================================
|
||||
|
||||
/**
|
||||
* 获取 {@link Pattern} 实例。
|
||||
*
|
||||
* @param pattern 正则表达式
|
||||
* @param flags 正则表达式匹配标识
|
||||
* @return {@link Pattern} 实例
|
||||
*/
|
||||
@Nonnull
|
||||
private static Pattern cacheAndGetPatternInternal(final String pattern, final int flags) {
|
||||
final RegexAndFlags regexAndFlags = new RegexAndFlags(pattern, flags);
|
||||
return PATTERN_CACHE.getUnchecked(regexAndFlags);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取 {@link Pattern} 实例,不缓存。
|
||||
*
|
||||
* @param pattern 正则表达式
|
||||
* @param flags 正则表达式匹配标识
|
||||
* @return {@link Pattern} 实例
|
||||
*/
|
||||
@Nonnull
|
||||
private static Pattern getPatternInternal(final String pattern, final int flags) {
|
||||
final RegexAndFlags regexAndFlags = new RegexAndFlags(pattern, flags);
|
||||
return Optional.ofNullable(PATTERN_CACHE.getIfPresent(regexAndFlags))
|
||||
.orElseGet(regexAndFlags::compilePattern);
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断 {@code input} 是否匹配 {@code pattern}。
|
||||
*
|
||||
* @param input 输入
|
||||
* @param pattern 正则
|
||||
* @return 判断结果
|
||||
*/
|
||||
private static boolean matchesInternal(@Nullable final CharSequence input, final Pattern pattern) {
|
||||
return input != null && pattern.matcher(input).matches();
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断 {@code input} 是否匹配至少一个正则。
|
||||
*
|
||||
* @param input 输入
|
||||
* @param patterns 正则表达式
|
||||
* @return 判断结果
|
||||
*/
|
||||
private static boolean matchesAnyInternal(@Nullable final CharSequence input, final Pattern[] patterns) {
|
||||
return input != null
|
||||
&& Arrays.stream(patterns)
|
||||
.anyMatch(pattern -> pattern.matcher(input).matches());
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断 {@code input} 是否匹配全部正则。
|
||||
*
|
||||
* @param input 输入
|
||||
* @param patterns 正则表达式
|
||||
* @return 判断结果
|
||||
*/
|
||||
private static boolean matchesAllInternal(@Nullable final CharSequence input, final Pattern[] patterns) {
|
||||
return input != null
|
||||
&& Arrays.stream(patterns)
|
||||
.allMatch(pattern -> pattern.matcher(input).matches());
|
||||
}
|
||||
|
||||
// ================================
|
||||
// #endregion - internal methods
|
||||
// ================================
|
||||
|
||||
private RegexTools() {
|
||||
// 不允许实例化
|
||||
throw new IllegalStateException("Utility class");
|
||||
}
|
||||
|
||||
// ================================
|
||||
// #region - RegexAndFlags
|
||||
// ================================
|
||||
|
||||
private static final class RegexAndFlags {
|
||||
private final String regex;
|
||||
private final int flags;
|
||||
|
||||
private RegexAndFlags(String regex, int flags) {
|
||||
this.regex = regex;
|
||||
this.flags = flags;
|
||||
}
|
||||
|
||||
private Pattern compilePattern() {
|
||||
return Pattern.compile(regex, flags);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hash(regex, flags);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(@Nullable Object obj) {
|
||||
if (this == obj)
|
||||
return true;
|
||||
if (!(obj instanceof RegexAndFlags))
|
||||
return false;
|
||||
RegexAndFlags other = (RegexAndFlags) obj;
|
||||
return Objects.equals(regex, other.regex) && flags == other.flags;
|
||||
}
|
||||
}
|
||||
|
||||
// ================================
|
||||
// #endregion - RegexAndFlags
|
||||
// ================================
|
||||
}
|
||||
@@ -1,14 +1,28 @@
|
||||
/*
|
||||
* Copyright 2023-2024 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* 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.util.concurrent.TimeUnit;
|
||||
|
||||
import com.google.common.annotations.Beta;
|
||||
import com.google.common.base.Preconditions;
|
||||
|
||||
/**
|
||||
* Twitter_Snowflake
|
||||
* Twitter 版雪花算法
|
||||
*/
|
||||
@Beta
|
||||
public class SnowflakeIdGenerator {
|
||||
|
||||
// ==============================Fields===========================================
|
||||
@@ -52,9 +66,6 @@ public class SnowflakeIdGenerator {
|
||||
/** 上次生成 ID 的时间截 */
|
||||
private long lastTimestamp = -1L;
|
||||
|
||||
/** 锁对象 */
|
||||
private final Object lock = new Object();
|
||||
|
||||
// ==============================Constructors=====================================
|
||||
|
||||
/**
|
||||
@@ -64,9 +75,9 @@ public class SnowflakeIdGenerator {
|
||||
* @param datacenterId 数据中心ID (0~31)
|
||||
*/
|
||||
public SnowflakeIdGenerator(final long workerId, final long datacenterId) {
|
||||
Preconditions.checkArgument((workerId > MAX_WORKER_ID || workerId < 0),
|
||||
checkArgument((workerId <= MAX_WORKER_ID && workerId >= 0),
|
||||
"WorkerId can't be greater than %s or less than 0.", MAX_WORKER_ID);
|
||||
Preconditions.checkArgument((datacenterId > MAX_DATACENTER_ID || datacenterId < 0),
|
||||
checkArgument((datacenterId <= MAX_DATACENTER_ID && datacenterId >= 0),
|
||||
"DatacenterId can't be greater than %s or less than 0.", MAX_DATACENTER_ID);
|
||||
this.datacenterIdAndWorkerId
|
||||
= (datacenterId << DATACENTER_ID_SHIFT) | (workerId << WORKER_ID_SHIFT);
|
||||
@@ -78,51 +89,47 @@ public class SnowflakeIdGenerator {
|
||||
*
|
||||
* @return SnowflakeId
|
||||
*/
|
||||
public long nextId() {
|
||||
long timestamp;
|
||||
synchronized (lock) {
|
||||
timestamp = timeGen();
|
||||
public synchronized long nextId() {
|
||||
long timestamp = timeGen();
|
||||
|
||||
// 发生了回拨,此刻时间小于上次发号时间
|
||||
if (timestamp < lastTimestamp) {
|
||||
long offset = lastTimestamp - timestamp;
|
||||
if (offset <= 5) {
|
||||
// 时间偏差大小小于5ms,则等待两倍时间
|
||||
try {
|
||||
TimeUnit.MILLISECONDS.sleep(offset << 1);
|
||||
} catch (InterruptedException e) {
|
||||
Thread.currentThread().interrupt();
|
||||
throw new IllegalStateException(e);
|
||||
}
|
||||
timestamp = timeGen();
|
||||
if (timestamp < lastTimestamp) {
|
||||
// 还是小于,抛异常上报
|
||||
throwClockBackwardsEx(lastTimestamp, timestamp);
|
||||
}
|
||||
} else {
|
||||
// 发生了回拨,此刻时间小于上次发号时间
|
||||
if (timestamp < lastTimestamp) {
|
||||
long offset = lastTimestamp - timestamp;
|
||||
if (offset <= 5) {
|
||||
// 时间偏差大小小于5ms,则等待两倍时间
|
||||
try {
|
||||
TimeUnit.MILLISECONDS.sleep(offset << 1);
|
||||
} catch (InterruptedException e) {
|
||||
Thread.currentThread().interrupt();
|
||||
throw new IllegalStateException(e);
|
||||
}
|
||||
timestamp = timeGen();
|
||||
if (timestamp < lastTimestamp) {
|
||||
// 还是小于,抛异常上报
|
||||
throwClockBackwardsEx(lastTimestamp, timestamp);
|
||||
}
|
||||
} else {
|
||||
throwClockBackwardsEx(lastTimestamp, timestamp);
|
||||
}
|
||||
|
||||
// 如果是同一时间生成的,则进行毫秒内序列
|
||||
if (lastTimestamp == timestamp) {
|
||||
sequence = (sequence + 1) & SEQUENCE_MASK;
|
||||
// 毫秒内序列溢出
|
||||
if (sequence == 0) {
|
||||
// 阻塞到下一个毫秒,获得新的时间戳
|
||||
timestamp = tilNextMillis(lastTimestamp);
|
||||
}
|
||||
}
|
||||
// 时间戳改变,毫秒内序列重置
|
||||
else {
|
||||
sequence = 0L;
|
||||
}
|
||||
|
||||
// 上次生成 ID 的时间截
|
||||
lastTimestamp = timestamp;
|
||||
|
||||
}
|
||||
|
||||
// 如果是同一时间生成的,则进行毫秒内序列
|
||||
if (lastTimestamp == timestamp) {
|
||||
sequence = (sequence + 1) & SEQUENCE_MASK;
|
||||
// 毫秒内序列溢出
|
||||
if (sequence == 0) {
|
||||
// 阻塞到下一个毫秒,获得新的时间戳
|
||||
timestamp = tilNextMillis(lastTimestamp);
|
||||
}
|
||||
}
|
||||
// 时间戳改变,毫秒内序列重置
|
||||
else {
|
||||
sequence = 0L;
|
||||
}
|
||||
|
||||
// 上次生成 ID 的时间截
|
||||
lastTimestamp = timestamp;
|
||||
|
||||
// 移位并通过或运算拼到一起组成64位的ID
|
||||
return ((timestamp - TWEPOCH) << TIMESTAMP_LEFT_SHIFT) | datacenterIdAndWorkerId | sequence;
|
||||
}
|
||||
@@ -0,0 +1,247 @@
|
||||
/*
|
||||
* Copyright 2024-present ZhouXY
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package xyz.zhouxy.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 javax.annotation.Nullable;
|
||||
|
||||
import com.google.common.annotations.Beta;
|
||||
|
||||
import xyz.zhouxy.plusone.commons.constant.PatternConsts;
|
||||
|
||||
/**
|
||||
* StringTools
|
||||
*
|
||||
* <p>
|
||||
* 字符串工具类。
|
||||
*
|
||||
* @author ZhouXY
|
||||
* @since 1.0.0
|
||||
*/
|
||||
public class StringTools {
|
||||
|
||||
public static final String EMPTY_STRING = "";
|
||||
|
||||
/**
|
||||
* 判断字符串是否非空白
|
||||
*
|
||||
* <pre>
|
||||
* StringTools.isNotBlank(null); // false
|
||||
* StringTools.isNotBlank(""); // false
|
||||
* StringTools.isNotBlank(" "); // false
|
||||
* StringTools.isNotBlank("Hello"); // true
|
||||
* </pre>
|
||||
*
|
||||
* @param cs 检查的字符串
|
||||
* @return 是否非空白
|
||||
*/
|
||||
public static boolean isNotBlank(@Nullable final String cs) {
|
||||
if (cs == null || cs.isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
for (int i = 0; i < cs.length(); i++) {
|
||||
if (!Character.isWhitespace(cs.charAt(i))) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断是否空白字符串
|
||||
*
|
||||
* <pre>
|
||||
* StringTools.isBlank(null); // true
|
||||
* StringTools.isBlank(""); // true
|
||||
* StringTools.isBlank(" "); // true
|
||||
* StringTools.isBlank("Hello"); // false
|
||||
* </pre>
|
||||
*
|
||||
* @param cs 检查的字符串
|
||||
* @return 是否空白
|
||||
* @since 1.1.0
|
||||
*/
|
||||
public static boolean isBlank(@Nullable String cs) {
|
||||
if (cs == null || cs.isEmpty()) {
|
||||
return true;
|
||||
}
|
||||
for (int i = 0; i < cs.length(); i++) {
|
||||
if (!Character.isWhitespace(cs.charAt(i))) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* 重复字符串
|
||||
*
|
||||
* @param str 要重复的字符串
|
||||
* @param times 重复次数
|
||||
* @return 结果
|
||||
*/
|
||||
public static String repeat(String str, int times) {
|
||||
return repeat(str, times, Integer.MAX_VALUE);
|
||||
}
|
||||
|
||||
/**
|
||||
* 重复字符串
|
||||
*
|
||||
* @param str 要重复的字符串
|
||||
* @param times 重复次数
|
||||
* @param maxLength 最大长度
|
||||
* @return 结果
|
||||
*/
|
||||
public static String repeat(final String str, int times, int maxLength) {
|
||||
checkArgumentNotNull(str);
|
||||
return String.valueOf(ArrayTools.repeat(str.toCharArray(), times, maxLength));
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断字符串是否非空
|
||||
*
|
||||
* <pre>
|
||||
* StringTools.isNotEmpty(null); // false
|
||||
* StringTools.isNotEmpty(""); // false
|
||||
* StringTools.isNotEmpty(" "); // true
|
||||
* StringTools.isNotEmpty("Hello"); // true
|
||||
* </pre>
|
||||
*
|
||||
* @param cs 检查的字符串
|
||||
* @return 是否非空
|
||||
* @since 1.1.0
|
||||
*/
|
||||
public static boolean isNotEmpty(@Nullable final String cs) {
|
||||
return cs != null && !cs.isEmpty();
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断字符串是否为空字符串
|
||||
*
|
||||
* <pre>
|
||||
* StringTools.isEmpty(null); // true
|
||||
* StringTools.isEmpty(""); // true
|
||||
* StringTools.isEmpty(" "); // false
|
||||
* StringTools.isEmpty("Hello"); // false
|
||||
* </pre>
|
||||
*
|
||||
* @param cs 检查的字符串
|
||||
* @return 是否空字符串
|
||||
* @since 1.1.0
|
||||
*/
|
||||
public static boolean isEmpty(@Nullable final String cs) {
|
||||
return cs == null || cs.isEmpty();
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断字符串是否为邮箱地址
|
||||
*
|
||||
* @param cs 检查的字符串
|
||||
* @return 是否是邮箱地址
|
||||
* @since 1.1.0
|
||||
*
|
||||
* @see PatternConsts#EMAIL
|
||||
*/
|
||||
@Beta
|
||||
public static boolean isEmail(@Nullable final String cs) {
|
||||
return RegexTools.matches(cs, PatternConsts.EMAIL);
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断字符串是否为 URL 地址
|
||||
*
|
||||
* @param cs 检查的字符串
|
||||
* @return 是否是 URL
|
||||
* @since 1.1.0
|
||||
*
|
||||
* @see URL
|
||||
*/
|
||||
@Beta
|
||||
public static boolean isURL(@Nullable final String cs) {
|
||||
if (cs == null) {
|
||||
return false;
|
||||
}
|
||||
try {
|
||||
new URL(cs);
|
||||
} catch (MalformedURLException e) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* 脱敏
|
||||
*
|
||||
* @param src 原字符串
|
||||
* @param front 前面保留的字符数
|
||||
* @param end 后面保留的字符数
|
||||
* @return 脱敏结果
|
||||
* @since 1.1.0
|
||||
*/
|
||||
public static String desensitize(@Nullable final String src, int front, int end) {
|
||||
return desensitize(src, '*', front, end);
|
||||
}
|
||||
|
||||
/**
|
||||
* 脱敏
|
||||
*
|
||||
* @param src 原字符串
|
||||
* @param replacedChar 用于替换的字符
|
||||
* @param front 前面保留的字符数
|
||||
* @param end 后面保留的字符数
|
||||
* @return 脱敏结果
|
||||
* @since 1.1.0
|
||||
*/
|
||||
public static String desensitize(@Nullable final String src, char replacedChar, int front, int end) {
|
||||
if (src == null || src.isEmpty()) {
|
||||
return EMPTY_STRING;
|
||||
}
|
||||
checkArgument(front >= 0 && end >= 0);
|
||||
checkArgument((front + end) <= src.length(), "需要截取的长度不能大于原字符串长度");
|
||||
final char[] charArray = src.toCharArray();
|
||||
for (int i = front; i < charArray.length - end; i++) {
|
||||
charArray[i] = replacedChar;
|
||||
}
|
||||
return String.valueOf(charArray);
|
||||
}
|
||||
|
||||
/**
|
||||
* 转换为带引号的字符串
|
||||
*
|
||||
* @param value 值
|
||||
* @return 带引号的字符串
|
||||
* @since 1.1.0
|
||||
*/
|
||||
public static String toQuotedString(@Nullable String value) {
|
||||
if (value == null) {
|
||||
return "null";
|
||||
}
|
||||
if (value.isEmpty()) {
|
||||
return "\"\"";
|
||||
}
|
||||
return "\"" + value + "\"";
|
||||
}
|
||||
|
||||
private StringTools() {
|
||||
throw new IllegalStateException("Utility class");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,137 @@
|
||||
/*
|
||||
* Copyright 2023-present ZhouXY
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package xyz.zhouxy.plusone.commons.util;
|
||||
|
||||
import static xyz.zhouxy.plusone.commons.util.AssertTools.checkNotNull;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.Comparator;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.function.BiConsumer;
|
||||
import java.util.function.Function;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
/**
|
||||
* TreeBuilder
|
||||
*
|
||||
* @author ZhouXY
|
||||
* @since 1.0.0
|
||||
*/
|
||||
public class TreeBuilder<T, TSubTree extends T, TIdentity> {
|
||||
private final Function<T, TIdentity> identityGetter;
|
||||
private final Function<T, Optional<TIdentity>> parentIdentityGetter;
|
||||
private final BiConsumer<TSubTree, T> addChildMethod;
|
||||
private final Comparator<? super T> defaultComparator;
|
||||
|
||||
/**
|
||||
* 构造一个 {@code TreeBuilder}。不指定用于排序的 {@code Comparator}。
|
||||
*
|
||||
* @param identityGetter 从节点中获取其标识的逻辑
|
||||
* @param parentIdentityGetter 获取父节点标识的逻辑
|
||||
* @param addChild 添加子节点的逻辑
|
||||
*/
|
||||
public TreeBuilder(Function<T, TIdentity> identityGetter, Function<T, Optional<TIdentity>> parentIdentityGetter,
|
||||
BiConsumer<TSubTree, T> addChild) {
|
||||
this(identityGetter, parentIdentityGetter, addChild, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* 构造一个 {@code TreeBuilder}。
|
||||
*
|
||||
* @param identityGetter 从节点中获取其标识的逻辑
|
||||
* @param parentIdentityGetter 获取父节点标识的逻辑
|
||||
* @param addChild 添加子节点的逻辑
|
||||
* @param defaultComparator 默认的 {@code Comparator},用于排序
|
||||
*/
|
||||
public TreeBuilder(Function<T, TIdentity> identityGetter, Function<T, Optional<TIdentity>> parentIdentityGetter,
|
||||
BiConsumer<TSubTree, T> addChild, @Nullable Comparator<? super T> defaultComparator) {
|
||||
this.identityGetter = identityGetter;
|
||||
this.parentIdentityGetter = parentIdentityGetter;
|
||||
this.addChildMethod = addChild;
|
||||
this.defaultComparator = defaultComparator;
|
||||
}
|
||||
|
||||
/**
|
||||
* 将节点构建成树。使用 {@link #defaultComparator} 进行排序。如果 {@link #defaultComparator}
|
||||
* <p>
|
||||
* <b>注意,该方法会直接操作 nodes 列表中的节点,并没有做深拷贝,
|
||||
* 注意避免 nodes 中的元素产生变化所带来的意料之外的影响。</b>
|
||||
*
|
||||
* @param nodes 平铺的节点列表
|
||||
* @return 构造结果
|
||||
*/
|
||||
public List<T> buildTree(Collection<T> nodes) {
|
||||
checkNotNull(nodes);
|
||||
return buildTreeInternal(nodes, this.defaultComparator);
|
||||
}
|
||||
|
||||
/**
|
||||
* 将节点构建成树。
|
||||
* <p>
|
||||
* <b>!!注意:该方法会直接操作 nodes 列表中的节点,并没有做深拷贝,
|
||||
* 注意避免 nodes 中的元素产生变化所带来的意料之外的影响。</b>
|
||||
*
|
||||
* @param nodes 平铺的节点列表
|
||||
* @param comparator 用于节点的排序。
|
||||
* 若为 {@code null},则使用 {@link #defaultComparator};
|
||||
* 若 {@link #defaultComparator} 也为 {@code null},则不排序。
|
||||
* <b>仅影响调用 addChild 的顺序,如果操作对象本身对应的控制了子节点的顺序,无法影响其相关逻辑。</b>
|
||||
* @return 构建的树形结构
|
||||
*/
|
||||
public List<T> buildTree(Collection<T> nodes, @Nullable Comparator<? super T> comparator) {
|
||||
checkNotNull(nodes);
|
||||
final Comparator<? super T> c = (comparator != null) ? comparator : this.defaultComparator;
|
||||
return buildTreeInternal(nodes, c);
|
||||
}
|
||||
|
||||
/**
|
||||
* 将节点构建成树。
|
||||
* <p>
|
||||
* <b>注意,该方法会直接操作 nodes 列表中的节点,并没有做深拷贝,
|
||||
* 注意避免 nodes 中的元素产生变化所带来的意料之外的影响。</b>
|
||||
*
|
||||
* @param nodes 平铺的节点列表
|
||||
* @param comparator 用于节点的排序。若为 {@code null},则不排序
|
||||
*/
|
||||
private List<T> buildTreeInternal(Collection<T> nodes, @Nullable Comparator<? super T> comparator) {
|
||||
final Collection<T> allNodes;
|
||||
if (comparator == null) {
|
||||
allNodes = nodes;
|
||||
} else {
|
||||
allNodes = nodes.stream().sorted(comparator).collect(Collectors.toList());
|
||||
}
|
||||
|
||||
final Map<TIdentity, T> identityNodeMap = allNodes.stream()
|
||||
.collect(Collectors.toMap(identityGetter, Function.identity(), (n1, n2) -> n1));
|
||||
// 根节点
|
||||
final List<T> rootNodes = allNodes.stream()
|
||||
.filter(node -> !this.parentIdentityGetter.apply(node).isPresent())
|
||||
.collect(Collectors.toList());
|
||||
allNodes.forEach(node -> parentIdentityGetter.apply(node).ifPresent(parentIdentity -> {
|
||||
if (identityNodeMap.containsKey(parentIdentity)) {
|
||||
@SuppressWarnings("unchecked")
|
||||
TSubTree parentNode = (TSubTree) identityNodeMap.get(parentIdentity);
|
||||
addChildMethod.accept(parentNode, node);
|
||||
}
|
||||
}));
|
||||
return rootNodes;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,107 @@
|
||||
/*
|
||||
* Copyright 2025-present ZhouXY
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package xyz.zhouxy.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 ZhouXY
|
||||
*
|
||||
* @see Deflater
|
||||
* @see Inflater
|
||||
*/
|
||||
public class ZipTools {
|
||||
|
||||
/**
|
||||
* 使用默认压缩级别压缩数据
|
||||
*
|
||||
* @param input 输入
|
||||
* @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");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
/*
|
||||
* Copyright 2025-present ZhouXY
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
/**
|
||||
* <h2>工具类</h2>
|
||||
* <p>
|
||||
* 包含树构建器({@link TreeBuilder})、断言工具({@link AssertTools})、
|
||||
* ID 生成器({@link IdGenerator})及其它实用工具类。
|
||||
*
|
||||
* @author ZhouXY
|
||||
*/
|
||||
@ParametersAreNonnullByDefault
|
||||
package xyz.zhouxy.plusone.commons.util;
|
||||
|
||||
import javax.annotation.ParametersAreNonnullByDefault;
|
||||
@@ -1,3 +1,19 @@
|
||||
/*
|
||||
* Copyright 2023-2024 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* 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;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertSame;
|
||||
@@ -11,6 +27,7 @@ import xyz.zhouxy.plusone.commons.util.Enumeration;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
|
||||
@SuppressWarnings("deprecation")
|
||||
class EnumerationTests {
|
||||
|
||||
private static final Logger log = LoggerFactory.getLogger(EnumerationTests.class);
|
||||
@@ -28,6 +45,7 @@ class EnumerationTests {
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("deprecation")
|
||||
final class EntityStatus extends Enumeration<EntityStatus> {
|
||||
|
||||
private EntityStatus(int value, String name) {
|
||||
@@ -38,7 +56,7 @@ final class EntityStatus extends Enumeration<EntityStatus> {
|
||||
public static final EntityStatus AVAILABLE = new EntityStatus(0, "正常");
|
||||
public static final EntityStatus DISABLED = new EntityStatus(1, "禁用");
|
||||
|
||||
private static final ValueSet<EntityStatus> VALUE_SET = ValueSet.of(AVAILABLE, DISABLED);
|
||||
private static final ValueSet<EntityStatus> VALUE_SET = ValueSet.of(new EntityStatus[] { AVAILABLE, DISABLED });
|
||||
|
||||
public static EntityStatus of(int value) {
|
||||
return VALUE_SET.get(value);
|
||||
@@ -49,6 +67,7 @@ final class EntityStatus extends Enumeration<EntityStatus> {
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("deprecation")
|
||||
final class Result extends Enumeration<Result> {
|
||||
private Result(int id, String name) {
|
||||
super(id, name);
|
||||
@@ -57,7 +76,7 @@ final class Result extends Enumeration<Result> {
|
||||
public static final Result SUCCESSFUL = new Result(1, "成功");
|
||||
public static final Result FAILURE = new Result(0, "失败");
|
||||
|
||||
private static final ValueSet<Result> VALUE_SET = ValueSet.of(SUCCESSFUL, FAILURE);
|
||||
private static final ValueSet<Result> VALUE_SET = ValueSet.of(new Result[] { SUCCESSFUL, FAILURE });
|
||||
|
||||
public static Result of(int id) {
|
||||
return VALUE_SET.get(id);
|
||||
@@ -0,0 +1,39 @@
|
||||
/*
|
||||
* Copyright 2023-2024 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* 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;
|
||||
|
||||
import java.io.ObjectStreamClass;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import xyz.zhouxy.plusone.commons.exception.system.NoAvailableMacFoundException;
|
||||
|
||||
@Slf4j
|
||||
class SerialTests {
|
||||
|
||||
@Test
|
||||
void testSerialVersionUID() {
|
||||
long uid = getSerialVersionUID(NoAvailableMacFoundException.class);
|
||||
log.info("\n private static final long serialVersionUID = {}L;", uid);
|
||||
}
|
||||
|
||||
private long getSerialVersionUID(Class<?> cl) {
|
||||
ObjectStreamClass c = ObjectStreamClass.lookup(cl);
|
||||
return c.getSerialVersionUID();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,140 @@
|
||||
/*
|
||||
* Copyright 2024-present ZhouXY
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package xyz.zhouxy.plusone.commons.base;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
import static xyz.zhouxy.plusone.commons.util.AssertTools.checkNotNull;
|
||||
|
||||
import javax.annotation.Nonnull;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
|
||||
class IWithCodeTests {
|
||||
|
||||
@Test
|
||||
void equalsCode_SameCode_ReturnsTrue() {
|
||||
assertTrue(WithCode.INSTANCE.isCodeEquals("testCode"));
|
||||
Integer intCode = 0;
|
||||
Long longCode = 0L;
|
||||
assertTrue(WithIntCode.INSTANCE.isCodeEquals(intCode));
|
||||
assertTrue(WithLongCode.INSTANCE.isCodeEquals(intCode));
|
||||
assertTrue(WithLongCode.INSTANCE.isCodeEquals(longCode));
|
||||
|
||||
assertTrue(WithCode.INSTANCE.isSameCodeAs(WithCode.SAME_CODE_INSTANCE));
|
||||
assertTrue(WithIntCode.INSTANCE.isSameCodeAs(WithIntCode.SAME_CODE_INSTANCE));
|
||||
assertTrue(WithIntCode.INSTANCE.isSameCodeAs(WithLongCode.SAME_CODE_INSTANCE));
|
||||
assertTrue(WithLongCode.INSTANCE.isSameCodeAs(WithLongCode.SAME_CODE_INSTANCE));
|
||||
assertTrue(WithLongCode.INSTANCE.isSameCodeAs(WithIntCode.SAME_CODE_INSTANCE));
|
||||
}
|
||||
|
||||
@Test
|
||||
void equalsCode_DifferentCode_ReturnsFalse() {
|
||||
assertFalse(WithCode.INSTANCE.isCodeEquals("wrongCode"));
|
||||
Integer intCode = 108;
|
||||
Long longCode = 108L;
|
||||
assertFalse(WithIntCode.INSTANCE.isCodeEquals(intCode));
|
||||
assertFalse(WithLongCode.INSTANCE.isCodeEquals(intCode));
|
||||
assertFalse(WithLongCode.INSTANCE.isCodeEquals(longCode));
|
||||
|
||||
assertFalse(WithCode.INSTANCE.isSameCodeAs(WithCode.WRONG_CODE_INSTANCE));
|
||||
assertFalse(WithIntCode.INSTANCE.isSameCodeAs(WithIntCode.WRONG_CODE_INSTANCE));
|
||||
assertFalse(WithIntCode.INSTANCE.isSameCodeAs(WithLongCode.WRONG_CODE_INSTANCE));
|
||||
assertFalse(WithLongCode.INSTANCE.isSameCodeAs(WithLongCode.WRONG_CODE_INSTANCE));
|
||||
assertFalse(WithLongCode.INSTANCE.isSameCodeAs(WithIntCode.WRONG_CODE_INSTANCE));
|
||||
}
|
||||
|
||||
@Test
|
||||
@SuppressWarnings("null")
|
||||
void equalsCode_NullCode_ReturnsFalse() {
|
||||
assertFalse(WithCode.INSTANCE.isSameCodeAs((WithCode) null));
|
||||
assertFalse(WithCode.INSTANCE.isSameCodeAs((WithIntCode) null));
|
||||
assertFalse(WithCode.INSTANCE.isSameCodeAs((WithLongCode) null));
|
||||
|
||||
assertFalse(WithIntCode.INSTANCE.isSameCodeAs((WithCode) null));
|
||||
assertFalse(WithIntCode.INSTANCE.isSameCodeAs((WithIntCode) null));
|
||||
assertFalse(WithIntCode.INSTANCE.isSameCodeAs((WithLongCode) null));
|
||||
|
||||
assertFalse(WithLongCode.INSTANCE.isSameCodeAs((WithCode) null));
|
||||
assertFalse(WithLongCode.INSTANCE.isSameCodeAs((WithIntCode) null));
|
||||
assertFalse(WithLongCode.INSTANCE.isSameCodeAs((WithLongCode) null));
|
||||
|
||||
assertFalse(WithCode.INSTANCE.isCodeEquals((String) null));
|
||||
Integer intCode = null;
|
||||
Long longCode = null;
|
||||
assertThrows(NullPointerException.class, () -> WithIntCode.INSTANCE.isCodeEquals(intCode));
|
||||
assertThrows(NullPointerException.class, () -> WithLongCode.INSTANCE.isCodeEquals(intCode));
|
||||
assertThrows(NullPointerException.class, () -> WithLongCode.INSTANCE.isCodeEquals(longCode));
|
||||
}
|
||||
|
||||
private enum WithCode implements IWithCode<String> {
|
||||
INSTANCE("testCode"),
|
||||
SAME_CODE_INSTANCE("testCode"),
|
||||
WRONG_CODE_INSTANCE("wrongCode"),
|
||||
;
|
||||
|
||||
@Nonnull
|
||||
private final String code;
|
||||
|
||||
WithCode(String code) {
|
||||
checkNotNull(code);
|
||||
this.code = code;
|
||||
}
|
||||
|
||||
@Override
|
||||
@Nonnull
|
||||
public String getCode() {
|
||||
return code;
|
||||
}
|
||||
}
|
||||
|
||||
private enum WithIntCode implements IWithIntCode {
|
||||
INSTANCE(0),
|
||||
SAME_CODE_INSTANCE(0),
|
||||
WRONG_CODE_INSTANCE(1),
|
||||
;
|
||||
|
||||
private final int code;
|
||||
|
||||
WithIntCode(int code) {
|
||||
this.code = code;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getCode() {
|
||||
return code;
|
||||
}
|
||||
}
|
||||
|
||||
private enum WithLongCode implements IWithLongCode {
|
||||
INSTANCE(0L),
|
||||
SAME_CODE_INSTANCE(0L),
|
||||
WRONG_CODE_INSTANCE(108L),
|
||||
;
|
||||
|
||||
private final long code;
|
||||
|
||||
WithLongCode(long code) {
|
||||
this.code = code;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getCode() {
|
||||
return code;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,138 @@
|
||||
/*
|
||||
* Copyright 2024-present ZhouXY
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package xyz.zhouxy.plusone.commons.collection;
|
||||
|
||||
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.assertSame;
|
||||
import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
|
||||
import java.lang.reflect.Constructor;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import com.google.common.collect.HashBasedTable;
|
||||
import com.google.common.collect.HashMultimap;
|
||||
import com.google.common.collect.HashMultiset;
|
||||
import com.google.common.collect.ImmutableMap;
|
||||
import com.google.common.collect.Lists;
|
||||
import com.google.common.collect.Multimap;
|
||||
import com.google.common.collect.Multiset;
|
||||
import com.google.common.collect.Range;
|
||||
import com.google.common.collect.RangeSet;
|
||||
import com.google.common.collect.Sets;
|
||||
import com.google.common.collect.Table;
|
||||
import com.google.common.collect.TreeRangeSet;
|
||||
|
||||
public class CollectionToolsTests {
|
||||
@Test
|
||||
void testIsEmpty() {
|
||||
|
||||
// Collection
|
||||
List<String> list = new ArrayList<>();
|
||||
assertTrue(CollectionTools.isEmpty(list));
|
||||
assertFalse(CollectionTools.isNotEmpty(list));
|
||||
|
||||
list.add("Test");
|
||||
assertFalse(CollectionTools.isEmpty(list));
|
||||
assertTrue(CollectionTools.isNotEmpty(list));
|
||||
|
||||
// Map
|
||||
Map<String, Integer> map = new HashMap<>();
|
||||
assertTrue(CollectionTools.isEmpty(map));
|
||||
assertFalse(CollectionTools.isNotEmpty(map));
|
||||
|
||||
map.put("2", 2);
|
||||
assertFalse(CollectionTools.isEmpty(map));
|
||||
assertTrue(CollectionTools.isNotEmpty(map));
|
||||
|
||||
// Table
|
||||
Table<String, String, Integer> table = HashBasedTable.create();
|
||||
assertTrue(CollectionTools.isEmpty(table));
|
||||
assertFalse(CollectionTools.isNotEmpty(table));
|
||||
|
||||
table.put("ABC", "d", 4);
|
||||
assertFalse(CollectionTools.isEmpty(table));
|
||||
assertTrue(CollectionTools.isNotEmpty(table));
|
||||
|
||||
// Multimap
|
||||
Multimap<String, String> multimap = HashMultimap.create();
|
||||
assertTrue(CollectionTools.isEmpty(multimap));
|
||||
assertFalse(CollectionTools.isNotEmpty(multimap));
|
||||
|
||||
multimap.put("ABC", "d");
|
||||
assertFalse(CollectionTools.isEmpty(multimap));
|
||||
assertTrue(CollectionTools.isNotEmpty(multimap));
|
||||
|
||||
// Multiset
|
||||
Multiset<String> multiset = HashMultiset.create();
|
||||
assertTrue(CollectionTools.isEmpty(multiset));
|
||||
assertFalse(CollectionTools.isNotEmpty(multiset));
|
||||
|
||||
multiset.add("ABC");
|
||||
assertFalse(CollectionTools.isEmpty(multiset));
|
||||
assertTrue(CollectionTools.isNotEmpty(multiset));
|
||||
|
||||
// RangeSet
|
||||
RangeSet<Integer> rangeSet = TreeRangeSet.create();
|
||||
assertTrue(CollectionTools.isEmpty(rangeSet));
|
||||
assertFalse(CollectionTools.isNotEmpty(rangeSet));
|
||||
|
||||
rangeSet.add(Range.closed(0, 100));
|
||||
rangeSet.add(Range.openClosed(100, 200));
|
||||
assertFalse(CollectionTools.isEmpty(rangeSet));
|
||||
assertTrue(CollectionTools.isNotEmpty(rangeSet));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testNullToEmpty() {
|
||||
List<String> list = Lists.newArrayList("Java", "C", "C++", "C#");
|
||||
assertSame(list, CollectionTools.nullToEmptyList(list));
|
||||
assertEquals(Collections.emptyList(), CollectionTools.nullToEmptyList(null));
|
||||
|
||||
Set<String> set = Sets.newHashSet("Java", "C", "C++", "C#");
|
||||
assertSame(set, CollectionTools.nullToEmptySet(set));
|
||||
assertEquals(Collections.emptySet(), CollectionTools.nullToEmptySet(null));
|
||||
|
||||
Map<String, Integer> map = ImmutableMap.of("K1", 1, "K2", 2, "K3", 3);
|
||||
assertSame(map, CollectionTools.nullToEmptyMap(map));
|
||||
assertEquals(Collections.emptyMap(), CollectionTools.nullToEmptyMap(null));
|
||||
}
|
||||
|
||||
@Test
|
||||
void test_constructor_isNotAccessible_ThrowsIllegalStateException() {
|
||||
Constructor<?>[] constructors = CollectionTools.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());
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,290 @@
|
||||
/*
|
||||
* Copyright 2025-present ZhouXY
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package xyz.zhouxy.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.assertNotNull;
|
||||
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));
|
||||
|
||||
// 创建一个有初始化数据的不可变的 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 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);
|
||||
|
||||
assertNotNull(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'");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,271 @@
|
||||
/*
|
||||
* Copyright 2024-present ZhouXY
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package xyz.zhouxy.plusone.commons.constant;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
|
||||
import java.lang.reflect.Constructor;
|
||||
import java.util.Arrays;
|
||||
import java.util.regex.Matcher;
|
||||
|
||||
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 PatternConstsTests {
|
||||
|
||||
// ================================
|
||||
// #region - BASIC_ISO_DATE
|
||||
// ================================
|
||||
|
||||
@Test
|
||||
void testBasicIsoDate_ValidDate() {
|
||||
Matcher matcher = PatternConsts.BASIC_ISO_DATE.matcher("20241229");
|
||||
assertTrue(matcher.matches());
|
||||
|
||||
assertEquals("2024", matcher.group(1));
|
||||
assertEquals("12", matcher.group(2));
|
||||
assertEquals("29", matcher.group(3));
|
||||
assertEquals("2024", matcher.group("yyyy"));
|
||||
assertEquals("12", matcher.group("MM"));
|
||||
assertEquals("29", matcher.group("dd"));
|
||||
|
||||
// LeapYearFeb29()
|
||||
assertTrue(PatternConsts.BASIC_ISO_DATE.matcher("20200229").matches());
|
||||
|
||||
// BoundaryMin()
|
||||
assertTrue(PatternConsts.BASIC_ISO_DATE.matcher("00000101").matches());
|
||||
|
||||
// BoundaryMax()
|
||||
assertTrue(PatternConsts.BASIC_ISO_DATE.matcher("9999999991231").matches());
|
||||
}
|
||||
|
||||
@ParameterizedTest
|
||||
@ValueSource(strings = {
|
||||
"20231301", // InvalidMonth
|
||||
"20230230", // InvalidDay
|
||||
"20210229", // NonLeapYearFeb29
|
||||
})
|
||||
void testBasicIsoDate_InvalidDate_butMatches(String date) {
|
||||
// 虽然日期有误,但这个正则无法判断。实际工作中,应使用日期时间 API。
|
||||
Matcher matcher = PatternConsts.BASIC_ISO_DATE.matcher(date);
|
||||
assertTrue(matcher.matches());
|
||||
}
|
||||
|
||||
@ParameterizedTest
|
||||
@ValueSource(strings = {
|
||||
"2023041", // TooShort
|
||||
"99999999990415", // TooLong
|
||||
"2023-04-15", // NonNumeric
|
||||
})
|
||||
void testBasicIsoDate_InvalidDate_Mismatches(String date) {
|
||||
Matcher matcher = PatternConsts.BASIC_ISO_DATE.matcher(date);
|
||||
assertFalse(matcher.matches());
|
||||
}
|
||||
|
||||
// ================================
|
||||
// #endregion - BASIC_ISO_DATE
|
||||
// ================================
|
||||
|
||||
// ================================
|
||||
// #region - ISO_LOCAL_DATE
|
||||
// ================================
|
||||
|
||||
@Test
|
||||
void testIsoLocalDate_ValidDate() {
|
||||
Matcher matcher = PatternConsts.ISO_LOCAL_DATE.matcher("2024-12-29");
|
||||
assertTrue(matcher.matches());
|
||||
assertEquals("2024", matcher.group("yyyy"));
|
||||
assertEquals("12", matcher.group("MM"));
|
||||
assertEquals("29", matcher.group("dd"));
|
||||
|
||||
// LeapYearFeb29()
|
||||
assertTrue(PatternConsts.ISO_LOCAL_DATE.matcher("2020-02-29").matches());
|
||||
|
||||
// BoundaryMin()
|
||||
assertTrue(PatternConsts.ISO_LOCAL_DATE.matcher("0000-01-01").matches());
|
||||
|
||||
// BoundaryMax()
|
||||
assertTrue(PatternConsts.ISO_LOCAL_DATE.matcher("999999999-12-31").matches());
|
||||
}
|
||||
|
||||
@ParameterizedTest
|
||||
@ValueSource(strings = {
|
||||
"2023-13-01", // InvalidMonth
|
||||
"2023-02-30", // InvalidDay
|
||||
"2021-02-29", // NonLeapYearFeb29
|
||||
})
|
||||
void testIsoLocalDate_InvalidDate_butMatches(String date) {
|
||||
// 虽然日期有误,但这个正则无法判断。实际工作中,应使用日期时间 API。
|
||||
Matcher matcher = PatternConsts.ISO_LOCAL_DATE.matcher(date);
|
||||
assertTrue(matcher.matches());
|
||||
}
|
||||
|
||||
@ParameterizedTest
|
||||
@ValueSource(strings = {
|
||||
"2023-04-1", // TooShort
|
||||
"9999999999-04-15", // TooLong
|
||||
"20230415",
|
||||
})
|
||||
void testIsoLocalDate_InvalidDate_Mismatches(String date) {
|
||||
Matcher matcher = PatternConsts.ISO_LOCAL_DATE.matcher(date);
|
||||
assertFalse(matcher.matches());
|
||||
}
|
||||
|
||||
// ================================
|
||||
// #endregion - ISO_LOCAL_DATE
|
||||
// ================================
|
||||
|
||||
// ================================
|
||||
// #region - PASSWORD
|
||||
// ================================
|
||||
|
||||
@Test
|
||||
void testPassword_ValidPassword_Matches() {
|
||||
assertTrue(PatternConsts.PASSWORD.matcher("Abc123!@#").matches());
|
||||
}
|
||||
|
||||
@Test
|
||||
void testPassword_InvalidPassword_Mismatches() {
|
||||
assertFalse(PatternConsts.PASSWORD.matcher("Abc123 !@#").matches()); // 带空格
|
||||
assertFalse(PatternConsts.PASSWORD.matcher("Abc123!@# ").matches()); // 带空格
|
||||
assertFalse(PatternConsts.PASSWORD.matcher(" Abc123!@#").matches()); // 带空格
|
||||
assertFalse(PatternConsts.PASSWORD.matcher(" Abc123!@# ").matches()); // 带空格
|
||||
assertFalse(PatternConsts.PASSWORD.matcher("77553366998844113322").matches()); // 纯数字
|
||||
assertFalse(PatternConsts.PASSWORD.matcher("poiujhgbfdsazxcfvghj").matches()); // 纯小写字母
|
||||
assertFalse(PatternConsts.PASSWORD.matcher("POIUJHGBFDSAZXCFVGHJ").matches()); // 纯大写字母
|
||||
assertFalse(PatternConsts.PASSWORD.matcher("!#$%&'*\\+-/=?^`{|}~@()[]\",.;':").matches()); // 纯特殊字符
|
||||
assertFalse(PatternConsts.PASSWORD.matcher("sdfrghbv525842582752").matches()); // 没有小写字母
|
||||
assertFalse(PatternConsts.PASSWORD.matcher("SDFRGHBV525842582752").matches()); // 没有小写字母
|
||||
assertFalse(PatternConsts.PASSWORD.matcher("sdfrghbvSDFRGHBV").matches()); // 没有数字
|
||||
assertFalse(PatternConsts.PASSWORD.matcher("Abc1!").matches()); // 太短
|
||||
assertFalse(PatternConsts.PASSWORD.matcher("Abc1!Abc1!Abc1!Abc1!Abc1!Abc1!Abc1!").matches()); // 太长
|
||||
assertFalse(PatternConsts.PASSWORD.matcher("").matches());
|
||||
assertFalse(PatternConsts.PASSWORD.matcher(" ").matches());
|
||||
}
|
||||
|
||||
// ================================
|
||||
// #endregion - PASSWORD
|
||||
// ================================
|
||||
|
||||
// ================================
|
||||
// #region - EMAIL
|
||||
// ================================
|
||||
|
||||
@Test
|
||||
public void testValidEmails() {
|
||||
assertTrue(PatternConsts.EMAIL.matcher("test@example.com").matches());
|
||||
assertTrue(PatternConsts.EMAIL.matcher("user.name+tag+sorting@example.com").matches());
|
||||
assertTrue(PatternConsts.EMAIL.matcher("user@sub.example.com").matches());
|
||||
assertTrue(PatternConsts.EMAIL.matcher("user@123.123.123.123").matches());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testInvalidEmails() {
|
||||
assertFalse(PatternConsts.EMAIL.matcher(".username@example.com").matches());
|
||||
assertFalse(PatternConsts.EMAIL.matcher("@missingusername.com").matches());
|
||||
assertFalse(PatternConsts.EMAIL.matcher("plainaddress").matches());
|
||||
assertFalse(PatternConsts.EMAIL.matcher("username..username@example.com").matches());
|
||||
assertFalse(PatternConsts.EMAIL.matcher("username.@example.com").matches());
|
||||
assertFalse(PatternConsts.EMAIL.matcher("username@-example.com").matches());
|
||||
assertFalse(PatternConsts.EMAIL.matcher("username@-example.com").matches());
|
||||
assertFalse(PatternConsts.EMAIL.matcher("username@.com.com").matches());
|
||||
assertFalse(PatternConsts.EMAIL.matcher("username@.com.my").matches());
|
||||
assertFalse(PatternConsts.EMAIL.matcher("username@.com").matches());
|
||||
assertFalse(PatternConsts.EMAIL.matcher("username@com.").matches());
|
||||
assertFalse(PatternConsts.EMAIL.matcher("username@com").matches());
|
||||
assertFalse(PatternConsts.EMAIL.matcher("username@example..com").matches());
|
||||
assertFalse(PatternConsts.EMAIL.matcher("username@example.com-").matches());
|
||||
assertFalse(PatternConsts.EMAIL.matcher("username@example.com.").matches());
|
||||
assertFalse(PatternConsts.EMAIL.matcher("username@example").matches());
|
||||
}
|
||||
|
||||
// ================================
|
||||
// #endregion - EMAIL
|
||||
// ================================
|
||||
|
||||
// ================================
|
||||
// #region - Chinese2ndIdCardNumber
|
||||
// ================================
|
||||
|
||||
@ParameterizedTest
|
||||
@ValueSource(strings = {
|
||||
"44520019900101456X",
|
||||
"44520019900101456x",
|
||||
"445200199001014566",
|
||||
})
|
||||
void testChinese2ndIdCardNumber_ValidChinese2ndIdCardNumber(String value) {
|
||||
Matcher matcher = PatternConsts.CHINESE_2ND_ID_CARD_NUMBER.matcher(value);
|
||||
assertTrue(matcher.matches());
|
||||
assertEquals("44", matcher.group("province"));
|
||||
assertEquals("4452", matcher.group("city"));
|
||||
assertEquals("445200", matcher.group("county"));
|
||||
}
|
||||
|
||||
@ParameterizedTest
|
||||
@ValueSource(strings = {
|
||||
"4452200199001014566",
|
||||
"44520199001014566",
|
||||
" ",
|
||||
"",
|
||||
})
|
||||
void testChinese2ndIdCardNumber_InvalidChinese2ndIdCardNumber(String value) {
|
||||
assertFalse(PatternConsts.CHINESE_2ND_ID_CARD_NUMBER.matcher(value).matches());
|
||||
}
|
||||
|
||||
// ================================
|
||||
// #endregion - Chinese2ndIdCardNumber
|
||||
// ================================
|
||||
|
||||
// ================================
|
||||
// #region - invoke constructor
|
||||
// ================================
|
||||
|
||||
@Test
|
||||
void test_constructor_isNotAccessible_ThrowsIllegalStateException() {
|
||||
Constructor<?>[] constructors;
|
||||
constructors = RegexConsts.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());
|
||||
});
|
||||
|
||||
constructors = PatternConsts.class.getDeclaredConstructors();
|
||||
Arrays.stream(constructors)
|
||||
.forEach(constructor -> {
|
||||
assertFalse(constructor.isAccessible());
|
||||
constructor.setAccessible(true);
|
||||
Throwable cause = assertThrows(Exception.class, constructor::newInstance)
|
||||
.getCause();
|
||||
assertInstanceOf(IllegalStateException.class, cause);
|
||||
assertEquals("Utility class", cause.getMessage());
|
||||
});
|
||||
}
|
||||
|
||||
// ================================
|
||||
// #endregion - invoke constructor
|
||||
// ================================
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user