├── lib ├── .gitignore ├── build.gradle ├── lib.iml └── src │ ├── test │ └── kotlin │ │ └── com │ │ └── github │ │ └── guepardoapps │ │ └── kulid │ │ └── ULIDUnitTests.kt │ └── main │ └── kotlin │ └── com │ └── github │ └── guepardoapps │ └── kulid │ └── Ulid.kt ├── settings.gradle ├── .gitignore ├── logo.png ├── .gitattributes ├── CHANGELOG ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradle.properties ├── kULID.iml ├── LICENSE.md ├── gradlew.bat ├── COLLABORATING.md ├── CODE_OF_CONDUCT.md ├── README.md └── gradlew /lib/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /settings.gradle: -------------------------------------------------------------------------------- 1 | include ':lib' 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .gradle 2 | .idea 3 | /build 4 | local.properties 5 | -------------------------------------------------------------------------------- /logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JonasSchubert/kULID/HEAD/logo.png -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | -------------------------------------------------------------------------------- /CHANGELOG: -------------------------------------------------------------------------------- 1 | 2.0.0.0: 2 | - `maxTime` is renamed to TIMESTAMP_MAX 3 | - `minTime` is renamed to TIMESTAMP_MIN 4 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JonasSchubert/kULID/HEAD/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Sun Jul 28 12:35:37 CEST 2019 2 | distributionBase=GRADLE_USER_HOME 3 | distributionPath=wrapper/dists 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | distributionUrl=https\://services.gradle.org/distributions/gradle-6.9-all.zip 7 | -------------------------------------------------------------------------------- /lib/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'kotlin' 2 | apply plugin: 'maven' 3 | apply plugin: 'jacoco' 4 | 5 | dependencies { 6 | implementation fileTree(dir: 'libs', include: ['*.jar']) 7 | implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:1.5.10" 8 | testImplementation "junit:junit:4.12" 9 | } 10 | 11 | compileKotlin { 12 | kotlinOptions.jvmTarget = "1.8" 13 | } 14 | 15 | compileTestKotlin { 16 | kotlinOptions.jvmTarget = "1.8" 17 | } -------------------------------------------------------------------------------- /gradle.properties: -------------------------------------------------------------------------------- 1 | # Project-wide Gradle settings. 2 | # IDE (e.g. Android Studio) users: 3 | # Gradle settings configured through the IDE *will override* 4 | # any settings specified in this file. 5 | # For more details on how to configure your build environment visit 6 | # http://www.gradle.org/docs/current/userguide/build_environment.html 7 | # Specifies the JVM arguments used for the daemon process. 8 | # The setting is particularly useful for tweaking memory settings. 9 | org.gradle.jvmargs=-Xmx1536m 10 | # When configured, Gradle will run in incubating parallel mode. 11 | # This option should only be used with decoupled projects. More details, visit 12 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects 13 | # org.gradle.parallel=true 14 | # Kotlin code style for this project: "official" or "obsolete": 15 | kotlin.code.style=official 16 | -------------------------------------------------------------------------------- /kULID.iml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 - 2021 Jonas Schubert 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. -------------------------------------------------------------------------------- /gradlew.bat: -------------------------------------------------------------------------------- 1 | @if "%DEBUG%" == "" @echo off 2 | @rem ########################################################################## 3 | @rem 4 | @rem Gradle startup script for Windows 5 | @rem 6 | @rem ########################################################################## 7 | 8 | @rem Set local scope for the variables with windows NT shell 9 | if "%OS%"=="Windows_NT" setlocal 10 | 11 | set DIRNAME=%~dp0 12 | if "%DIRNAME%" == "" set DIRNAME=. 13 | set APP_BASE_NAME=%~n0 14 | set APP_HOME=%DIRNAME% 15 | 16 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 17 | set DEFAULT_JVM_OPTS= 18 | 19 | @rem Find java.exe 20 | if defined JAVA_HOME goto findJavaFromJavaHome 21 | 22 | set JAVA_EXE=java.exe 23 | %JAVA_EXE% -version >NUL 2>&1 24 | if "%ERRORLEVEL%" == "0" goto init 25 | 26 | echo. 27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 28 | echo. 29 | echo Please set the JAVA_HOME variable in your environment to match the 30 | echo location of your Java installation. 31 | 32 | goto fail 33 | 34 | :findJavaFromJavaHome 35 | set JAVA_HOME=%JAVA_HOME:"=% 36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 37 | 38 | if exist "%JAVA_EXE%" goto init 39 | 40 | echo. 41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 42 | echo. 43 | echo Please set the JAVA_HOME variable in your environment to match the 44 | echo location of your Java installation. 45 | 46 | goto fail 47 | 48 | :init 49 | @rem Get command-line arguments, handling Windows variants 50 | 51 | if not "%OS%" == "Windows_NT" goto win9xME_args 52 | 53 | :win9xME_args 54 | @rem Slurp the command line arguments. 55 | set CMD_LINE_ARGS= 56 | set _SKIP=2 57 | 58 | :win9xME_args_slurp 59 | if "x%~1" == "x" goto execute 60 | 61 | set CMD_LINE_ARGS=%* 62 | 63 | :execute 64 | @rem Setup the command line 65 | 66 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 67 | 68 | @rem Execute Gradle 69 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% 70 | 71 | :end 72 | @rem End local scope for the variables with windows NT shell 73 | if "%ERRORLEVEL%"=="0" goto mainEnd 74 | 75 | :fail 76 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 77 | rem the _cmd.exe /c_ return code! 78 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 79 | exit /b 1 80 | 81 | :mainEnd 82 | if "%OS%"=="Windows_NT" endlocal 83 | 84 | :omega 85 | -------------------------------------------------------------------------------- /COLLABORATING.md: -------------------------------------------------------------------------------- 1 | # Collaborator team rules and guidelines for community moderation 2 | 3 | **As a contributor/member of the community, remember that you can always open issues about moderation and problems with how community moderation is done. We are always open to constructive criticism and feedback!** 4 | 5 | ## Responsibilities of a collaborator 6 | 7 | As a member of the team that manages **kULID**, you have the following responsibilities: 8 | 9 | - **Be part of the conversation in the issue tracker.** That includes (but is not limited to) helping out new members, discussing new features and explaining decisions to people. 10 | - **Review pull requests.** You do not have to read through all of the pull requests and review them, but taking the time each day to review a few can help a great deal. 11 | - **Be civil and polite.** If you are about to lose your temper, take a step back and do something else. We want our interactions with the community to be polite so that more people can join the project and contribute in any way they can. Remember to always thank contributors for their help, even if it's minor changes or changes that did not make it into the project. This way we can reward and encourage people to keep being part of the community. 12 | - **Contribute when you want, moderate when you can.** If you have a lot on your plate outside of this project, it's alright. It's better to take a break for a few days rather than hastily deal with issues and pull requests that might break things. 13 | 14 | ## Guidelines for merging pull requests and making changes to the project 15 | 16 | - **[Usual guidelines](CONTRIBUTING.md) apply.** Make sure to follow them, like everybody else. 17 | - **For a pull request to be considered ready to merge, there should be at least 1 (preferably 2) reviews approving it for merge.** There are, however, certain exceptions: 18 | - **If a pull request only fixes typos**, there is no need to wait for a second reviewer (unless you are not certain these were not typos in the first place). 19 | - **If a pull request only clarifies a snippet's description or enforces the style guide for an existing snippet**, you might be able to merge it without getting a second reviewer to review it, but only if you are certain about it. 20 | - **Changes to build scripts, guidelines and things that might break the processes we have in place need to be reviewed by [@JonasSchubert](https://github.com/JonasSchubert)** (this is temporary, but we need a baseline to make sure we break as few things as possible in the beginning). 21 | - **After merging a pull request, make sure to check for untagged snippets and tag them appropriately.** Try to keep all snippets tagged, so that the list and website are up to date. 22 | - **If you make changes or additions to existing snippets or if you want to add your own snippets, you will go through the pull request process that everyone else goes.** Exceptions apply similarly to the ones mentioned above about merging pull requests (i.e. typos, description clarification and the way script and build process changes are handled). Pull requests suggested by collaborators should be reviewed by at least two other collaborators to be considered ready to merge. 23 | - **Pull requests that are inactive for over a week should be closed or put on hold.** -------------------------------------------------------------------------------- /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 [jonas.schubert.projects@web.de]. 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 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

