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}