├── gradle ├── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties └── libs.versions.toml ├── math-toolbox ├── README.md ├── src │ └── main │ │ ├── resources │ │ └── logback.xml │ │ └── java │ │ └── com │ │ └── onixbyte │ │ └── math │ │ ├── model │ │ └── QuartileBounds.java │ │ └── PercentileCalculator.java └── build.gradle.kts ├── crypto-toolbox ├── src │ ├── test │ │ └── resources │ │ │ ├── ec_public_key.pem │ │ │ ├── ec_private_key.pem │ │ │ ├── rsa_public_key.pem │ │ │ └── rsa_private_key.pem │ └── main │ │ ├── java │ │ └── com │ │ │ └── onixbyte │ │ │ └── crypto │ │ │ ├── PrivateKeyLoader.java │ │ │ ├── util │ │ │ └── CryptoUtil.java │ │ │ ├── PublicKeyLoader.java │ │ │ ├── algorithm │ │ │ ├── ecdsa │ │ │ │ ├── ECPrivateKeyLoader.java │ │ │ │ └── ECPublicKeyLoader.java │ │ │ └── rsa │ │ │ │ ├── RSAPrivateKeyLoader.java │ │ │ │ └── RSAPublicKeyLoader.java │ │ │ └── exception │ │ │ └── KeyLoadingException.java │ │ └── resources │ │ └── logback.xml ├── README.md └── build.gradle.kts ├── common-toolbox ├── README.md ├── src │ ├── main │ │ ├── resources │ │ │ └── logback.xml │ │ └── java │ │ │ └── com │ │ │ └── onixbyte │ │ │ └── common │ │ │ └── util │ │ │ ├── ObjectMapAdapter.java │ │ │ ├── BoolUtil.java │ │ │ ├── MapUtil.java │ │ │ ├── CollectionUtil.java │ │ │ ├── Base64Util.java │ │ │ └── RangeUtil.java │ └── test │ │ └── java │ │ └── com │ │ └── onixbyte │ │ └── common │ │ └── util │ │ ├── AesUtilTest.java │ │ ├── Base64UtilTest.java │ │ ├── BoolUtilTest.java │ │ ├── CollectionUtilTest.java │ │ ├── RangeUtilTest.java │ │ ├── BranchUtilTest.java │ │ └── HashUtilTest.java └── build.gradle.kts ├── .gitignore ├── settings.gradle.kts ├── .editorconfig ├── LICENSE ├── identity-generator ├── README.md ├── src │ └── main │ │ ├── resources │ │ └── logback.xml │ │ └── java │ │ └── com │ │ └── onixbyte │ │ └── identitygenerator │ │ ├── IdentityGenerator.java │ │ ├── exceptions │ │ └── TimingException.java │ │ └── impl │ │ ├── SequentialUuidGenerator.java │ │ └── SnowflakeIdentityGenerator.java └── build.gradle.kts ├── gradle.properties ├── version-catalogue ├── README.md └── build.gradle.kts ├── README.md ├── tuple ├── src │ └── main │ │ └── java │ │ └── com │ │ └── onixbyte │ │ └── tuple │ │ ├── ImmutableBiTuple.java │ │ ├── ImmutableTriTuple.java │ │ ├── BiTuple.java │ │ └── TriTuple.java ├── README.md └── build.gradle.kts ├── CONTRIBUTING.md ├── .github ├── ISSUE_TEMPLATE │ ├── bug-report.yml │ └── feature-request.yml └── workflows │ └── github-packages-publish.yml ├── CODE_OF_CONDUCT.md └── gradlew.bat /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/onixbyte/onixbyte-toolbox/HEAD/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /math-toolbox/README.md: -------------------------------------------------------------------------------- 1 | # Math Toolkit 2 | 3 | **Math Toolkit** provides some mathematical algorithms and utilities such as chained high-precision 4 | mathematical calculator and percentile statistic algorithm. 5 | -------------------------------------------------------------------------------- /crypto-toolbox/src/test/resources/ec_public_key.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN PUBLIC KEY----- 2 | MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEZAOxkWNouIIPKsdF1YgF1D/XD8Pa 3 | ZyYNcH60ONRWjMqlQXozWMb2i7WphKxf8kopp42nzCflWQod+JQY+hM/EQ== 4 | -----END PUBLIC KEY----- 5 | -------------------------------------------------------------------------------- /crypto-toolbox/src/test/resources/ec_private_key.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN PRIVATE KEY----- 2 | MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQgs79JlARgXEf6EDV7 3 | +PHQCTHEMtqIoHOy1GZ1+ynQJ6yhRANCAARkA7GRY2i4gg8qx0XViAXUP9cPw9pn 4 | Jg1wfrQ41FaMyqVBejNYxvaLtamErF/ySimnjafMJ+VZCh34lBj6Ez8R 5 | -----END PRIVATE KEY----- 6 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | distributionUrl=https\://services.gradle.org/distributions/gradle-8.10.2-all.zip 4 | networkTimeout=10000 5 | validateDistributionUrl=true 6 | zipStoreBase=GRADLE_USER_HOME 7 | zipStorePath=wrapper/dists 8 | -------------------------------------------------------------------------------- /common-toolbox/README.md: -------------------------------------------------------------------------------- 1 | # Common Toolbox 2 | 3 | ## Introduction 4 | 5 | Common Toolbox is a Java SE utility library, that provides a collection of utility to streamline 6 | your Java coding experience. 7 | 8 | ## Features 9 | 10 | - AES encryption and decryption; 11 | - Base64 encode and decode; 12 | - Boolean calculation; 13 | - Reduce `if...else...` with **lambdas**; 14 | - Hash calculation for strings; 15 | - Convert Java beans to map and map to Java beans; 16 | - Simplified range generator. 17 | 18 | -------------------------------------------------------------------------------- /crypto-toolbox/src/test/resources/rsa_public_key.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN PUBLIC KEY----- 2 | MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA+FSBWCRTALNeYyd1dyIn 3 | B3e7CNkyBak2TBONM5TpVQJTyi2Zk0L2RSzYGlAALLz4uYtQ0dssMCugJQX58FZ8 4 | 75g1wuZihzs+KMTkFQOjZadslWkDM4nZn2DUpDLXL/+B0T7GrXOyvzOZeqQj9tA1 5 | UY5IpAXSMvHvcPsegayBaeUPD2XP19gRjQFDDGAAnXmB+UfwNAsapDQ9d3rHOIJM 6 | pzDq72a2Rhws+dbrH0gqsg1lsLDfhhui2FomuBpDZUtHq0Jz/IEvd3X45XvegSH8 7 | t8+yL/pFK3+YpDVtj/IzMSwL+izvnXFALvZOO+8CABeyKuSjLh/6LbAzrvoftql5 8 | gQIDAQAB 9 | -----END PUBLIC KEY----- 10 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | target/ 2 | !.mvn/wrapper/maven-wrapper.jar 3 | !**/src/main/**/target/ 4 | !**/src/test/**/target/ 5 | 6 | ### IntelliJ IDEA ### 7 | .idea 8 | .idea/modules.xml 9 | .idea/jarRepositories.xml 10 | .idea/compiler.xml 11 | .idea/libraries/ 12 | *.iws 13 | *.iml 14 | *.ipr 15 | 16 | ### Eclipse ### 17 | .apt_generated 18 | .classpath 19 | .factorypath 20 | .project 21 | .settings 22 | .springBeans 23 | .sts4-cache 24 | 25 | ### NetBeans ### 26 | /nbproject/private/ 27 | /nbbuild/ 28 | /dist/ 29 | /nbdist/ 30 | /.nb-gradle/ 31 | build/ 32 | !**/src/main/**/build/ 33 | !**/src/test/**/build/ 34 | 35 | ### VS Code ### 36 | .vscode/ 37 | 38 | ### Mac OS ### 39 | .DS_Store 40 | 41 | ### Gradle ### 42 | .gradle -------------------------------------------------------------------------------- /settings.gradle.kts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2024-2025 OnixByte. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | 18 | rootProject.name = "onixbyte-toolbox" 19 | 20 | include( 21 | "version-catalogue", 22 | "common-toolbox", 23 | "identity-generator", 24 | "crypto-toolbox", 25 | "math-toolbox", 26 | ) 27 | 28 | include("tuple") -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | [*] 2 | charset = utf-8 3 | end_of_line = lf 4 | indent_size = 4 5 | indent_style = space 6 | insert_final_newline = true 7 | max_line_length = 100 8 | tab_width = 4 9 | 10 | [*.less] 11 | indent_size = 2 12 | 13 | [*.proto] 14 | indent_size = 2 15 | tab_width = 2 16 | 17 | [*.sass] 18 | indent_size = 2 19 | 20 | [*.scss] 21 | indent_size = 2 22 | 23 | [*.vue] 24 | indent_size = 2 25 | tab_width = 2 26 | 27 | [{*.bash,*.sh,*.zsh}] 28 | indent_size = 2 29 | tab_width = 2 30 | 31 | [{*.default,*.yaml,*.yml}] 32 | indent_size = 2 33 | 34 | [{*.har,*.jsb2,*.jsb3,*.json,*.jsonc,*.postman_collection,*.postman_collection.json,*.postman_environment,*.postman_environment.json,.babelrc,.eslintrc,.prettierrc,.stylelintrc,.ws-context,jest.config}] 35 | indent_size = 2 36 | 37 | [{*.pb,*.textproto,*.txtpb}] 38 | indent_size = 2 39 | tab_width = 2 40 | 41 | [{*.ps1,*.psd1,*.psm1}] 42 | max_line_length = 115 43 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2024-2025 OnixByte 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /identity-generator/README.md: -------------------------------------------------------------------------------- 1 | # Module `guid` 2 | 3 | ## Introduction 4 | 5 | Module `guid` serves as a guid creator for other `JDevKit` modules. You can also use this module as a guid creator standards. 6 | 7 | We have already implemented `SnowflakeGuidCreator`, you can also implement a custom guid creations by implementing `com.onixbyte.identitygenerator.IdentityGenerator`. 8 | 9 | ## Example usage 10 | 11 | ### A UUID creator 12 | 13 | ```java 14 | GuidCreator uuidCreator = (GuidCreator) UUID::randomUUID; 15 | ``` 16 | 17 | ### A custom guid creator 18 | 19 | Assume that you need serial guid creator. 20 | 21 | ```java 22 | @Component 23 | public class CustomGuidCreator implementes GuidCreator { 24 | 25 | public final RedisTemplate serialRedisTemplate; 26 | 27 | @Autowired 28 | public CustomGuidCreator(RedisTemplate serialRedisTemplate) { 29 | this.serialRedisTemplate = serialRedisTemplate; 30 | } 31 | 32 | @Override public String nextId() { 33 | return "SOME_PREFIX" + serialRedisTemplate.opsForValue().get("some_serial_key"); 34 | } 35 | 36 | } 37 | ``` 38 | 39 | -------------------------------------------------------------------------------- /gradle.properties: -------------------------------------------------------------------------------- 1 | # 2 | # Copyright (c) 2024-2025 OnixByte 3 | # 4 | # Permission is hereby granted, free of charge, to any person obtaining a copy 5 | # of this software and associated documentation files (the "Software"), to deal 6 | # in the Software without restriction, including without limitation the rights 7 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | # copies of the Software, and to permit persons to whom the Software is 9 | # furnished to do so, subject to the following conditions: 10 | # 11 | # The above copyright notice and this permission notice shall be included in all 12 | # copies or substantial portions of the Software. 13 | # 14 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 20 | # SOFTWARE. 21 | # 22 | 23 | artefactVersion=3.2.0 24 | projectUrl=https://onixbyte.com/projects/onixbyte-toolbox 25 | projectGithubUrl=https://github.com/onixbyte/onixbyte-toolbox 26 | licenseName=MIT 27 | licenseUrl=https://onixbyte.com/projects/onixbyte-toolbox/LICENSE.txt 28 | -------------------------------------------------------------------------------- /version-catalogue/README.md: -------------------------------------------------------------------------------- 1 | # Version Catalogue 2 | 3 | The **Version Catalogue** (Bill of Materials) is a Maven POM file provided by OnixByte to manage 4 | dependency versions for the **OnixByte Toolbox**. By incorporating this BOM into your build 5 | configuration, you can ensure consistent versioning across all included dependencies without 6 | needing to specify versions explicitly in your project files. Published with Gradle metadata, 7 | this BOM supports both Maven and Gradle projects, and this document outlines how to integrate 8 | and use it effectively in both ecosystems. 9 | 10 | ## Using in Maven 11 | 12 | Add the `version-catalogue` to your `pom.xml` under ``: 13 | 14 | ```xml 15 | 16 | 17 | 18 | com.onixbyte 19 | version-catalogue 20 | 3.0.0 21 | pom 22 | import 23 | 24 | 25 | 26 | ``` 27 | 28 | Then reference any dependency built by OnixByte without a version. 29 | 30 | ## Using in Gradle 31 | 32 | In your `build.gradle[.kts]`, apply the BOM using the `platform` dependency: 33 | 34 | ```groovy 35 | dependencies { 36 | implementation platform('com.onixbyte:version-catalogue:3.0.0') 37 | implementation 'com.onixbyte:common-toolbox' 38 | } 39 | ``` 40 | 41 | If you are using Kotlin DSL: 42 | 43 | ```kotlin 44 | dependencies { 45 | implementation(platform("com.onixbyte:version-catalogue:3.0.0")) 46 | implementation("com.onixbyte:common-toolbox") 47 | } 48 | ``` 49 | 50 | -------------------------------------------------------------------------------- /crypto-toolbox/src/test/resources/rsa_private_key.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN PRIVATE KEY----- 2 | MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQD4VIFYJFMAs15j 3 | J3V3IicHd7sI2TIFqTZME40zlOlVAlPKLZmTQvZFLNgaUAAsvPi5i1DR2ywwK6Al 4 | BfnwVnzvmDXC5mKHOz4oxOQVA6Nlp2yVaQMzidmfYNSkMtcv/4HRPsatc7K/M5l6 5 | pCP20DVRjkikBdIy8e9w+x6BrIFp5Q8PZc/X2BGNAUMMYACdeYH5R/A0CxqkND13 6 | esc4gkynMOrvZrZGHCz51usfSCqyDWWwsN+GG6LYWia4GkNlS0erQnP8gS93dfjl 7 | e96BIfy3z7Iv+kUrf5ikNW2P8jMxLAv6LO+dcUAu9k477wIAF7Iq5KMuH/otsDOu 8 | +h+2qXmBAgMBAAECggEAdRqcmC0g+y6arxV3fkObthjPGYAa57KBCWUa7B0n30+m 9 | pavVRS2Jpttb2SSqwG4ouI6rARti/iBEd9EWqTCP4AieKZetFOpqCJ24lPRPRGus 10 | d9S6jr5N4qut+vSCp37NABijZj4uJ540nTH0R7qtuhTnynl4Q0/1wwiYvTvVF1Lg 11 | dn+I/8aRbshwDhdAOWOUe6GL7/eaCYgN8/UmlKIpp8tg0w2iWxbaFiR7gZiM41LA 12 | M6SXXfcCas+ZVXsGbzQ3SNiVurCGuuRNcCScXS3/WoEDIb3cNtp49iOmQS+nmEoo 13 | wh4uiEd+0+BrzxngS4o5+mKnHJnwgY0+veGVYLMR5QKBgQD9WKQmevMDU5c+NPq9 14 | 8jaR457Fuxq1gwzeFNJdWfOc/K2LEWh+nFNFCb++EboEj6FdxWaWNMxbrmJps5gs 15 | EoBUYy/Tl7UycDqDfiYLmDdTsf2pVjjh9jaIADiLcJ8S6wwJMZKub7Tp8UVkenAl 16 | 535MqShLUC11Y7VxLb3Tsll4XwKBgQD67mm6iCmshr/eszPfNE3ylZ+PiNa7nat7 17 | N7lQzBIiRJflT1kmVidC5gE+jASqH728ChkZZKxbHsjxpmWdAhLOITdXoTB4sDsd 18 | wtV1lxkXxK9FnrpFvO3y1wZ/QsD3Z2KXxHYZqawkUETO9F3nqAXW0b2GDar5Qiyo 19 | J3Tx/43aHwKBgDC0NMJtCoDONhowZy/S+6iqQKC0qprQec3L5PErVMkOTnKYwyTr 20 | +pogGKt6ju9HiXcUdvdTaSIK8UJu00dNuzv94XjlBmGO78DNpJTAC4rcge5m9AKE 21 | qdEVcclkukARzbuKuy8rrHT4/CUn4J141m/4aRWpcUPLCluato6XD9ozAoGBANvf 22 | JhOFFgcPd3YazfvpZ9eE1XA+tfFlYYmxNRcgCU+vjO0oDvSxjutmgHae18N91pG6 23 | w21lskSRf/+GDwl5dKLbphOJsOA/gz07qDDGOf2CoRW+1Hcg6drcINxH0K+4DkLv 24 | qZApBSY4k2JH6zR+HMeztn6M4WBRZLHfCPC3PUN/AoGAA3AoHbLTZvqMIKSDkP4Y 25 | U/tTsSFDY4aYo7LG/jk8af3oPU3KyGh4ZFBd6aMmXbS8f8FjvmrM+/e+y9OOGAlq 26 | iOl0hYrs5cJSMLW6i4KnJYuYbMkgmk3bN2t9apu64xKR94gbPrI6AGnPZp+iIzp0 27 | hXKe4HcuhQ3G0a2hjayiQ84= 28 | -----END PRIVATE KEY----- 29 | -------------------------------------------------------------------------------- /crypto-toolbox/src/main/java/com/onixbyte/crypto/PrivateKeyLoader.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2024-2025 OnixByte 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy 5 | * of this software and associated documentation files (the "Software"), to deal 6 | * in the Software without restriction, including without limitation the rights 7 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | * copies of the Software, and to permit persons to whom the Software is 9 | * furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in all 12 | * copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 20 | * SOFTWARE. 21 | */ 22 | 23 | package com.onixbyte.crypto; 24 | 25 | import java.security.PrivateKey; 26 | 27 | /** 28 | * The {@code PrivateKeyLoader} provides utility methods for loading private keys from 29 | * PEM-formatted key text. 30 | * 31 | * @author zihluwang 32 | * @author siujamo 33 | * @version 3.0.0 34 | */ 35 | public interface PrivateKeyLoader { 36 | 37 | /** 38 | * Load private key from pem-formatted key text. 39 | * 40 | * @param pemKeyText pem-formatted key text 41 | * @return loaded private key 42 | */ 43 | PrivateKey loadPrivateKey(String pemKeyText); 44 | } 45 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # OnixByte Toolbox 2 | 3 | ![Static Badge](https://img.shields.io/badge/dynamic/xml?url=https%3A%2F%2Frepo1.maven.org%2Fmaven2%2Fcom%2Fonixbyte%2Fversion-catalogue%2Fmaven-metadata.xml&query=%2F%2Fmetadata%2Fversioning%2Flatest&label=version) 4 | ![Static Badge](https://img.shields.io/badge/licence-MIT-green) 5 | ![Static Badge](https://img.shields.io/badge/java-%E2%89%A517-blue) 6 | 7 | 8 | OnixByte Toolbox is a Java Development Kit that offers a set of convenient tools for writing code efficiently. 9 | 10 | ## Installation and Usage 11 | 12 | If you are using **Maven**, please paste the following codes to _pom.xml_ in your project. 13 | 14 | ```xml 15 | 16 | com.onixbyte 17 | ${artifactId} 18 | ${version} 19 | 20 | ``` 21 | 22 | If you are using **Gradle**, please paste the following codes to _buile.gradle\[.kts\]_ in your project. 23 | 24 | ```groovy 25 | implementation 'com.onixbyte:$artifactId:$version' 26 | ``` 27 | 28 | ```kotlin 29 | implementation("com.onixbyte:$artifactId:$version") 30 | ``` 31 | 32 | If you want to check the available versions, please check out at our [official site](https://codecrafters.org.cn/devkit/changelog). 33 | 34 | ## Contribution 35 | 36 | Contributions are welcome! If you encounter any issues or want to contribute to the project, please feel free to **[raise an issue](https://github.com/CodeCraftersCN/jdevkit/issues/new)** or **[submit a pull request](https://github.com/CodeCraftersCN/jdevkit/compare)**. 37 | 38 | ## License 39 | 40 | This project is licensed under the [MIT](/LICENSE). 41 | 42 | ## Contact 43 | 44 | If you have any suggestions, ideas, don't hesitate contacting us via [GitHub Issues](https://github.com/CodeCraftersCN/jdevkit/issues/new) or [Discord Community](https://discord.gg/NQK9tjcBB8). 45 | 46 | If you face any bugs while using our library and you are able to fix any bugs in our library, we would be happy to accept pull requests from you on [GitHub](https://github.com/CodeCraftersCN/jdevkit/compare). 47 | -------------------------------------------------------------------------------- /tuple/src/main/java/com/onixbyte/tuple/ImmutableBiTuple.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2024-2025 OnixByte. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | 18 | package com.onixbyte.tuple; 19 | 20 | /** 21 | * Represents an immutable pair of two elements, referred to as 'left' and 'right'. This class 22 | * provides a simple way to group two values without the need to create a custom class for each 23 | * specific pair. 24 | *

