├── .editorconfig ├── .github ├── ISSUE_TEMPLATE │ ├── bug_report.md │ └── feature_request.md ├── check-new-version-number.sh └── workflows │ ├── build.yml │ ├── codeql-analysis.yml │ ├── create-release.yml │ └── package-release.yml ├── .gitignore ├── CODE_OF_CONDUCT.md ├── LICENSE ├── README.md ├── pom.xml └── src ├── main └── java │ ├── io │ └── github │ │ └── thibaultmeyer │ │ └── cuid │ │ ├── CUID.java │ │ ├── exception │ │ ├── CUIDGenerationException.java │ │ └── package-info.java │ │ └── package-info.java │ └── module-info.java └── test └── java └── io └── github └── thibaultmeyer └── cuid ├── CUIDv1Test.java ├── CUIDv2Test.java └── PerformanceTest.java /.editorconfig: -------------------------------------------------------------------------------- 1 | # editorconfig.org 2 | 3 | root = true 4 | 5 | [*] 6 | charset = utf-8 7 | end_of_line = lf 8 | indent_size = 4 9 | indent_style = space 10 | insert_final_newline = true 11 | trim_trailing_whitespace = true 12 | spaces_around_operators = true 13 | max_line_length = 160 14 | 15 | [*.java] 16 | ij_java_names_count_to_use_import_on_demand = 999 17 | ij_java_class_count_to_use_import_on_demand = 999 18 | ij_java_blank_lines_before_imports = 1 19 | ij_java_blank_lines_after_imports = 1 20 | ij_java_blank_lines_after_package = 1 21 | ij_java_layout_static_imports_separately = true 22 | 23 | [*.md] 24 | trim_trailing_whitespace = false 25 | indent_size = 2 26 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Describe the bug** 11 | A clear and concise description of what the bug is. 12 | 13 | **To Reproduce** 14 | Steps to reproduce the behavior: 15 | 16 | 1. Go to '...' 17 | 2. Click on '....' 18 | 3. Scroll down to '....' 19 | 4. See error 20 | 21 | **Expected behavior** 22 | A clear and concise description of what you expected to happen. 23 | 24 | **Screenshots** 25 | If applicable, add screenshots to help explain your problem. 26 | 27 | **Additional context** 28 | Add any other context about the problem here. 29 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for this project 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Is your feature request related to a problem? Please describe.** 11 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] 12 | 13 | **Describe the solution you'd like** 14 | A clear and concise description of what you want to happen. 15 | 16 | **Describe alternatives you've considered** 17 | A clear and concise description of any alternative solutions or features you've considered. 18 | 19 | **Additional context** 20 | Add any other context or screenshots about the feature request here. 21 | -------------------------------------------------------------------------------- /.github/check-new-version-number.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | NEW_RELEASE_VERSION=$1 4 | REGEXP='^([0-9]+){1}\.([0-9]+){1}\.([0-9]+){1}$' 5 | 6 | if [[ ${NEW_RELEASE_VERSION} =~ ${REGEXP} ]]; then 7 | echo "${NEW_RELEASE_VERSION} is a valid version number" 8 | else 9 | echo "${NEW_RELEASE_VERSION} is an invalid version number" 10 | exit 1 11 | fi 12 | -------------------------------------------------------------------------------- /.github/workflows/build.yml: -------------------------------------------------------------------------------- 1 | name: CI Build 2 | 3 | on: [ push, pull_request ] 4 | 5 | jobs: 6 | build: 7 | name: Build 8 | runs-on: ubuntu-latest 9 | 10 | steps: 11 | - uses: actions/checkout@v3 12 | 13 | - name: Set up JDK 17 14 | uses: actions/setup-java@v3 15 | with: 16 | java-version: '17' 17 | distribution: 'adopt' 18 | 19 | - name: Build with Maven 20 | run: mvn --batch-mode --update-snapshots -Dorg.slf4j.simpleLogger.log.org.apache.maven.cli.transfer.Slf4jMavenTransferListener=warn test 21 | -------------------------------------------------------------------------------- /.github/workflows/codeql-analysis.yml: -------------------------------------------------------------------------------- 1 | name: "CodeQL" 2 | 3 | on: 4 | push: 5 | branches: [ "master" ] 6 | pull_request: 7 | # The branches below must be a subset of the branches above 8 | branches: [ "master" ] 9 | schedule: 10 | - cron: '36 5 * * 4' 11 | 12 | jobs: 13 | analyze: 14 | name: Analyze 15 | runs-on: ubuntu-latest 16 | permissions: 17 | actions: read 18 | contents: read 19 | security-events: write 20 | 21 | strategy: 22 | fail-fast: false 23 | matrix: 24 | language: [ 'java' ] 25 | 26 | steps: 27 | - uses: actions/setup-java@v1 28 | with: 29 | java-version: 17 30 | 31 | - name: Checkout repository 32 | uses: actions/checkout@v3 33 | 34 | # Initializes the CodeQL tools for scanning. 35 | - name: Initialize CodeQL 36 | uses: github/codeql-action/init@v2 37 | with: 38 | languages: ${{ matrix.language }} 39 | 40 | - name: Autobuild 41 | uses: github/codeql-action/autobuild@v2 42 | 43 | - name: Perform CodeQL Analysis 44 | uses: github/codeql-action/analyze@v2 45 | -------------------------------------------------------------------------------- /.github/workflows/create-release.yml: -------------------------------------------------------------------------------- 1 | name: Create Release 2 | 3 | on: 4 | workflow_dispatch: 5 | inputs: 6 | NEW_RELEASE_VERSION: 7 | description: 'Release version (without -SNAPSHOT)' 8 | required: true 9 | 10 | jobs: 11 | create-release: 12 | name: Create Release ${{ github.event.inputs.NEW_RELEASE_VERSION }} 13 | runs-on: ubuntu-latest 14 | 15 | steps: 16 | - uses: actions/checkout@v3 17 | 18 | - name: Check new Version Number 19 | run: bash ./.github/check-new-version-number.sh "${{ github.event.inputs.NEW_RELEASE_VERSION }}" 20 | 21 | - name: Configure Git 22 | run: | 23 | git config --global user.name 'Thibault Meyer' 24 | git config --global user.email 'thibaultmeyer@users.noreply.github.com' 25 | git remote set-url origin https://x-access-token:${{ secrets.GITHUB_TOKEN }}@github.com/$GITHUB_REPOSITORY 26 | 27 | - name: Create Release ${{ github.event.inputs.NEW_RELEASE_VERSION }} 28 | run: | 29 | mvn versions:set -DnewVersion="${{ github.event.inputs.NEW_RELEASE_VERSION }}" 30 | mvn versions:commit 31 | git add . 32 | git commit -m "Release ${{ github.event.inputs.NEW_RELEASE_VERSION }}" 33 | git push 34 | 35 | - name: Determine next SNAPSHOT 36 | run: | 37 | echo "NEXT_RELEASE_VERSION=$(echo ${{ github.event.inputs.NEW_RELEASE_VERSION }} | awk -F. -v OFS=. '{$NF += 1 ; print}')" >> $GITHUB_ENV 38 | 39 | - name: Move to next SNAPSHOT 40 | run: | 41 | mvn versions:set -DnewVersion="${{ env.NEXT_RELEASE_VERSION }}-SNAPSHOT" 42 | mvn versions:commit 43 | git add . 44 | git commit -m "Move to next SNAPSHOT" 45 | git push 46 | -------------------------------------------------------------------------------- /.github/workflows/package-release.yml: -------------------------------------------------------------------------------- 1 | name: Package and Publish 2 | 3 | on: 4 | release: 5 | types: [ published ] 6 | 7 | jobs: 8 | publish-ossrh: 9 | name: Publish to Central Repository 10 | runs-on: ubuntu-latest 11 | 12 | steps: 13 | - uses: actions/checkout@v3 14 | 15 | - name: Set up JDK 17 16 | uses: actions/setup-java@v3 17 | with: 18 | java-version: '17' 19 | distribution: 'adopt' 20 | server-id: ossrh 21 | server-username: MAVEN_USERNAME 22 | server-password: MAVEN_PASSWORD 23 | 24 | - name: Install GPG secret key 25 | run: | 26 | cat <(echo -e "${{ secrets.OSSRH_GPG_SECRET_KEY }}") | gpg --batch --import 27 | gpg --list-secret-keys --keyid-format LONG 28 | 29 | - name: Build and Publish 30 | env: 31 | MAVEN_USERNAME: ${{ secrets.OSSRH_USERNAME }} 32 | MAVEN_PASSWORD: ${{ secrets.OSSRH_TOKEN }} 33 | run: mvn --batch-mode --update-snapshots -DskipTests --no-transfer-progress -Dorg.slf4j.simpleLogger.log.org.apache.maven.cli.transfer.Slf4jMavenTransferListener=warn -P sign-jars deploy 34 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.class 2 | *.java~ 3 | *.versionsBackup 4 | classpath.bootstrap 5 | 6 | 7 | # JetBrains (IntelliJ / Fleet) 8 | .fleet/ 9 | .idea/ 10 | *.iml 11 | 12 | 13 | # Visual Studio Code 14 | .vscode 15 | 16 | 17 | # Eclipse 18 | .project 19 | .settings/ 20 | .worksheet 21 | .metadata 22 | .loadpath 23 | .recommenders 24 | 25 | 26 | # Package Files # 27 | *.jar 28 | target/ 29 | 30 | 31 | # virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml 32 | hs_err_pid* 33 | /nb-configuration.xml 34 | 35 | 36 | # Other 37 | .DS_Store 38 | Thumbs.db 39 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Contributor Covenant Code of Conduct 2 | 3 | ## Our Pledge 4 | 5 | In the interest of fostering an open and welcoming environment, we as 6 | contributors and maintainers pledge to making participation in our project and 7 | our community a harassment-free experience for everyone, regardless of age, body 8 | size, disability, ethnicity, sex characteristics, gender identity and expression, 9 | level of experience, education, socio-economic status, nationality, personal 10 | appearance, race, religion, or sexual identity and orientation. 11 | 12 | ## Our Standards 13 | 14 | Examples of behavior that contributes to creating a positive environment 15 | include: 16 | 17 | * Using welcoming and inclusive language 18 | * Being respectful of differing viewpoints and experiences 19 | * Gracefully accepting constructive criticism 20 | * Focusing on what is best for the community 21 | * Showing empathy towards other community members 22 | 23 | Examples of unacceptable behavior by participants include: 24 | 25 | * The use of sexualized language or imagery and unwelcome sexual attention or 26 | advances 27 | * Trolling, insulting/derogatory comments, and personal or political attacks 28 | * Public or private harassment 29 | * Publishing others' private information, such as a physical or electronic 30 | address, without explicit permission 31 | * Other conduct which could reasonably be considered inappropriate in a 32 | professional setting 33 | 34 | ## Our Responsibilities 35 | 36 | Project maintainers are responsible for clarifying the standards of acceptable 37 | behavior and are expected to take appropriate and fair corrective action in 38 | response to any instances of unacceptable behavior. 39 | 40 | Project maintainers have the right and responsibility to remove, edit, or 41 | reject comments, commits, code, wiki edits, issues, and other contributions 42 | that are not aligned to this Code of Conduct, or to ban temporarily or 43 | permanently any contributor for other behaviors that they deem inappropriate, 44 | threatening, offensive, or harmful. 45 | 46 | ## Scope 47 | 48 | This Code of Conduct applies both within project spaces and in public spaces 49 | when an individual is representing the project or its community. Examples of 50 | representing a project or community include using an official project e-mail 51 | address, posting via an official social media account, or acting as an appointed 52 | representative at an online or offline event. Representation of a project may be 53 | further defined and clarified by project maintainers. 54 | 55 | ## Enforcement 56 | 57 | Instances of abusive, harassing, or otherwise unacceptable behavior may be 58 | reported by contacting the project team at meyer.thibault@gmail.com. All 59 | complaints will be reviewed and investigated and will result in a response that 60 | is deemed necessary and appropriate to the circumstances. The project team is 61 | obligated to maintain confidentiality with regard to the reporter of an incident. 62 | Further details of specific enforcement policies may be posted separately. 63 | 64 | Project maintainers who do not follow or enforce the Code of Conduct in good 65 | faith may face temporary or permanent repercussions as determined by other 66 | members of the project's leadership. 67 | 68 | ## Attribution 69 | 70 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, 71 | available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html 72 | 73 | [homepage]: https://www.contributor-covenant.org 74 | 75 | For answers to common questions about this code of conduct, see 76 | https://www.contributor-covenant.org/faq 77 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2022 CUID Java contributors 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 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # CUID for Java 2 | 3 | [![GitHub license](https://img.shields.io/badge/license-MIT-blue.svg?logo=github)](https://raw.githubusercontent.com/thibaultmeyer/cuid-java/master/LICENSE) 4 | [![Repository release](https://img.shields.io/github/v/release/thibaultmeyer/cuid-java?logo=github)](https://github.com/thibaultmeyer/cuid-java/releases) 5 | [![Maven](https://img.shields.io/maven-central/v/io.github.thibaultmeyer/cuid.svg?logo=apache-maven)](https://central.sonatype.com/artifact/io.github.thibaultmeyer/cuid/2.0.3/versions) 6 | [![Repository size](https://img.shields.io/github/repo-size/thibaultmeyer/cuid-java.svg?logo=git)](https://github.com/thibaultmeyer/cuid-java) 7 | 8 | [![Javadoc](https://javadoc.io/badge2/io.github.thibaultmeyer/cuid/javadoc.svg)](https://javadoc.io/doc/io.github.thibaultmeyer/cuid) 9 | 10 | Java implementation of CUID. Read more at CUID official website. 11 | ***** 12 | 13 | 14 | ## Build 15 | To compile CUID for Java, you must ensure that Java 11 (or above) and Maven are correctly 16 | installed. 17 | 18 | #> mvn package 19 | #> mvn install 20 | 21 | To speed up process, you can ignore unit tests by using: `-DskipTests=true -Dmaven.test.skip=true`. 22 | 23 | 24 | 25 | ## How to use 26 | 27 | ```xml 28 | 29 | io.github.thibaultmeyer 30 | cuid 31 | x.y.z 32 | 33 | ``` 34 | 35 | ```java 36 | final CUID cuid = CUID.randomCUID1(); 37 | System.out.println("CUID: " + cuid); 38 | ``` 39 | 40 | ```java 41 | final CUID cuid = CUID.randomCUID2(); 42 | System.out.println("CUID (Version 2): " + cuid); 43 | ``` 44 | 45 | ```java 46 | final int customLength = 8; // Length must be, at least, 1 47 | final CUID cuid = CUID.randomCUID2(customLength); 48 | System.out.println("CUID (Version 2): " + cuid); 49 | ``` 50 | 51 | ```java 52 | final CUID cuid = CUID.fromString("cl9gts1kw00393647w1z4v2tc"); 53 | System.out.println("CUID: " + cuid); 54 | ``` 55 | 56 | ```java 57 | final boolean isValid = CUID.isValid("cl9gts1kw00393647w1z4v2tc"); 58 | System.out.println("Is 'cl9gts1kw00393647w1z4v2tc' a valid CUID ? " + isValid); 59 | ``` 60 | 61 | 62 | ## License 63 | This project is released under terms of the [MIT license](https://raw.githubusercontent.com/thibaultmeyer/cuid-java/master/LICENSE). 64 | -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 4.0.0 6 | 7 | io.github.thibaultmeyer 8 | cuid 9 | 2.0.4-SNAPSHOT 10 | CUID Java 11 | Collision-resistant ids optimized for horizontal scaling and performance 12 | https://github.com/thibaultmeyer/cuid-java 13 | 14 | 15 | scm:git:https://github.com/thibaultmeyer/cuid-java.git 16 | scm:git:https://github.com/thibaultmeyer/cuid-java.git 17 | https://github.com/thibaultmeyer/cuid-java.git 18 | 19 | 20 | 21 | https://github.com/thibaultmeyer/cuid-java/issues 22 | GitHub Issues 23 | 24 | 25 | 26 | 27 | The MIT License (MIT) 28 | https://github.com/thibaultmeyer/cuid-java/blob/master/LICENSE 29 | 30 | 31 | 32 | 33 | 34 | thibaultmeyer 35 | Thibault Meyer 36 | 37 | Owner 38 | Developer 39 | 40 | 41 | 42 | 43 | 44 | UTF-8 45 | UTF-8 46 | 11 47 | 11 48 | 49 | 50 | 3.0.1 51 | 3.4.1 52 | 3.2.1 53 | 1.6.13 54 | 2.22.2 55 | 56 | 57 | 5.9.1 58 | 59 | 60 | 61 | 62 | 63 | sign-jars 64 | 65 | false 66 | 67 | 68 | 69 | 70 | org.apache.maven.plugins 71 | maven-gpg-plugin 72 | ${plugin.version.mavengpg} 73 | 74 | 75 | --pinentry-mode 76 | loopback 77 | 78 | 79 | 80 | 81 | sign-artifacts 82 | verify 83 | 84 | sign 85 | 86 | 87 | 0xEC2C9047 88 | 0xEC2C9047 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | org.apache.maven.plugins 102 | maven-javadoc-plugin 103 | ${plugin.version.mavenjavadoc} 104 | 105 | all,-missing 106 | UTF-8 107 | 108 | 109 | 110 | attach-javadocs 111 | 112 | jar 113 | 114 | 115 | 116 | 117 | 118 | org.apache.maven.plugins 119 | maven-surefire-plugin 120 | ${plugin.version.surefire} 121 | 122 | 123 | --add-opens cuid/io.github.thibaultmeyer.cuid=ALL-UNNAMED 124 | 125 | 126 | 127 | 128 | org.apache.maven.plugins 129 | maven-source-plugin 130 | ${plugin.version.mavensource} 131 | 132 | 133 | attach-sources 134 | 135 | jar-no-fork 136 | 137 | 138 | 139 | 140 | 141 | org.sonatype.plugins 142 | nexus-staging-maven-plugin 143 | ${plugin.version.sonatypenexus} 144 | true 145 | 146 | ossrh 147 | https://s01.oss.sonatype.org/ 148 | true 149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | ossrh 157 | https://s01.oss.sonatype.org/service/local/staging/deploy/maven2/ 158 | 159 | 160 | ossrh 161 | https://s01.oss.sonatype.org/content/repositories/snapshots 162 | 163 | 164 | 165 | 166 | 167 | org.junit.jupiter 168 | junit-jupiter 169 | ${dependency.version.junit} 170 | test 171 | 172 | 173 | 174 | -------------------------------------------------------------------------------- /src/main/java/io/github/thibaultmeyer/cuid/CUID.java: -------------------------------------------------------------------------------- 1 | package io.github.thibaultmeyer.cuid; 2 | 3 | import io.github.thibaultmeyer.cuid.exception.CUIDGenerationException; 4 | 5 | import java.io.Serializable; 6 | import java.lang.management.ManagementFactory; 7 | import java.math.BigInteger; 8 | import java.nio.charset.StandardCharsets; 9 | import java.security.MessageDigest; 10 | import java.security.NoSuchAlgorithmException; 11 | import java.security.SecureRandom; 12 | import java.util.Objects; 13 | 14 | /** 15 | * Collision-resistant ID optimized for horizontal scaling and performance. 16 | * 17 | * @see CUID official website 18 | * @since 1.0.0 19 | */ 20 | public final class CUID implements Serializable, Comparable { 21 | 22 | // Explicit serialVersionUID for interoperability. 23 | private static final long serialVersionUID = -2441709761088574861L; 24 | 25 | // Base to use 26 | private static final int NUMBER_BASE = 36; 27 | 28 | /** 29 | * CUID internal value holder. 30 | */ 31 | private final String value; 32 | 33 | /** 34 | * Creates a new instance. 35 | * 36 | * @param value A valid CUID value 37 | * @since 1.0.0 38 | */ 39 | private CUID(final String value) { 40 | 41 | this.value = value; 42 | } 43 | 44 | /** 45 | * Generates a new random CUID (Version 2). 46 | * 47 | * @return Newly generated CUID (Version 2) 48 | * @since 2.0.0 49 | */ 50 | public static CUID randomCUID2() { 51 | 52 | return randomCUID2(CUIDv2.LENGTH_STANDARD); 53 | } 54 | 55 | /** 56 | * Generates a new random CUID (Version 2). 57 | * 58 | * @param length requested CUID length 59 | * @return Newly generated CUID (Version 2) 60 | * @since 2.0.1 61 | */ 62 | public static CUID randomCUID2(final int length) { 63 | 64 | if (length <= 0) { 65 | throw new CUIDGenerationException("the length must be at least 1"); 66 | } 67 | 68 | final String time = Long.toString(System.currentTimeMillis(), NUMBER_BASE); 69 | final char firstLetter = CUIDv2.ALPHABET_ARRAY[safeAbs(Common.nextIntValue()) % CUIDv2.ALPHABET_ARRAY.length]; 70 | final String hash = CUIDv2.computeHash( 71 | time + CUIDv2.createEntropy(length) + CUIDv2.nextCounterValue() + Common.MACHINE_FINGERPRINT, 72 | length); 73 | 74 | return new CUID(firstLetter + hash.substring(1, length)); 75 | } 76 | 77 | /** 78 | * Generates a new random CUID (Version 1). 79 | * 80 | * @return Newly generated CUID (Version 1) 81 | * @since 2.0.0 82 | */ 83 | public static CUID randomCUID1() { 84 | 85 | final String timestamp = Long.toString(System.currentTimeMillis(), NUMBER_BASE); 86 | final String counter = Common.padWithZero(Integer.toString(CUIDv1.nextCounterValue(), NUMBER_BASE), CUIDv1.BLOCK_SIZE); 87 | final String random = CUIDv1.getRandomBlock() + CUIDv1.getRandomBlock(); 88 | 89 | return new CUID(CUIDv1.START_CHARACTER + timestamp + counter + Common.MACHINE_FINGERPRINT + random); 90 | } 91 | 92 | /** 93 | * Creates a {@code CUID} from the string standard representation. 94 | * 95 | * @param cuidAsString A string that specifies a {@code CUID} (Version 1 or 2) 96 | * @return A {@code CUID} with the specified value 97 | * @throws IllegalArgumentException If the string is not conform 98 | * @since 1.0.0 99 | */ 100 | public static CUID fromString(final String cuidAsString) { 101 | 102 | if (isValid(cuidAsString)) { 103 | return new CUID(cuidAsString); 104 | } 105 | 106 | throw new IllegalArgumentException("CUID string is invalid: '" + cuidAsString + "'"); 107 | } 108 | 109 | /** 110 | * Checks the {@code CUID} from the string standard representation. 111 | * 112 | * @param cuidAsString A string that specifies a {@code CUID} (Version 1 or 2) 113 | * @return {@code true} If the string is conforms, otherwise, {@code false} 114 | * @since 1.0.0 115 | */ 116 | public static boolean isValid(final String cuidAsString) { 117 | 118 | return cuidAsString != null 119 | && (cuidAsString.length() == CUIDv1.LENGTH_STANDARD && cuidAsString.startsWith(CUIDv1.START_CHARACTER) // Version 1 120 | || (!cuidAsString.isEmpty())) // Version 2 121 | && cuidAsString.chars() 122 | .filter(c -> !((c >= '0' && c <= '9') || (c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z'))) 123 | .count() == 0; 124 | } 125 | 126 | /** 127 | * Always return non-negative value. 128 | * 129 | * @param i the integer value 130 | * @return a non-negative value 131 | * @since 2.0.3 132 | */ 133 | private static int safeAbs(final int i) { 134 | 135 | return i == Integer.MIN_VALUE ? 0 : Math.abs(i); 136 | } 137 | 138 | /** 139 | * {@inheritDoc} 140 | * 141 | * @since 1.0.0 142 | */ 143 | @Override 144 | public int compareTo(final CUID cuid) { 145 | 146 | if (cuid == null) { 147 | return -1; 148 | } 149 | 150 | return this.value.compareTo(cuid.value); 151 | } 152 | 153 | /** 154 | * Returns the string representation. 155 | * 156 | * @return String containing the {@code CUID} 157 | * @since 1.0.0 158 | */ 159 | @Override 160 | public String toString() { 161 | 162 | return this.value; 163 | } 164 | 165 | /** 166 | * Returns {@code true} if the argument is equal to current object, {@code false} otherwise. 167 | * 168 | * @param o An object 169 | * @since 1.0.0 170 | */ 171 | @Override 172 | public boolean equals(final Object o) { 173 | 174 | if (this == o) return true; 175 | if (o == null || getClass() != o.getClass()) return false; 176 | final CUID cuid = (CUID) o; 177 | return Objects.equals(value, cuid.value); 178 | } 179 | 180 | /** 181 | * Generates a hash code. 182 | * 183 | * @return Generated hashcode 184 | * @since 1.0.0 185 | */ 186 | @Override 187 | public int hashCode() { 188 | 189 | return Objects.hash(value); 190 | } 191 | 192 | /** 193 | * CUID Version 1. 194 | * 195 | * @since 1.0.0 196 | */ 197 | private static final class CUIDv1 { 198 | 199 | // CUID configuration 200 | private static final int BLOCK_SIZE = 4; 201 | private static final int LENGTH_STANDARD = 25; 202 | private static final String START_CHARACTER = "c"; 203 | private static final int DISCRETE_VALUE = (int) Math.pow(NUMBER_BASE, BLOCK_SIZE); 204 | 205 | // Counter 206 | private static int counter = 0; 207 | 208 | /** 209 | * Retrieves the counter next value. 210 | * 211 | * @return The counter next value 212 | * @since 1.0.0 213 | */ 214 | private static synchronized int nextCounterValue() { 215 | 216 | counter = counter < DISCRETE_VALUE ? counter : 0; 217 | return counter++; 218 | } 219 | 220 | /** 221 | * Generates a random block of data. 222 | * 223 | * @return Newly generated block of data 224 | * @since 1.0.0 225 | */ 226 | private static String getRandomBlock() { 227 | 228 | return Common.padWithZero(Integer.toString(Common.nextIntValue() * DISCRETE_VALUE, NUMBER_BASE), BLOCK_SIZE); 229 | } 230 | } 231 | 232 | /** 233 | * CUID Version 2. 234 | * 235 | * @since 2.0.0 236 | */ 237 | private static final class CUIDv2 { 238 | 239 | private static final char[] ALPHABET_ARRAY = new char[]{ 240 | 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 241 | 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z'}; 242 | private static final int[] PRIME_NUMBER_ARRAY = new int[]{ 243 | 109717, 244 | 109721, 245 | 109741, 246 | 109751, 247 | 109789, 248 | 109793, 249 | 109807, 250 | 109819, 251 | 109829, 252 | 109831}; 253 | 254 | // CUID configuration 255 | private static final int LENGTH_STANDARD = 24; 256 | 257 | // Counter 258 | private static int counter = Integer.MAX_VALUE; 259 | 260 | /** 261 | * Retrieves the counter next value. 262 | * 263 | * @return The counter next value 264 | */ 265 | private static synchronized int nextCounterValue() { 266 | 267 | counter = counter < Integer.MAX_VALUE ? counter : safeAbs(Common.nextIntValue()); 268 | return counter++; 269 | } 270 | 271 | /** 272 | * Creates an entropy string. 273 | * 274 | * @param length Length of the entropy string 275 | * @return String containing entropy in base {@link CUID#NUMBER_BASE} 276 | */ 277 | private static String createEntropy(final int length) { 278 | 279 | int primeNumber; 280 | final StringBuilder stringBuilder = new StringBuilder(length); 281 | 282 | while (stringBuilder.length() < length) { 283 | primeNumber = PRIME_NUMBER_ARRAY[safeAbs(Common.nextIntValue()) % PRIME_NUMBER_ARRAY.length]; 284 | stringBuilder.append(Integer.toString(primeNumber * Common.nextIntValue(), NUMBER_BASE)); 285 | } 286 | 287 | return stringBuilder.toString(); 288 | } 289 | 290 | /** 291 | * Computes hash. 292 | * 293 | * @return String containing hash 294 | */ 295 | private static String computeHash(final String content, final int saltLength) { 296 | 297 | final String salt = createEntropy(saltLength); 298 | try { 299 | return new BigInteger(MessageDigest.getInstance("SHA3-256").digest((content + salt).getBytes(StandardCharsets.UTF_8))) 300 | .toString(NUMBER_BASE); 301 | } catch (final NoSuchAlgorithmException exception) { 302 | throw new CUIDGenerationException(exception); 303 | } 304 | } 305 | } 306 | 307 | /* 308 | * Holder class to defer initialization until needed. 309 | * 310 | * @since 1.0.0 311 | */ 312 | private static final class Common { 313 | 314 | private static final int RANDOM_BUFFER_SIZE = 4096; 315 | private static final byte[] RANDOM_BUFFER = new byte[RANDOM_BUFFER_SIZE]; 316 | private static final SecureRandom NUMBER_GENERATOR = new SecureRandom(); 317 | private static final String MACHINE_FINGERPRINT = getMachineFingerprint(); 318 | 319 | private static int randomBufferIndex = RANDOM_BUFFER_SIZE; 320 | 321 | /** 322 | * Retrieves next random integer value. 323 | * 324 | * @return A random integer 325 | * @since 1.0.0 326 | */ 327 | private static synchronized int nextIntValue() { 328 | 329 | if (randomBufferIndex == RANDOM_BUFFER_SIZE) { 330 | Common.NUMBER_GENERATOR.nextBytes(Common.RANDOM_BUFFER); 331 | randomBufferIndex = 0; 332 | } 333 | 334 | return Common.RANDOM_BUFFER[randomBufferIndex++] << 24 335 | | (Common.RANDOM_BUFFER[randomBufferIndex++] & 0xff) << 16 336 | | (Common.RANDOM_BUFFER[randomBufferIndex++] & 0xff) << 8 337 | | (Common.RANDOM_BUFFER[randomBufferIndex++] & 0xff); 338 | } 339 | 340 | /** 341 | * Pads string with leading zero. 342 | * 343 | * @param str The string to pad 344 | * @param size The size to keep 345 | * @return The padded string 346 | * @since 1.0.0 347 | */ 348 | private static String padWithZero(final String str, final int size) { 349 | 350 | final String paddedString = "000000000" + str; 351 | return paddedString.substring(paddedString.length() - size); 352 | } 353 | 354 | /** 355 | * retrieves the machine fingerprint. 356 | * 357 | * @return The machine fingerprint 358 | * @since 1.0.0 359 | */ 360 | private static String getMachineFingerprint() { 361 | 362 | final String machineName = ManagementFactory.getRuntimeMXBean().getName(); 363 | final String[] machineNameTokenArray = machineName.split("@"); 364 | final String pid = machineNameTokenArray[0]; 365 | final String hostname = machineNameTokenArray[1]; 366 | 367 | int acc = hostname.length() + NUMBER_BASE; 368 | for (int i = 0; i < hostname.length(); i += 1) { 369 | acc = acc + hostname.charAt(i); 370 | } 371 | 372 | final String idBlock = padWithZero(pid, 2); 373 | final String nameBlock = padWithZero(Integer.toString(acc), 2); 374 | 375 | return idBlock + nameBlock; 376 | } 377 | } 378 | } 379 | -------------------------------------------------------------------------------- /src/main/java/io/github/thibaultmeyer/cuid/exception/CUIDGenerationException.java: -------------------------------------------------------------------------------- 1 | package io.github.thibaultmeyer.cuid.exception; 2 | 3 | /** 4 | * Exception indicates that the generation of a new CUID has failed. 5 | * 6 | * @since 2.0.0 7 | */ 8 | public class CUIDGenerationException extends RuntimeException { 9 | 10 | /** 11 | * Creates a new instance. 12 | * 13 | * @param cause Cause of the exception 14 | * @since 2.0.0 15 | */ 16 | public CUIDGenerationException(final Throwable cause) { 17 | 18 | super("CUID generation failure", cause); 19 | } 20 | 21 | /** 22 | * Creates a new instance. 23 | * 24 | * @param cause Cause of the exception 25 | * @since 2.0.1 26 | */ 27 | public CUIDGenerationException(final String cause) { 28 | 29 | super("CUID generation failure: " + cause); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/main/java/io/github/thibaultmeyer/cuid/exception/package-info.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Contains the exceptions that can be thrown when generating or manipulating a CUID. 3 | * 4 | * @since 2.0.0 5 | */ 6 | package io.github.thibaultmeyer.cuid.exception; 7 | -------------------------------------------------------------------------------- /src/main/java/io/github/thibaultmeyer/cuid/package-info.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Collision-resistant ID optimized for horizontal scaling and performance. 3 | *
{@code
 4 |  *  // Generates a random CUID (Version 1)
 5 |  *  final CUID cuid = CUID.randomCUID1();
 6 |  *  System.out.println("CUID: " + cuid);
 7 |  *
 8 |  *  // Generates a random CUID (Version 2)
 9 |  *  final CUID cuid = CUID.randomCUID2();
10 |  *  System.out.println("CUID: " + cuid);
11 |  *
12 |  *  // Generates a random CUID with a custom length (Version 2)
13 |  *  final int customLength = 8;  // Length must be, at least, 1
14 |  *  final CUID cuid = CUID.randomCUID2(customLength);
15 |  *  System.out.println("CUID: " + cuid);
16 |  *
17 |  *  // Creates a CUID from a string
18 |  *  final CUID cuid = CUID.fromString("cl9gts1kw00393647w1z4v2tc");
19 |  *  System.out.println("CUID: " + cuid);
20 |  *
21 |  *  // Verifies if string contains a valid CUID
22 |  *  final boolean isValid = CUID.isValid("cl9gts1kw00393647w1z4v2tc");
23 |  *  System.out.println("Is 'cl9gts1kw00393647w1z4v2tc' a valid CUID ? " + isValid);
24 |  * }
25 | * 26 | * @since 1.0.0 27 | */ 28 | package io.github.thibaultmeyer.cuid; 29 | -------------------------------------------------------------------------------- /src/main/java/module-info.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Java implementation of CUID. 3 | * 4 | * @see io.github.thibaultmeyer.cuid.CUID 5 | * @see CUID official website 6 | */ 7 | module cuid { 8 | 9 | requires java.management; 10 | exports io.github.thibaultmeyer.cuid; 11 | } 12 | -------------------------------------------------------------------------------- /src/test/java/io/github/thibaultmeyer/cuid/CUIDv1Test.java: -------------------------------------------------------------------------------- 1 | package io.github.thibaultmeyer.cuid; 2 | 3 | import org.junit.jupiter.api.Assertions; 4 | import org.junit.jupiter.api.MethodOrderer; 5 | import org.junit.jupiter.api.Test; 6 | import org.junit.jupiter.api.TestMethodOrder; 7 | 8 | import java.util.HashSet; 9 | import java.util.Set; 10 | 11 | @TestMethodOrder(MethodOrderer.MethodName.class) 12 | final class CUIDv1Test { 13 | 14 | @Test 15 | void fromString() { 16 | 17 | // Arrange 18 | final String cuidAsString = "cl9gts1kw00393647w1z4v2tc"; 19 | 20 | // Act 21 | final CUID cuid = CUID.fromString(cuidAsString); 22 | 23 | // Assert 24 | Assertions.assertNotNull(cuid); 25 | Assertions.assertEquals(25, cuid.toString().length()); 26 | Assertions.assertEquals("cl9gts1kw00393647w1z4v2tc", cuid.toString()); 27 | } 28 | 29 | @Test 30 | void fromStringInvalid() { 31 | 32 | // Arrange 33 | final String cuidAsString = "invalid-cuid"; 34 | 35 | // Act 36 | final IllegalArgumentException exception = Assertions.assertThrows( 37 | IllegalArgumentException.class, 38 | () -> CUID.fromString(cuidAsString)); 39 | 40 | // Assert 41 | Assertions.assertNotNull(exception); 42 | Assertions.assertEquals("CUID string is invalid: 'invalid-cuid'", exception.getMessage()); 43 | } 44 | 45 | @Test 46 | void isValid() { 47 | 48 | // Arrange 49 | final String cuidAsString = "cl9gts1kw00393647w1z4v2tc"; 50 | 51 | // Act 52 | final boolean isValid = CUID.isValid(cuidAsString); 53 | 54 | // Assert 55 | Assertions.assertTrue(isValid); 56 | } 57 | 58 | @Test 59 | void isValidInvalid() { 60 | 61 | // Arrange 62 | final String cuidAsString = "not-a-cuid"; 63 | 64 | // Act 65 | final boolean isValid = CUID.isValid(cuidAsString); 66 | 67 | // Assert 68 | Assertions.assertFalse(isValid); 69 | } 70 | 71 | @Test 72 | void randomCUID() { 73 | 74 | // Act 75 | final CUID cuid = CUID.randomCUID1(); 76 | 77 | // Assert 78 | Assertions.assertNotNull(cuid); 79 | Assertions.assertEquals(25, cuid.toString().length()); 80 | } 81 | 82 | @Test 83 | void compareToNotSame() { 84 | 85 | // Arrange 86 | final CUID cuidOne = CUID.fromString("cl9gts1kw00393647w1z4v2tc"); 87 | final CUID cuidTwo = CUID.fromString("cl9gts1kw00393647ee45bn56"); 88 | 89 | // Act 90 | final int result = cuidOne.compareTo(cuidTwo); 91 | 92 | // Assert 93 | Assertions.assertEquals(18, result); 94 | } 95 | 96 | @Test 97 | void compareToNotSameNull() { 98 | 99 | // Arrange 100 | final CUID cuidOne = CUID.fromString("cl9gts1kw00393647w1z4v2tc"); 101 | 102 | // Act 103 | final int result = cuidOne.compareTo(null); 104 | 105 | // Assert 106 | Assertions.assertEquals(-1, result); 107 | } 108 | 109 | @Test 110 | void compareToSame() { 111 | 112 | // Arrange 113 | final CUID cuidOne = CUID.fromString("cl9gts1kw00393647w1z4v2tc"); 114 | final CUID cuidTwo = CUID.fromString("cl9gts1kw00393647w1z4v2tc"); 115 | 116 | // Act 117 | final int result = cuidOne.compareTo(cuidTwo); 118 | 119 | // Assert 120 | Assertions.assertEquals(0, result); 121 | } 122 | 123 | @Test 124 | void unicityOver500000() { 125 | 126 | // Act 127 | final Set cuidSet = new HashSet<>(); 128 | for (int i = 0; i < 500000; i += 1) { 129 | cuidSet.add(CUID.randomCUID1()); 130 | } 131 | 132 | // Assert 133 | Assertions.assertEquals(500000, cuidSet.size()); 134 | } 135 | } 136 | -------------------------------------------------------------------------------- /src/test/java/io/github/thibaultmeyer/cuid/CUIDv2Test.java: -------------------------------------------------------------------------------- 1 | package io.github.thibaultmeyer.cuid; 2 | 3 | import io.github.thibaultmeyer.cuid.exception.CUIDGenerationException; 4 | import org.junit.jupiter.api.Assertions; 5 | import org.junit.jupiter.api.MethodOrderer; 6 | import org.junit.jupiter.api.Test; 7 | import org.junit.jupiter.api.TestMethodOrder; 8 | 9 | import java.util.HashSet; 10 | import java.util.Set; 11 | 12 | @TestMethodOrder(MethodOrderer.MethodName.class) 13 | final class CUIDv2Test { 14 | 15 | @Test 16 | void fromString() { 17 | 18 | // Arrange 19 | final String cuidAsString = "n1ht3jch1r23dy9ramd6ts16"; 20 | 21 | // Act 22 | final CUID cuid = CUID.fromString(cuidAsString); 23 | 24 | // Assert 25 | Assertions.assertNotNull(cuid); 26 | Assertions.assertEquals(24, cuid.toString().length()); 27 | Assertions.assertEquals("n1ht3jch1r23dy9ramd6ts16", cuid.toString()); 28 | } 29 | 30 | @Test 31 | void fromStringInvalid() { 32 | 33 | // Arrange 34 | final String cuidAsString = "invalid-cuid"; 35 | 36 | // Act 37 | final IllegalArgumentException exception = Assertions.assertThrows( 38 | IllegalArgumentException.class, 39 | () -> CUID.fromString(cuidAsString)); 40 | 41 | // Assert 42 | Assertions.assertNotNull(exception); 43 | Assertions.assertEquals("CUID string is invalid: 'invalid-cuid'", exception.getMessage()); 44 | } 45 | 46 | @Test 47 | void isValid() { 48 | 49 | // Arrange 50 | final String cuidAsString = "n1ht3jch1r23dy9ramd6ts16"; 51 | 52 | // Act 53 | final boolean isValid = CUID.isValid(cuidAsString); 54 | 55 | // Assert 56 | Assertions.assertTrue(isValid); 57 | } 58 | 59 | @Test 60 | void isValidInvalid() { 61 | 62 | // Arrange 63 | final String cuidAsString = "not-a-cuid"; 64 | 65 | // Act 66 | final boolean isValid = CUID.isValid(cuidAsString); 67 | 68 | // Assert 69 | Assertions.assertFalse(isValid); 70 | } 71 | 72 | @Test 73 | void randomCUIDv2() { 74 | 75 | // Act 76 | final CUID cuid = CUID.randomCUID2(); 77 | 78 | // Assert 79 | Assertions.assertNotNull(cuid); 80 | Assertions.assertEquals(24, cuid.toString().length()); 81 | } 82 | 83 | @Test 84 | void randomCUIDv2BigLength() { 85 | 86 | // Act 87 | final CUID cuid = CUID.randomCUID2(32); 88 | 89 | // Assert 90 | Assertions.assertNotNull(cuid); 91 | Assertions.assertEquals(32, cuid.toString().length()); 92 | } 93 | 94 | @Test 95 | void compareToNotSame() { 96 | 97 | // Arrange 98 | final CUID cuidOne = CUID.fromString("g346rykdwn4m117cupchv9m6"); 99 | final CUID cuidTwo = CUID.fromString("o7ti2h84195cdvxmbx9vb2gg"); 100 | 101 | // Act 102 | final int result = cuidOne.compareTo(cuidTwo); 103 | 104 | // Assert 105 | Assertions.assertEquals(-8, result); 106 | } 107 | 108 | @Test 109 | void compareToNotSameNull() { 110 | 111 | // Arrange 112 | final CUID cuidOne = CUID.fromString("f23nsqjlsmd1oo0kooedsg07"); 113 | 114 | // Act 115 | final int result = cuidOne.compareTo(null); 116 | 117 | // Assert 118 | Assertions.assertEquals(-1, result); 119 | } 120 | 121 | @Test 122 | void compareToSame() { 123 | 124 | // Arrange 125 | final CUID cuidOne = CUID.fromString("z976prixkgxs0u13x7g67fo3"); 126 | final CUID cuidTwo = CUID.fromString("z976prixkgxs0u13x7g67fo3"); 127 | 128 | // Act 129 | final int result = cuidOne.compareTo(cuidTwo); 130 | 131 | // Assert 132 | Assertions.assertEquals(0, result); 133 | } 134 | 135 | @Test 136 | void parameterizedLength() { 137 | 138 | // Act 139 | final CUID cuid = CUID.randomCUID2(2); 140 | 141 | // Assert 142 | Assertions.assertNotNull(cuid); 143 | Assertions.assertEquals(2, cuid.toString().length()); 144 | } 145 | 146 | @Test 147 | void parameterizedLengthInvalidSize() { 148 | 149 | // Act 150 | final CUIDGenerationException exception = Assertions.assertThrows( 151 | CUIDGenerationException.class, 152 | () -> CUID.randomCUID2(-1)); 153 | 154 | // Assert 155 | Assertions.assertNotNull(exception); 156 | Assertions.assertEquals("CUID generation failure: the length must be at least 1", exception.getMessage()); 157 | } 158 | 159 | @Test 160 | void unicityOver500000() { 161 | 162 | // Act 163 | final Set cuidSet = new HashSet<>(); 164 | for (int i = 0; i < 500000; i += 1) { 165 | cuidSet.add(CUID.randomCUID2()); 166 | } 167 | 168 | // Assert 169 | Assertions.assertEquals(500000, cuidSet.size()); 170 | } 171 | } 172 | -------------------------------------------------------------------------------- /src/test/java/io/github/thibaultmeyer/cuid/PerformanceTest.java: -------------------------------------------------------------------------------- 1 | package io.github.thibaultmeyer.cuid; 2 | 3 | import org.junit.jupiter.api.Assertions; 4 | import org.junit.jupiter.api.BeforeAll; 5 | import org.junit.jupiter.api.MethodOrderer; 6 | import org.junit.jupiter.api.Test; 7 | import org.junit.jupiter.api.TestMethodOrder; 8 | 9 | import java.util.ArrayList; 10 | import java.util.List; 11 | import java.util.UUID; 12 | 13 | @TestMethodOrder(MethodOrderer.MethodName.class) 14 | final class PerformanceTest { 15 | 16 | @BeforeAll 17 | public static void beforeAll() { 18 | 19 | System.gc(); 20 | } 21 | 22 | @Test 23 | void speedUUID() { 24 | 25 | for (int i = 0; i < 10; i += 1) { 26 | UUID.randomUUID(); 27 | } 28 | 29 | final List uuidList = new ArrayList<>(); 30 | final long start = System.nanoTime(); 31 | for (int i = 0; i < 1_000_000; i += 1) { 32 | uuidList.add(UUID.randomUUID()); 33 | } 34 | final long end = System.nanoTime(); 35 | 36 | System.err.println("1,000,000 UUID have been generated in " + (end - start) / 1_000_000 + " ms"); 37 | Assertions.assertEquals(1_000_000, uuidList.size()); 38 | } 39 | 40 | @Test 41 | void speedCUIDv1() { 42 | for (int i = 0; i < 10; i += 1) { 43 | CUID.randomCUID1(); 44 | } 45 | 46 | final List cuidList = new ArrayList<>(); 47 | final long start = System.nanoTime(); 48 | for (int i = 0; i < 1_000_000; i += 1) { 49 | cuidList.add(CUID.randomCUID1()); 50 | } 51 | final long end = System.nanoTime(); 52 | 53 | System.err.println("1,000,000 CUIDv1 have been generated in " + (end - start) / 1_000_000 + " ms"); 54 | Assertions.assertEquals(1_000_000, cuidList.size()); 55 | } 56 | 57 | @Test 58 | void speedCUIDv2Standard() { 59 | for (int i = 0; i < 10; i += 1) { 60 | CUID.randomCUID2(); 61 | } 62 | 63 | final List cuidList = new ArrayList<>(); 64 | final long start = System.nanoTime(); 65 | for (int i = 0; i < 1_000_000; i += 1) { 66 | cuidList.add(CUID.randomCUID2()); 67 | } 68 | final long end = System.nanoTime(); 69 | 70 | System.err.println("1,000,000 CUIDv2 have been generated in " + (end - start) / 1_000_000 + " ms"); 71 | Assertions.assertEquals(1_000_000, cuidList.size()); 72 | } 73 | 74 | @Test 75 | void speedCUIDv2Big() { 76 | for (int i = 0; i < 10; i += 1) { 77 | CUID.randomCUID2(32); 78 | } 79 | 80 | final List cuidList = new ArrayList<>(); 81 | final long start = System.nanoTime(); 82 | for (int i = 0; i < 1_000_000; i += 1) { 83 | cuidList.add(CUID.randomCUID2(32)); 84 | } 85 | final long end = System.nanoTime(); 86 | 87 | System.err.println("1,000,000 CUIDv2 have been generated in " + (end - start) / 1_000_000 + " ms"); 88 | Assertions.assertEquals(1_000_000, cuidList.size()); 89 | } 90 | } 91 | --------------------------------------------------------------------------------