2 |
3 |
4 | ulid 5 |
6 |
7 |
8 |

9 | 10 |

KUlid

11 | 12 |

13 | 14 | 15 | 16 | 17 |

18 | 19 |

Unit test coverage

20 | 21 |

22 | 23 |

24 | 25 |

Support me

26 | 27 |

28 | 29 |

30 | 31 | ULID (Universally Unique Lexicographically Sortable Identifier) generator and parser for Kotlin. 32 | 33 | Refer the [ULID spec](https://github.com/ulid/spec) for a more detailed ULID specification. 34 | 35 | ## Installation 36 | 37 | ### Kotlin DSL 38 | 39 | Add the JitPack repository to your `build.gradle.kts`: 40 | 41 | ```kotlin 42 | allprojects { 43 | repositories { 44 | ... 45 | maven { url = uri("https://jitpack.io") } 46 | } 47 | } 48 | ``` 49 | 50 | Add the dependency to your `build.gradle.kts`: 51 | 52 | ```kotlin 53 | dependencies { 54 | implementation("com.github.guepardoapps:kulid:2.0.0.0") 55 | } 56 | ``` 57 | 58 | ### Groovy DSL 59 | 60 | Add the JitPack repository to your `build.gradle`: 61 | 62 | ```groovy 63 | allprojects { 64 | repositories { 65 | ... 66 | maven { url 'https://jitpack.io' } 67 | } 68 | } 69 | ``` 70 | 71 | Add the dependency to your `build.gradle`: 72 | 73 | ```groovy 74 | dependencies { 75 | implementation 'com.github.guepardoapps:kulid:2.0.0.0' 76 | } 77 | ``` 78 | 79 | ## Usage 80 | 81 | ULID generation examples: 82 | 83 | ```kotlin 84 | val randomUlid = ULID.random() 85 | val generateUlid = ULID.generate(System.currentTimeMillis(), byteArrayOf(0x0, 0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7, 0x8, 0x9)) 86 | val stringUlid = ULID.fromString("003JZ9J6G80123456789ABCDEF") 87 | ``` 88 | 89 | ULID parsing examples: 90 | 91 | ```kotlin 92 | val ulid = "003JZ9J6G80123456789ABCDEF" 93 | val isValid = ULID.isValid(ulid) // returns a Boolean indicating if the ULID is valid 94 | val timestamp = ULID.getTimestamp(ulid) // returns a Long 95 | val entropy = ULID.getEntropy(ulid) // returns a ByteArray 96 | ``` 97 | 98 | ## Prior Projects 99 | 100 | - [azam/ulidj](https://github.com/azam/ulidj) 101 | - [Lewiscowles1986/jULID](https://github.com/Lewiscowles1986/jULID) 102 | - [alizain/ulid](https://github.com/alizain/ulid) 103 | 104 | ## Requirements 105 | 106 | - Use at least JVM 1.8 107 | 108 | ## Contributors 109 | 110 | | [JonasSchubert](https://github.com/JonasSchubert) | 111 | | :---------------------------------------------------------------------------------------------------------------------------------------: | 112 | | [Jonas Schubert](https://github.com/JonasSchubert) | 113 | 114 | ## License 115 | 116 | KUlid is distributed under the MIT license. [See LICENSE](LICENSE.md) for details. 117 | 118 | ``` 119 | MIT License 120 | 121 | Copyright (c) 2019 - 2021 Jonas Schubert 122 | 123 | Permission is hereby granted, free of charge, to any person obtaining a copy 124 | of this software and associated documentation files (the "Software"), to deal 125 | in the Software without restriction, including without limitation the rights 126 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 127 | copies of the Software, and to permit persons to whom the Software is 128 | furnished to do so, subject to the following conditions: 129 | 130 | The above copyright notice and this permission notice shall be included in all 131 | copies or substantial portions of the Software. 132 | 133 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 134 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 135 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 136 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 137 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 138 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 139 | SOFTWARE. 140 | ``` 141 | -------------------------------------------------------------------------------- /gradlew: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | 3 | ############################################################################## 4 | ## 5 | ## Gradle start up script for UN*X 6 | ## 7 | ############################################################################## 8 | 9 | # Attempt to set APP_HOME 10 | # Resolve links: $0 may be a link 11 | PRG="$0" 12 | # Need this for relative symlinks. 13 | while [ -h "$PRG" ] ; do 14 | ls=`ls -ld "$PRG"` 15 | link=`expr "$ls" : '.*-> \(.*\)$'` 16 | if expr "$link" : '/.*' > /dev/null; then 17 | PRG="$link" 18 | else 19 | PRG=`dirname "$PRG"`"/$link" 20 | fi 21 | done 22 | SAVED="`pwd`" 23 | cd "`dirname \"$PRG\"`/" >/dev/null 24 | APP_HOME="`pwd -P`" 25 | cd "$SAVED" >/dev/null 26 | 27 | APP_NAME="Gradle" 28 | APP_BASE_NAME=`basename "$0"` 29 | 30 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 31 | DEFAULT_JVM_OPTS="" 32 | 33 | # Use the maximum available, or set MAX_FD != -1 to use that value. 34 | MAX_FD="maximum" 35 | 36 | warn () { 37 | echo "$*" 38 | } 39 | 40 | die () { 41 | echo 42 | echo "$*" 43 | echo 44 | exit 1 45 | } 46 | 47 | # OS specific support (must be 'true' or 'false'). 48 | cygwin=false 49 | msys=false 50 | darwin=false 51 | nonstop=false 52 | case "`uname`" in 53 | CYGWIN* ) 54 | cygwin=true 55 | ;; 56 | Darwin* ) 57 | darwin=true 58 | ;; 59 | MINGW* ) 60 | msys=true 61 | ;; 62 | NONSTOP* ) 63 | nonstop=true 64 | ;; 65 | esac 66 | 67 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 68 | 69 | # Determine the Java command to use to start the JVM. 70 | if [ -n "$JAVA_HOME" ] ; then 71 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 72 | # IBM's JDK on AIX uses strange locations for the executables 73 | JAVACMD="$JAVA_HOME/jre/sh/java" 74 | else 75 | JAVACMD="$JAVA_HOME/bin/java" 76 | fi 77 | if [ ! -x "$JAVACMD" ] ; then 78 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 79 | 80 | Please set the JAVA_HOME variable in your environment to match the 81 | location of your Java installation." 82 | fi 83 | else 84 | JAVACMD="java" 85 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 86 | 87 | Please set the JAVA_HOME variable in your environment to match the 88 | location of your Java installation." 89 | fi 90 | 91 | # Increase the maximum file descriptors if we can. 92 | if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then 93 | MAX_FD_LIMIT=`ulimit -H -n` 94 | if [ $? -eq 0 ] ; then 95 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then 96 | MAX_FD="$MAX_FD_LIMIT" 97 | fi 98 | ulimit -n $MAX_FD 99 | if [ $? -ne 0 ] ; then 100 | warn "Could not set maximum file descriptor limit: $MAX_FD" 101 | fi 102 | else 103 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" 104 | fi 105 | fi 106 | 107 | # For Darwin, add options to specify how the application appears in the dock 108 | if $darwin; then 109 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" 110 | fi 111 | 112 | # For Cygwin, switch paths to Windows format before running java 113 | if $cygwin ; then 114 | APP_HOME=`cygpath --path --mixed "$APP_HOME"` 115 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` 116 | JAVACMD=`cygpath --unix "$JAVACMD"` 117 | 118 | # We build the pattern for arguments to be converted via cygpath 119 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` 120 | SEP="" 121 | for dir in $ROOTDIRSRAW ; do 122 | ROOTDIRS="$ROOTDIRS$SEP$dir" 123 | SEP="|" 124 | done 125 | OURCYGPATTERN="(^($ROOTDIRS))" 126 | # Add a user-defined pattern to the cygpath arguments 127 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then 128 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" 129 | fi 130 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 131 | i=0 132 | for arg in "$@" ; do 133 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` 134 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option 135 | 136 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition 137 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` 138 | else 139 | eval `echo args$i`="\"$arg\"" 140 | fi 141 | i=$((i+1)) 142 | done 143 | case $i in 144 | (0) set -- ;; 145 | (1) set -- "$args0" ;; 146 | (2) set -- "$args0" "$args1" ;; 147 | (3) set -- "$args0" "$args1" "$args2" ;; 148 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;; 149 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; 150 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; 151 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; 152 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; 153 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; 154 | esac 155 | fi 156 | 157 | # Escape application args 158 | save () { 159 | for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done 160 | echo " " 161 | } 162 | APP_ARGS=$(save "$@") 163 | 164 | # Collect all arguments for the java command, following the shell quoting and substitution rules 165 | eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" 166 | 167 | # by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong 168 | if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then 169 | cd "$(dirname "$0")" 170 | fi 171 | 172 | exec "$JAVACMD" "$@" 173 | -------------------------------------------------------------------------------- /lib/lib.iml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 10 | 11 | 12 | 13 | 16 | 17 | 18 | 19 | 20 | 22 | 23 | 34 | 46 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | -------------------------------------------------------------------------------- /lib/src/test/kotlin/com/github/guepardoapps/kulid/ULIDUnitTests.kt: -------------------------------------------------------------------------------- 1 | /** 2 | * MIT License 3 | * 4 | * Copyright (c) 2019-2021 GuepardoApps (Jonas Schubert) 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in all 14 | * copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | * SOFTWARE. 23 | */ 24 | 25 | package com.github.guepardoapps.kulid 26 | 27 | import org.junit.Assert.* 28 | import org.junit.Test 29 | import kotlin.experimental.and 30 | import kotlin.random.Random 31 | 32 | /** 33 | * Test class for {@link com.github.guepardoapps.kulid} 34 | * @author GuepardoApps (Jonas Schubert) 35 | * @since 1.0.0.0 (06.01.2019) 36 | */ 37 | class ULIDUnitTests { 38 | private val zeroEntropy = byteArrayOf(0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0) 39 | 40 | private class TestParam(val timestamp: Long, val entropy: ByteArray?, val value: String, val isIllegalArgument: Boolean) { 41 | val reproducer: String 42 | 43 | init { 44 | val stringBuilder = StringBuilder() 45 | stringBuilder.append("ULID.generate(").append(timestamp.toString()).append("L,") 46 | if (entropy != null) { 47 | stringBuilder.append("new byte[]{") 48 | for (index in entropy.indices) { 49 | stringBuilder.append("0x").append(Integer.toHexString((entropy[index] and 0xFF.toByte()) + 0x100).substring(1)) 50 | if (index + 1 < entropy.size) { 51 | stringBuilder.append(",") 52 | } 53 | } 54 | stringBuilder.append("}") 55 | } else { 56 | stringBuilder.append("null") 57 | } 58 | stringBuilder.append(")") 59 | this.reproducer = stringBuilder.toString() 60 | } 61 | } 62 | 63 | private val testParameters = arrayOf( 64 | TestParam(ULID.TIMESTAMP_MIN, zeroEntropy, "00000000000000000000000000", false), 65 | TestParam(ULID.TIMESTAMP_MAX, zeroEntropy, "7ZZZZZZZZZ0000000000000000", false), 66 | TestParam(0x00000001L, zeroEntropy, "00000000010000000000000000", false), 67 | TestParam(0x0000000fL, zeroEntropy, "000000000F0000000000000000", false), 68 | TestParam(0x00000010L, zeroEntropy, "000000000G0000000000000000", false), 69 | TestParam(0x00000011L, zeroEntropy, "000000000H0000000000000000", false), 70 | TestParam(0x0000001fL, zeroEntropy, "000000000Z0000000000000000", false), 71 | TestParam(0x00000020L, zeroEntropy, "00000000100000000000000000", false), 72 | TestParam(0x00000021L, zeroEntropy, "00000000110000000000000000", false), 73 | TestParam(0x0000002fL, zeroEntropy, "000000001F0000000000000000", false), 74 | TestParam(0x00000030L, zeroEntropy, "000000001G0000000000000000", false), 75 | TestParam(0x00000031L, zeroEntropy, "000000001H0000000000000000", false), 76 | TestParam(0x0000003fL, zeroEntropy, "000000001Z0000000000000000", false), 77 | TestParam(0x00000040L, zeroEntropy, "00000000200000000000000000", false), 78 | TestParam(0x000000f0L, zeroEntropy, "000000007G0000000000000000", false), 79 | TestParam(0x000000ffL, zeroEntropy, "000000007Z0000000000000000", false), 80 | TestParam(0x00000100L, zeroEntropy, "00000000800000000000000000", false), 81 | TestParam(0x00000101L, zeroEntropy, "00000000810000000000000000", false), 82 | TestParam(0x000001ffL, zeroEntropy, "00000000FZ0000000000000000", false), 83 | TestParam(0x00000200L, zeroEntropy, "00000000G00000000000000000", false), 84 | TestParam(0x00000201L, zeroEntropy, "00000000G10000000000000000", false), 85 | TestParam(0x000002ffL, zeroEntropy, "00000000QZ0000000000000000", false), 86 | TestParam(0x00000300L, zeroEntropy, "00000000R00000000000000000", false), 87 | TestParam(0x00000301L, zeroEntropy, "00000000R10000000000000000", false), 88 | TestParam(0x000003ffL, zeroEntropy, "00000000ZZ0000000000000000", false), 89 | TestParam(0x00000400L, zeroEntropy, "00000001000000000000000000", false), 90 | TestParam(0x00000401L, zeroEntropy, "00000001010000000000000000", false), 91 | TestParam(0x000007ffL, zeroEntropy, "00000001ZZ0000000000000000", false), 92 | TestParam(0x00000800L, zeroEntropy, "00000002000000000000000000", false), 93 | TestParam(0x00007fffL, zeroEntropy, "0000000ZZZ0000000000000000", false), 94 | TestParam(ULID.TIMESTAMP_MIN, byteArrayOf(0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x01), "00000000000000000000000001", false), 95 | TestParam(ULID.TIMESTAMP_MIN, byteArrayOf(0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0f), "0000000000000000000000000F", false), 96 | TestParam(ULID.TIMESTAMP_MIN, byteArrayOf(0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x10), "0000000000000000000000000G", false), 97 | TestParam(ULID.TIMESTAMP_MIN, byteArrayOf(0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1f), "0000000000000000000000000Z", false), 98 | TestParam(ULID.TIMESTAMP_MIN, byteArrayOf(0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x20), "00000000000000000000000010", false), 99 | TestParam(ULID.TIMESTAMP_MIN, byteArrayOf(0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x21), "00000000000000000000000011", false), 100 | TestParam(ULID.TIMESTAMP_MIN, byteArrayOf(0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x2f), "0000000000000000000000001F", false), 101 | TestParam(ULID.TIMESTAMP_MIN, byteArrayOf(0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x30), "0000000000000000000000001G", false), 102 | TestParam(ULID.TIMESTAMP_MIN, byteArrayOf(0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x3f), "0000000000000000000000001Z", false)) 103 | 104 | @Test 105 | fun testRandom() { 106 | val value = ULID.random() 107 | assertNotNull("Generated ULID must not be null", value) 108 | assertEquals("Generated ULID length must be 26", 26, value.length) 109 | assertTrue("Generated ULID characters must only include [0123456789ABCDEFGHJKMNPQRSTVWXYZ]", value.matches("[0123456789ABCDEFGHJKMNPQRSTVWXYZ]{26}".toRegex())) 110 | } 111 | 112 | @Test 113 | fun testGenerateRandom() { 114 | val value = ULID.generate(System.currentTimeMillis(), Random.nextBytes(10)) 115 | assertNotNull("Generated ULID must not be null", value) 116 | assertEquals("Generated ULID length must be 26, but returned " + value.length + " instead", 26, value.length) 117 | assertTrue("Generated ULID characters must only include [0123456789ABCDEFGHJKMNPQRSTVWXYZ], but returned $value instead", value.matches(("[0123456789ABCDEFGHJKMNPQRSTVWXYZ]{26}").toRegex())) 118 | } 119 | 120 | @Test 121 | fun testFromString() { 122 | val value = ULID.fromString("003JZ9J6G80123456789ABCDEF") 123 | assertNotNull("Generated ULID must not be null", value) 124 | assertEquals("Generated ULID length must be 26", 26, value.length) 125 | assertTrue("Generated ULID characters must only include [0123456789ABCDEFGHJKMNPQRSTVWXYZ]", value.matches("[0123456789ABCDEFGHJKMNPQRSTVWXYZ]{26}".toRegex())) 126 | } 127 | 128 | @Test 129 | fun testGenerateFixedValues() { 130 | for (params in testParameters) { 131 | var hasIllegalArgumentException = false 132 | try { 133 | val value = ULID.generate(params.timestamp, params.entropy) 134 | assertEquals(("Generated ULID must be equal to \"" + params.value + "\" for " + params.reproducer + " , but returned \"" + value + "\" instead"), params.value, value) 135 | assertNotNull("Generated ULID must not be null", value) 136 | assertEquals("Generated ULID length must be 26, but returned " + value.length + " instead", 26, value.length) 137 | assertTrue(("Generated ULID characters must only include [0123456789ABCDEFGHJKMNPQRSTVWXYZ], but returned $value instead"), value.matches(("[0123456789ABCDEFGHJKMNPQRSTVWXYZ]{26}").toRegex())) 138 | } catch (e: IllegalArgumentException) { 139 | hasIllegalArgumentException = true 140 | } 141 | 142 | if (params.isIllegalArgument) { 143 | assertTrue("IllegalArgumentException is expected for " + params.reproducer, hasIllegalArgumentException) 144 | } else { 145 | assertFalse("IllegalArgumentException is not expected for " + params.reproducer, hasIllegalArgumentException) 146 | } 147 | } 148 | } 149 | 150 | @Test 151 | fun testIsValidNegative() { 152 | val invalidUlids = arrayOf( 153 | null, 154 | "", 155 | "0", 156 | "000000000000000000000000000", 157 | "-0000000000000000000000000", 158 | "0000000000000000000000000U", 159 | "0000000000000000000000000/u3042", 160 | "0000000000000000000000000#") 161 | 162 | for (ulid in invalidUlids) { 163 | assertFalse("ULID \"$ulid\" should be invalid", ULID.isValid(ulid)) 164 | } 165 | } 166 | 167 | @Test 168 | fun testIsValidFixedValues() { 169 | for (params in testParameters) { 170 | if (!params.isIllegalArgument) { 171 | assertTrue("ULID string is valid", ULID.isValid(params.value)) 172 | } 173 | } 174 | } 175 | 176 | @Test 177 | fun testGetTimestampFixedValues() { 178 | for (params in testParameters) { 179 | if (!params.isIllegalArgument) { 180 | assertEquals("ULID timestamp is different", params.timestamp, ULID.getTimestamp(params.value)) 181 | } 182 | } 183 | } 184 | 185 | @Test 186 | fun testGetEntropyFixedValues() { 187 | for (params in testParameters) { 188 | if (!params.isIllegalArgument) { 189 | assertArrayEquals("ULID entropy is different", params.entropy, ULID.getEntropy(params.value)) 190 | } 191 | } 192 | } 193 | } 194 | -------------------------------------------------------------------------------- /lib/src/main/kotlin/com/github/guepardoapps/kulid/Ulid.kt: -------------------------------------------------------------------------------- 1 | /** 2 | * MIT License 3 | * 4 | * Copyright (c) 2019-2021 GuepardoApps (Jonas Schubert) 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in all 14 | * copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | * SOFTWARE. 23 | */ 24 | 25 | package com.github.guepardoapps.kulid 26 | 27 | import kotlin.experimental.and 28 | import kotlin.random.Random 29 | 30 | class ULID { 31 | companion object { 32 | /** 33 | * The default entropy size 34 | */ 35 | private const val DEFAULT_ENTROPY_SIZE = 10 36 | 37 | /** 38 | * Maximum allowed timestamp value. 39 | */ 40 | const val TIMESTAMP_MAX = 0x0000ffffffffffffL 41 | 42 | /** 43 | * Minimum allowed timestamp value. 44 | */ 45 | const val TIMESTAMP_MIN = 0x0L 46 | 47 | /** 48 | * ULID string length. 49 | */ 50 | private const val ULID_LENGTH = 26 51 | 52 | /** 53 | * Base32 characters mapping 54 | */ 55 | private val charMapping = charArrayOf( 56 | 0x30.toChar(), 0x31.toChar(), 0x32.toChar(), 0x33.toChar(), 0x34.toChar(), 0x35.toChar(), 57 | 0x36.toChar(), 0x37.toChar(), 0x38.toChar(), 0x39.toChar(), 0x41.toChar(), 0x42.toChar(), 58 | 0x43.toChar(), 0x44.toChar(), 0x45.toChar(), 0x46.toChar(), 0x47.toChar(), 0x48.toChar(), 59 | 0x4a.toChar(), 0x4b.toChar(), 0x4d.toChar(), 0x4e.toChar(), 0x50.toChar(), 0x51.toChar(), 60 | 0x52.toChar(), 0x53.toChar(), 0x54.toChar(), 0x56.toChar(), 0x57.toChar(), 0x58.toChar(), 61 | 0x59.toChar(), 0x5a.toChar() 62 | ) 63 | 64 | /** 65 | * `char` to `byte` O(1) mapping with alternative chars mapping 66 | */ 67 | private val charToByteMapping = byteArrayOf( 68 | 0xff.toByte(), 0xff.toByte(), 0xff.toByte(), 0xff.toByte(), 0xff.toByte(), 0xff.toByte(), 69 | 0xff.toByte(), 0xff.toByte(), 0xff.toByte(), 0xff.toByte(), 0xff.toByte(), 0xff.toByte(), 70 | 0xff.toByte(), 0xff.toByte(), 0xff.toByte(), 0xff.toByte(), 0xff.toByte(), 0xff.toByte(), 71 | 0xff.toByte(), 0xff.toByte(), 0xff.toByte(), 0xff.toByte(), 0xff.toByte(), 0xff.toByte(), 72 | 0xff.toByte(), 0xff.toByte(), 0xff.toByte(), 0xff.toByte(), 0xff.toByte(), 0xff.toByte(), 73 | 0xff.toByte(), 0xff.toByte(), 0xff.toByte(), 0xff.toByte(), 0xff.toByte(), 0xff.toByte(), 74 | 0xff.toByte(), 0xff.toByte(), 0xff.toByte(), 0xff.toByte(), 0xff.toByte(), 0xff.toByte(), 75 | 0xff.toByte(), 0xff.toByte(), 0xff.toByte(), 0xff.toByte(), 0xff.toByte(), 0xff.toByte(), 76 | 0x00.toByte(), 0x01.toByte(), 0x02.toByte(), 0x03.toByte(), 0x04.toByte(), 0x05.toByte(), 77 | 0x06.toByte(), 0x07.toByte(), 0x08.toByte(), 0x09.toByte(), 0xff.toByte(), 0xff.toByte(), 78 | 0xff.toByte(), 0xff.toByte(), 0xff.toByte(), 0xff.toByte(), 0xff.toByte(), 0x0a.toByte(), 79 | 0x0b.toByte(), 0x0c.toByte(), 0x0d.toByte(), 0x0e.toByte(), 0x0f.toByte(), 0x10.toByte(), 80 | 0x11.toByte(), 0xff.toByte(), 0x12.toByte(), 0x13.toByte(), 0xff.toByte(), 0x14.toByte(), 81 | 0x15.toByte(), 0xff.toByte(), 0x16.toByte(), 0x17.toByte(), 0x18.toByte(), 0x19.toByte(), 82 | 0x1a.toByte(), 0xff.toByte(), 0x1b.toByte(), 0x1c.toByte(), 0x1d.toByte(), 0x1e.toByte(), 83 | 0x1f.toByte(), 0xff.toByte(), 0xff.toByte(), 0xff.toByte(), 0xff.toByte(), 0xff.toByte(), 84 | 0xff.toByte(), 0x0a.toByte(), 0x0b.toByte(), 0x0c.toByte(), 0x0d.toByte(), 0x0e.toByte(), 85 | 0x0f.toByte(), 0x10.toByte(), 0x11.toByte(), 0xff.toByte(), 0x12.toByte(), 0x13.toByte(), 86 | 0xff.toByte(), 0x14.toByte(), 0x15.toByte(), 0xff.toByte(), 0x16.toByte(), 0x17.toByte(), 87 | 0x18.toByte(), 0x19.toByte(), 0x1a.toByte(), 0xff.toByte(), 0x1b.toByte(), 0x1c.toByte(), 88 | 0x1d.toByte(), 0x1e.toByte(), 0x1f.toByte(), 0xff.toByte(), 0xff.toByte(), 0xff.toByte(), 89 | 0xff.toByte(), 0xff.toByte(), 0xff.toByte(), 0xff.toByte(), 0xff.toByte(), 0xff.toByte(), 90 | 0xff.toByte(), 0xff.toByte(), 0xff.toByte(), 0xff.toByte(), 0xff.toByte(), 0xff.toByte(), 91 | 0xff.toByte(), 0xff.toByte(), 0xff.toByte(), 0xff.toByte(), 0xff.toByte(), 0xff.toByte(), 92 | 0xff.toByte(), 0xff.toByte(), 0xff.toByte(), 0xff.toByte(), 0xff.toByte(), 0xff.toByte(), 93 | 0xff.toByte(), 0xff.toByte(), 0xff.toByte(), 0xff.toByte(), 0xff.toByte(), 0xff.toByte(), 94 | 0xff.toByte(), 0xff.toByte(), 0xff.toByte(), 0xff.toByte(), 0xff.toByte(), 0xff.toByte(), 95 | 0xff.toByte(), 0xff.toByte(), 0xff.toByte(), 0xff.toByte(), 0xff.toByte(), 0xff.toByte(), 96 | 0xff.toByte(), 0xff.toByte(), 0xff.toByte(), 0xff.toByte(), 0xff.toByte(), 0xff.toByte(), 97 | 0xff.toByte(), 0xff.toByte(), 0xff.toByte(), 0xff.toByte(), 0xff.toByte(), 0xff.toByte(), 98 | 0xff.toByte(), 0xff.toByte(), 0xff.toByte(), 0xff.toByte(), 0xff.toByte(), 0xff.toByte(), 99 | 0xff.toByte(), 0xff.toByte(), 0xff.toByte(), 0xff.toByte(), 0xff.toByte(), 0xff.toByte(), 100 | 0xff.toByte(), 0xff.toByte(), 0xff.toByte(), 0xff.toByte(), 0xff.toByte(), 0xff.toByte(), 101 | 0xff.toByte(), 0xff.toByte(), 0xff.toByte(), 0xff.toByte(), 0xff.toByte(), 0xff.toByte(), 102 | 0xff.toByte(), 0xff.toByte(), 0xff.toByte(), 0xff.toByte(), 0xff.toByte(), 0xff.toByte(), 103 | 0xff.toByte(), 0xff.toByte(), 0xff.toByte(), 0xff.toByte(), 0xff.toByte(), 0xff.toByte(), 104 | 0xff.toByte(), 0xff.toByte(), 0xff.toByte(), 0xff.toByte(), 0xff.toByte(), 0xff.toByte(), 105 | 0xff.toByte(), 0xff.toByte(), 0xff.toByte(), 0xff.toByte(), 0xff.toByte(), 0xff.toByte(), 106 | 0xff.toByte(), 0xff.toByte(), 0xff.toByte(), 0xff.toByte(), 0xff.toByte(), 0xff.toByte(), 107 | 0xff.toByte(), 0xff.toByte(), 0xff.toByte(), 0xff.toByte(), 0xff.toByte(), 0xff.toByte(), 108 | 0xff.toByte(), 0xff.toByte(), 0xff.toByte(), 0xff.toByte(), 0xff.toByte(), 0xff.toByte(), 109 | 0xff.toByte(), 0xff.toByte(), 0xff.toByte(), 0xff.toByte(), 0xff.toByte(), 0xff.toByte(), 110 | 0xff.toByte(), 0xff.toByte(), 0xff.toByte(), 0xff.toByte() 111 | ) 112 | 113 | /** 114 | * Generate ULID string from provided string 115 | * @param string String 116 | * @return ULID string 117 | */ 118 | fun fromString(string: String): String = if (!isValid(string)) { throw IllegalArgumentException("Invalid string value for an ulid") } else { string } 119 | 120 | /** 121 | * Generate random ULID string using [kotlin.random.Random] instance. 122 | * @return ULID string 123 | */ 124 | fun random(): String = generate(System.currentTimeMillis(), Random.nextBytes(DEFAULT_ENTROPY_SIZE)) 125 | 126 | /** 127 | * Generate ULID from Unix epoch timestamp in millisecond and entropy bytes. 128 | * Throws [java.lang.IllegalArgumentException] if timestamp is less than {@value #MIN_TIME}, 129 | * is more than {@value #MAX_TIME}, or entropy bytes is null or less than 10 bytes. 130 | * @param time Unix epoch timestamp in millisecond 131 | * @param entropy Entropy bytes 132 | * @return ULID string 133 | */ 134 | fun generate(time: Long, entropy: ByteArray?): String { 135 | if (time < TIMESTAMP_MIN || time > TIMESTAMP_MAX || entropy == null || entropy.size < DEFAULT_ENTROPY_SIZE) { 136 | throw IllegalArgumentException("Time is too long, or entropy is less than 10 bytes or null") 137 | } 138 | 139 | val chars = CharArray(ULID_LENGTH) 140 | 141 | // time 142 | chars[0] = charMapping[time.ushr(45).toInt() and 0x1f] 143 | chars[1] = charMapping[time.ushr(40).toInt() and 0x1f] 144 | chars[2] = charMapping[time.ushr(35).toInt() and 0x1f] 145 | chars[3] = charMapping[time.ushr(30).toInt() and 0x1f] 146 | chars[4] = charMapping[time.ushr(25).toInt() and 0x1f] 147 | chars[5] = charMapping[time.ushr(20).toInt() and 0x1f] 148 | chars[6] = charMapping[time.ushr(15).toInt() and 0x1f] 149 | chars[7] = charMapping[time.ushr(10).toInt() and 0x1f] 150 | chars[8] = charMapping[time.ushr(5).toInt() and 0x1f] 151 | chars[9] = charMapping[time.toInt() and 0x1f] 152 | 153 | // entropy 154 | chars[10] = charMapping[(entropy[0].toShort() and 0xff).toInt().ushr(3)] 155 | chars[11] = charMapping[(entropy[0].toInt() shl 2 or (entropy[1].toShort() and 0xff).toInt().ushr(6) and 0x1f)] 156 | chars[12] = charMapping[((entropy[1].toShort() and 0xff).toInt().ushr(1) and 0x1f)] 157 | chars[13] = charMapping[(entropy[1].toInt() shl 4 or (entropy[2].toShort() and 0xff).toInt().ushr(4) and 0x1f)] 158 | chars[14] = charMapping[(entropy[2].toInt() shl 5 or (entropy[3].toShort() and 0xff).toInt().ushr(7) and 0x1f)] 159 | chars[15] = charMapping[((entropy[3].toShort() and 0xff).toInt().ushr(2) and 0x1f)] 160 | chars[16] = charMapping[(entropy[3].toInt() shl 3 or (entropy[4].toShort() and 0xff).toInt().ushr(5) and 0x1f)] 161 | chars[17] = charMapping[(entropy[4].toInt() and 0x1f)] 162 | chars[18] = charMapping[(entropy[5].toShort() and 0xff).toInt().ushr(3)] 163 | chars[19] = charMapping[(entropy[5].toInt() shl 2 or (entropy[6].toShort() and 0xff).toInt().ushr(6) and 0x1f)] 164 | chars[20] = charMapping[((entropy[6].toShort() and 0xff).toInt().ushr(1) and 0x1f)] 165 | chars[21] = charMapping[(entropy[6].toInt() shl 4 or (entropy[7].toShort() and 0xff).toInt().ushr(4) and 0x1f)] 166 | chars[22] = charMapping[(entropy[7].toInt() shl 5 or (entropy[8].toShort() and 0xff).toInt().ushr(7) and 0x1f)] 167 | chars[23] = charMapping[((entropy[8].toShort() and 0xff).toInt().ushr(2) and 0x1f)] 168 | chars[24] = charMapping[(entropy[8].toInt() shl 3 or (entropy[9].toShort() and 0xff).toInt().ushr(5) and 0x1f)] 169 | chars[25] = charMapping[(entropy[9].toInt() and 0x1f)] 170 | 171 | return String(chars) 172 | } 173 | 174 | /** 175 | * Checks ULID string validity. 176 | * @param ulid ULID nullable string 177 | * @return true if ULID string is valid 178 | */ 179 | fun isValid(ulid: String?): Boolean { 180 | if (ulid == null || ulid.length != ULID_LENGTH) { 181 | return false 182 | } 183 | 184 | for (char in ulid) { 185 | if (char.code < 0 186 | || char.code > charToByteMapping.size 187 | || charToByteMapping[char.code] == 0xff.toByte()) { 188 | return false 189 | } 190 | } 191 | 192 | return true 193 | } 194 | 195 | /** 196 | * Extract and return the timestamp part from ULID. Expects a valid ULID string. 197 | * Call [ULID.isValid] and check validity before calling this method if you 198 | * do not trust the origin of the ULID string. 199 | * @param ulid ULID string 200 | * @return Unix epoch timestamp in millisecond 201 | */ 202 | fun getTimestamp(ulid: CharSequence): Long { 203 | return (charToByteMapping[ulid[0].code].toLong() shl 45 204 | or (charToByteMapping[ulid[1].code].toLong() shl 40) 205 | or (charToByteMapping[ulid[2].code].toLong() shl 35) 206 | or (charToByteMapping[ulid[3].code].toLong() shl 30) 207 | or (charToByteMapping[ulid[4].code].toLong() shl 25) 208 | or (charToByteMapping[ulid[5].code].toLong() shl 20) 209 | or (charToByteMapping[ulid[6].code].toLong() shl 15) 210 | or (charToByteMapping[ulid[7].code].toLong() shl 10) 211 | or (charToByteMapping[ulid[8].code].toLong() shl 5) 212 | or charToByteMapping[ulid[9].code].toLong()) 213 | } 214 | 215 | /** 216 | * Extract and return the entropy part from ULID. Expects a valid ULID string. 217 | * Call [ULID.isValid] and check validity before calling this method if you 218 | * do not trust the origin of the ULID string. 219 | * @param ulid ULID string 220 | * @return Entropy bytes 221 | */ 222 | fun getEntropy(ulid: CharSequence): ByteArray { 223 | val bytes = ByteArray(DEFAULT_ENTROPY_SIZE) 224 | 225 | bytes[0] = (charToByteMapping[ulid[10].code].toInt() shl 3 226 | or (charToByteMapping[ulid[11].code] and 0xff.toByte()).toInt().ushr(2)).toByte() 227 | bytes[1] = (charToByteMapping[ulid[11].code].toInt() shl 6 228 | or (charToByteMapping[ulid[12].code].toInt() shl 1) 229 | or (charToByteMapping[ulid[13].code] and 0xff.toByte()).toInt().ushr(4)).toByte() 230 | bytes[2] = (charToByteMapping[ulid[13].code].toInt() shl 4 231 | or (charToByteMapping[ulid[14].code] and 0xff.toByte()).toInt().ushr(1)).toByte() 232 | bytes[3] = (charToByteMapping[ulid[14].code].toInt() shl 7 233 | or (charToByteMapping[ulid[15].code].toInt() shl 2) 234 | or (charToByteMapping[ulid[16].code] and 0xff.toByte()).toInt().ushr(3)).toByte() 235 | bytes[4] = (charToByteMapping[ulid[16].code].toInt() shl 5 236 | or (charToByteMapping[ulid[17].code].toInt())).toByte() 237 | bytes[5] = (charToByteMapping[ulid[18].code].toInt() shl 3 238 | or (charToByteMapping[ulid[19].code] and 0xff.toByte()).toInt().ushr(2)).toByte() 239 | bytes[6] = (charToByteMapping[ulid[19].code].toInt() shl 6 240 | or (charToByteMapping[ulid[20].code].toInt() shl 1) 241 | or (charToByteMapping[ulid[21].code] and 0xff.toByte()).toInt().ushr(4)).toByte() 242 | bytes[7] = (charToByteMapping[ulid[21].code].toInt() shl 4 243 | or (charToByteMapping[ulid[22].code] and 0xff.toByte()).toInt().ushr(1)).toByte() 244 | bytes[8] = (charToByteMapping[ulid[22].code].toInt() shl 7 245 | or (charToByteMapping[ulid[23].code].toInt() shl 2) 246 | or (charToByteMapping[ulid[24].code] and 0xff.toByte()).toInt().ushr(3)).toByte() 247 | bytes[9] = (charToByteMapping[ulid[24].code].toInt() shl 5 248 | or (charToByteMapping[ulid[25].code].toInt())).toByte() 249 | 250 | return bytes 251 | } 252 | } 253 | } --------------------------------------------------------------------------------