25 | * The generic types {@code L} and {@code R} denote the types of the left and right elements, 26 | * respectively. Instances of this class are immutable once created. 27 | * 28 | * @param the type of the left element 29 | * @param the type of the right element 30 | * @param left the left element of this tuple 31 | * @param right the right element of this tuple 32 | * @author siujamo 33 | * @author zihluwang 34 | */ 35 | public record ImmutableBiTuple( 36 | L left, 37 | R right 38 | ) { 39 | 40 | /** 41 | * Creates a new {@code ImmutableBiTuple} with the specified left and right elements. 42 | * 43 | * @param the type of the left element 44 | * @param the type of the right element 45 | * @param left the left element 46 | * @param right the right element 47 | * @return a new {@code ImmutableBiTuple} containing the specified elements 48 | */ 49 | public static ImmutableBiTuple of(L left, R right) { 50 | return new ImmutableBiTuple<>(left, right); 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /common-toolbox/src/main/resources/logback.xml: -------------------------------------------------------------------------------- 1 | 2 | 23 | 24 | 25 | 27 | 29 | 30 | 31 | 32 | 33 | ${COLOURFUL_OUTPUT} 34 | 35 | 36 | 37 | 38 | 39 | 40 | -------------------------------------------------------------------------------- /crypto-toolbox/src/main/resources/logback.xml: -------------------------------------------------------------------------------- 1 | 2 | 23 | 24 | 25 | 27 | 29 | 30 | 31 | 32 | 33 | ${COLOURFUL_OUTPUT} 34 | 35 | 36 | 37 | 38 | 39 | 40 | -------------------------------------------------------------------------------- /math-toolbox/src/main/resources/logback.xml: -------------------------------------------------------------------------------- 1 | 2 | 23 | 24 | 25 | 27 | 29 | 30 | 31 | 32 | 33 | ${COLOURFUL_OUTPUT} 34 | 35 | 36 | 37 | 38 | 39 | 40 | -------------------------------------------------------------------------------- /identity-generator/src/main/resources/logback.xml: -------------------------------------------------------------------------------- 1 | 2 | 23 | 24 | 25 | 27 | 29 | 30 | 31 | 32 | 33 | ${COLOURFUL_OUTPUT} 34 | 35 | 36 | 37 | 38 | 39 | 40 | -------------------------------------------------------------------------------- /tuple/README.md: -------------------------------------------------------------------------------- 1 | # Tuple 2 | 3 | ## Introduction 4 | 5 | The `tuple` module provides simple and efficient implementations of mutable and immutable bi-tuples and tri-tuples. These tuples allow you to group two or three values together without creating custom classes, supporting convenient usage in various programming scenarios. 6 | 7 | ## Features 8 | 9 | - Immutable and mutable versions of bi-tuples (pairs) and tri-tuples (triplets); 10 | - Factory method of() for easy instantiation of all tuple types; 11 | - Simple, lightweight implementation compatible with standard Java usage; 12 | - Clear distinction between mutable and immutable tuples for flexibility. 13 | 14 | ## Installation 15 | 16 | ### Maven 17 | 18 | Add the following dependency to your `pom.xml`: 19 | 20 | ```xml 21 | 22 | com.onixbyte 23 | tuple 24 | $artefactVersion 25 | 26 | ``` 27 | 28 | ### Gradle 29 | 30 | #### Version Catalogue 31 | 32 | Add the following codes to you `gradle/libs.versions.toml`: 33 | 34 | ```toml 35 | [version] 36 | onixbyteToolbox = "$artefactVersion" 37 | 38 | [libraries] 39 | onixbyteToolbox-tuple = { group = "com.onixbyte", name = "tuple", version.ref = "onixbyteToolbox" } 40 | ``` 41 | 42 | Then add the following codes to your `build.gradle.kts` or `build.gradle` dependencies block: 43 | 44 | ```kotlin 45 | implementation(libs.onixbyteToolbox.tuple) 46 | ``` 47 | 48 | ```groovy 49 | implementation libs.onixbyteToolbox.tuple 50 | ``` 51 | 52 | #### Kotlin DSL 53 | 54 | Add the following line to your `build.gradle.kts` dependencies block: 55 | 56 | ```kotlin 57 | implementation("com.onixbyte:tuple:$artefactVersion") 58 | ``` 59 | 60 | #### Groovy DSL 61 | 62 | Add the following line to your `build.gradle` dependencies block: 63 | 64 | ```groovy 65 | implementation 'com.onixbyte:tuple:${artefactVersion}' 66 | ``` 67 | 68 | ## Dependencies 69 | 70 | This module has no external dependencies other than the standard Java SDK. 71 | 72 | ## Acknowledgement 73 | 74 | Thanks to all contributors who helped develop this module, improving its design, performance, and documentation over time. 75 | -------------------------------------------------------------------------------- /gradle/libs.versions.toml: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2024-2025 OnixByte. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # 13 | # See the License for the specific language governing permissions and 14 | # limitations under the License. 15 | 16 | [versions] 17 | slf4j = "2.0.17" 18 | logback = "1.5.18" 19 | jackson = "2.18.4" 20 | jwt = "4.5.0" 21 | spring = "6.2.6" 22 | springBoot = "3.4.5" 23 | junit = "5.11.4" 24 | 25 | [libraries] 26 | slf4j = { group = "org.slf4j", name = "slf4j-api", version.ref = "slf4j" } 27 | logback = { group = "ch.qos.logback", name = "logback-classic", version.ref = "logback" } 28 | jackson-core = { group = "com.fasterxml.jackson.core", name = "jackson-core", version.ref = "jackson" } 29 | jackson-databind = { group = "com.fasterxml.jackson.core", name = "jackson-databind", version.ref = "jackson" } 30 | jwt-core = { group = "com.auth0", name = "java-jwt", version.ref = "jwt" } 31 | springBoot-autoconfigure = { group = "org.springframework.boot", name = "spring-boot-autoconfigure", version.ref = "springBoot" } 32 | springBoot-starter-logging = { group = "org.springframework.boot", name = "spring-boot-starter-logging", version.ref = "springBoot" } 33 | springBoot-configurationProcessor = { group = "org.springframework.boot", name = "spring-boot-configuration-processor", version.ref = "springBoot" } 34 | springBoot-starter-redis = { group = "org.springframework.boot", name = "spring-boot-starter-data-redis", version.ref = "springBoot" } 35 | springBoot-starter-test = { group = "org.springframework.boot", name = "spring-boot-starter-test", version.ref = "springBoot" } 36 | junit-bom = { group = "org.junit", name = "junit-bom", version.ref = "junit" } 37 | junit-jupiter = { group = "org.junit.jupiter", name = "junit-jupiter", version.ref = "junit" } 38 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing to OnixByte Toolbox 2 | 3 | We appreciate your interest in contributing to our Java Enterprise Utility Library. Contributions 4 | are welcome in various forms such as code, documentation, bug reports, and test cases. To ensure a 5 | smooth collaboration, please follow the guidelines outlined below. 6 | 7 | ## Getting Started 8 | 9 | The OnixByte Toolbox is an open-source Java library designed to ease Java enterprise 10 | application development. It is built with Java 17, and we require contributors to use OpenJDK 17. 11 | 12 | ## Development Setup 13 | 14 | There is no need for manual setup beyond ensuring you have JDK 17 and a stable internet connection. 15 | Our project uses Gradle Wrapper for building, simplifying the setup process. 16 | 17 | ```shell 18 | ./gradlew build 19 | ``` 20 | 21 | ## Branching Strategy 22 | 23 | We follow the `git-flow` branching model. Contributors should fork this repository and create their 24 | work on separate branches prefixed with `feature/` or `hotfix/` as appropriate. 25 | 26 | ## Code Style 27 | 28 | Please adhere to the coding standards specified in our `.editorconfig` file in the root of 29 | the repository. Consistent style helps in maintaining readability and uniformity across the codebase. 30 | 31 | ## Commit Messages 32 | 33 | We require that commit messages follow this structure: 34 | 35 | ```text 36 | type[(scope)]: subject 37 | 38 | [body] 39 | 40 | [BREAKING CHANGE: breaking changes] 41 | ``` 42 | 43 | This format helps in auto-generating changelogs and understanding the purpose behind changes. 44 | 45 | ## Submitting Contributions 46 | 47 | 1. Fork the repository. 48 | 2. Create a branch: `git checkout -b feature/my-new-feature` or `git checkout -b hotfix/my-fix`. 49 | 3. Make your changes and commit them. 50 | 4. Push to the branch: `git push origin feature/my-new-feature`. 51 | 5. Create a Pull Request with a succinct subject and a detailed body. 52 | 53 | ## Testing 54 | 55 | We encourage using JUnit Jupiter for unit and integration tests. Pull Requests with accompanying 56 | test reports are prioritised for review and merging. 57 | 58 | ## License 59 | 60 | By contributing to the OnixByte Toolbox, you agree that your contributions will be licensed under the 61 | MIT license. If you do not agree to this, please refrain from contributing. 62 | -------------------------------------------------------------------------------- /tuple/src/main/java/com/onixbyte/tuple/ImmutableTriTuple.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2024-2025 OnixByte. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | 18 | package com.onixbyte.tuple; 19 | 20 | /** 21 | * Represents an immutable triple of three elements, referred to as 'left', 'middle', and 'right'. 22 | * This class provides a generic way to group three values without the need to create a custom class 23 | * for each specific combination. 24 | *

25 | * The generic types {@code L}, {@code M}, and {@code R} denote the types of the left, middle, and 26 | * right elements, respectively. Instances of this class are immutable once created. 27 | * 28 | * @param the type of the left element 29 | * @param the type of the middle element 30 | * @param the type of the right element 31 | * @param left the left element of this triple 32 | * @param middle the middle element of this triple 33 | * @param right the right element of this triple 34 | * @author siujamo 35 | * @author zihluwang 36 | */ 37 | public record ImmutableTriTuple( 38 | L left, 39 | M middle, 40 | R right 41 | ) { 42 | 43 | /** 44 | * Creates a new {@code ImmutableTriTuple} with the specified left, middle, and right elements. 45 | * 46 | * @param the type of the left element 47 | * @param the type of the middle element 48 | * @param the type of the right element 49 | * @param left the left element 50 | * @param middle the middle element 51 | * @param right the right element 52 | * @return a new {@code ImmutableTriTuple} containing the specified elements 53 | */ 54 | public static ImmutableTriTuple of(L left, M middle, R right) { 55 | return new ImmutableTriTuple<>(left, middle, right); 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug-report.yml: -------------------------------------------------------------------------------- 1 | name: Bug Report 2 | description: Report a bug to help us improve the application. 3 | title: "[Bug] " 4 | labels: [bug] 5 | type: bug 6 | body: 7 | - type: markdown 8 | attributes: 9 | value: | 10 | Thanks for taking the time to fill out this bug report! 11 | - type: textarea 12 | id: bug-description 13 | attributes: 14 | label: Describe the bug 15 | description: | 16 | A clear and concise description of what the bug is. If applicable, add screenshots to help 17 | explain your problem. 18 | placeholder: Describe the bug you encountered... 19 | validations: 20 | required: true 21 | - type: textarea 22 | id: reproduction-steps 23 | attributes: 24 | label: Steps to Reproduce 25 | description: Steps to help us reproduce the behaviour. 26 | placeholder: | 27 | 1. Go to '...' 28 | 2. Click on '....' 29 | 3. Scroll down to '....' 30 | 4. See error 31 | validations: 32 | required: true 33 | - type: textarea 34 | id: expected-behaviour 35 | attributes: 36 | label: Expected Behaviour 37 | description: A clear and concise description of what you expected to happen. 38 | placeholder: Describe what you expected to happen... 39 | validations: 40 | required: true 41 | 42 | - type: dropdown 43 | id: os 44 | attributes: 45 | label: Operating System (OS) 46 | description: Your operating system. 47 | options: 48 | - Windows 49 | - macOS 50 | - Linux 51 | validations: 52 | required: true 53 | - type: input 54 | id: application-version 55 | attributes: 56 | label: Application Version 57 | description: The version of the Application you are using. 58 | placeholder: e.g., x.y.z 59 | validations: 60 | required: true 61 | - type: input 62 | id: java-version 63 | attributes: 64 | label: Java Version 65 | description: | 66 | The Java Development Kit (JDK) version used. 67 | placeholder: | 68 | eg. Amazon Corretto x.y.z, Microsoft Open JDK x.y.z 69 | validations: 70 | required: true 71 | - type: textarea 72 | id: additional-context 73 | attributes: 74 | label: Additional Context (Optional) 75 | description: | 76 | Add any other context about the problem here, such as logs, specific scenarios, 77 | or workarounds. 78 | placeholder: Any other relevant information... -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature-request.yml: -------------------------------------------------------------------------------- 1 | name: Feature Request 2 | description: Suggest an idea or new feature to enhance the Calendar Toolbox. 3 | title: "[Feature Request] " 4 | labels: ["feature request"] 5 | type: feature 6 | body: 7 | - type: markdown 8 | attributes: 9 | value: | 10 | Thanks for considering a new feature for Calendar Toolbox! Please provide as much detail as possible. 11 | - type: textarea 12 | id: summary 13 | attributes: 14 | label: Summary 15 | description: A concise summary of the feature you'd like to see added. 16 | placeholder: Briefly describe your feature idea here. 17 | validations: 18 | required: true 19 | - type: textarea 20 | id: why-needed 21 | attributes: 22 | label: Why is this needed? 23 | description: | 24 | Explain the problem you’re facing or what could be improved by adding this feature. How 25 | would it make development with the Calendar Toolbox easier or better? 26 | placeholder: Describe the problem or improvement this feature addresses. 27 | validations: 28 | required: true 29 | - type: textarea 30 | id: idea_description 31 | attributes: 32 | label: What's your idea? 33 | description: | 34 | Describe how you imagine this feature working within the class library. Feel free to share 35 | any technical details, examples, or use cases. 36 | placeholder: Provide details on how the feature would function. 37 | validations: 38 | required: true 39 | - type: textarea 40 | id: alternatives 41 | attributes: 42 | label: Alternatives 43 | description: | 44 | Have you tried any alternatives or workarounds? If so, what are they and why don’t they 45 | fully meet your needs? 46 | placeholder: List any alternatives or workarounds you've considered. 47 | validations: 48 | required: false 49 | - type: input 50 | id: target-version 51 | attributes: 52 | label: Target Version (Optional) 53 | description: If you have a preferred release version for this feature, please mention it here. 54 | placeholder: e.g., v1.2.0 or Next Minor Release 55 | validations: 56 | required: false 57 | - type: textarea 58 | id: other-info 59 | attributes: 60 | label: Any other info (Optional) 61 | description: | 62 | Anything else you think might help us understand your request better—like links, 63 | related issues, or thoughts. 64 | placeholder: Add any additional relevant information here. 65 | validations: 66 | required: false -------------------------------------------------------------------------------- /identity-generator/src/main/java/com/onixbyte/identitygenerator/IdentityGenerator.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2024-2025 OnixByte 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy 5 | * of this software and associated documentation files (the "Software"), to deal 6 | * in the Software without restriction, including without limitation the rights 7 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | * copies of the Software, and to permit persons to whom the Software is 9 | * furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in all 12 | * copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 20 | * SOFTWARE. 21 | */ 22 | 23 | package com.onixbyte.identitygenerator; 24 | 25 | /** 26 | * The {@code IdentityGenerator} is a generic interface for generating globally unique identifiers 27 | * (GUIDs) of a specific type. 28 | *

29 | * The type of ID is determined by the class implementing this interface. 30 | *

31 | * 32 | *

Example usage:

33 | *
{@code
34 |  * public class StringGuidCreator implements IdentityGenerator {
35 |  *     private final AtomicLong counter = new AtomicLong();
36 |  *
37 |  *     @Override
38 |  *     public String nextId() {
39 |  *         return UUID.randomUUID().toString() + "-" + counter.incrementAndGet();
40 |  *     }
41 |  * }
42 |  *
43 |  * public class Example {
44 |  *     public static void main(String[] args) {
45 |  *         IdentityGenerator guidCreator = new StringGuidCreator();
46 |  *         String guid = guidCreator.nextId();
47 |  *         System.out.println("Generated GUID: " + guid);
48 |  *     }
49 |  * }
50 |  * }
51 | * 52 | * @param this represents the type of the Global Unique Identifier 53 | * @author zihluwang 54 | * @version 3.0.0 55 | */ 56 | public interface IdentityGenerator { 57 | 58 | /** 59 | * Generates and returns the next globally unique ID. 60 | * 61 | * @return the next globally unique ID 62 | */ 63 | IdType nextId(); 64 | 65 | } 66 | -------------------------------------------------------------------------------- /crypto-toolbox/src/main/java/com/onixbyte/crypto/util/CryptoUtil.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2024-2025 OnixByte 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy 5 | * of this software and associated documentation files (the "Software"), to deal 6 | * in the Software without restriction, including without limitation the rights 7 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | * copies of the Software, and to permit persons to whom the Software is 9 | * furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in all 12 | * copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 20 | * SOFTWARE. 21 | */ 22 | 23 | package com.onixbyte.crypto.util; 24 | 25 | /** 26 | * Utility class for cryptographic operations. 27 | * 28 | * @author zihluwang 29 | * @author siujamo 30 | * @version 3.0.0 31 | */ 32 | public final class CryptoUtil { 33 | 34 | /** 35 | * Private constructor to prevent instantiation of this utility class. 36 | */ 37 | private CryptoUtil() { 38 | } 39 | 40 | /** 41 | * Extracts the raw content from a PEM-formatted key by removing any headers, footers, 42 | * and newline characters. 43 | * 44 | *

45 | * This method processes the given PEM key text and returns a cleaned string containing only 46 | * the key material. It removes the lines matching the 47 | * {@code "-----BEGIN (EC )?(RSA )?(PRIVATE|PUBLIC) KEY-----"} and 48 | * {@code "-----END (EC )?(RSA )?(PRIVATE|PUBLIC) KEY-----"} patterns, 49 | * as well as any newline characters, 50 | * resulting in a continuous string that can be used directly for cryptographic operations. 51 | * 52 | * @param pemKeyText the PEM-formatted key as a string, which may include headers, footers, 53 | * and line breaks 54 | * @return a string containing the raw key content without any unnecessary formatting or whitespace 55 | */ 56 | public static String getRawContent(String pemKeyText) { 57 | return pemKeyText 58 | .replaceAll("-----BEGIN ((EC )|(RSA ))?(PRIVATE|PUBLIC) KEY-----", "") 59 | .replaceAll("-----END ((EC )|(RSA ))?(PRIVATE|PUBLIC) KEY-----", "") 60 | .replaceAll("\n", ""); 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /.github/workflows/github-packages-publish.yml: -------------------------------------------------------------------------------- 1 | # This workflow uses actions that are not certified by GitHub. 2 | # They are provided by a third-party and are governed by 3 | # separate terms of service, privacy policy, and support 4 | # documentation. 5 | # This workflow will build a package using Gradle and then publish it to GitHub packages when a release is created 6 | # For more information see: https://github.com/actions/setup-java/blob/main/docs/advanced-usage.md#Publishing-using-gradle 7 | 8 | name: Publish Packages to GitHub Packages with Gradle 9 | 10 | on: 11 | release: 12 | types: 13 | - published 14 | 15 | jobs: 16 | build: 17 | name: Build and Publish 18 | runs-on: ubuntu-latest 19 | permissions: 20 | contents: read 21 | packages: write 22 | 23 | steps: 24 | - name: Checkout 25 | uses: actions/checkout@v4.2.2 26 | 27 | - name: Setup GPG TTY 28 | run: export GPG_TTY=$(tty) 29 | 30 | - name: Import PGP Private Key 31 | uses: crazy-max/ghaction-import-gpg@v6.3.0 32 | with: 33 | gpg_private_key: ${{ secrets.GPG_PRIVATE_KEY }} 34 | passphrase: ${{ secrets.GPG_PASSPHRASE }} 35 | trust_level: 5 36 | 37 | - name: Creating PGP Ring Key 38 | run: | 39 | mkdir -p ~/.gnupg 40 | echo ${{ secrets.GPG_PASSPHRASE }} | gpg --batch --yes --pinentry-mode loopback --passphrase-fd 0 --export-secret-keys -o ~/.gnupg/gpg_key.ring 41 | 42 | - name: Restore gradle.properties 43 | env: 44 | GRADLE_PROPERTIES: ${{ secrets.GRADLE_PROPERTIES }} 45 | shell: bash 46 | run: | 47 | mkdir -p ~/.gradle/ 48 | echo "GRADLE_USER_HOME=${HOME}/.gradle" >> $GITHUB_ENV 49 | echo "${GRADLE_PROPERTIES}" > ~/.gradle/gradle.properties 50 | 51 | - name: Set up JDK 17 52 | uses: actions/setup-java@v4 53 | with: 54 | java-version: "17" 55 | distribution: "corretto" 56 | 57 | - name: Setup Gradle 58 | uses: gradle/actions/setup-gradle@v4.4.1 59 | 60 | - name: Grant Execution Authority to Gradlew 61 | run: chmod +x ./gradlew 62 | 63 | - name: Build with Gradle 64 | # Overwrite artefactVersion with tag name 65 | run: ./gradlew build -PartefactVersion=${{ github.event.release.tag_name }} 66 | 67 | - name: List Output Items 68 | run: ls -l ./**/build/libs 69 | 70 | - name: Publish to Maven Central 71 | run: ./gradlew publish 72 | 73 | - name: Create Deployment on Central Publisher Portal 74 | run: | 75 | curl --fail -X 'POST' \ 76 | 'https://ossrh-staging-api.central.sonatype.com/manual/upload/defaultRepository/com.onixbyte?publishing_type=user_managed' \ 77 | -H 'accept: */*' \ 78 | -H 'Authorization: Bearer ${{ secrets.MAVEN_PORTAL_TOKEN }}' \ 79 | -d '' 80 | -------------------------------------------------------------------------------- /identity-generator/src/main/java/com/onixbyte/identitygenerator/exceptions/TimingException.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2024-2025 OnixByte 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy 5 | * of this software and associated documentation files (the "Software"), to deal 6 | * in the Software without restriction, including without limitation the rights 7 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | * copies of the Software, and to permit persons to whom the Software is 9 | * furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in all 12 | * copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 20 | * SOFTWARE. 21 | */ 22 | 23 | package com.onixbyte.identitygenerator.exceptions; 24 | 25 | /** 26 | * The {@code TimingException} class represents an exception that is thrown when there is an error 27 | * related to time sequence. 28 | *

29 | * Instances of TimingException can be created with or without a message and a cause. The message 30 | * provides a description of the exception, while the cause represents the underlying cause of the 31 | * exception and provides additional information about the error. 32 | * 33 | * @author zihluwang 34 | * @since 3.0.0 35 | */ 36 | public class TimingException extends RuntimeException { 37 | 38 | /** 39 | * A custom exception that is thrown when there is an issue with timing or scheduling. 40 | */ 41 | public TimingException() { 42 | } 43 | 44 | /** 45 | * A custom exception that is thrown when there is an issue with timing or scheduling with 46 | * customised error message. 47 | * 48 | * @param message customised message 49 | */ 50 | public TimingException(String message) { 51 | super(message); 52 | } 53 | 54 | /** 55 | * A custom exception that is thrown when there is an issue with timing or scheduling with 56 | * customised error message. 57 | * 58 | * @param message customised message 59 | * @param cause the cause of this exception 60 | */ 61 | public TimingException(String message, Throwable cause) { 62 | super(message, cause); 63 | } 64 | 65 | /** 66 | * A custom exception that is thrown when there is an issue with timing or scheduling with 67 | * customised error message. 68 | * 69 | * @param cause the cause of this exception 70 | */ 71 | public TimingException(Throwable cause) { 72 | super(cause); 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Code of Conduct 2 | 3 | ## Our Pledge 4 | 5 | We as members, contributors, and leaders pledge to make participation in our project 6 | a harassment-free experience for everyone, regardless of age, body size, disability, ethnicity, 7 | gender identity and expression, level of experience, nationality, personal appearance, race, 8 | religion, or sexual identity and orientation. 9 | 10 | ## Our Standards 11 | 12 | Examples of behaviour that contributes to creating a positive environment include: 13 | 14 | - Using welcoming and inclusive language 15 | - Being respectful of differing viewpoints and experiences 16 | - Gracefully accepting constructive criticism 17 | - Focusing on what is best for the community 18 | - Showing empathy towards other community members 19 | 20 | Examples of unacceptable behaviour by participants include: 21 | 22 | - The use of sexualised language or imagery, and unwelcome sexual attention or advances 23 | - Trolling, insulting, or derogatory comments 24 | - Personal or political attacks 25 | - Public or private harassment 26 | - Publishing others’ private information, such as a physical or electronic address, without 27 | explicit permission 28 | - Other conduct which could reasonably be considered inappropriate in a professional setting 29 | 30 | ## Enforcement Responsibilities 31 | 32 | Project maintainers are responsible for clarifying the standards of acceptable behaviour and are 33 | expected to take appropriate and fair corrective action in response to any instances of 34 | unacceptable behaviour. 35 | 36 | Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, 37 | code, wiki edits, issues, and other contributions that do not align with this Code of Conduct, and 38 | will communicate reasons for moderation decisions when appropriate. 39 | 40 | ## Scope 41 | 42 | This Code of Conduct applies both within project spaces and in public spaces when an individual is 43 | representing the project or its community. Examples of representing a project or community include 44 | using an official project email address, posting via an official social media account, or acting as 45 | an appointed representative at an online or offline event. 46 | 47 | ## Enforcement 48 | 49 | Instances of abusive, harassing, or unacceptable behaviour may be reported by contacting the 50 | project team at [opensource@onixbyte.com](mailto:opensource@onixbyte.com). All complaints will be 51 | reviewed and investigated and will result in a response that is deemed necessary and appropriate to 52 | the circumstances. The project team is obligated to maintain confidentiality with regard to the 53 | reporter of an incident. 54 | 55 | Project maintainers who do not follow or enforce the Code of Conduct in good faith may face 56 | temporary or permanent repercussions as determined by other members of the project's leadership. 57 | 58 | ## Attribution 59 | 60 | This Code of Conduct is adapted from the [Contributor Covenant](https://www.contributor-covenant.org/), 61 | version 1.4, available at https://www.contributor-covenant.org/version/1/4/ 62 | 63 | -------------------------------------------------------------------------------- /common-toolbox/src/test/java/com/onixbyte/common/util/AesUtilTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2024-2025 OnixByte. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | 18 | package com.onixbyte.common.util; 19 | 20 | import org.junit.jupiter.api.Test; 21 | 22 | import java.nio.charset.StandardCharsets; 23 | import java.security.GeneralSecurityException; 24 | 25 | import static org.junit.jupiter.api.Assertions.*; 26 | 27 | class AesUtilTest { 28 | 29 | @Test 30 | void testEncryptAndDecryptByte() throws GeneralSecurityException { 31 | byte[] secretKey = "43f72073956d4c81".getBytes(StandardCharsets.UTF_8); 32 | byte[] originalData = "Hello World".getBytes(StandardCharsets.UTF_8); 33 | 34 | byte[] encryptedData = AesUtil.encrypt(originalData, secretKey); 35 | assertNotNull(encryptedData); 36 | 37 | byte[] decryptedData = AesUtil.decrypt(encryptedData, secretKey); 38 | assertNotNull(decryptedData); 39 | 40 | assertArrayEquals(originalData, decryptedData); 41 | } 42 | 43 | @Test 44 | void testEncryptAndDecryptString() throws GeneralSecurityException { 45 | var secret = "43f72073956d4c81"; 46 | var originalData = "Hello World"; 47 | 48 | var encryptedData = AesUtil.encrypt(originalData, secret); 49 | assertNotNull(encryptedData); 50 | assertNotEquals(originalData, encryptedData); 51 | 52 | var decryptedData = AesUtil.decrypt(encryptedData, secret); 53 | assertNotNull(decryptedData); 54 | assertEquals(originalData, decryptedData); 55 | } 56 | 57 | @Test 58 | void testEncryptWithWrongKeyFails() throws GeneralSecurityException { 59 | var secret = "43f72073956d4c81"; 60 | var wrongSecret = "0000000000000000"; 61 | var originalData = "Hello World"; 62 | 63 | var encryptedData = AesUtil.encrypt(originalData.getBytes(StandardCharsets.UTF_8), 64 | secret.getBytes(StandardCharsets.UTF_8)); 65 | assertNotNull(encryptedData); 66 | 67 | // When decrypting with the wrong key, a BadPaddingException or IllegalBlockSizeException is expected to be thrown 68 | assertThrows(GeneralSecurityException.class, () -> { 69 | AesUtil.decrypt(encryptedData, wrongSecret.getBytes(StandardCharsets.UTF_8)); 70 | }); 71 | } 72 | 73 | @Test 74 | void testGenerateRandomSecret() { 75 | var randomSecret = AesUtil.generateRandomSecret(); 76 | assertNotNull(randomSecret); 77 | assertEquals(16, randomSecret.length()); 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /gradlew.bat: -------------------------------------------------------------------------------- 1 | @rem 2 | @rem Copyright 2015 the original author or authors. 3 | @rem 4 | @rem Licensed under the Apache License, Version 2.0 (the "License"); 5 | @rem you may not use this file except in compliance with the License. 6 | @rem You may obtain a copy of the License at 7 | @rem 8 | @rem https://www.apache.org/licenses/LICENSE-2.0 9 | @rem 10 | @rem Unless required by applicable law or agreed to in writing, software 11 | @rem distributed under the License is distributed on an "AS IS" BASIS, 12 | @rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | @rem See the License for the specific language governing permissions and 14 | @rem limitations under the License. 15 | @rem 16 | @rem SPDX-License-Identifier: Apache-2.0 17 | @rem 18 | 19 | @if "%DEBUG%"=="" @echo off 20 | @rem ########################################################################## 21 | @rem 22 | @rem Gradle startup script for Windows 23 | @rem 24 | @rem ########################################################################## 25 | 26 | @rem Set local scope for the variables with windows NT shell 27 | if "%OS%"=="Windows_NT" setlocal 28 | 29 | set DIRNAME=%~dp0 30 | if "%DIRNAME%"=="" set DIRNAME=. 31 | @rem This is normally unused 32 | set APP_BASE_NAME=%~n0 33 | set APP_HOME=%DIRNAME% 34 | 35 | @rem Resolve any "." and ".." in APP_HOME to make it shorter. 36 | for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi 37 | 38 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 39 | set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" 40 | 41 | @rem Find java.exe 42 | if defined JAVA_HOME goto findJavaFromJavaHome 43 | 44 | set JAVA_EXE=java.exe 45 | %JAVA_EXE% -version >NUL 2>&1 46 | if %ERRORLEVEL% equ 0 goto execute 47 | 48 | echo. 1>&2 49 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2 50 | echo. 1>&2 51 | echo Please set the JAVA_HOME variable in your environment to match the 1>&2 52 | echo location of your Java installation. 1>&2 53 | 54 | goto fail 55 | 56 | :findJavaFromJavaHome 57 | set JAVA_HOME=%JAVA_HOME:"=% 58 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 59 | 60 | if exist "%JAVA_EXE%" goto execute 61 | 62 | echo. 1>&2 63 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2 64 | echo. 1>&2 65 | echo Please set the JAVA_HOME variable in your environment to match the 1>&2 66 | echo location of your Java installation. 1>&2 67 | 68 | goto fail 69 | 70 | :execute 71 | @rem Setup the command line 72 | 73 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 74 | 75 | 76 | @rem Execute Gradle 77 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* 78 | 79 | :end 80 | @rem End local scope for the variables with windows NT shell 81 | if %ERRORLEVEL% equ 0 goto mainEnd 82 | 83 | :fail 84 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 85 | rem the _cmd.exe /c_ return code! 86 | set EXIT_CODE=%ERRORLEVEL% 87 | if %EXIT_CODE% equ 0 set EXIT_CODE=1 88 | if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE% 89 | exit /b %EXIT_CODE% 90 | 91 | :mainEnd 92 | if "%OS%"=="Windows_NT" endlocal 93 | 94 | :omega 95 | -------------------------------------------------------------------------------- /common-toolbox/src/main/java/com/onixbyte/common/util/ObjectMapAdapter.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2024-2025 OnixByte 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy 5 | * of this software and associated documentation files (the "Software"), to deal 6 | * in the Software without restriction, including without limitation the rights 7 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | * copies of the Software, and to permit persons to whom the Software is 9 | * furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in all 12 | * copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 20 | * SOFTWARE. 21 | */ 22 | 23 | package com.onixbyte.common.util; 24 | 25 | import java.util.Map; 26 | 27 | /** 28 | * The {@link ObjectMapAdapter} interface provides methods to convert between objects and maps. 29 | * This interface is useful for scenarios where objects need to be represented as maps for 30 | * serialization, deserialization, or other purposes. 31 | * 32 | *

Implementations of this interface should provide the logic to convert an object of type 33 | * {@code T} to a {@link Map} and vice versa.

34 | * 35 | *

Example usage:

36 | *
{@code
37 |  * public class User {
38 |  *     private String name;
39 |  *     private int age;
40 |  *     
41 |  *     // getters and setters
42 |  * }
43 |  * 
44 |  * public class UserMapAdapter implements ObjectMapAdapter {
45 |  *     @Override
46 |  *     public Map toMap(User user) {
47 |  *         Map map = new HashMap<>();
48 |  *         map.put("name", user.getName());
49 |  *         map.put("age", user.getAge());
50 |  *         return map;
51 |  *     }
52 |  * 
53 |  *     @Override
54 |  *     public User fromMap(Map map) {
55 |  *         User user = new User();
56 |  *         user.setName((String) map.get("name"));
57 |  *         user.setAge((Integer) map.get("age"));
58 |  *         return user;
59 |  *     }
60 |  * }
61 |  * }
62 | * 63 | * @param the type of the object to be converted 64 | * @author zihluwang 65 | * @version 3.0.0 66 | */ 67 | public interface ObjectMapAdapter { 68 | 69 | /** 70 | * Convert an object to a map. 71 | * 72 | * @param element the element that will be converted to Map 73 | * @return a Map that is converted from the element 74 | */ 75 | Map toMap(T element); 76 | 77 | /** 78 | * Convert a Map to an object. 79 | * 80 | * @param map the map that will be converted to an object 81 | * @return the object that is converted from the Map 82 | */ 83 | T toObject(Map map); 84 | 85 | } 86 | -------------------------------------------------------------------------------- /common-toolbox/src/test/java/com/onixbyte/common/util/Base64UtilTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2024-2025 OnixByte. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | 18 | package com.onixbyte.common.util; 19 | 20 | import org.junit.jupiter.api.Test; 21 | 22 | import java.nio.charset.StandardCharsets; 23 | 24 | import static org.junit.jupiter.api.Assertions.*; 25 | 26 | public class Base64UtilTest { 27 | 28 | @Test 29 | void testEncodeAndDecodeWithUtf8() { 30 | var original = "Hello, Base64!"; 31 | var encoded = Base64Util.encode(original); 32 | assertNotNull(encoded); 33 | assertNotEquals(original, encoded); 34 | 35 | var decoded = Base64Util.decode(encoded); 36 | assertNotNull(decoded); 37 | assertEquals(original, decoded); 38 | } 39 | 40 | @Test 41 | void testEncodeAndDecodeWithCharset() { 42 | var original = "编码测试"; // Some unicode characters (Chinese) 43 | var charset = StandardCharsets.UTF_8; 44 | 45 | var encoded = Base64Util.encode(original, charset); 46 | assertNotNull(encoded); 47 | assertNotEquals(original, encoded); 48 | 49 | var decoded = Base64Util.decode(encoded, charset); 50 | assertNotNull(decoded); 51 | assertEquals(original, decoded); 52 | } 53 | 54 | @Test 55 | void testEncodeUrlComponentsAndDecodeWithUtf8() { 56 | var original = "This is a test for URL-safe Base64 encoding+!"; 57 | 58 | var encodedUrl = Base64Util.encodeUrlComponents(original); 59 | assertNotNull(encodedUrl); 60 | assertNotEquals(original, encodedUrl); 61 | // URL-safe encoding should not contain '+' or '/' characters 62 | assertFalse(encodedUrl.contains("+")); 63 | assertFalse(encodedUrl.contains("/")); 64 | 65 | var decodedUrl = Base64Util.decodeUrlComponents(encodedUrl); 66 | assertNotNull(decodedUrl); 67 | assertEquals(original, decodedUrl); 68 | } 69 | 70 | @Test 71 | void testEncodeUrlComponentsAndDecodeWithCharset() { 72 | var original = "测试 URL 安全编码"; // Unicode string 73 | var charset = StandardCharsets.UTF_8; 74 | 75 | var encodedUrl = Base64Util.encodeUrlComponents(original, charset); 76 | assertNotNull(encodedUrl); 77 | assertNotEquals(original, encodedUrl); 78 | 79 | var decodedUrl = Base64Util.decodeUrlComponents(encodedUrl, charset); 80 | assertNotNull(decodedUrl); 81 | assertEquals(original, decodedUrl); 82 | } 83 | 84 | @Test 85 | void testEncodeAndDecodeEmptyString() { 86 | var original = ""; 87 | 88 | var encoded = Base64Util.encode(original); 89 | assertNotNull(encoded); 90 | assertEquals("", Base64Util.decode(encoded)); 91 | } 92 | 93 | @Test 94 | void testEncodeAndDecodeNullSafety() { 95 | // Since Base64Util does not explicitly handle null, the test expects NPE if null is input 96 | assertThrows(NullPointerException.class, () -> Base64Util.encode(null)); 97 | assertThrows(NullPointerException.class, () -> Base64Util.decode(null)); 98 | } 99 | 100 | } 101 | -------------------------------------------------------------------------------- /identity-generator/src/main/java/com/onixbyte/identitygenerator/impl/SequentialUuidGenerator.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2024-2025 OnixByte 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy 5 | * of this software and associated documentation files (the "Software"), to deal 6 | * in the Software without restriction, including without limitation the rights 7 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | * copies of the Software, and to permit persons to whom the Software is 9 | * furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in all 12 | * copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 20 | * SOFTWARE. 21 | */ 22 | 23 | package com.onixbyte.identitygenerator.impl; 24 | 25 | import com.onixbyte.identitygenerator.IdentityGenerator; 26 | 27 | import java.nio.ByteBuffer; 28 | import java.security.SecureRandom; 29 | import java.util.UUID; 30 | 31 | /** 32 | * A {@code SequentialUuidGenerator} is responsible for generating UUIDs following the 33 | * UUID version 7 specification, which combines a timestamp with random bytes to create time-ordered 34 | * unique identifiers. 35 | *

36 | * This implementation utilises a cryptographically strong {@link SecureRandom} instance to produce 37 | * the random component of the UUID. The first 6 bytes of the UUID encode the current timestamp in 38 | * milliseconds, ensuring that generated UUIDs are roughly ordered by creation time. 39 | *

40 | * The generated UUID adheres strictly to the layout and variant bits of UUID version 7 as defined 41 | * in the specification. 42 | * 43 | * @author zihluwang 44 | * @author siujamo 45 | * @version 3.0.0 46 | */ 47 | public class SequentialUuidGenerator implements IdentityGenerator { 48 | 49 | private final SecureRandom random; 50 | 51 | /** 52 | * Constructs a new {@code SequentialUuidGenerator} with its own {@link SecureRandom} instance. 53 | */ 54 | public SequentialUuidGenerator() { 55 | this.random = new SecureRandom(); 56 | } 57 | 58 | /** 59 | * Generates and returns the next UUID version 7 identifier. 60 | * 61 | * @return a {@link UUID} instance representing a unique, time-ordered identifier 62 | */ 63 | @Override 64 | public UUID nextId() { 65 | var value = randomBytes(); 66 | var buf = ByteBuffer.wrap(value); 67 | var high = buf.getLong(); 68 | var low = buf.getLong(); 69 | return new UUID(high, low); 70 | } 71 | 72 | /** 73 | * Produces a byte array representation of a UUID version 7, 74 | * combining the current timestamp with random bytes. 75 | * 76 | * @return a 16-byte array conforming to UUIDv7 layout and variant bits 77 | */ 78 | private byte[] randomBytes() { 79 | var value = new byte[16]; 80 | random.nextBytes(value); 81 | 82 | var timestamp = ByteBuffer.allocate(Long.BYTES); 83 | timestamp.putLong(System.currentTimeMillis()); 84 | 85 | System.arraycopy(timestamp.array(), 2, value, 0, 6); 86 | 87 | // Set version to 7 (UUIDv7) 88 | value[6] = (byte) ((value[6] & 0x0F) | 0x70); 89 | 90 | // Set variant bits as per RFC 4122 91 | value[8] = (byte) ((value[8] & 0x3F) | 0x80); 92 | 93 | return value; 94 | } 95 | } 96 | -------------------------------------------------------------------------------- /crypto-toolbox/src/main/java/com/onixbyte/crypto/PublicKeyLoader.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2024-2025 OnixByte 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy 5 | * of this software and associated documentation files (the "Software"), to deal 6 | * in the Software without restriction, including without limitation the rights 7 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | * copies of the Software, and to permit persons to whom the Software is 9 | * furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in all 12 | * copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 20 | * SOFTWARE. 21 | */ 22 | 23 | package com.onixbyte.crypto; 24 | 25 | import com.onixbyte.crypto.exception.KeyLoadingException; 26 | 27 | import java.security.PublicKey; 28 | import java.security.interfaces.ECPublicKey; 29 | import java.security.interfaces.RSAPublicKey; 30 | 31 | /** 32 | * The {@code PublicKeyLoader} provides utility methods for loading public keys from PEM-formatted 33 | * key text. 34 | * 35 | * @author zihluwang 36 | * @author siujamo 37 | * @version 3.0.0 38 | */ 39 | public interface PublicKeyLoader { 40 | 41 | /** 42 | * Load public key from PEM-formatted key text. 43 | * 44 | * @param pemKeyText PEM-formatted key text 45 | * @return loaded private key 46 | */ 47 | PublicKey loadPublicKey(String pemKeyText); 48 | 49 | /** 50 | * Loads an EC public key using the provided x and y coordinates together with the curve name. 51 | *

52 | * This default implementation throws a {@link KeyLoadingException} to signify that this key 53 | * loader does not support loading an EC public key. Implementing classes are expected to 54 | * override this method to supply their own loading logic. 55 | * 56 | * @param xHex the hexadecimal string representing the x coordinate of the EC point 57 | * @param yHex the hexadecimal string representing the y coordinate of the EC point 58 | * @param curveName the name of the elliptic curve 59 | * @return the loaded {@link ECPublicKey} instance 60 | * @throws KeyLoadingException if loading is not supported or fails 61 | */ 62 | default ECPublicKey loadPublicKey(String xHex, String yHex, String curveName) { 63 | throw new KeyLoadingException("This key loader does not support loading an EC public key."); 64 | } 65 | 66 | /** 67 | * Loads an RSA public key using the provided modulus and exponent. 68 | *

69 | * This default implementation throws a {@link KeyLoadingException} to signify that this key 70 | * loader does not support loading an RSA public key. Implementing classes are expected to 71 | * override this method to supply their own loading logic. 72 | * 73 | * @param modulus the modulus value of the RSA public key, usually represented in hexadecimal 74 | * or Base64 string format 75 | * @param exponent the public exponent value of the RSA public key, usually represented in 76 | * hexadecimal or Base64 string format 77 | * @return the loaded {@link RSAPublicKey} instance 78 | * @throws KeyLoadingException if loading is not supported or fails 79 | */ 80 | default RSAPublicKey loadPublicKey(String modulus, String exponent) { 81 | throw new KeyLoadingException("This key loader does not support loading an RSA public key."); 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /crypto-toolbox/src/main/java/com/onixbyte/crypto/algorithm/ecdsa/ECPrivateKeyLoader.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2024-2025 OnixByte 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy 5 | * of this software and associated documentation files (the "Software"), to deal 6 | * in the Software without restriction, including without limitation the rights 7 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | * copies of the Software, and to permit persons to whom the Software is 9 | * furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in all 12 | * copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 20 | * SOFTWARE. 21 | */ 22 | 23 | package com.onixbyte.crypto.algorithm.ecdsa; 24 | 25 | import com.onixbyte.crypto.PrivateKeyLoader; 26 | import com.onixbyte.crypto.exception.KeyLoadingException; 27 | import com.onixbyte.crypto.util.CryptoUtil; 28 | 29 | import java.security.KeyFactory; 30 | import java.security.NoSuchAlgorithmException; 31 | import java.security.interfaces.ECPrivateKey; 32 | import java.security.spec.*; 33 | import java.util.Base64; 34 | 35 | /** 36 | * A class responsible for loading private ECDSA keys from PEM formatted text. 37 | *

38 | * This class implements the {@link PrivateKeyLoader} interface and provides methods to load private 39 | * RSA keys. The keys are expected to be in the standard PEM format, which includes Base64-encoded 40 | * key content surrounded by header and footer lines. The class handles the decoding of Base64 41 | * content and the generation of keys using the RSA key factory. 42 | *

43 | * Any exceptions encountered during the loading process are encapsulated in a 44 | * {@link KeyLoadingException}, allowing for flexible error handling. 45 | * 46 | * @author zihluwang 47 | * @author siujamo 48 | * @version 3.0.0 49 | * @see PrivateKeyLoader 50 | * @see KeyLoadingException 51 | */ 52 | public class ECPrivateKeyLoader implements PrivateKeyLoader { 53 | 54 | private final KeyFactory keyFactory; 55 | 56 | private final Base64.Decoder decoder; 57 | 58 | /** 59 | * Initialise a key loader for EC-based algorithms. 60 | */ 61 | public ECPrivateKeyLoader() { 62 | try { 63 | this.keyFactory = KeyFactory.getInstance("EC"); 64 | this.decoder = Base64.getDecoder(); 65 | } catch (NoSuchAlgorithmException e) { 66 | throw new KeyLoadingException(e); 67 | } 68 | } 69 | 70 | /** 71 | * Load ECDSA private key from pem-formatted key text. 72 | * 73 | * @param pemKeyText pem-formatted key text 74 | * @return loaded private key 75 | * @throws KeyLoadingException if the generated key is not a {@link ECPrivateKey} instance, 76 | * or EC Key Factory is not loaded, or key spec is invalid 77 | */ 78 | @Override 79 | public ECPrivateKey loadPrivateKey(String pemKeyText) { 80 | try { 81 | pemKeyText = CryptoUtil.getRawContent(pemKeyText); 82 | var decodedKeyString = decoder.decode(pemKeyText); 83 | var keySpec = new PKCS8EncodedKeySpec(decodedKeyString); 84 | 85 | var _key = keyFactory.generatePrivate(keySpec); 86 | if (_key instanceof ECPrivateKey privateKey) { 87 | return privateKey; 88 | } else { 89 | throw new KeyLoadingException("Unable to load private key from pem-formatted key text."); 90 | } 91 | } catch (InvalidKeySpecException e) { 92 | throw new KeyLoadingException("Key spec is invalid.", e); 93 | } 94 | } 95 | } 96 | -------------------------------------------------------------------------------- /common-toolbox/src/main/java/com/onixbyte/common/util/BoolUtil.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2024-2025 OnixByte 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy 5 | * of this software and associated documentation files (the "Software"), to deal 6 | * in the Software without restriction, including without limitation the rights 7 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | * copies of the Software, and to permit persons to whom the Software is 9 | * furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in all 12 | * copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 20 | * SOFTWARE. 21 | */ 22 | 23 | package com.onixbyte.common.util; 24 | 25 | import java.util.Arrays; 26 | import java.util.Objects; 27 | import java.util.function.BooleanSupplier; 28 | 29 | /** 30 | * The {@link BoolUtil} class provides utility methods for boolean calculations. 31 | * This class offers methods to perform logical operations such as AND, OR, and NOT on boolean values. 32 | *

33 | * The utility methods in this class are useful for scenarios where multiple boolean values need to be 34 | * evaluated together, and for simplifying complex boolean expressions. 35 | *

36 | * 37 | *

Example usage:

38 | *
{@code
 39 |  * boolean result1 = BoolUtil.and(true, true, false); // false
 40 |  * boolean result2 = BoolUtil.or(true, false, false); // true
 41 |  * boolean result3 = BoolUtil.not(false); // true
 42 |  * }
43 | * 44 | * @author zihluwang 45 | * @version 3.0.0 46 | */ 47 | public final class BoolUtil { 48 | 49 | /** 50 | * Private constructor to prevent instantiation of this utility class. 51 | */ 52 | private BoolUtil() { 53 | } 54 | 55 | /** 56 | * Logical and calculation. 57 | * 58 | * @param values the values to be calculated 59 | * @return {@code true} if all value in values is {@code true}, otherwise {@code false} 60 | */ 61 | public static boolean and(Boolean... values) { 62 | return Arrays.stream(values) 63 | .filter(Objects::nonNull) 64 | .allMatch(Boolean::booleanValue); 65 | } 66 | 67 | /** 68 | * Logical and calculation. 69 | * 70 | * @param valueSuppliers the suppliers of value to be calculated 71 | * @return {@code true} if all value in values is {@code true}, otherwise {@code false} 72 | */ 73 | public static boolean and(BooleanSupplier... valueSuppliers) { 74 | return Arrays.stream(valueSuppliers) 75 | .filter(Objects::nonNull) 76 | .allMatch(BooleanSupplier::getAsBoolean); 77 | } 78 | 79 | /** 80 | * Logical or calculation. 81 | * 82 | * @param values the values to be calculated 83 | * @return {@code true} if any value in values is {@code true}, otherwise {@code false} 84 | */ 85 | public static boolean or(Boolean... values) { 86 | return Arrays.stream(values) 87 | .filter(Objects::nonNull) 88 | .anyMatch(Boolean::booleanValue); 89 | } 90 | 91 | /** 92 | * Logical or calculation. 93 | * 94 | * @param valueSuppliers the suppliers of value to be calculated 95 | * @return {@code true} if any value in values is {@code true}, otherwise {@code false} 96 | */ 97 | public static boolean or(BooleanSupplier... valueSuppliers) { 98 | return Arrays.stream(valueSuppliers) 99 | .filter(Objects::nonNull) 100 | .anyMatch(BooleanSupplier::getAsBoolean); 101 | } 102 | } 103 | -------------------------------------------------------------------------------- /crypto-toolbox/src/main/java/com/onixbyte/crypto/exception/KeyLoadingException.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2024-2025 OnixByte 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy 5 | * of this software and associated documentation files (the "Software"), to deal 6 | * in the Software without restriction, including without limitation the rights 7 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | * copies of the Software, and to permit persons to whom the Software is 9 | * furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in all 12 | * copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 20 | * SOFTWARE. 21 | */ 22 | 23 | package com.onixbyte.crypto.exception; 24 | 25 | /** 26 | * The {@code KeyLoadingException} class represents an exception that is thrown when there is an 27 | * error loading cryptographic keys. This exception can be used to indicate various issues such as 28 | * invalid key specifications, unsupported key algorithms, or other key loading errors. 29 | *

30 | * This class extends {@link RuntimeException}, allowing it to be thrown without being declared in 31 | * a method's {@code throws} clause. 32 | *

33 | * 34 | *

Example usage:

35 | *
{@code
 36 |  * try {
 37 |  *     PrivateKeyLoader keyLoader = new ECPrivateKeyLoader();
 38 |  *     ECPrivateKey privateKey = keyLoader.loadPrivateKey(pemPrivateKey);
 39 |  * } catch (KeyLoadingException e) {
 40 |  *     // Handle the exception
 41 |  *     e.printStackTrace();
 42 |  * }
 43 |  * }
44 | * 45 | * @author zihluwang 46 | * @version 3.0.0 47 | */ 48 | public class KeyLoadingException extends RuntimeException { 49 | 50 | /** 51 | * Creates a new instance of {@code KeyLoadingException} without a specific message or cause. 52 | */ 53 | public KeyLoadingException() { 54 | } 55 | 56 | /** 57 | * Creates a new instance of {@code KeyLoadingException} with the specified detail message. 58 | * 59 | * @param message the detail message 60 | */ 61 | public KeyLoadingException(String message) { 62 | super(message); 63 | } 64 | 65 | /** 66 | * Creates a new instance of {@code KeyLoadingException} with the specified detail message 67 | * and cause. 68 | * 69 | * @param message the detail message 70 | * @param cause the cause of this exception 71 | */ 72 | public KeyLoadingException(String message, Throwable cause) { 73 | super(message, cause); 74 | } 75 | 76 | /** 77 | * Creates a new instance of {@code KeyLoadingException} with the specified cause. 78 | * 79 | * @param cause the cause of this exception 80 | */ 81 | public KeyLoadingException(Throwable cause) { 82 | super(cause); 83 | } 84 | 85 | /** 86 | * Constructs a new exception with the specified detail message, cause, suppression enabled 87 | * or disabled, and writable stack trace enabled or disabled. 88 | * 89 | * @param message the detail message 90 | * @param cause the cause of this exception 91 | * @param enableSuppression whether suppression is enabled or disabled 92 | * @param writableStackTrace whether the stack trace should be writable 93 | */ 94 | public KeyLoadingException(String message, 95 | Throwable cause, 96 | boolean enableSuppression, 97 | boolean writableStackTrace) { 98 | super(message, cause, enableSuppression, writableStackTrace); 99 | } 100 | } 101 | -------------------------------------------------------------------------------- /version-catalogue/build.gradle.kts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2024-2025 OnixByte 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy 5 | * of this software and associated documentation files (the "Software"), to deal 6 | * in the Software without restriction, including without limitation the rights 7 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | * copies of the Software, and to permit persons to whom the Software is 9 | * furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in all 12 | * copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 20 | * SOFTWARE. 21 | */ 22 | 23 | import java.net.URI 24 | 25 | plugins { 26 | id("java-platform") 27 | id("maven-publish") 28 | id("signing") 29 | } 30 | 31 | val artefactVersion: String by project 32 | val projectUrl: String by project 33 | val projectGithubUrl: String by project 34 | val licenseName: String by project 35 | val licenseUrl: String by project 36 | 37 | group = "com.onixbyte" 38 | version = artefactVersion 39 | 40 | repositories { 41 | mavenCentral() 42 | } 43 | 44 | dependencies { 45 | constraints { 46 | api("com.onixbyte:common-toolbox:$artefactVersion") 47 | api("com.onixbyte:identity-generator:$artefactVersion") 48 | api("com.onixbyte:crypto-toolbox:$artefactVersion") 49 | api("com.onixbyte:math-toolbox:$artefactVersion") 50 | } 51 | } 52 | 53 | publishing { 54 | publications { 55 | create("versionCatalogue") { 56 | groupId = group.toString() 57 | artifactId = "version-catalogue" 58 | version = artefactVersion 59 | 60 | pom { 61 | name = "OnixByte Version Catalogue" 62 | description = "OnixByte DevKit BOM is designed to manage dependency versions centrally." 63 | url = projectUrl 64 | 65 | licenses { 66 | license { 67 | name = licenseName 68 | url = licenseUrl 69 | } 70 | } 71 | 72 | scm { 73 | connection = "scm:git:git://github.com:onixbyte/onixbyte-toolbox.git" 74 | developerConnection = "scm:git:git://github.com:onixbyte/onixbyte-toolbox.git" 75 | url = projectGithubUrl 76 | } 77 | 78 | developers { 79 | developer { 80 | id = "zihluwang" 81 | name = "Zihlu Wang" 82 | email = "really@zihlu.wang" 83 | timezone = "Asia/Hong_Kong" 84 | } 85 | 86 | developer { 87 | id = "siujamo" 88 | name = "Siu Jam'o" 89 | email = "jamo.siu@outlook.com" 90 | timezone = "Asia/Shanghai" 91 | } 92 | } 93 | } 94 | 95 | from(components["javaPlatform"]) 96 | 97 | signing { 98 | sign(publishing.publications["versionCatalogue"]) 99 | } 100 | } 101 | 102 | repositories { 103 | maven { 104 | name = "sonatypeNexus" 105 | url = URI(providers.gradleProperty("repo.maven-central.host").get()) 106 | credentials { 107 | username = providers.gradleProperty("repo.maven-central.username").get() 108 | password = providers.gradleProperty("repo.maven-central.password").get() 109 | } 110 | } 111 | } 112 | } 113 | } 114 | -------------------------------------------------------------------------------- /crypto-toolbox/README.md: -------------------------------------------------------------------------------- 1 | # Crypto Toolbox 2 | 3 | Crypto Toolbox provides methods to simplify your codes on key pairs. 4 | 5 | ## ECDSA-based algorithm 6 | 7 | ### Generate key pair 8 | 9 | #### Generate private key 10 | 11 | Generate a private key by `genpkey` command provided by OpenSSL: 12 | 13 | ```shell 14 | openssl genpkey -algorithm EC -pkeyopt ec_paramgen_curve:P-256 -out ec_private_key.pem 15 | ``` 16 | 17 | The output of this command is a file called `ec_private_key.pem` and its content looks like the 18 | following: 19 | 20 | ```text 21 | -----BEGIN PRIVATE KEY----- 22 | MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQgs79JlARgXEf6EDV7 23 | +PHQCTHEMtqIoHOy1GZ1+ynQJ6yhRANCAARkA7GRY2i4gg8qx0XViAXUP9cPw9pn 24 | Jg1wfrQ41FaMyqVBejNYxvaLtamErF/ySimnjafMJ+VZCh34lBj6Ez8R 25 | -----END PRIVATE KEY----- 26 | ``` 27 | 28 | #### Generate public key by private key 29 | 30 | Export public key from private key with `ec` command provided by OpenSSL: 31 | 32 | ```shell 33 | openssl ec -in ec_private_key.pem -pubout -out ec_public_key.pem 34 | ``` 35 | 36 | The output of this command is a file called `ec_public_key.pem` and its content looks like the 37 | following: 38 | 39 | ```text 40 | -----BEGIN PUBLIC KEY----- 41 | MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEZAOxkWNouIIPKsdF1YgF1D/XD8Pa 42 | ZyYNcH60ONRWjMqlQXozWMb2i7WphKxf8kopp42nzCflWQod+JQY+hM/EQ== 43 | -----END PUBLIC KEY----- 44 | ``` 45 | 46 | ## RSA-based algorithm 47 | 48 | ### Generate key pair 49 | 50 | #### Generate private key 51 | 52 | Generate a private key by `genpkey` command provided by OpenSSL: 53 | 54 | ```shell 55 | openssl genpkey -algorithm RSA -out rsa_private_key.pem -pkeyopt rsa_keygen_bits:2048 56 | ``` 57 | 58 | The output of this command is a file called `rsa_private_key.pem` and its content looks like the 59 | following: 60 | 61 | ```text 62 | -----BEGIN PRIVATE KEY----- 63 | MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQD4VIFYJFMAs15j 64 | J3V3IicHd7sI2TIFqTZME40zlOlVAlPKLZmTQvZFLNgaUAAsvPi5i1DR2ywwK6Al 65 | BfnwVnzvmDXC5mKHOz4oxOQVA6Nlp2yVaQMzidmfYNSkMtcv/4HRPsatc7K/M5l6 66 | pCP20DVRjkikBdIy8e9w+x6BrIFp5Q8PZc/X2BGNAUMMYACdeYH5R/A0CxqkND13 67 | esc4gkynMOrvZrZGHCz51usfSCqyDWWwsN+GG6LYWia4GkNlS0erQnP8gS93dfjl 68 | e96BIfy3z7Iv+kUrf5ikNW2P8jMxLAv6LO+dcUAu9k477wIAF7Iq5KMuH/otsDOu 69 | +h+2qXmBAgMBAAECggEAdRqcmC0g+y6arxV3fkObthjPGYAa57KBCWUa7B0n30+m 70 | pavVRS2Jpttb2SSqwG4ouI6rARti/iBEd9EWqTCP4AieKZetFOpqCJ24lPRPRGus 71 | d9S6jr5N4qut+vSCp37NABijZj4uJ540nTH0R7qtuhTnynl4Q0/1wwiYvTvVF1Lg 72 | dn+I/8aRbshwDhdAOWOUe6GL7/eaCYgN8/UmlKIpp8tg0w2iWxbaFiR7gZiM41LA 73 | M6SXXfcCas+ZVXsGbzQ3SNiVurCGuuRNcCScXS3/WoEDIb3cNtp49iOmQS+nmEoo 74 | wh4uiEd+0+BrzxngS4o5+mKnHJnwgY0+veGVYLMR5QKBgQD9WKQmevMDU5c+NPq9 75 | 8jaR457Fuxq1gwzeFNJdWfOc/K2LEWh+nFNFCb++EboEj6FdxWaWNMxbrmJps5gs 76 | EoBUYy/Tl7UycDqDfiYLmDdTsf2pVjjh9jaIADiLcJ8S6wwJMZKub7Tp8UVkenAl 77 | 535MqShLUC11Y7VxLb3Tsll4XwKBgQD67mm6iCmshr/eszPfNE3ylZ+PiNa7nat7 78 | N7lQzBIiRJflT1kmVidC5gE+jASqH728ChkZZKxbHsjxpmWdAhLOITdXoTB4sDsd 79 | wtV1lxkXxK9FnrpFvO3y1wZ/QsD3Z2KXxHYZqawkUETO9F3nqAXW0b2GDar5Qiyo 80 | J3Tx/43aHwKBgDC0NMJtCoDONhowZy/S+6iqQKC0qprQec3L5PErVMkOTnKYwyTr 81 | +pogGKt6ju9HiXcUdvdTaSIK8UJu00dNuzv94XjlBmGO78DNpJTAC4rcge5m9AKE 82 | qdEVcclkukARzbuKuy8rrHT4/CUn4J141m/4aRWpcUPLCluato6XD9ozAoGBANvf 83 | JhOFFgcPd3YazfvpZ9eE1XA+tfFlYYmxNRcgCU+vjO0oDvSxjutmgHae18N91pG6 84 | w21lskSRf/+GDwl5dKLbphOJsOA/gz07qDDGOf2CoRW+1Hcg6drcINxH0K+4DkLv 85 | qZApBSY4k2JH6zR+HMeztn6M4WBRZLHfCPC3PUN/AoGAA3AoHbLTZvqMIKSDkP4Y 86 | U/tTsSFDY4aYo7LG/jk8af3oPU3KyGh4ZFBd6aMmXbS8f8FjvmrM+/e+y9OOGAlq 87 | iOl0hYrs5cJSMLW6i4KnJYuYbMkgmk3bN2t9apu64xKR94gbPrI6AGnPZp+iIzp0 88 | hXKe4HcuhQ3G0a2hjayiQ84= 89 | -----END PRIVATE KEY----- 90 | ``` 91 | 92 | #### Generate public key by private key 93 | 94 | Export public key from private key by OpenSSL: 95 | 96 | ```shell 97 | openssl pkey -in rsa_private_key.pem -pubout -out rsa_public_key.pem 98 | ``` 99 | 100 | The output of this command is a file called `rsa_public_key.pem` and its content looks like the 101 | following: 102 | 103 | ```text 104 | -----BEGIN PUBLIC KEY----- 105 | MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA+FSBWCRTALNeYyd1dyIn 106 | B3e7CNkyBak2TBONM5TpVQJTyi2Zk0L2RSzYGlAALLz4uYtQ0dssMCugJQX58FZ8 107 | 75g1wuZihzs+KMTkFQOjZadslWkDM4nZn2DUpDLXL/+B0T7GrXOyvzOZeqQj9tA1 108 | UY5IpAXSMvHvcPsegayBaeUPD2XP19gRjQFDDGAAnXmB+UfwNAsapDQ9d3rHOIJM 109 | pzDq72a2Rhws+dbrH0gqsg1lsLDfhhui2FomuBpDZUtHq0Jz/IEvd3X45XvegSH8 110 | t8+yL/pFK3+YpDVtj/IzMSwL+izvnXFALvZOO+8CABeyKuSjLh/6LbAzrvoftql5 111 | gQIDAQAB 112 | -----END PUBLIC KEY----- 113 | ``` 114 | -------------------------------------------------------------------------------- /common-toolbox/build.gradle.kts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2024-2025 OnixByte 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy 5 | * of this software and associated documentation files (the "Software"), to deal 6 | * in the Software without restriction, including without limitation the rights 7 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | * copies of the Software, and to permit persons to whom the Software is 9 | * furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in all 12 | * copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 20 | * SOFTWARE. 21 | */ 22 | 23 | import java.net.URI 24 | 25 | plugins { 26 | java 27 | id("java-library") 28 | id("maven-publish") 29 | id("signing") 30 | } 31 | 32 | val artefactVersion: String by project 33 | val projectUrl: String by project 34 | val projectGithubUrl: String by project 35 | val licenseName: String by project 36 | val licenseUrl: String by project 37 | 38 | group = "com.onixbyte" 39 | version = artefactVersion 40 | 41 | repositories { 42 | mavenCentral() 43 | } 44 | 45 | java { 46 | sourceCompatibility = JavaVersion.VERSION_17 47 | targetCompatibility = JavaVersion.VERSION_17 48 | withSourcesJar() 49 | withJavadocJar() 50 | } 51 | 52 | tasks.withType { 53 | options.encoding = "UTF-8" 54 | } 55 | 56 | tasks.withType { 57 | exclude("logback.xml") 58 | } 59 | 60 | dependencies { 61 | compileOnly(libs.slf4j) 62 | implementation(libs.logback) 63 | testImplementation(platform(libs.junit.bom)) 64 | testImplementation(libs.junit.jupiter) 65 | } 66 | 67 | tasks.test { 68 | useJUnitPlatform() 69 | } 70 | 71 | publishing { 72 | publications { 73 | create("commonToolbox") { 74 | groupId = group.toString() 75 | artifactId = "common-toolbox" 76 | version = artefactVersion 77 | 78 | pom { 79 | name = "OnixByte Common Toolbox" 80 | description = "The utils module of OnixByte toolbox." 81 | url = projectUrl 82 | 83 | licenses { 84 | license { 85 | name = licenseName 86 | url = licenseUrl 87 | } 88 | } 89 | 90 | scm { 91 | connection = "scm:git:git://github.com:onixbyte/onixbyte-toolbox.git" 92 | developerConnection = "scm:git:git://github.com:onixbyte/onixbyte-toolbox.git" 93 | url = projectGithubUrl 94 | } 95 | 96 | developers { 97 | developer { 98 | id = "zihluwang" 99 | name = "Zihlu Wang" 100 | email = "really@zihlu.wang" 101 | timezone = "Asia/Hong_Kong" 102 | } 103 | 104 | developer { 105 | id = "siujamo" 106 | name = "Siu Jam'o" 107 | email = "jamo.siu@outlook.com" 108 | timezone = "Asia/Shanghai" 109 | } 110 | } 111 | } 112 | 113 | from(components["java"]) 114 | 115 | signing { 116 | sign(publishing.publications["commonToolbox"]) 117 | } 118 | } 119 | 120 | repositories { 121 | maven { 122 | name = "sonatypeNexus" 123 | url = URI(providers.gradleProperty("repo.maven-central.host").get()) 124 | credentials { 125 | username = providers.gradleProperty("repo.maven-central.username").get() 126 | password = providers.gradleProperty("repo.maven-central.password").get() 127 | } 128 | } 129 | } 130 | } 131 | } 132 | -------------------------------------------------------------------------------- /identity-generator/build.gradle.kts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2024-2025 OnixByte 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy 5 | * of this software and associated documentation files (the "Software"), to deal 6 | * in the Software without restriction, including without limitation the rights 7 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | * copies of the Software, and to permit persons to whom the Software is 9 | * furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in all 12 | * copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 20 | * SOFTWARE. 21 | */ 22 | 23 | import java.net.URI 24 | 25 | plugins { 26 | java 27 | id("java-library") 28 | id("maven-publish") 29 | id("signing") 30 | } 31 | 32 | val artefactVersion: String by project 33 | val projectUrl: String by project 34 | val projectGithubUrl: String by project 35 | val licenseName: String by project 36 | val licenseUrl: String by project 37 | 38 | group = "com.onixbyte" 39 | version = artefactVersion 40 | 41 | repositories { 42 | mavenCentral() 43 | } 44 | 45 | java { 46 | sourceCompatibility = JavaVersion.VERSION_17 47 | targetCompatibility = JavaVersion.VERSION_17 48 | withSourcesJar() 49 | withJavadocJar() 50 | } 51 | 52 | tasks.withType { 53 | options.encoding = "UTF-8" 54 | } 55 | 56 | tasks.withType { 57 | exclude("logback.xml") 58 | } 59 | 60 | dependencies { 61 | compileOnly(libs.slf4j) 62 | implementation(libs.logback) 63 | testImplementation(platform(libs.junit.bom)) 64 | testImplementation(libs.junit.jupiter) 65 | } 66 | 67 | tasks.test { 68 | useJUnitPlatform() 69 | } 70 | 71 | publishing { 72 | publications { 73 | create("identityGenerator") { 74 | groupId = group.toString() 75 | artifactId = "identity-generator" 76 | version = artefactVersion 77 | 78 | pom { 79 | name = "OnixByte Identity Generator" 80 | description = "The module for generating GUIDs of JDevKit." 81 | url = projectUrl 82 | 83 | licenses { 84 | license { 85 | name = licenseName 86 | url = licenseUrl 87 | } 88 | } 89 | 90 | scm { 91 | connection = "scm:git:git://github.com:OnixByte/JDevKit.git" 92 | developerConnection = "scm:git:git://github.com:OnixByte/JDevKit.git" 93 | url = projectGithubUrl 94 | } 95 | 96 | developers { 97 | developer { 98 | id = "zihluwang" 99 | name = "Zihlu Wang" 100 | email = "really@zihlu.wang" 101 | timezone = "Asia/Hong_Kong" 102 | } 103 | 104 | developer { 105 | id = "siujamo" 106 | name = "Siu Jam'o" 107 | email = "jamo.siu@outlook.com" 108 | timezone = "Asia/Shanghai" 109 | } 110 | } 111 | } 112 | 113 | from(components["java"]) 114 | 115 | signing { 116 | sign(publishing.publications["identityGenerator"]) 117 | } 118 | } 119 | 120 | repositories { 121 | maven { 122 | name = "sonatypeNexus" 123 | url = URI(providers.gradleProperty("repo.maven-central.host").get()) 124 | credentials { 125 | username = providers.gradleProperty("repo.maven-central.username").get() 126 | password = providers.gradleProperty("repo.maven-central.password").get() 127 | } 128 | } 129 | } 130 | } 131 | } 132 | -------------------------------------------------------------------------------- /tuple/build.gradle.kts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2024-2025 OnixByte 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy 5 | * of this software and associated documentation files (the "Software"), to deal 6 | * in the Software without restriction, including without limitation the rights 7 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | * copies of the Software, and to permit persons to whom the Software is 9 | * furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in all 12 | * copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 20 | * SOFTWARE. 21 | */ 22 | 23 | import java.net.URI 24 | 25 | plugins { 26 | java 27 | id("java-library") 28 | id("maven-publish") 29 | id("signing") 30 | } 31 | 32 | val artefactVersion: String by project 33 | val projectUrl: String by project 34 | val projectGithubUrl: String by project 35 | val licenseName: String by project 36 | val licenseUrl: String by project 37 | 38 | group = "com.onixbyte" 39 | version = artefactVersion 40 | 41 | repositories { 42 | mavenCentral() 43 | } 44 | 45 | java { 46 | sourceCompatibility = JavaVersion.VERSION_17 47 | targetCompatibility = JavaVersion.VERSION_17 48 | withSourcesJar() 49 | withJavadocJar() 50 | } 51 | 52 | tasks.withType { 53 | options.encoding = "UTF-8" 54 | } 55 | 56 | tasks.withType { 57 | exclude("logback.xml") 58 | } 59 | 60 | dependencies { 61 | compileOnly(libs.slf4j) 62 | implementation(libs.logback) 63 | testImplementation(platform(libs.junit.bom)) 64 | testImplementation(libs.junit.jupiter) 65 | } 66 | 67 | tasks.test { 68 | useJUnitPlatform() 69 | } 70 | 71 | publishing { 72 | publications { 73 | create("tuple") { 74 | groupId = group.toString() 75 | artifactId = "tuple" 76 | version = artefactVersion 77 | 78 | pom { 79 | name = "OnixByte Tuple" 80 | description = 81 | "The tuple module is designed to offer a convenient and efficient way to handle grouped data." 82 | url = projectUrl 83 | 84 | licenses { 85 | license { 86 | name = licenseName 87 | url = licenseUrl 88 | } 89 | } 90 | 91 | scm { 92 | connection = "scm:git:git://github.com:onixbyte/onixbyte-toolbox.git" 93 | developerConnection = "scm:git:git://github.com:onixbyte/onixbyte-toolbox.git" 94 | url = projectGithubUrl 95 | } 96 | 97 | developers { 98 | developer { 99 | id = "zihluwang" 100 | name = "Zihlu Wang" 101 | email = "really@zihlu.wang" 102 | timezone = "Asia/Hong_Kong" 103 | } 104 | 105 | developer { 106 | id = "siujamo" 107 | name = "Siu Jam'o" 108 | email = "jamo.siu@outlook.com" 109 | timezone = "Asia/Shanghai" 110 | } 111 | } 112 | } 113 | 114 | from(components["java"]) 115 | 116 | signing { 117 | sign(publishing.publications["tuple"]) 118 | } 119 | } 120 | 121 | repositories { 122 | maven { 123 | name = "sonatypeNexus" 124 | url = URI(providers.gradleProperty("repo.maven-central.host").get()) 125 | credentials { 126 | username = providers.gradleProperty("repo.maven-central.username").get() 127 | password = providers.gradleProperty("repo.maven-central.password").get() 128 | } 129 | } 130 | } 131 | } 132 | } 133 | -------------------------------------------------------------------------------- /math-toolbox/build.gradle.kts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2024-2025 OnixByte 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy 5 | * of this software and associated documentation files (the "Software"), to deal 6 | * in the Software without restriction, including without limitation the rights 7 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | * copies of the Software, and to permit persons to whom the Software is 9 | * furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in all 12 | * copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 20 | * SOFTWARE. 21 | */ 22 | 23 | import java.net.URI 24 | 25 | plugins { 26 | java 27 | id("java-library") 28 | id("maven-publish") 29 | id("signing") 30 | } 31 | 32 | val artefactVersion: String by project 33 | val projectUrl: String by project 34 | val projectGithubUrl: String by project 35 | val licenseName: String by project 36 | val licenseUrl: String by project 37 | 38 | group = "com.onixbyte" 39 | version = artefactVersion 40 | 41 | repositories { 42 | mavenCentral() 43 | } 44 | 45 | java { 46 | sourceCompatibility = JavaVersion.VERSION_17 47 | targetCompatibility = JavaVersion.VERSION_17 48 | withSourcesJar() 49 | withJavadocJar() 50 | } 51 | 52 | tasks.withType { 53 | options.encoding = "UTF-8" 54 | } 55 | 56 | tasks.withType { 57 | exclude("logback.xml") 58 | } 59 | 60 | dependencies { 61 | compileOnly(libs.slf4j) 62 | implementation(libs.logback) 63 | testImplementation(platform(libs.junit.bom)) 64 | testImplementation(libs.junit.jupiter) 65 | } 66 | 67 | tasks.test { 68 | useJUnitPlatform() 69 | } 70 | 71 | publishing { 72 | publications { 73 | create("mathToolbox") { 74 | groupId = group.toString() 75 | artifactId = "math-toolbox" 76 | version = artefactVersion 77 | 78 | pom { 79 | name = "OnixByte Math Toolbox" 80 | description = 81 | "This module is an easy-to-use util for mathematical calculations in Java." 82 | url = projectUrl 83 | 84 | licenses { 85 | license { 86 | name = licenseName 87 | url = licenseUrl 88 | } 89 | } 90 | 91 | scm { 92 | connection = "scm:git:git://github.com:onixbyte/onixbyte-toolbox.git" 93 | developerConnection = "scm:git:git://github.com:onixbyte/onixbyte-toolbox.git" 94 | url = projectGithubUrl 95 | } 96 | 97 | developers { 98 | developer { 99 | id = "zihluwang" 100 | name = "Zihlu Wang" 101 | email = "really@zihlu.wang" 102 | timezone = "Asia/Hong_Kong" 103 | } 104 | 105 | developer { 106 | id = "siujamo" 107 | name = "Siu Jam'o" 108 | email = "jamo.siu@outlook.com" 109 | timezone = "Asia/Shanghai" 110 | } 111 | } 112 | } 113 | 114 | from(components["java"]) 115 | 116 | signing { 117 | sign(publishing.publications["mathToolbox"]) 118 | } 119 | } 120 | 121 | repositories { 122 | maven { 123 | name = "sonatypeNexus" 124 | url = URI(providers.gradleProperty("repo.maven-central.host").get()) 125 | credentials { 126 | username = providers.gradleProperty("repo.maven-central.username").get() 127 | password = providers.gradleProperty("repo.maven-central.password").get() 128 | } 129 | } 130 | } 131 | } 132 | } 133 | -------------------------------------------------------------------------------- /common-toolbox/src/test/java/com/onixbyte/common/util/BoolUtilTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2024-2025 OnixByte. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | 18 | package com.onixbyte.common.util; 19 | 20 | import org.junit.jupiter.api.Test; 21 | 22 | import java.util.function.BooleanSupplier; 23 | 24 | import static org.junit.jupiter.api.Assertions.assertFalse; 25 | import static org.junit.jupiter.api.Assertions.assertTrue; 26 | 27 | public class BoolUtilTest { 28 | 29 | // Tests for and(Boolean... values) 30 | 31 | @Test 32 | void and_AllTrueValues_ReturnsTrue() { 33 | assertTrue(BoolUtil.and(true, true, true)); 34 | } 35 | 36 | @Test 37 | void and_SomeFalseValues_ReturnsFalse() { 38 | assertFalse(BoolUtil.and(true, false, true)); 39 | } 40 | 41 | @Test 42 | void and_AllFalseValues_ReturnsFalse() { 43 | assertFalse(BoolUtil.and(false, false)); 44 | } 45 | 46 | @Test 47 | void and_WithNullValues_IgnoresNulls() { 48 | assertTrue(BoolUtil.and(true, null, true)); 49 | assertFalse(BoolUtil.and(true, null, false)); 50 | } 51 | 52 | @Test 53 | void and_AllNullValues_ReturnsTrue() { 54 | // Stream after filtering null is empty, allMatch on empty returns true 55 | assertTrue(BoolUtil.and((Boolean) null, null)); 56 | } 57 | 58 | // Tests for and(BooleanSupplier... valueSuppliers) 59 | 60 | @Test 61 | void and_AllSuppliersTrue_ReturnsTrue() { 62 | BooleanSupplier trueSupplier = () -> true; 63 | BooleanSupplier falseSupplier = () -> false; 64 | 65 | assertTrue(BoolUtil.and(trueSupplier, trueSupplier)); 66 | assertFalse(BoolUtil.and(trueSupplier, falseSupplier)); 67 | } 68 | 69 | @Test 70 | void and_WithNullSuppliers_IgnoresNull() { 71 | BooleanSupplier trueSupplier = () -> true; 72 | 73 | assertTrue(BoolUtil.and(trueSupplier, null, trueSupplier)); 74 | assertFalse(BoolUtil.and(trueSupplier, null, () -> false)); 75 | } 76 | 77 | @Test 78 | void and_AllNullSuppliers_ReturnsTrue() { 79 | assertTrue(BoolUtil.and((BooleanSupplier) null, null)); 80 | } 81 | 82 | 83 | // Tests for or(Boolean... values) 84 | 85 | @Test 86 | void or_AllTrueValues_ReturnsTrue() { 87 | assertTrue(BoolUtil.or(true, true, true)); 88 | } 89 | 90 | @Test 91 | void or_SomeTrueValues_ReturnsTrue() { 92 | assertTrue(BoolUtil.or(false, true, false)); 93 | } 94 | 95 | @Test 96 | void or_AllFalseValues_ReturnsFalse() { 97 | assertFalse(BoolUtil.or(false, false)); 98 | } 99 | 100 | @Test 101 | void or_WithNullValues_IgnoresNull() { 102 | assertTrue(BoolUtil.or(false, null, true)); 103 | assertFalse(BoolUtil.or(false, null, false)); 104 | } 105 | 106 | @Test 107 | void or_AllNullValues_ReturnsFalse() { 108 | // Stream after filtering null is empty, anyMatch on empty returns false 109 | assertFalse(BoolUtil.or((Boolean) null, null)); 110 | } 111 | 112 | // Tests for or(BooleanSupplier... valueSuppliers) 113 | 114 | @Test 115 | void or_AllSuppliersTrue_ReturnsTrue() { 116 | BooleanSupplier trueSupplier = () -> true; 117 | BooleanSupplier falseSupplier = () -> false; 118 | 119 | assertTrue(BoolUtil.or(trueSupplier, trueSupplier)); 120 | assertTrue(BoolUtil.or(falseSupplier, trueSupplier)); 121 | assertFalse(BoolUtil.or(falseSupplier, falseSupplier)); 122 | } 123 | 124 | @Test 125 | void or_WithNullSuppliers_IgnoresNull() { 126 | BooleanSupplier trueSupplier = () -> true; 127 | BooleanSupplier falseSupplier = () -> false; 128 | 129 | assertTrue(BoolUtil.or(falseSupplier, null, trueSupplier)); 130 | assertFalse(BoolUtil.or(falseSupplier, null, falseSupplier)); 131 | } 132 | 133 | @Test 134 | void or_AllNullSuppliers_ReturnsFalse() { 135 | assertFalse(BoolUtil.or((BooleanSupplier) null, null)); 136 | } 137 | } 138 | -------------------------------------------------------------------------------- /crypto-toolbox/build.gradle.kts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2024-2025 OnixByte 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy 5 | * of this software and associated documentation files (the "Software"), to deal 6 | * in the Software without restriction, including without limitation the rights 7 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | * copies of the Software, and to permit persons to whom the Software is 9 | * furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in all 12 | * copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 20 | * SOFTWARE. 21 | */ 22 | 23 | import java.net.URI 24 | 25 | plugins { 26 | java 27 | id("java-library") 28 | id("maven-publish") 29 | id("signing") 30 | } 31 | 32 | val artefactVersion: String by project 33 | val projectUrl: String by project 34 | val projectGithubUrl: String by project 35 | val licenseName: String by project 36 | val licenseUrl: String by project 37 | 38 | group = "com.onixbyte" 39 | version = artefactVersion 40 | 41 | repositories { 42 | mavenCentral() 43 | } 44 | 45 | java { 46 | sourceCompatibility = JavaVersion.VERSION_17 47 | targetCompatibility = JavaVersion.VERSION_17 48 | withSourcesJar() 49 | withJavadocJar() 50 | } 51 | 52 | tasks.withType { 53 | options.encoding = "UTF-8" 54 | } 55 | 56 | tasks.withType { 57 | exclude("logback.xml") 58 | } 59 | 60 | dependencies { 61 | compileOnly(libs.slf4j) 62 | implementation(libs.logback) 63 | testImplementation(libs.jwt.core) 64 | testImplementation(platform(libs.junit.bom)) 65 | testImplementation(libs.junit.jupiter) 66 | } 67 | 68 | tasks.test { 69 | useJUnitPlatform() 70 | testLogging { 71 | events("passed", "skipped", "failed") 72 | } 73 | } 74 | 75 | publishing { 76 | publications { 77 | create("cryptoToolbox") { 78 | groupId = group.toString() 79 | artifactId = "crypto-toolbox" 80 | version = artefactVersion 81 | 82 | pom { 83 | name = "OnixByte Crypto Toolbox" 84 | description = 85 | "This module can easily load key pairs from a PEM content." 86 | url = projectUrl 87 | 88 | licenses { 89 | license { 90 | name = licenseName 91 | url = licenseUrl 92 | } 93 | } 94 | 95 | scm { 96 | connection = "scm:git:git://github.com:onixbyte/onixbyte-toolbox.git" 97 | developerConnection = "scm:git:git://github.com:onixbyte/onixbyte-toolbox.git" 98 | url = projectGithubUrl 99 | } 100 | 101 | developers { 102 | developer { 103 | id = "zihluwang" 104 | name = "Zihlu Wang" 105 | email = "really@zihlu.wang" 106 | timezone = "Asia/Hong_Kong" 107 | } 108 | 109 | developer { 110 | id = "siujamo" 111 | name = "Siu Jam'o" 112 | email = "jamo.siu@outlook.com" 113 | timezone = "Asia/Shanghai" 114 | } 115 | } 116 | } 117 | 118 | from(components["java"]) 119 | 120 | signing { 121 | sign(publishing.publications["cryptoToolbox"]) 122 | } 123 | } 124 | 125 | repositories { 126 | maven { 127 | name = "sonatypeNexus" 128 | url = URI(providers.gradleProperty("repo.maven-central.host").get()) 129 | credentials { 130 | username = providers.gradleProperty("repo.maven-central.username").get() 131 | password = providers.gradleProperty("repo.maven-central.password").get() 132 | } 133 | } 134 | } 135 | } 136 | } 137 | -------------------------------------------------------------------------------- /common-toolbox/src/test/java/com/onixbyte/common/util/CollectionUtilTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2024-2025 OnixByte. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | 18 | package com.onixbyte.common.util; 19 | 20 | import org.junit.jupiter.api.Test; 21 | 22 | import java.util.*; 23 | import java.util.function.Supplier; 24 | 25 | import static org.junit.jupiter.api.Assertions.*; 26 | 27 | class CollectionUtilTest { 28 | 29 | @Test 30 | void chunk_NullOriginalCollection_ThrowsException() { 31 | IllegalArgumentException ex = assertThrows(IllegalArgumentException.class, 32 | () -> CollectionUtil.chunk(null, 3, ArrayList::new)); 33 | assertEquals("Collection must not be null.", ex.getMessage()); 34 | } 35 | 36 | @Test 37 | void chunk_NegativeMaxSize_ThrowsException() { 38 | IllegalArgumentException ex = assertThrows(IllegalArgumentException.class, 39 | () -> CollectionUtil.chunk(List.of(1, 2), -1, ArrayList::new)); 40 | assertEquals("maxSize must greater than 0.", ex.getMessage()); 41 | } 42 | 43 | @Test 44 | void chunk_NullCollectionFactory_ThrowsException() { 45 | IllegalArgumentException ex = assertThrows(IllegalArgumentException.class, 46 | () -> CollectionUtil.chunk(List.of(1, 2), 2, null)); 47 | assertEquals("Factory method cannot be null.", ex.getMessage()); 48 | } 49 | 50 | @Test 51 | void chunk_EmptyCollection_ReturnsOneEmptySubCollection() { 52 | List> chunks = CollectionUtil.chunk(Collections.emptyList(), 3, ArrayList::new); 53 | assertEquals(1, chunks.size()); 54 | assertTrue(chunks.get(0).isEmpty()); 55 | } 56 | 57 | @Test 58 | void chunk_CollectionSizeLessThanMaxSize_ReturnsOneSubCollectionWithAllElements() { 59 | List list = List.of(1, 2); 60 | List> chunks = CollectionUtil.chunk(list, 5, ArrayList::new); 61 | assertEquals(1, chunks.size()); 62 | assertEquals(list, chunks.get(0)); 63 | } 64 | 65 | @Test 66 | void chunk_CollectionSizeEqualMaxSize_ReturnsOneSubCollectionWithAllElements() { 67 | List list = List.of(1, 2, 3); 68 | List> chunks = CollectionUtil.chunk(list, 3, ArrayList::new); 69 | assertEquals(1, chunks.size()); 70 | assertEquals(list, chunks.get(0)); 71 | } 72 | 73 | @Test 74 | void chunk_CollectionSizeGreaterThanMaxSize_ReturnsMultipleSubCollections() { 75 | List list = List.of(1, 2, 3, 4, 5, 6, 7); 76 | int maxSize = 3; 77 | List> chunks = CollectionUtil.chunk(list, maxSize, ArrayList::new); 78 | 79 | // Expect 3 subcollections: [1,2,3], [4,5,6], [7] 80 | assertEquals(3, chunks.size()); 81 | assertEquals(List.of(1, 2, 3), chunks.get(0)); 82 | assertEquals(List.of(4, 5, 6), chunks.get(1)); 83 | assertEquals(List.of(7), chunks.get(2)); 84 | } 85 | 86 | @Test 87 | void chunk_UsesDifferentCollectionTypeAsSubCollections() { 88 | LinkedList list = new LinkedList<>(List.of(1, 2, 3, 4)); 89 | Supplier> factory = LinkedList::new; 90 | List> chunks = CollectionUtil.chunk(list, 2, factory); 91 | assertEquals(2, chunks.size()); 92 | assertInstanceOf(LinkedList.class, chunks.get(0)); 93 | assertInstanceOf(LinkedList.class, chunks.get(1)); 94 | assertEquals(List.of(1, 2), chunks.get(0)); 95 | assertEquals(List.of(3, 4), chunks.get(1)); 96 | } 97 | 98 | @Test 99 | void chunk_CollectionWithOneElementAndMaxSizeOne_ReturnsOneSubCollection() { 100 | List list = List.of("a"); 101 | List> chunks = CollectionUtil.chunk(list, 1, ArrayList::new); 102 | assertEquals(1, chunks.size()); 103 | assertEquals(list, chunks.get(0)); 104 | } 105 | 106 | @Test 107 | void chunk_MaxSizeZero_ThrowsException() { 108 | IllegalArgumentException ex = assertThrows(IllegalArgumentException.class, 109 | () -> CollectionUtil.chunk(List.of(1), 0, ArrayList::new)); 110 | assertEquals("maxSize must greater than 0.", ex.getMessage()); 111 | } 112 | } 113 | -------------------------------------------------------------------------------- /common-toolbox/src/main/java/com/onixbyte/common/util/MapUtil.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2024-2025 OnixByte 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy 5 | * of this software and associated documentation files (the "Software"), to deal 6 | * in the Software without restriction, including without limitation the rights 7 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | * copies of the Software, and to permit persons to whom the Software is 9 | * furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in all 12 | * copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 20 | * SOFTWARE. 21 | */ 22 | 23 | package com.onixbyte.common.util; 24 | 25 | import java.util.Map; 26 | 27 | /** 28 | * The {@link MapUtil} class provides utility methods for converting between objects and maps. 29 | * This class leverages the {@link ObjectMapAdapter} interface to perform the conversions. 30 | *

31 | * The utility methods in this class are useful for scenarios where objects need to be represented 32 | * as maps for serialization, deserialization, or other purposes. 33 | *

34 | * 35 | *

Example usage:

36 | *
{@code
 37 |  * // User.java
 38 |  * public class User {
 39 |  *     private String name;
 40 |  *     private int age;
 41 |  *     
 42 |  *     // getters and setters
 43 |  * }
 44 |  *
 45 |  * // UserMapAdapter.java
 46 |  * public class UserMapAdapter implements ObjectMapAdapter {
 47 |  *     @Override
 48 |  *     public Map toMap(User user) {
 49 |  *         Map map = new HashMap<>();
 50 |  *         map.put("name", user.getName());
 51 |  *         map.put("age", user.getAge());
 52 |  *         return map;
 53 |  *     }
 54 |  * 
 55 |  *     @Override
 56 |  *     public User fromMap(Map map) {
 57 |  *         User user = new User();
 58 |  *         user.setName((String) map.get("name"));
 59 |  *         user.setAge((Integer) map.get("age"));
 60 |  *         return user;
 61 |  *     }
 62 |  * }
 63 |  * 
 64 |  * public class Example {
 65 |  *     public static void main(String[] args) {
 66 |  *         User user = new User();
 67 |  *         user.setName("John");
 68 |  *         user.setAge(30);
 69 |  *         
 70 |  *         UserMapAdapter adapter = new UserMapAdapter();
 71 |  *         
 72 |  *         // Convert object to map
 73 |  *         Map userMap = MapUtil.objectToMap(user, adapter);
 74 |  *         System.out.println(userMap); // Output: {name=John, age=30}
 75 |  *         
 76 |  *         // Convert map to object
 77 |  *         User newUser = MapUtil.mapToObject(userMap, adapter);
 78 |  *         System.out.println(newUser.getName()); // Output: John
 79 |  *         System.out.println(newUser.getAge());  // Output: 30
 80 |  *     }
 81 |  * }
 82 |  * }
83 | * 84 | * @author zihluwang 85 | * @version 3.0.0 86 | */ 87 | public final class MapUtil { 88 | 89 | /** 90 | * Private constructor to prevent instantiation of this utility class. 91 | */ 92 | private MapUtil() { 93 | } 94 | 95 | /** 96 | * Converts an object to a map by mapping the field names to their corresponding values. 97 | * 98 | * @param the type of the object 99 | * @param entity the object to be converted to a map 100 | * @param adapter adapts the entity for mapping to a map 101 | * @return a map representing the fields and their values of the object 102 | */ 103 | public static Map objectToMap(T entity, ObjectMapAdapter adapter) { 104 | return adapter.toMap(entity); 105 | } 106 | 107 | /** 108 | * Converts a map to an object of the specified type by setting the field values using the 109 | * map entries. 110 | * 111 | * @param objectMap the map representing the fields and their values 112 | * @param adapter the adapter to execute the setter for the entity 113 | * @param the type of the object to be created 114 | * @return an object of the specified type with the field values set from the map 115 | */ 116 | public static T mapToObject(Map objectMap, ObjectMapAdapter adapter) { 117 | return adapter.toObject(objectMap); 118 | } 119 | } 120 | -------------------------------------------------------------------------------- /common-toolbox/src/test/java/com/onixbyte/common/util/RangeUtilTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2024-2025 OnixByte. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | 18 | package com.onixbyte.common.util; 19 | 20 | import org.junit.jupiter.api.Test; 21 | 22 | import static org.junit.jupiter.api.Assertions.*; 23 | 24 | class RangeUtilTest { 25 | 26 | /** 27 | * Tests generating ascending range from 0 up to end (exclusive). 28 | */ 29 | @Test 30 | void testRangeEndValid() { 31 | int[] expected = {0, 1, 2, 3, 4}; 32 | assertArrayEquals(expected, RangeUtil.range(5).toArray()); 33 | } 34 | 35 | /** 36 | * Tests that range(end) throws IllegalArgumentException for end less than or equal to zero. 37 | */ 38 | @Test 39 | void testRangeEndInvalidThrows() { 40 | IllegalArgumentException ex1 = assertThrows(IllegalArgumentException.class, 41 | () -> RangeUtil.range(0)); 42 | assertTrue(ex1.getMessage().contains("should not be less than or equal to 0")); 43 | 44 | IllegalArgumentException ex2 = assertThrows(IllegalArgumentException.class, 45 | () -> RangeUtil.range(-3)); 46 | assertTrue(ex2.getMessage().contains("should not be less than or equal to 0")); 47 | } 48 | 49 | /** 50 | * Tests ascending range where start is less than end. 51 | */ 52 | @Test 53 | void testRangeStartEndAscending() { 54 | int[] expected = {3, 4, 5, 6, 7}; 55 | assertArrayEquals(expected, RangeUtil.range(3, 8).toArray()); 56 | } 57 | 58 | /** 59 | * Tests descending range where start is greater than end. 60 | */ 61 | @Test 62 | void testRangeStartEndDescending() { 63 | int[] expected = {8, 7, 6, 5, 4}; 64 | assertArrayEquals(expected, RangeUtil.range(8, 3).toArray()); 65 | } 66 | 67 | /** 68 | * Tests empty stream when start equals end. 69 | */ 70 | @Test 71 | void testRangeStartEqualsEndReturnsEmpty() { 72 | assertEquals(0, RangeUtil.range(5, 5).count()); 73 | } 74 | 75 | /** 76 | * Tests that rangeClosed generates inclusive range in ascending order. 77 | */ 78 | @Test 79 | void testRangeClosedAscending() { 80 | int[] expected = {3, 4, 5, 6, 7, 8}; 81 | assertArrayEquals(expected, RangeUtil.rangeClosed(3, 8).toArray()); 82 | } 83 | 84 | /** 85 | * Tests range method with positive step generating ascending sequence. 86 | */ 87 | @Test 88 | void testRangeWithPositiveStep() { 89 | int[] expected = {2, 4, 6, 8}; 90 | assertArrayEquals(expected, RangeUtil.range(2, 10, 2).toArray()); 91 | } 92 | 93 | /** 94 | * Tests range method with negative step generating descending sequence. 95 | */ 96 | @Test 97 | void testRangeWithNegativeStep() { 98 | int[] expected = {10, 7, 4, 1}; 99 | assertArrayEquals(expected, RangeUtil.range(10, 0, -3).toArray()); 100 | } 101 | 102 | /** 103 | * Tests that passing zero step throws IllegalArgumentException. 104 | */ 105 | @Test 106 | void testRangeStepZeroThrows() { 107 | IllegalArgumentException ex = assertThrows(IllegalArgumentException.class, 108 | () -> RangeUtil.range(0, 10, 0)); 109 | assertEquals("Step value must not be zero.", ex.getMessage()); 110 | } 111 | 112 | /** 113 | * Tests that range with positive step but invalid start/end throws IllegalArgumentException. 114 | */ 115 | @Test 116 | void testRangePositiveStepInvalidRangeThrows() { 117 | IllegalArgumentException ex = assertThrows(IllegalArgumentException.class, 118 | () -> RangeUtil.range(10, 5, 1)); 119 | assertEquals("Range parameters are inconsistent with the step value.", ex.getMessage()); 120 | } 121 | 122 | /** 123 | * Tests that range with negative step but invalid start/end throws IllegalArgumentException. 124 | */ 125 | @Test 126 | void testRangeNegativeStepInvalidRangeThrows() { 127 | IllegalArgumentException ex = assertThrows(IllegalArgumentException.class, 128 | () -> RangeUtil.range(5, 10, -1)); 129 | assertEquals("Range parameters are inconsistent with the step value.", ex.getMessage()); 130 | } 131 | } 132 | -------------------------------------------------------------------------------- /crypto-toolbox/src/main/java/com/onixbyte/crypto/algorithm/rsa/RSAPrivateKeyLoader.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2024-2025 OnixByte 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy 5 | * of this software and associated documentation files (the "Software"), to deal 6 | * in the Software without restriction, including without limitation the rights 7 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | * copies of the Software, and to permit persons to whom the Software is 9 | * furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in all 12 | * copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 20 | * SOFTWARE. 21 | */ 22 | 23 | package com.onixbyte.crypto.algorithm.rsa; 24 | 25 | import com.onixbyte.crypto.PrivateKeyLoader; 26 | import com.onixbyte.crypto.exception.KeyLoadingException; 27 | import com.onixbyte.crypto.util.CryptoUtil; 28 | 29 | import java.math.BigInteger; 30 | import java.security.KeyFactory; 31 | import java.security.NoSuchAlgorithmException; 32 | import java.security.interfaces.RSAPrivateKey; 33 | import java.security.interfaces.RSAPublicKey; 34 | import java.security.spec.*; 35 | import java.util.Base64; 36 | 37 | /** 38 | * A class responsible for loading private RSA keys from PEM formatted text. 39 | *

40 | * This class implements the {@link PrivateKeyLoader} interface and provides methods to load private 41 | * RSA keys. The keys are expected to be in the standard PEM format, which includes Base64-encoded 42 | * key content surrounded by header and footer lines. The class handles the decoding of Base64 43 | * content and the generation of keys using the RSA key factory. 44 | *

45 | * Any exceptions encountered during the loading process are encapsulated in a 46 | * {@link KeyLoadingException}, allowing for flexible error handling. 47 | * 48 | * @author zihluwang 49 | * @author siujamo 50 | * @version 3.0.0 51 | * @see PrivateKeyLoader 52 | * @see KeyLoadingException 53 | */ 54 | public class RSAPrivateKeyLoader implements PrivateKeyLoader { 55 | 56 | private final Base64.Decoder decoder; 57 | 58 | private final KeyFactory keyFactory; 59 | 60 | /** 61 | * Constructs an instance of {@code RsaKeyLoader}. 62 | *

63 | * This constructor initialises the Base64 decoder and the RSA {@link KeyFactory}. It may throw 64 | * a {@link KeyLoadingException} if the RSA algorithm is not available. 65 | */ 66 | public RSAPrivateKeyLoader() { 67 | try { 68 | this.decoder = Base64.getDecoder(); 69 | this.keyFactory = KeyFactory.getInstance("RSA"); 70 | } catch (NoSuchAlgorithmException e) { 71 | throw new KeyLoadingException(e); 72 | } 73 | } 74 | 75 | /** 76 | * Loads an RSA private key from a given PEM formatted key text. 77 | *

78 | * This method extracts the raw key content from the provided PEM text, decodes the 79 | * Base64-encoded content, and generates an instance of {@link RSAPrivateKey}. If the key cannot 80 | * be loaded due to invalid specifications or types, a {@link KeyLoadingException} is thrown. 81 | * 82 | * @param pemKeyText the PEM formatted private key text 83 | * @return an instance of {@link RSAPrivateKey} 84 | * @throws KeyLoadingException if the key loading process encounters an error 85 | */ 86 | @Override 87 | public RSAPrivateKey loadPrivateKey(String pemKeyText) { 88 | // Extract the raw key content 89 | var rawKeyContent = CryptoUtil.getRawContent(pemKeyText); 90 | 91 | // Decode the Base64-encoded content 92 | var keyBytes = decoder.decode(rawKeyContent); 93 | 94 | // Create a PKCS8EncodedKeySpec from the decoded bytes 95 | var keySpec = new PKCS8EncodedKeySpec(keyBytes); 96 | 97 | try { 98 | // Get an RSA KeyFactory and generate the private key 99 | var _key = keyFactory.generatePrivate(keySpec); 100 | if (_key instanceof RSAPrivateKey key) { 101 | return key; 102 | } else { 103 | throw new KeyLoadingException("Unable to load private key from pem-formatted key text."); 104 | } 105 | } catch (InvalidKeySpecException e) { 106 | throw new KeyLoadingException("Key spec is invalid.", e); 107 | } 108 | } 109 | } 110 | -------------------------------------------------------------------------------- /tuple/src/main/java/com/onixbyte/tuple/BiTuple.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2024-2025 OnixByte. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | 18 | package com.onixbyte.tuple; 19 | 20 | import java.util.Objects; 21 | 22 | /** 23 | * Represents an ordered pair of elements, where the first element is of type {@code L} and the 24 | * second is of type {@code R}. This class provides a simple way to group two related 25 | * values together. 26 | *

27 | * The class is mutable, allowing the values of the left and right elements to be changed 28 | * after creation. It also overrides the {@code equals}, {@code hashCode}, and {@code toString} 29 | * methods to provide meaningful comparisons and string representations. 30 | * 31 | * @param the type of the left element 32 | * @param the type of the right element 33 | * @author siujamo 34 | * @author zihluwang 35 | */ 36 | public final class BiTuple { 37 | 38 | /** 39 | * The left element of the tuple. 40 | */ 41 | private L left; 42 | 43 | /** 44 | * The right element of the tuple. 45 | */ 46 | private R right; 47 | 48 | /** 49 | * Constructs a new {@code BiTuple} with the specified left and right elements. 50 | * 51 | * @param left the left element to be stored in the tuple 52 | * @param right the right element to be stored in the tuple 53 | */ 54 | public BiTuple(L left, R right) { 55 | this.left = left; 56 | this.right = right; 57 | } 58 | 59 | /** 60 | * Retrieves the left element of the tuple. 61 | * 62 | * @return the left element of the tuple 63 | */ 64 | public L getLeft() { 65 | return left; 66 | } 67 | 68 | /** 69 | * Sets the left element of the tuple to the specified value. 70 | * 71 | * @param left the new value for the left element of the tuple 72 | */ 73 | public void setLeft(L left) { 74 | this.left = left; 75 | } 76 | 77 | /** 78 | * Retrieves the right element of the tuple. 79 | * 80 | * @return the right element of the tuple 81 | */ 82 | public R getRight() { 83 | return right; 84 | } 85 | 86 | /** 87 | * Sets the right element of the tuple to the specified value. 88 | * 89 | * @param right the new value for the right element of the tuple 90 | */ 91 | public void setRight(R right) { 92 | this.right = right; 93 | } 94 | 95 | /** 96 | * Compares this {@code BiTuple} with the specified object for equality. 97 | *

98 | * Two {@code BiTuple}s are considered equal if they are of the same type and their left and 99 | * right elements are equal according to their respective {@code equals} methods. 100 | * 101 | * @param object the object to compare with this {@code BiTuple} 102 | * @return {@code true} if the specified object is equal to this {@code BiTuple}, 103 | * {@code false} otherwise 104 | */ 105 | @Override 106 | public boolean equals(Object object) { 107 | if (!(object instanceof BiTuple biTuple)) return false; 108 | return Objects.equals(left, biTuple.left) && Objects.equals(right, biTuple.right); 109 | } 110 | 111 | /** 112 | * Returns a hash code value for the {@code BiTuple}. 113 | *

114 | * The hash code is calculated based on the hash codes of the left and right elements. 115 | * 116 | * @return a hash code value for this {@code BiTuple} 117 | */ 118 | @Override 119 | public int hashCode() { 120 | return Objects.hash(left, right); 121 | } 122 | 123 | /** 124 | * Returns a string representation of the {@code BiTuple}. 125 | *

126 | * The string representation consists of the class name, followed by the values of 127 | * the left and right elements in the format {@code "BiTuple{left=value1, right=value2}"}. 128 | * 129 | * @return a string representation of the {@code BiTuple} 130 | */ 131 | @Override 132 | public String toString() { 133 | return "BiTuple{" + 134 | "left=" + left + 135 | ", right=" + right + 136 | '}'; 137 | } 138 | 139 | /** 140 | * Creates a new {@code BiTuple} with the specified left and right elements. 141 | * 142 | * @param the type of the left element 143 | * @param the type of the right element 144 | * @param left the left element 145 | * @param right the right element 146 | * @return a new {@code BiTuple} containing the specified elements 147 | */ 148 | public static BiTuple of(L left, R right) { 149 | return new BiTuple<>(left, right); 150 | } 151 | } 152 | -------------------------------------------------------------------------------- /math-toolbox/src/main/java/com/onixbyte/math/model/QuartileBounds.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2024-2025 OnixByte 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy 5 | * of this software and associated documentation files (the "Software"), to deal 6 | * in the Software without restriction, including without limitation the rights 7 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | * copies of the Software, and to permit persons to whom the Software is 9 | * furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in all 12 | * copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 20 | * SOFTWARE. 21 | */ 22 | 23 | package com.onixbyte.math.model; 24 | 25 | /** 26 | * A record representing the quartile bounds of a dataset. 27 | *

28 | * This class encapsulates the lower and upper bounds of a dataset, which are typically used for 29 | * detecting outliers in the data. The bounds are calculated based on the interquartile range (IQR) 30 | * of the dataset. Values below the lower bound or above the upper bound may be considered outliers. 31 | *

32 | * Quartile bounds consist of: 33 | *

    34 | *
  • 35 | * {@code lowerBound} - The lower bound of the dataset, typically {@code Q1 - 1.5 * IQR}. 36 | *
  • 37 | *
  • 38 | * {@code upperBound} - The upper bound of the dataset, typically {@code Q3 + 1.5 * IQR}. 39 | *
  • 40 | *
41 | *

42 | * Example usage: 43 | *

{@code
 44 |  * QuartileBounds bounds = QuartileBounds.builder()
 45 |  *      .lowerBound(1.5)
 46 |  *      .upperBound(7.5)
 47 |  *      .build();
 48 |  * }
49 | * 50 | * @param upperBound the upper bound of the dataset 51 | * @param lowerBound the lower bound of the dataset 52 | * @author zihluwang 53 | * @version 3.0.0 54 | * @since 1.6.5 55 | */ 56 | public record QuartileBounds( 57 | Double upperBound, 58 | Double lowerBound 59 | ) { 60 | 61 | /** 62 | * Creates a new {@link Builder} instance for building a {@code QuartileBounds} object. 63 | *

64 | * The {@link Builder} pattern is used to construct the {@code QuartileBounds} object with 65 | * optional values for the upper and lower bounds. 66 | *

67 | * 68 | * @return a new instance of the {@link Builder} class 69 | */ 70 | public static Builder builder() { 71 | return new Builder(); 72 | } 73 | 74 | /** 75 | * A builder class for constructing instances of the {@code QuartileBounds} record. 76 | *

77 | * The {@link Builder} pattern allows for the step-by-step construction of a 78 | * {@code QuartileBounds} object, providing a flexible way to set values for the lower and 79 | * upper bounds. Once the builder has the required values, the {@link #build()} method creates 80 | * and returns a new {@code QuartileBounds} object. 81 | *

82 | *

83 | * Example usage: 84 | *

 85 |      * {@code
 86 |      * QuartileBounds bounds = QuartileBounds.builder()
 87 |      *     .lowerBound(1.5)
 88 |      *     .upperBound(7.5)
 89 |      *     .build();
 90 |      * }
 91 |      * 
92 | */ 93 | public static class Builder { 94 | private Double upperBound; 95 | private Double lowerBound; 96 | 97 | /** 98 | * Private constructor to prevent instantiation of this utility class. 99 | */ 100 | private Builder() { 101 | } 102 | 103 | /** 104 | * Sets the upper bound for the {@code QuartileBounds}. 105 | * 106 | * @param upperBound the upper bound of the dataset 107 | * @return the current {@code Builder} instance, for method chaining 108 | */ 109 | public Builder upperBound(Double upperBound) { 110 | this.upperBound = upperBound; 111 | return this; 112 | } 113 | 114 | /** 115 | * Sets the lower bound for the {@code QuartileBounds}. 116 | * 117 | * @param lowerBound the lower bound of the dataset 118 | * @return the current {@code Builder} instance, for method chaining 119 | */ 120 | public Builder lowerBound(Double lowerBound) { 121 | this.lowerBound = lowerBound; 122 | return this; 123 | } 124 | 125 | /** 126 | * Builds and returns a new {@code QuartileBounds} instance with the specified upper and 127 | * lower bounds. 128 | * 129 | * @return a new {@code QuartileBounds} object containing the specified bounds 130 | */ 131 | public QuartileBounds build() { 132 | return new QuartileBounds(upperBound, lowerBound); 133 | } 134 | } 135 | 136 | } 137 | -------------------------------------------------------------------------------- /math-toolbox/src/main/java/com/onixbyte/math/PercentileCalculator.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2024-2025 OnixByte 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy 5 | * of this software and associated documentation files (the "Software"), to deal 6 | * in the Software without restriction, including without limitation the rights 7 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | * copies of the Software, and to permit persons to whom the Software is 9 | * furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in all 12 | * copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 20 | * SOFTWARE. 21 | */ 22 | 23 | package com.onixbyte.math; 24 | 25 | import com.onixbyte.math.model.QuartileBounds; 26 | 27 | import java.util.List; 28 | 29 | /** 30 | * A utility class that provides methods for calculating percentiles and interquartile range (IQR) 31 | * bounds for a dataset. 32 | *

33 | * This class contains static methods to: 34 | *

    35 | *
  • 36 | * Calculate a specified percentile from a list of double values using linear interpolation. 37 | *
  • 38 | *
  • 39 | * Calculate interquartile bounds (Q1, Q3) and the corresponding lower and upper bounds, 40 | * which can be used to identify outliers in the dataset. 41 | *
  • 42 | *
43 | *

44 | * This class is final, meaning it cannot be subclassed, and it only contains static methods, 45 | * so instances of the class cannot be created. 46 | *

Example usage:

47 | *
{@code
 48 |  * List data = Arrays.asList(1.0, 2.0, 3.0, 4.0, 5.0);
 49 |  * Double percentileValue = PercentileCalculator.calculatePercentile(data, 50.0);  // Calculates median
 50 |  * QuartileBounds bounds = PercentileCalculator.calculatePercentileBounds(data);   // Calculates IQR bounds
 51 |  * }
52 | * 53 | * @author zihluwang 54 | * @version 3.0.0 55 | * @since 1.6.5 56 | */ 57 | public final class PercentileCalculator { 58 | 59 | /** 60 | * Private constructor to prevent instantiation of this utility class. 61 | */ 62 | private PercentileCalculator() { 63 | } 64 | 65 | /** 66 | * Calculates the specified percentile from a list of values. 67 | *

68 | * This method takes a list of double values and calculates the given percentile using linear 69 | * interpolation between the two closest ranks. The list is first sorted in ascending order, 70 | * and the specified percentile is then calculated. 71 | * 72 | * @param values a list of {@code Double} values from which the percentile is calculated. 73 | * @param percentile a {@code Double} representing the percentile to be calculated (e.g., 50.0 74 | * for the median) 75 | * @return a {@code Double} value representing the calculated percentile 76 | */ 77 | public static Double calculatePercentile(List values, Double percentile) { 78 | if (values.isEmpty()) { 79 | throw new IllegalArgumentException("Unable to sort an empty list."); 80 | } 81 | var sorted = values.stream().sorted().toList(); 82 | 83 | var rank = percentile / 100. * (sorted.size() - 1); 84 | var lowerIndex = (int) Math.floor(rank); 85 | var upperIndex = (int) Math.ceil(rank); 86 | var weight = rank - lowerIndex; 87 | 88 | return sorted.get(lowerIndex) * (1 - weight) + sorted.get(upperIndex) * weight; 89 | } 90 | 91 | /** 92 | * Calculates the interquartile range (IQR) and the corresponding lower and upper bounds 93 | * based on the first (Q1) and third (Q3) quartiles of a dataset. 94 | *

95 | * This method takes a list of double values, calculates the first quartile (Q1), 96 | * the third quartile (Q3), and the interquartile range (IQR). Using the IQR, it computes 97 | * the lower and upper bounds, which can be used to detect outliers in the dataset. 98 | * The lower bound is defined as {@code Q1 - 1.5 * IQR}, and the upper bound is defined as 99 | * {@code Q3 + 1.5 * IQR}. 100 | * 101 | * @param data a list of {@code Double} values for which the quartile bounds will be calculated 102 | * @return a {@code QuartileBounds} object containing the calculated lower and upper bounds 103 | */ 104 | public static QuartileBounds calculatePercentileBounds(List data) { 105 | var sorted = data.stream().sorted().toList(); 106 | var q1 = calculatePercentile(sorted, 25.); 107 | var q3 = calculatePercentile(sorted, 75.); 108 | 109 | var iqr = q3 - q1; 110 | 111 | var lowerBound = q1 - 1.5 * iqr; 112 | var upperBound = q3 + 1.5 * iqr; 113 | 114 | return QuartileBounds.builder() 115 | .upperBound(upperBound) 116 | .lowerBound(lowerBound) 117 | .build(); 118 | } 119 | 120 | } 121 | -------------------------------------------------------------------------------- /tuple/src/main/java/com/onixbyte/tuple/TriTuple.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2024-2025 OnixByte. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | 18 | package com.onixbyte.tuple; 19 | 20 | import java.util.Objects; 21 | 22 | /** 23 | * Represents a mutable triple of three elements, referred to as 'left', 'middle' and 'right'. 24 | * This class provides a way to group three values of different types. 25 | *

26 | * The generic types {@code L}, {@code M} and {@code R} denote the types of the left, middle and 27 | * right elements respectively. 28 | * 29 | * @param the type of the left element 30 | * @param the type of the middle element 31 | * @param the type of the right element 32 | * @author siujamo 33 | * @author zihluwang 34 | */ 35 | public final class TriTuple { 36 | 37 | /** 38 | * The left element of the triple. 39 | */ 40 | private L left; 41 | 42 | /** 43 | * The middle element of the triple. 44 | */ 45 | private M middle; 46 | 47 | /** 48 | * The right element of the triple. 49 | */ 50 | private R right; 51 | 52 | /** 53 | * Constructs a new {@code TriTuple} with the given left, middle and right elements. 54 | * 55 | * @param left the left element 56 | * @param middle the middle element 57 | * @param right the right element 58 | */ 59 | public TriTuple(L left, M middle, R right) { 60 | this.left = left; 61 | this.middle = middle; 62 | this.right = right; 63 | } 64 | 65 | /** 66 | * Retrieves the left element of the triple. 67 | * 68 | * @return the left element 69 | */ 70 | public L getLeft() { 71 | return left; 72 | } 73 | 74 | /** 75 | * Sets the left element of the triple. 76 | * 77 | * @param left the new left element 78 | */ 79 | public void setLeft(L left) { 80 | this.left = left; 81 | } 82 | 83 | /** 84 | * Retrieves the middle element of the triple. 85 | * 86 | * @return the middle element 87 | */ 88 | public M getMiddle() { 89 | return middle; 90 | } 91 | 92 | /** 93 | * Sets the middle element of the triple. 94 | * 95 | * @param middle the new middle element 96 | */ 97 | public void setMiddle(M middle) { 98 | this.middle = middle; 99 | } 100 | 101 | /** 102 | * Retrieves the right element of the triple. 103 | * 104 | * @return the right element 105 | */ 106 | public R getRight() { 107 | return right; 108 | } 109 | 110 | /** 111 | * Sets the right element of the triple. 112 | * 113 | * @param right the new right element 114 | */ 115 | public void setRight(R right) { 116 | this.right = right; 117 | } 118 | 119 | /** 120 | * Checks if this {@code TriTuple} is equal to the specified object. 121 | * Two {@code TriTuple}s are considered equal if their left, middle and right elements are equal. 122 | * 123 | * @param object the object to compare with 124 | * @return {@code true} if the objects are equal, {@code false} otherwise 125 | */ 126 | @Override 127 | public boolean equals(Object object) { 128 | if (!(object instanceof TriTuple triTuple)) return false; 129 | return Objects.equals(left, triTuple.left) && 130 | Objects.equals(middle, triTuple.middle) && 131 | Objects.equals(right, triTuple.right); 132 | } 133 | 134 | /** 135 | * Calculates the hash code for this {@code TriTuple} based on its left, middle and right elements. 136 | * 137 | * @return the hash code value for this object 138 | */ 139 | @Override 140 | public int hashCode() { 141 | return Objects.hash(left, middle, right); 142 | } 143 | 144 | /** 145 | * Returns a string representation of this {@code TriTuple}, including its left, middle and right elements. 146 | * 147 | * @return a string representation of the object 148 | */ 149 | @Override 150 | public String toString() { 151 | return "TriTuple{" + 152 | "left=" + left + 153 | ", middle=" + middle + 154 | ", right=" + right + 155 | '}'; 156 | } 157 | 158 | /** 159 | * Factory method to create a new {@code TriTuple} instance with the given left, middle and right elements. 160 | * 161 | * @param left the left element 162 | * @param middle the middle element 163 | * @param right the right element 164 | * @param the type of the left element 165 | * @param the type of the middle element 166 | * @param the type of the right element 167 | * @return a new {@code TriTuple} instance 168 | */ 169 | public static TriTuple of(L left, M middle, R right) { 170 | return new TriTuple<>(left, middle, right); 171 | } 172 | } 173 | -------------------------------------------------------------------------------- /common-toolbox/src/test/java/com/onixbyte/common/util/BranchUtilTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2024-2025 OnixByte. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | 18 | package com.onixbyte.common.util; 19 | 20 | import org.junit.jupiter.api.Test; 21 | 22 | import java.util.concurrent.atomic.AtomicBoolean; 23 | import java.util.function.BooleanSupplier; 24 | 25 | import static org.junit.jupiter.api.Assertions.*; 26 | 27 | class BranchUtilTest { 28 | 29 | // Test the static methods or(Boolean... values) and and(Boolean... values) 30 | @Test 31 | void testOrWithBooleanValues() { 32 | BranchUtil trueResult = BranchUtil.or(true, false, false); 33 | assertNotNull(trueResult); 34 | 35 | BranchUtil falseResult = BranchUtil.or(false, false, false); 36 | assertNotNull(falseResult); 37 | } 38 | 39 | @Test 40 | void testAndWithBooleanValues() { 41 | BranchUtil trueResult = BranchUtil.and(true, true, true); 42 | assertNotNull(trueResult); 43 | 44 | BranchUtil falseResult = BranchUtil.and(true, false, true); 45 | assertNotNull(falseResult); 46 | } 47 | 48 | // Test the static methods or(BooleanSupplier... valueSuppliers) and and(BooleanSupplier... valueSuppliers) 49 | @Test 50 | void testOrWithBooleanSuppliers() { 51 | BooleanSupplier trueSupplier = () -> true; 52 | BooleanSupplier falseSupplier = () -> false; 53 | 54 | BranchUtil trueResult = BranchUtil.or(falseSupplier, trueSupplier); 55 | 56 | BranchUtil falseResult = BranchUtil.or(falseSupplier, falseSupplier); 57 | } 58 | 59 | @Test 60 | void testAndWithBooleanSuppliers() { 61 | BooleanSupplier trueSupplier = () -> true; 62 | BooleanSupplier falseSupplier = () -> false; 63 | 64 | BranchUtil trueResult = BranchUtil.and(trueSupplier, trueSupplier); 65 | 66 | BranchUtil falseResult = BranchUtil.and(trueSupplier, falseSupplier); 67 | } 68 | 69 | // Test thenSupply(T, T) 70 | @Test 71 | void testThenSupplyBothSuppliers_ResultTrue() { 72 | BranchUtil b = BranchUtil.and(true); 73 | String trueVal = "yes"; 74 | String falseVal = "no"; 75 | 76 | String result = b.thenSupply(() -> trueVal, () -> falseVal); 77 | assertEquals(trueVal, result); 78 | } 79 | 80 | @Test 81 | void testThenSupplyBothSuppliers_ResultFalse_WithFalseSupplier() { 82 | BranchUtil b = BranchUtil.and(false); 83 | String trueVal = "yes"; 84 | String falseVal = "no"; 85 | 86 | String result = b.thenSupply(() -> trueVal, () -> falseVal); 87 | assertEquals(falseVal, result); 88 | } 89 | 90 | @Test 91 | void testThenSupplyBothSuppliers_ResultFalse_NoFalseSupplier() { 92 | BranchUtil b = BranchUtil.and(false); 93 | String trueVal = "yes"; 94 | 95 | String result = b.thenSupply(() -> trueVal, null); 96 | assertNull(result); 97 | } 98 | 99 | @Test 100 | void testThenSupplySingleTrueSupplier_ResultTrue() { 101 | BranchUtil b = BranchUtil.and(true); 102 | String trueVal = "success"; 103 | 104 | String result = b.thenSupply(() -> trueVal); 105 | assertEquals(trueVal, result); 106 | } 107 | 108 | @Test 109 | void testThenSupplySingleTrueSupplier_ResultFalse() { 110 | BranchUtil b = BranchUtil.and(false); 111 | String trueVal = "success"; 112 | 113 | String result = b.thenSupply(() -> trueVal); 114 | assertNull(result); 115 | } 116 | 117 | // Test then(Runnable, Runnable) 118 | @Test 119 | void testThenWithBothHandlers_ResultTrue() { 120 | BranchUtil b = BranchUtil.and(true); 121 | AtomicBoolean trueRun = new AtomicBoolean(false); 122 | AtomicBoolean falseRun = new AtomicBoolean(false); 123 | 124 | b.then(() -> trueRun.set(true), () -> falseRun.set(true)); 125 | 126 | assertTrue(trueRun.get()); 127 | assertFalse(falseRun.get()); 128 | } 129 | 130 | @Test 131 | void testThenWithBothHandlers_ResultFalse() { 132 | BranchUtil b = BranchUtil.and(false); 133 | AtomicBoolean trueRun = new AtomicBoolean(false); 134 | AtomicBoolean falseRun = new AtomicBoolean(false); 135 | 136 | b.then(() -> trueRun.set(true), () -> falseRun.set(true)); 137 | 138 | assertFalse(trueRun.get()); 139 | assertTrue(falseRun.get()); 140 | } 141 | 142 | @Test 143 | void testThenWithOnlyTrueHandler_ResultTrue() { 144 | BranchUtil b = BranchUtil.and(true); 145 | AtomicBoolean trueRun = new AtomicBoolean(false); 146 | 147 | b.then(() -> trueRun.set(true)); 148 | 149 | assertTrue(trueRun.get()); 150 | } 151 | 152 | @Test 153 | void testThenWithOnlyTrueHandler_ResultFalse() { 154 | BranchUtil b = BranchUtil.and(false); 155 | AtomicBoolean trueRun = new AtomicBoolean(false); 156 | 157 | b.then(() -> trueRun.set(true)); 158 | 159 | assertFalse(trueRun.get()); 160 | } 161 | } 162 | -------------------------------------------------------------------------------- /common-toolbox/src/test/java/com/onixbyte/common/util/HashUtilTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2024-2025 OnixByte. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | 18 | package com.onixbyte.common.util; 19 | 20 | import org.junit.jupiter.api.Test; 21 | 22 | import java.nio.charset.StandardCharsets; 23 | 24 | import static org.junit.jupiter.api.Assertions.assertEquals; 25 | 26 | class HashUtilTest { 27 | 28 | // Test MD2 hashing with explicit charset and default charset 29 | @Test 30 | void testMd2() { 31 | String input = "test"; 32 | // Known MD2 hash of "test" with UTF-8 33 | String expectedHash = "dd34716876364a02d0195e2fb9ae2d1b"; 34 | assertEquals(expectedHash, HashUtil.md2(input, StandardCharsets.UTF_8)); 35 | assertEquals(expectedHash, HashUtil.md2(input)); 36 | // Test null charset fallback to UTF-8 37 | assertEquals(expectedHash, HashUtil.md2(input, null)); 38 | } 39 | 40 | // Test MD5 hashing with explicit charset and default charset 41 | @Test 42 | void testMd5() { 43 | String input = "test"; 44 | // Known MD5 hash of "test" 45 | String expectedHash = "098f6bcd4621d373cade4e832627b4f6"; 46 | assertEquals(expectedHash, HashUtil.md5(input, StandardCharsets.UTF_8)); 47 | assertEquals(expectedHash, HashUtil.md5(input)); 48 | assertEquals(expectedHash, HashUtil.md5(input, null)); 49 | } 50 | 51 | // Test SHA-1 hashing with explicit charset and default charset 52 | @Test 53 | void testSha1() { 54 | String input = "test"; 55 | // Known SHA-1 hash of "test" 56 | String expectedHash = "a94a8fe5ccb19ba61c4c0873d391e987982fbbd3"; 57 | assertEquals(expectedHash, HashUtil.sha1(input, StandardCharsets.UTF_8)); 58 | assertEquals(expectedHash, HashUtil.sha1(input)); 59 | assertEquals(expectedHash, HashUtil.sha1(input, null)); 60 | } 61 | 62 | // Test SHA-224 hashing with explicit charset and default charset 63 | @Test 64 | void testSha224() { 65 | String input = "test"; 66 | // Known SHA-224 hash of "test" 67 | String expectedHash = "90a3ed9e32b2aaf4c61c410eb925426119e1a9dc53d4286ade99a809"; 68 | assertEquals(expectedHash, HashUtil.sha224(input, StandardCharsets.UTF_8)); 69 | assertEquals(expectedHash, HashUtil.sha224(input)); 70 | assertEquals(expectedHash, HashUtil.sha224(input, null)); 71 | } 72 | 73 | // Test SHA-256 hashing with explicit charset and default charset 74 | @Test 75 | void testSha256() { 76 | String input = "test"; 77 | // Known SHA-256 hash of "test" 78 | String expectedHash = "9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08"; 79 | assertEquals(expectedHash, HashUtil.sha256(input, StandardCharsets.UTF_8)); 80 | assertEquals(expectedHash, HashUtil.sha256(input)); 81 | assertEquals(expectedHash, HashUtil.sha256(input, null)); 82 | } 83 | 84 | // Test SHA-384 hashing with explicit charset and default charset 85 | @Test 86 | void testSha384() { 87 | String input = "test"; 88 | // Known SHA-384 hash of "test" 89 | String expectedHash = "768412320f7b0aa5812fce428dc4706b3cae50e02a64caa16a782249bfe8efc4b7ef1ccb126255d196047dfedf17a0a9"; 90 | assertEquals(expectedHash, HashUtil.sha384(input, StandardCharsets.UTF_8)); 91 | assertEquals(expectedHash, HashUtil.sha384(input)); 92 | assertEquals(expectedHash, HashUtil.sha384(input, null)); 93 | } 94 | 95 | // Test SHA-512 hashing with explicit charset and default charset 96 | @Test 97 | void testSha512() { 98 | String input = "test"; 99 | // Known SHA-512 hash of "test" 100 | String expectedHash = "ee26b0dd4af7e749aa1a8ee3c10ae9923f618980772e473f8819a5d4940e0db27ac185f8a0e1d5f84f88bc887fd67b143732c304cc5fa9ad8e6f57f50028a8ff"; 101 | // remove all whitespace in expected to match format generated 102 | expectedHash = expectedHash.replaceAll("\\s+", ""); 103 | assertEquals(expectedHash, HashUtil.sha512(input, StandardCharsets.UTF_8)); 104 | assertEquals(expectedHash, HashUtil.sha512(input)); 105 | assertEquals(expectedHash, HashUtil.sha512(input, null)); 106 | } 107 | 108 | // Test empty string input 109 | @Test 110 | void testEmptyString() { 111 | String input = ""; 112 | // MD5 hash of empty string 113 | String expectedMd5 = "d41d8cd98f00b204e9800998ecf8427e"; 114 | assertEquals(expectedMd5, HashUtil.md5(input)); 115 | // SHA-256 hash of empty string 116 | String expectedSha256 = "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"; 117 | assertEquals(expectedSha256, HashUtil.sha256(input)); 118 | } 119 | 120 | // Test null charset fallback for one algorithm as a sample 121 | @Test 122 | void testNullCharsetFallsBackToUtf8() { 123 | String input = "abc"; 124 | String hashWithNull = HashUtil.md5(input, null); 125 | String hashWithUtf8 = HashUtil.md5(input, StandardCharsets.UTF_8); 126 | assertEquals(hashWithUtf8, hashWithNull); 127 | } 128 | } 129 | -------------------------------------------------------------------------------- /common-toolbox/src/main/java/com/onixbyte/common/util/CollectionUtil.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2024-2025 OnixByte 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy 5 | * of this software and associated documentation files (the "Software"), to deal 6 | * in the Software without restriction, including without limitation the rights 7 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | * copies of the Software, and to permit persons to whom the Software is 9 | * furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in all 12 | * copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 20 | * SOFTWARE. 21 | */ 22 | 23 | package com.onixbyte.common.util; 24 | 25 | import java.util.ArrayList; 26 | import java.util.Collection; 27 | import java.util.List; 28 | import java.util.Objects; 29 | import java.util.function.Supplier; 30 | 31 | /** 32 | * A utility class providing static methods for manipulating collections. 33 | * 34 | * @author zihluwang 35 | * @version 3.0.0 36 | */ 37 | public final class CollectionUtil { 38 | 39 | /** 40 | * Private constructor to prevent instantiation of this utility class. 41 | */ 42 | private CollectionUtil() { 43 | } 44 | 45 | /** 46 | * Splits a collection into a list of sub-collections, each with a maximum size specified by 47 | * the caller. 48 | *

49 | * This method takes an original collection and divides it into smaller sub-collections, 50 | * ensuring that each sub-collection contains no more than the specified maximum size. If the 51 | * original collection's size is less than or equal to the maximum size, it is returned as a 52 | * single sub-collection. The sub-collections are created using the provided collection factory. 53 | * 54 | * @param the type of elements in the collection 55 | * @param the type of the collection, which must extend {@link Collection} 56 | * @param originalCollection the collection to be split into sub-collections 57 | * @param maxSize the maximum number of elements allowed in each sub-collection 58 | * @param collectionFactory a supplier that creates new instances of the sub-collection type 59 | * @return a list of sub-collections, each containing up to {@code maxSize} elements 60 | * @throws IllegalArgumentException if {@code originalCollection} is {@code null}, 61 | * {@code maxSize} is less than zero, or 62 | * {@code collectionFactory} is {@code null} 63 | */ 64 | public static > List chunk( 65 | C originalCollection, 66 | int maxSize, 67 | Supplier collectionFactory 68 | ) { 69 | // check inputs 70 | if (Objects.isNull(originalCollection)) { 71 | throw new IllegalArgumentException("Collection must not be null."); 72 | } 73 | 74 | if (maxSize <= 0) { 75 | throw new IllegalArgumentException("maxSize must greater than 0."); 76 | } 77 | 78 | if (Objects.isNull(collectionFactory)) { 79 | throw new IllegalArgumentException("Factory method cannot be null."); 80 | } 81 | 82 | var result = new ArrayList(); 83 | var size = originalCollection.size(); 84 | 85 | // if original collection is empty or the size less than maxSize, return it as a single 86 | // sub collection 87 | if (size <= maxSize) { 88 | var singleCollection = collectionFactory.get(); 89 | singleCollection.addAll(originalCollection); 90 | result.add(singleCollection); 91 | return result; 92 | } 93 | 94 | // use iterator to split the given collection 95 | var iter = originalCollection.iterator(); 96 | var count = 0; 97 | var currentSubCollection = collectionFactory.get(); 98 | 99 | while (iter.hasNext()) { 100 | var element = iter.next(); 101 | currentSubCollection.add(element); 102 | count++; 103 | 104 | // add sub collection to result when current sub collection reached maxSize or 105 | // collection traverse is completed 106 | if (count % maxSize == 0 || !iter.hasNext()) { 107 | result.add(currentSubCollection); 108 | currentSubCollection = collectionFactory.get(); 109 | } 110 | } 111 | 112 | return result; 113 | } 114 | 115 | /** 116 | * Check if a collection is not null and not empty. 117 | * 118 | * @param collection the collection to check 119 | * @return {@code true} if the collection is not null and not empty, {@code false} otherwise 120 | */ 121 | public static boolean notEmpty(Collection collection) { 122 | return Objects.nonNull(collection) && !collection.isEmpty(); 123 | } 124 | 125 | /** 126 | * Check if a collection is null or empty. 127 | * 128 | * @param collection the collection to check 129 | * @return {@code true} if the collection is null or empty, {@code false} otherwise 130 | */ 131 | public static boolean isEmpty(Collection collection) { 132 | return Objects.isNull(collection) || collection.isEmpty(); 133 | } 134 | 135 | } 136 | -------------------------------------------------------------------------------- /crypto-toolbox/src/main/java/com/onixbyte/crypto/algorithm/rsa/RSAPublicKeyLoader.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2024-2025 OnixByte 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy 5 | * of this software and associated documentation files (the "Software"), to deal 6 | * in the Software without restriction, including without limitation the rights 7 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | * copies of the Software, and to permit persons to whom the Software is 9 | * furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in all 12 | * copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 20 | * SOFTWARE. 21 | */ 22 | 23 | package com.onixbyte.crypto.algorithm.rsa; 24 | 25 | import com.onixbyte.crypto.PrivateKeyLoader; 26 | import com.onixbyte.crypto.PublicKeyLoader; 27 | import com.onixbyte.crypto.exception.KeyLoadingException; 28 | import com.onixbyte.crypto.util.CryptoUtil; 29 | 30 | import java.math.BigInteger; 31 | import java.security.KeyFactory; 32 | import java.security.NoSuchAlgorithmException; 33 | import java.security.interfaces.RSAPublicKey; 34 | import java.security.spec.InvalidKeySpecException; 35 | import java.security.spec.KeySpec; 36 | import java.security.spec.RSAPublicKeySpec; 37 | import java.security.spec.X509EncodedKeySpec; 38 | import java.util.Base64; 39 | 40 | /** 41 | * A class responsible for loading public RSA keys from PEM formatted text. 42 | *

43 | * This class implements the {@link PublicKeyLoader} interface and provides methods to load public 44 | * RSA keys. The keys are expected to be in the standard PEM format, which includes Base64-encoded 45 | * key content surrounded by header and footer lines. The class handles the decoding of Base64 46 | * content and the generation of keys using the RSA key factory. 47 | *

48 | * Any exceptions encountered during the loading process are encapsulated in a 49 | * {@link KeyLoadingException}, allowing for flexible error handling. 50 | * 51 | * @author zihluwang 52 | * @author siujamo 53 | * @version 3.0.0 54 | * @see PrivateKeyLoader 55 | * @see KeyLoadingException 56 | */ 57 | public class RSAPublicKeyLoader implements PublicKeyLoader { 58 | 59 | private final Base64.Decoder decoder; 60 | 61 | private final Base64.Decoder urlDecoder; 62 | 63 | private final KeyFactory keyFactory; 64 | 65 | /** 66 | * Constructs an instance of {@code RsaKeyLoader}. 67 | *

68 | * This constructor initialises the Base64 decoder and the RSA {@link KeyFactory}. It may throw 69 | * a {@link KeyLoadingException} if the RSA algorithm is not available. 70 | */ 71 | public RSAPublicKeyLoader() { 72 | try { 73 | this.decoder = Base64.getDecoder(); 74 | this.urlDecoder = Base64.getUrlDecoder(); 75 | this.keyFactory = KeyFactory.getInstance("RSA"); 76 | } catch (NoSuchAlgorithmException e) { 77 | throw new KeyLoadingException(e); 78 | } 79 | } 80 | 81 | /** 82 | * Loads an RSA public key from a given PEM formatted key text. 83 | *

84 | * This method extracts the raw key content from the provided PEM text, decodes the 85 | * Base64-encoded content, and generates an instance of {@link RSAPublicKey}. If the key cannot 86 | * be loaded due to invalid specifications or types, a {@link KeyLoadingException} is thrown. 87 | * 88 | * @param pemKeyText the PEM formatted public key text 89 | * @return an instance of {@link RSAPublicKey} 90 | * @throws KeyLoadingException if the key loading process encounters an error 91 | */ 92 | @Override 93 | public RSAPublicKey loadPublicKey(String pemKeyText) { 94 | // Extract the raw key content 95 | var rawKeyContent = CryptoUtil.getRawContent(pemKeyText); 96 | 97 | // Decode the Base64-encoded content 98 | var keyBytes = decoder.decode(rawKeyContent); 99 | 100 | // Create an X509EncodedKeySpec from the decoded bytes 101 | var keySpec = new X509EncodedKeySpec(keyBytes); 102 | 103 | // Get an RSA KeyFactory and generate the public key 104 | try { 105 | var _key = keyFactory.generatePublic(keySpec); 106 | if (_key instanceof RSAPublicKey key) { 107 | return key; 108 | } else { 109 | throw new KeyLoadingException("Unable to load public key from pem-formatted key text."); 110 | } 111 | } catch (InvalidKeySpecException e) { 112 | throw new KeyLoadingException("Key spec is invalid.", e); 113 | } 114 | } 115 | 116 | /** 117 | * Get the public key with given modulus and public exponent. 118 | * 119 | * @param modulus the modulus 120 | * @param exponent the public exponent 121 | * @return generated public key object from the provided key specification 122 | * @see KeyFactory#getInstance(String) 123 | * @see KeyFactory#generatePublic(KeySpec) 124 | */ 125 | @Override 126 | public RSAPublicKey loadPublicKey(String modulus, String exponent) { 127 | try { 128 | var _modulus = new BigInteger(1, urlDecoder.decode(modulus)); 129 | var _exponent = new BigInteger(1, urlDecoder.decode(exponent)); 130 | 131 | var keySpec = new RSAPublicKeySpec(_modulus, _exponent); 132 | var kf = KeyFactory.getInstance("RSA"); 133 | if (kf.generatePublic(keySpec) instanceof RSAPublicKey rsaPublicKey) { 134 | return rsaPublicKey; 135 | } else { 136 | throw new KeyLoadingException("Cannot generate RSA public key with given modulus and exponent."); 137 | } 138 | } catch (NoSuchAlgorithmException | InvalidKeySpecException e) { 139 | throw new KeyLoadingException("Cannot generate RSA public key with given modulus and exponent.", e); 140 | } 141 | } 142 | } 143 | -------------------------------------------------------------------------------- /crypto-toolbox/src/main/java/com/onixbyte/crypto/algorithm/ecdsa/ECPublicKeyLoader.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2024-2025 OnixByte 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy 5 | * of this software and associated documentation files (the "Software"), to deal 6 | * in the Software without restriction, including without limitation the rights 7 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | * copies of the Software, and to permit persons to whom the Software is 9 | * furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in all 12 | * copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 20 | * SOFTWARE. 21 | */ 22 | 23 | package com.onixbyte.crypto.algorithm.ecdsa; 24 | 25 | import com.onixbyte.crypto.PrivateKeyLoader; 26 | import com.onixbyte.crypto.PublicKeyLoader; 27 | import com.onixbyte.crypto.exception.KeyLoadingException; 28 | import com.onixbyte.crypto.util.CryptoUtil; 29 | 30 | import java.math.BigInteger; 31 | import java.security.AlgorithmParameters; 32 | import java.security.KeyFactory; 33 | import java.security.NoSuchAlgorithmException; 34 | import java.security.interfaces.ECPrivateKey; 35 | import java.security.interfaces.ECPublicKey; 36 | import java.security.spec.*; 37 | import java.util.Base64; 38 | import java.util.HashSet; 39 | import java.util.Set; 40 | 41 | /** 42 | * A class responsible for loading public ECDSA keys from PEM formatted text. 43 | *

44 | * This class implements the {@link PublicKeyLoader} interface and provides methods to load private 45 | * RSA keys. The keys are expected to be in the standard PEM format, which includes Base64-encoded 46 | * key content surrounded by header and footer lines. The class handles the decoding of Base64 47 | * content and the generation of keys using the RSA key factory. 48 | *

49 | * Any exceptions encountered during the loading process are encapsulated in a 50 | * {@link KeyLoadingException}, allowing for flexible error handling. 51 | * 52 | * @author zihluwang 53 | * @author siujamo 54 | * @version 3.0.0 55 | * @see PrivateKeyLoader 56 | * @see KeyLoadingException 57 | */ 58 | public class ECPublicKeyLoader implements PublicKeyLoader { 59 | 60 | /** 61 | * Supported curves. 62 | */ 63 | public static final Set SUPPORTED_CURVES = new HashSet<>(Set.of( 64 | "secp256r1", "secp384r1", "secp521r1", "secp224r1" 65 | )); 66 | 67 | private final KeyFactory keyFactory; 68 | 69 | private final Base64.Decoder decoder; 70 | 71 | /** 72 | * Initialise a key loader for EC-based algorithms. 73 | */ 74 | public ECPublicKeyLoader() { 75 | try { 76 | this.keyFactory = KeyFactory.getInstance("EC"); 77 | this.decoder = Base64.getDecoder(); 78 | } catch (NoSuchAlgorithmException e) { 79 | throw new KeyLoadingException(e); 80 | } 81 | } 82 | 83 | /** 84 | * Load public key from pem-formatted key text. 85 | * 86 | * @param pemKeyText pem-formatted key text 87 | * @return loaded private key 88 | * @throws KeyLoadingException if the generated key is not a {@link ECPrivateKey} instance, 89 | * or EC Key Factory is not loaded, or key spec is invalid 90 | */ 91 | @Override 92 | public ECPublicKey loadPublicKey(String pemKeyText) { 93 | try { 94 | pemKeyText = CryptoUtil.getRawContent(pemKeyText); 95 | var keyBytes = decoder.decode(pemKeyText); 96 | var spec = new X509EncodedKeySpec(keyBytes); 97 | var key = keyFactory.generatePublic(spec); 98 | if (key instanceof ECPublicKey publicKey) { 99 | return publicKey; 100 | } else { 101 | throw new KeyLoadingException("Unable to load public key from pem-formatted key text."); 102 | } 103 | } catch (InvalidKeySpecException e) { 104 | throw new KeyLoadingException("Key spec is invalid.", e); 105 | } 106 | } 107 | 108 | /** 109 | * Loads an EC public key from the given hexadecimal x and y coordinates alongside the curve name. 110 | *

111 | * This method converts the hexadecimal string representations of the EC point coordinates into {@link BigInteger} 112 | * instances, then constructs an {@link ECPoint} and retrieves the corresponding {@link ECParameterSpec} for the 113 | * named curve. Subsequently, it utilises the {@link KeyFactory} to generate an {@link ECPublicKey}. 114 | *

115 | * Only curves listed in {@link #SUPPORTED_CURVES} are supported. Should the specified curve name be unsupported, 116 | * or if key construction fails due to invalid parameters or unsupported algorithms, a {@link KeyLoadingException} 117 | * will be thrown. 118 | * 119 | * @param xHex the hexadecimal string representing the x-coordinate of the EC point 120 | * @param yHex the hexadecimal string representing the y-coordinate of the EC point 121 | * @param curveName the name of the elliptic curve 122 | * @return the {@link ECPublicKey} generated from the specified coordinates and curve 123 | * @throws KeyLoadingException if the curve is unsupported or key generation fails 124 | */ 125 | @Override 126 | public ECPublicKey loadPublicKey(String xHex, String yHex, String curveName) { 127 | if (!SUPPORTED_CURVES.contains(curveName)) { 128 | throw new KeyLoadingException("Given curve is not supported yet."); 129 | } 130 | 131 | try { 132 | // Convert hex string coordinates to BigInteger 133 | var x = new BigInteger(xHex, 16); 134 | var y = new BigInteger(yHex, 16); 135 | 136 | // Create ECPoint with (x, y) 137 | var ecPoint = new ECPoint(x, y); 138 | 139 | // Get EC parameter spec for the named curve 140 | var parameters = AlgorithmParameters.getInstance("EC"); 141 | parameters.init(new ECGenParameterSpec(curveName)); 142 | var ecParameterSpec = parameters.getParameterSpec(ECParameterSpec.class); 143 | 144 | // Create ECPublicKeySpec with point and curve params 145 | var pubSpec = new ECPublicKeySpec(ecPoint, ecParameterSpec); 146 | 147 | // Generate public key using KeyFactory 148 | var publicKey = keyFactory.generatePublic(pubSpec); 149 | 150 | if (publicKey instanceof ECPublicKey ecPublicKey) { 151 | return ecPublicKey; 152 | } else { 153 | throw new KeyLoadingException("Cannot load EC public key with given x, y and curve name."); 154 | } 155 | } catch (NoSuchAlgorithmException | InvalidParameterSpecException | InvalidKeySpecException e) { 156 | throw new KeyLoadingException("Cannot load EC public key with given x, y and curve name.", e); 157 | } 158 | } 159 | } 160 | -------------------------------------------------------------------------------- /common-toolbox/src/main/java/com/onixbyte/common/util/Base64Util.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2024-2025 OnixByte 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy 5 | * of this software and associated documentation files (the "Software"), to deal 6 | * in the Software without restriction, including without limitation the rights 7 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | * copies of the Software, and to permit persons to whom the Software is 9 | * furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in all 12 | * copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 20 | * SOFTWARE. 21 | */ 22 | 23 | package com.onixbyte.common.util; 24 | 25 | import java.nio.charset.Charset; 26 | import java.nio.charset.StandardCharsets; 27 | import java.util.Base64; 28 | import java.util.Objects; 29 | 30 | /** 31 | * The {@link Base64Util} class provides static methods to encode and decode strings with Base64 32 | * encoding. It utilizes the {@link Base64} class from the Java standard library for performing the 33 | * encoding and decoding operations. This utility class offers convenient methods to encode and 34 | * decode strings with different character sets. 35 | *

36 | * This class is designed as a final class with a private constructor to prevent instantiation. 37 | * All methods in this class are static, allowing easy access to the Base64 encoding and 38 | * decoding functionality. 39 | *

40 | * Example usage: 41 | *

 42 |  * String original = "Hello, World!";
 43 |  *
 44 |  * // Encode the string using UTF-8 charset
 45 |  * String encoded = Base64Util.encode(original);
 46 |  * System.out.println("Encoded string: " + encoded);
 47 |  *
 48 |  * // Decode the encoded string using UTF-8 charset
 49 |  * String decoded = Base64Util.decode(encoded);
 50 |  * System.out.println("Decoded string: " + decoded);
 51 |  * 
52 | *

53 | * Note: This utility class uses the default charset (UTF-8) if no specific charset is 54 | * provided. It is recommended to specify the charset explicitly to ensure consistent 55 | * encoding and decoding. 56 | * 57 | * @author zihluwang 58 | * @version 3.0.0 59 | */ 60 | public final class Base64Util { 61 | 62 | /** 63 | * Private constructor to prevent instantiation of this utility class. 64 | */ 65 | private Base64Util() { 66 | } 67 | 68 | private static Base64.Encoder encoder; 69 | private static Base64.Decoder decoder; 70 | private static Base64.Encoder urlEncoder; 71 | private static Base64.Decoder urlDecoder; 72 | 73 | /** 74 | * Ensure that there is only one Base64 Encoder. 75 | * 76 | * @return the {@link Base64.Encoder} instance 77 | */ 78 | private static Base64.Encoder getEncoder() { 79 | if (Objects.isNull(encoder)) { 80 | encoder = Base64.getEncoder(); 81 | } 82 | return encoder; 83 | } 84 | 85 | /** 86 | * Ensure that there is only one Base64 Encoder. 87 | * 88 | * @return the {@link Base64.Encoder} instance 89 | */ 90 | private static Base64.Decoder getDecoder() { 91 | if (Objects.isNull(decoder)) { 92 | decoder = Base64.getDecoder(); 93 | } 94 | return decoder; 95 | } 96 | 97 | /** 98 | * Ensure that there is only one Base64 URL Encoder. 99 | * 100 | * @return the {@link Base64.Encoder} instance 101 | */ 102 | private static Base64.Encoder getUrlEncoder() { 103 | if (Objects.isNull(urlEncoder)) { 104 | urlEncoder = Base64.getUrlEncoder(); 105 | } 106 | return urlEncoder; 107 | } 108 | 109 | /** 110 | * Ensure that there is only one Base64 URL Decoder. 111 | * 112 | * @return the {@link Base64.Encoder} instance 113 | */ 114 | public static Base64.Decoder getUrlDecoder() { 115 | if (Objects.isNull(urlDecoder)) { 116 | urlDecoder = Base64.getUrlDecoder(); 117 | } 118 | return urlDecoder; 119 | } 120 | 121 | /** 122 | * Encodes the given string using the specified charset. 123 | * 124 | * @param value the string to be encoded 125 | * @param charset the charset to be used for encoding 126 | * @return the Base64 encoded string 127 | */ 128 | public static String encode(String value, Charset charset) { 129 | var encoded = getEncoder().encode(value.getBytes(charset)); 130 | 131 | return new String(encoded); 132 | } 133 | 134 | /** 135 | * Encodes the given string using the default UTF-8 charset. 136 | * 137 | * @param value the string to be encoded 138 | * @return the Base64 encoded string 139 | */ 140 | public static String encode(String value) { 141 | return encode(value, StandardCharsets.UTF_8); 142 | } 143 | 144 | /** 145 | * Decodes the given Base64 encoded string using the specified charset. 146 | * 147 | * @param value the Base64 encoded string to be decoded 148 | * @param charset the charset to be used for decoding 149 | * @return the decoded string 150 | */ 151 | public static String decode(String value, Charset charset) { 152 | var decoded = getDecoder().decode(value.getBytes(charset)); 153 | 154 | return new String(decoded); 155 | } 156 | 157 | /** 158 | * Decodes the given Base64 encoded string using the default UTF-8 charset. 159 | * 160 | * @param value the Base64 encoded string to be decoded 161 | * @return the decoded string 162 | */ 163 | public static String decode(String value) { 164 | return decode(value, StandardCharsets.UTF_8); 165 | } 166 | 167 | /** 168 | * Encodes the given string using the specified charset. 169 | * 170 | * @param value the string to be encoded 171 | * @param charset the charset to be used for encoding 172 | * @return the Base64 encoded string 173 | */ 174 | public static String encodeUrlComponents(String value, Charset charset) { 175 | var encoded = getUrlEncoder().encode(value.getBytes(charset)); 176 | 177 | return new String(encoded); 178 | } 179 | 180 | /** 181 | * Encodes the given string using the default UTF-8 charset. 182 | * 183 | * @param value the string to be encoded 184 | * @return the Base64 encoded string 185 | */ 186 | public static String encodeUrlComponents(String value) { 187 | return encodeUrlComponents(value, StandardCharsets.UTF_8); 188 | } 189 | 190 | /** 191 | * Decodes the given Base64 encoded string using the specified charset. 192 | * 193 | * @param value the Base64 encoded string to be decoded 194 | * @param charset the charset to be used for decoding 195 | * @return the decoded string 196 | */ 197 | public static String decodeUrlComponents(String value, Charset charset) { 198 | var decoded = getUrlDecoder().decode(value.getBytes(charset)); 199 | 200 | return new String(decoded); 201 | } 202 | 203 | /** 204 | * Decodes the given Base64 encoded string using the default UTF-8 charset. 205 | * 206 | * @param value the Base64 encoded string to be decoded 207 | * @return the decoded string 208 | */ 209 | public static String decodeUrlComponents(String value) { 210 | return decodeUrlComponents(value, StandardCharsets.UTF_8); 211 | } 212 | } 213 | -------------------------------------------------------------------------------- /common-toolbox/src/main/java/com/onixbyte/common/util/RangeUtil.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2024-2025 OnixByte 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy 5 | * of this software and associated documentation files (the "Software"), to deal 6 | * in the Software without restriction, including without limitation the rights 7 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | * copies of the Software, and to permit persons to whom the Software is 9 | * furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in all 12 | * copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 20 | * SOFTWARE. 21 | */ 22 | 23 | package com.onixbyte.common.util; 24 | 25 | import java.util.stream.IntStream; 26 | 27 | /** 28 | * {@code RangeUtil} is a utility class providing methods for generating streams of integers that 29 | * emulate the behaviour of Python's {@code range} function. 30 | *

31 | * This class offers static methods to create ranges with various configurations. These methods 32 | * leverage the {@link IntStream} to provide efficient and versatile integer sequences. 33 | * 34 | * @author zihluwang 35 | * @version 3.0.0 36 | * @see IntStream 37 | */ 38 | public final class RangeUtil { 39 | 40 | /** 41 | * Private constructor to prevent instantiation of this utility class. 42 | */ 43 | private RangeUtil() { 44 | } 45 | 46 | /** 47 | * Generates a stream of integers starting from {@code 0} up to the specified {@code end} value. 48 | *

49 | * It creates a sequential, ordered {@code IntStream} that can be used for iteration or 50 | * further processing. 51 | *

52 | * Example Usage: 53 | *

{@code
 54 |      * RangeUtil.range(5).forEach(System.out::println);
 55 |      *
 56 |      * // Output:
 57 |      * // 0
 58 |      * // 1
 59 |      * // 2
 60 |      * // 3
 61 |      * // 4
 62 |      * }
63 | * 64 | * @param end upper-bound of the range (exclusive) 65 | * @return an {@code IntStream} of integers from {@code 0} (inclusive) to 66 | * {@code end} (exclusive) 67 | * @throws IllegalArgumentException if the given {@code end} value is less equal to 0 68 | * @see IntStream 69 | */ 70 | public static IntStream range(int end) { 71 | if (end <= 0) { 72 | throw new IllegalArgumentException("Parameter [end] should not be less than or equal to 0, provided: " + 73 | end); 74 | } 75 | return IntStream.range(0, end); 76 | } 77 | 78 | /** 79 | * Generates a stream of integers starting from the specified {@code start} value up to the 80 | * specified {@code end} value. 81 | *

82 | * It creates a sequential, ordered {@code IntStream} that can be used for iteration or 83 | * further processing. 84 | *

85 | * If {@code start} is less than {@code end}, an ascending range (exclusive of {@code end}) 86 | * is generated. If {@code start} is greater than {@code end}, a descending range (exclusive 87 | * of {@code end}) is generated. If {@code start} equals {@code end}, an empty stream 88 | * is returned. 89 | *

90 | * Example Usage: 91 | *

{@code
 92 |      * RangeUtil.range(3, 8).forEach(System.out::println);
 93 |      *
 94 |      * // Output:
 95 |      * // 3
 96 |      * // 4
 97 |      * // 5
 98 |      * // 6
 99 |      * // 7
100 |      *
101 |      * RangeUtil.range(8, 3).forEach(System.out::println);
102 |      *
103 |      * // Output:
104 |      * // 8
105 |      * // 7
106 |      * // 6
107 |      * // 5
108 |      * // 4
109 |      * }
110 | * 111 | * @param start the starting value of the range (inclusive) 112 | * @param end upper-bound of the range (exclusive) 113 | * @return an {@code IntStream} of integers in ascending or descending order, exclusive 114 | * of {@code end} 115 | * @see IntStream 116 | */ 117 | public static IntStream range(int start, int end) { 118 | if (start == end) { 119 | return IntStream.empty(); 120 | } 121 | if (start < end) { 122 | return IntStream.range(start, end); 123 | } else { 124 | // Descending range (exclusive of end) 125 | return IntStream.iterate(start, (n) -> n > end, (n) -> n - 1); 126 | } 127 | } 128 | 129 | /** 130 | * Generates a stream of integers starting from the specified {@code start} value up to the 131 | * specified {@code end} value. 132 | *

133 | * It creates a sequential, ordered {@code IntStream} that can be used for iteration or 134 | * further processing. 135 | *

136 | * The range includes both {@code start} and {@code end}. 137 | *

138 | * Example Usage: 139 | *

{@code
140 |      * RangeUtil.rangeClosed(3, 8).forEach(System.out::println);
141 |      *
142 |      * // Output:
143 |      * // 3
144 |      * // 4
145 |      * // 5
146 |      * // 6
147 |      * // 7
148 |      * // 8
149 |      * }
150 | * 151 | * @param start the starting value of the range (inclusive) 152 | * @param end upper-bound of the range (inclusive) 153 | * @return an {@code IntStream} of integers from {@code start} to {@code end} inclusive 154 | * @see IntStream 155 | */ 156 | public static IntStream rangeClosed(int start, int end) { 157 | return IntStream.rangeClosed(start, end); 158 | } 159 | 160 | /** 161 | * Generates a stream of integers starting from the specified {@code start} value, incremented 162 | * by the specified {@code step}, up to the specified {@code end} value. 163 | *

164 | * It creates a sequential, ordered {@code IntStream} that can be used for iteration or 165 | * further processing. 166 | *

167 | * The stream excludes the {@code end} value. 168 | *

169 | * Example Usage: 170 | *

{@code
171 |      * RangeUtil.range(3, 10, 2).forEach(System.out::println);
172 |      *
173 |      * // Output:
174 |      * // 3
175 |      * // 5
176 |      * // 7
177 |      * // 9
178 |      *
179 |      * RangeUtil.range(10, 3, -2).forEach(System.out::println);
180 |      *
181 |      * // Output:
182 |      * // 10
183 |      * // 8
184 |      * // 6
185 |      * // 4
186 |      * }
187 | * 188 | * @param start the starting value of the range (inclusive) 189 | * @param end upper-bound of the range (exclusive) 190 | * @param step the increment or decrement between each value (non-zero) 191 | * @return an {@code IntStream} of integers from {@code start} to {@code end} exclusive stepping 192 | * by {@code step} 193 | * @throws IllegalArgumentException if {@code step} is zero or if {@code start} and {@code end} 194 | * are inconsistent with the direction imposed by {@code step} 195 | * @see IntStream 196 | */ 197 | public static IntStream range(int start, int end, int step) { 198 | if (step == 0) { 199 | throw new IllegalArgumentException("Step value must not be zero."); 200 | } 201 | if ((step > 0 && start >= end) || (step < 0 && start <= end)) { 202 | throw new IllegalArgumentException("Range parameters are inconsistent with the step value."); 203 | } 204 | return IntStream.iterate(start, (n) -> step > 0 ? n < end : n > end, (n) -> n + step); 205 | } 206 | 207 | } 208 | -------------------------------------------------------------------------------- /identity-generator/src/main/java/com/onixbyte/identitygenerator/impl/SnowflakeIdentityGenerator.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2024-2025 OnixByte 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy 5 | * of this software and associated documentation files (the "Software"), to deal 6 | * in the Software without restriction, including without limitation the rights 7 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | * copies of the Software, and to permit persons to whom the Software is 9 | * furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in all 12 | * copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 20 | * SOFTWARE. 21 | */ 22 | 23 | package com.onixbyte.identitygenerator.impl; 24 | 25 | import com.onixbyte.identitygenerator.IdentityGenerator; 26 | import com.onixbyte.identitygenerator.exceptions.TimingException; 27 | 28 | import java.time.LocalDateTime; 29 | import java.time.ZoneId; 30 | 31 | /** 32 | * The {@code SnowflakeIdentityGenerator} generates unique identifiers using the Snowflake algorithm, 33 | * which combines a timestamp, worker ID, and data centre ID to create 64-bit long integers. The bit 34 | * distribution for the generated IDs is as follows: 35 | *
    36 | *
  • 1 bit for sign
  • 37 | *
  • 41 bits for timestamp (in milliseconds)
  • 38 | *
  • 5 bits for data centre ID
  • 39 | *
  • 5 bits for worker ID
  • 40 | *
  • 12 bits for sequence number (per millisecond)
  • 41 | *
42 | *

43 | * When initializing a {@link SnowflakeIdentityGenerator}, you must provide the worker ID and data 44 | * centre ID, ensuring they are within the valid range defined by the bit size. The generator 45 | * maintains an internal sequence number that increments for IDs generated within the 46 | * same millisecond. If the system clock moves backward, an exception is thrown to prevent 47 | * generating IDs with repeated timestamps. 48 | * 49 | * @author zihluwang 50 | * @version 3.0.0 51 | */ 52 | public final class SnowflakeIdentityGenerator implements IdentityGenerator { 53 | 54 | /** 55 | * Default custom epoch. 56 | * 57 | * @value 2015-01-01T00:00:00Z 58 | */ 59 | private static final long DEFAULT_CUSTOM_EPOCH = 1_420_070_400_000L; 60 | 61 | /** 62 | * The start epoch timestamp to generate IDs from. 63 | */ 64 | private final long startEpoch; 65 | 66 | /** 67 | * The number of bits reserved for the worker ID. 68 | */ 69 | private final long workerIdBits = 5L; 70 | 71 | /** 72 | * The number of bits reserved for the data centre ID. 73 | */ 74 | private final long dataCentreIdBits = 5L; 75 | 76 | /** 77 | * The worker ID assigned to this generator. 78 | */ 79 | private final long workerId; 80 | 81 | /** 82 | * The data centre ID assigned to this generator. 83 | */ 84 | private final long dataCentreId; 85 | 86 | /** 87 | * The current sequence number. 88 | */ 89 | private long sequence = 0L; 90 | 91 | /** 92 | * The timestamp of the last generated ID. 93 | */ 94 | private long lastTimestamp = -1L; 95 | 96 | /** 97 | * Constructs a SnowflakeGuidGenerator with the default start epoch and custom worker ID, data 98 | * centre ID. 99 | * 100 | * @param dataCentreId the data centre ID (between 0 and 31) 101 | * @param workerId the worker ID (between 0 and 31) 102 | */ 103 | public SnowflakeIdentityGenerator(long dataCentreId, long workerId) { 104 | this(dataCentreId, workerId, DEFAULT_CUSTOM_EPOCH); 105 | } 106 | 107 | /** 108 | * Constructs a SnowflakeGuidGenerator with a custom epoch, worker ID, and data centre ID. 109 | * 110 | * @param dataCentreId the data centre ID (between 0 and 31) 111 | * @param workerId the worker ID (between 0 and 31) 112 | * @param startEpoch the custom epoch timestamp (in milliseconds) to start generating IDs from 113 | * @throws IllegalArgumentException if the start epoch is greater than the current timestamp, 114 | * or if the worker ID or data centre ID is out of range 115 | */ 116 | public SnowflakeIdentityGenerator(long dataCentreId, long workerId, long startEpoch) { 117 | if (startEpoch > currentTimestamp()) { 118 | throw new IllegalArgumentException("Start Epoch can not be greater than current timestamp!"); 119 | } 120 | 121 | var maxWorkerId = ~(-1L << workerIdBits); 122 | if (workerId > maxWorkerId || workerId < 0) { 123 | throw new IllegalArgumentException(String.format("Worker Id can't be greater than %d or less than 0", 124 | maxWorkerId)); 125 | } 126 | 127 | var maxDataCentreId = ~(-1L << dataCentreIdBits); 128 | if (dataCentreId > maxDataCentreId || dataCentreId < 0) { 129 | throw new IllegalArgumentException(String.format("Data Centre Id can't be greater than %d or less than 0", 130 | maxDataCentreId)); 131 | } 132 | 133 | this.startEpoch = startEpoch; 134 | this.workerId = workerId; 135 | this.dataCentreId = dataCentreId; 136 | } 137 | 138 | /** 139 | * Generates the next unique ID. 140 | * 141 | * @return the generated unique ID 142 | * @throws TimingException if the system clock moves backwards, indicating an invalid sequence 143 | * of timestamps. 144 | */ 145 | @Override 146 | public synchronized Long nextId() { 147 | var timestamp = currentTimestamp(); 148 | 149 | // if the current time is less than the timestamp of the last ID generation, it means that 150 | // the system clock has been set back and an exception should be thrown 151 | if (timestamp < lastTimestamp) { 152 | throw new TimingException("Clock moved backwards. Refusing to generate id for %d milliseconds" 153 | .formatted(lastTimestamp - timestamp)); 154 | } 155 | 156 | // if generated at the same time, perform intra-millisecond sequences 157 | long sequenceBits = 12L; 158 | if (lastTimestamp == timestamp) { 159 | long sequenceMask = ~(-1L << sequenceBits); 160 | sequence = (sequence + 1) & sequenceMask; 161 | // sequence overflow in milliseconds 162 | if (sequence == 0) { 163 | // block to the next millisecond, get a new timestamp 164 | timestamp = awaitToNextMillis(lastTimestamp); 165 | } 166 | } 167 | // timestamp change, sequence reset in milliseconds 168 | else { 169 | sequence = 0L; 170 | } 171 | 172 | // timestamp of last ID generation 173 | lastTimestamp = timestamp; 174 | 175 | // shifted and put together by or operations to form a 64-bit ID 176 | var timestampLeftShift = sequenceBits + workerIdBits + dataCentreIdBits; 177 | var dataCentreIdShift = sequenceBits + workerIdBits; 178 | return ((timestamp - startEpoch) << timestampLeftShift) 179 | | (dataCentreId << dataCentreIdShift) 180 | | (workerId << sequenceBits) 181 | | sequence; 182 | } 183 | 184 | /** 185 | * Blocks until the next millisecond to obtain a new timestamp. 186 | * 187 | * @param lastTimestamp the timestamp when the last ID was generated 188 | * @return the current timestamp 189 | */ 190 | private long awaitToNextMillis(long lastTimestamp) { 191 | var timestamp = currentTimestamp(); 192 | while (timestamp <= lastTimestamp) { 193 | timestamp = currentTimestamp(); 194 | } 195 | return timestamp; 196 | } 197 | 198 | /** 199 | * Returns the current timestamp in milliseconds. 200 | * 201 | * @return the current timestamp 202 | */ 203 | private long currentTimestamp() { 204 | return LocalDateTime.now().atZone(ZoneId.systemDefault()).toInstant().toEpochMilli(); 205 | } 206 | 207 | } 208 | 209 | --------------------------------------------------------------------------------