001// -------------------------------------------------------------------------------- 002// Copyright 2002-2024 Echo Three, LLC 003// 004// Licensed under the Apache License, Version 2.0 (the "License"); 005// you may not use this file except in compliance with the License. 006// You may obtain a copy of the License at 007// 008// http://www.apache.org/licenses/LICENSE-2.0 009// 010// Unless required by applicable law or agreed to in writing, software 011// distributed under the License is distributed on an "AS IS" BASIS, 012// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 013// See the License for the specific language governing permissions and 014// limitations under the License. 015// -------------------------------------------------------------------------------- 016 017/** 018 * MIT License 019 * 020 * Copyright (c) 2016 Azamshul Azizy 021 * 022 * Permission is hereby granted, free of charge, to any person obtaining a copy 023 * of this software and associated documentation files (the "Software"), to deal 024 * in the Software without restriction, including without limitation the rights 025 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 026 * copies of the Software, and to permit persons to whom the Software is 027 * furnished to do so, subject to the following conditions: 028 * 029 * The above copyright notice and this permission notice shall be included in all 030 * copies or substantial portions of the Software. 031 * 032 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 033 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 034 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 035 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 036 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 037 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 038 * SOFTWARE. 039 */ 040 041// Based on: https://github.com/azam/ulidj 042 043package com.echothree.util.server.ulid; 044 045import com.echothree.util.server.persistence.EncryptionUtils; 046import com.google.common.primitives.Longs; 047import java.util.regex.Matcher; 048import java.util.regex.Pattern; 049 050public class ULID { 051 052 private static final int RANDOM_LENGTH = 10; 053 private static final int ULID_LENGTH = 26; 054 055 private static final Pattern ULID = Pattern.compile("^([0123456789ABCDEFGHJKMNPQRSTVWXYZ]{" + ULID_LENGTH + "})$"); 056 057 private static final char[] base32 = { 058 '0', '1', '2', '3', '4', '5', '6', '7', 059 '8', '9', 'A', 'B', 'C', 'D', 'E', 'F', 060 'G', 'H', 'J', 'K', 'M', 'N', 'P', 'Q', 061 'R', 'S', 'T', 'V', 'W', 'X', 'Y', 'Z' 062 }; 063 064 // Time must be only 48 bits, not 64, and cannot be signed. 065 private static final long MAX_TIME = 0x0000ffffffffffffL; 066 067 private static byte[] getTime(Long time) { 068 // If time is null, then we use currentTimeMillis 069 time = time == null ? System.currentTimeMillis() : time; 070 071 if (time > MAX_TIME) { 072 throw new RuntimeException("time > MAX_TIME"); 073 } 074 075 return Longs.toByteArray(time); 076 } 077 078 private static byte[] getRandom() { 079 byte[] random = new byte[RANDOM_LENGTH]; 080 081 EncryptionUtils.getInstance().getRandom().nextBytes(random); 082 083 return random; 084 } 085 086 private static String encodeTimeAndRandom(byte[] time, byte[] random) { 087 char[] ulid = new char[ULID_LENGTH]; 088 089 // 0 1 2 3 090 // 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 091 // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ 092 // |0 0 0*0 0 0 0 0*0 0 0 0 0*0 0 0|0 0*0 0 0 0 0*0 0 0 0 0*0 0 0 0| 093 // | time[2] | time[3] | time[4] | time[5] | 094 // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ 095 // |0*0 0 0 0 0*0 0 0 0 0*0 0 0 0 0|0 0 0 0 0*0 0 0 0 0*0 0 0 0 0*0| 096 // | time[6] | time[7] | random[0] | random[1] | 097 // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ 098 // |0 0 0 0*0 0 0 0 0*0 0 0 0 0*0 0|0 0 0*0 0 0 0 0*0 0 0 0 0*0 0 0| 099 // | random[2] | random[3] | random[4] | random[5] | 100 // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ 101 // |0 0*0 0 0 0 0*0 0 0 0 0*0 0 0 0|0*0 0 0 0 0*0 0 0 0 0*0 0 0 0 0| 102 // | random[6] | random[7] | random[8] | random[9] | 103 // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ 104 105 // Time Portion 106 ulid[0] = base32[(byte)((time[2] & 0xff) >>> 5)]; 107 ulid[1] = base32[(byte)(time[2] & 0x1f)]; 108 ulid[2] = base32[(byte)((time[3] & 0xff) >>> 3)]; 109 ulid[3] = base32[(byte)(((time[3] << 2) | ((time[4] & 0xff) >>> 6)) & 0x1f)]; 110 ulid[4] = base32[(byte)(((time[4] & 0xff) >>> 1) & 0x1f)]; 111 ulid[5] = base32[(byte)(((time[4] << 4) | ((time[5] & 0xff) >>> 4)) & 0x1f)]; 112 ulid[6] = base32[(byte)(((time[5] << 1) | ((time[6] & 0xff) >>> 7)) & 0x1f)]; 113 ulid[7] = base32[(byte)(((time[6] & 0xff) >>> 2) & 0x1f)]; 114 ulid[8] = base32[(byte)(((time[6] << 3) | ((time[7] & 0xff) >>> 5)) & 0x1f)]; 115 ulid[9] = base32[(byte)(time[7] & 0x1f)]; 116 117 // Random Portion 118 ulid[10] = base32[(byte)((random[0] & 0xff) >>> 3)]; 119 ulid[11] = base32[(byte)(((random[0] << 2) | ((random[1] & 0xff) >>> 6)) & 0x1f)]; 120 ulid[12] = base32[(byte)(((random[1] & 0xff) >>> 1) & 0x1f)]; 121 ulid[13] = base32[(byte)(((random[1] << 4) | ((random[2] & 0xff) >>> 4)) & 0x1f)]; 122 ulid[14] = base32[(byte)(((random[2] << 1) | ((random[3] & 0xff) >>> 7)) & 0x1f)]; 123 ulid[15] = base32[(byte)(((random[3] & 0xff) >>> 2) & 0x1f)]; 124 ulid[16] = base32[(byte)(((random[3] << 3) | ((random[4] & 0xff) >>> 5)) & 0x1f)]; 125 ulid[17] = base32[(byte)(random[4] & 0x1f)]; 126 127 ulid[18] = base32[(byte)((random[5] & 0xff) >>> 3)]; 128 ulid[19] = base32[(byte)(((random[5] << 2) | ((random[6] & 0xff) >>> 6)) & 0x1f)]; 129 ulid[20] = base32[(byte)(((random[6] & 0xff) >>> 1) & 0x1f)]; 130 ulid[21] = base32[(byte)(((random[6] << 4) | ((random[7] & 0xff) >>> 4)) & 0x1f)]; 131 ulid[22] = base32[(byte)(((random[7] << 1) | ((random[8] & 0xff) >>> 7)) & 0x1f)]; 132 ulid[23] = base32[(byte)(((random[8] & 0xff) >>> 2) & 0x1f)]; 133 ulid[24] = base32[(byte)(((random[8] << 3) | ((random[9] & 0xff) >>> 5)) & 0x1f)]; 134 ulid[25] = base32[(byte)(random[9] & 0x1f)]; 135 136 return new String(ulid); 137 } 138 139 private static String generateUlid(Long time) { 140 return encodeTimeAndRandom(getTime(time), getRandom()); 141 } 142 143 public static ULID randomULID() { 144 return new ULID(generateUlid(null)); 145 } 146 147 public static ULID randomULID(Long time) { 148 return new ULID(generateUlid(time)); 149 } 150 151 public static ULID fromString(String ulid) { 152 Matcher matcher = ULID.matcher(ulid); 153 154 if(!matcher.matches()) { 155 throw new IllegalArgumentException(); 156 } 157 158 return new ULID(ulid); 159 } 160 161 private final String ulid; 162 163 private ULID(String suppliedUlid) { 164 ulid = suppliedUlid; 165 } 166 167 @Override 168 public String toString() { 169 return ulid; 170 } 171}