├── 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 |
7 |
8 |
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 |
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 | | [](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 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
34 |
46 |
47 |
48 |
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 | }
--------------------------------------------------------------------------------