hienlt0610
10/30/2019 - 10:42 AM

[Java] ULID Generate Unique ID

/**
 * MIT License
 * 
 * Copyright (c) 2016 Azamshul Azizy
 * 
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 * 
 * The above copyright notice and this permission notice shall be included in all
 * copies or substantial portions of the Software.
 * 
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 */
package io.azam.ulidj;

import java.util.Random;

/**
 * ULID string generator and parser class, using Crockford Base32 encoding. Only
 * upper case letters are used for generation. Parsing allows upper and lower
 * case letters, and i and l will be treated as 1 and o will be treated as 0.
 * <br>
 * <br>
 * ULID generation examples:<br>
 * 
 * <pre>
 * String ulid1 = ULID.random();
 * String ulid2 = ULID.random(ThreadLocalRandom.current());
 * String ulid3 = ULID.random(SecureRandom.newInstance("SHA1PRNG"));
 * byte[] entropy = new byte[] { 0x0, 0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7, 0x8, 0x9 };
 * String ulid4 = ULID.generate(System.currentTimeMillis(), entropy);
 * </pre>
 * 
 * ULID parsing examples:<br>
 * 
 * <pre>
 * String ulid = "003JZ9J6G80123456789abcdef";
 * assert ULID.isValid(ulid);
 * long ts = ULID.getTimestamp(ulid);
 * assert ts == 123456789000L;
 * byte[] entropy = ULID.getEntropy(ulid);
 * </pre>
 * 
 * @author azam
 * @since 0.0.1
 * 
 * @see <a href="http://www.crockford.com/wrmg/base32.html">Base32 Encoding</a>
 * @see <a href="https://github.com/alizain/ulid">ULID</a>
 */
public class ULID {
	/**
	 * ULID string length.
	 */
	public static final int ULID_LENGTH = 26;

	/**
	 * Minimum allowed timestamp value.
	 */
	public static final long MIN_TIME = 0x0L;

	/**
	 * Maximum allowed timestamp value.
	 */
	public static final long MAX_TIME = 0x0000ffffffffffffL;

	/**
	 * Base32 characters mapping
	 */
	private static final char[] C = new char[] { //
			0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, //
			0x38, 0x39, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, //
			0x47, 0x48, 0x4a, 0x4b, 0x4d, 0x4e, 0x50, 0x51, //
			0x52, 0x53, 0x54, 0x56, 0x57, 0x58, 0x59, 0x5a };

	/**
	 * {@code char} to {@code byte} O(1) mapping with alternative chars mapping
	 */
	private static final byte[] V = new byte[] { //
			(byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, //
			(byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, //
			(byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, //
			(byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, //
			(byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, //
			(byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, //
			(byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, //
			(byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, //
			(byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, //
			(byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, //
			(byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, //
			(byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, //
			(byte) 0x00, (byte) 0x01, (byte) 0x02, (byte) 0x03, //
			(byte) 0x04, (byte) 0x05, (byte) 0x06, (byte) 0x07, //
			(byte) 0x08, (byte) 0x09, (byte) 0xff, (byte) 0xff, //
			(byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, //
			(byte) 0xff, (byte) 0x0a, (byte) 0x0b, (byte) 0x0c, //
			(byte) 0x0d, (byte) 0x0e, (byte) 0x0f, (byte) 0x10, //
			(byte) 0x11, (byte) 0xff, (byte) 0x12, (byte) 0x13, //
			(byte) 0xff, (byte) 0x14, (byte) 0x15, (byte) 0xff, //
			(byte) 0x16, (byte) 0x17, (byte) 0x18, (byte) 0x19, //
			(byte) 0x1a, (byte) 0xff, (byte) 0x1b, (byte) 0x1c, //
			(byte) 0x1d, (byte) 0x1e, (byte) 0x1f, (byte) 0xff, //
			(byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, //
			(byte) 0xff, (byte) 0x0a, (byte) 0x0b, (byte) 0x0c, //
			(byte) 0x0d, (byte) 0x0e, (byte) 0x0f, (byte) 0x10, //
			(byte) 0x11, (byte) 0xff, (byte) 0x12, (byte) 0x13, //
			(byte) 0xff, (byte) 0x14, (byte) 0x15, (byte) 0xff, //
			(byte) 0x16, (byte) 0x17, (byte) 0x18, (byte) 0x19, //
			(byte) 0x1a, (byte) 0xff, (byte) 0x1b, (byte) 0x1c, //
			(byte) 0x1d, (byte) 0x1e, (byte) 0x1f, (byte) 0xff, //
			(byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, //
			(byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, //
			(byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, //
			(byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, //
			(byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, //
			(byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, //
			(byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, //
			(byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, //
			(byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, //
			(byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, //
			(byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, //
			(byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, //
			(byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, //
			(byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, //
			(byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, //
			(byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, //
			(byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, //
			(byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, //
			(byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, //
			(byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, //
			(byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, //
			(byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, //
			(byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, //
			(byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, //
			(byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, //
			(byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, //
			(byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, //
			(byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, //
			(byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, //
			(byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, //
			(byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, //
			(byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, //
			(byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff //
	};

	/**
	 * Generate random ULID string using {@link java.util.Random} instance.
	 * 
	 * @return ULID string
	 */
	public static String random() {
		byte[] entropy = new byte[10];
		Random random = new Random();
		random.nextBytes(entropy);
		return generate(System.currentTimeMillis(), entropy);
	}

	/**
	 * Generate random ULID string using provided {@link java.util.Random}
	 * instance.
	 * 
	 * @param random
	 *            {@link java.util.Random} instance
	 * @return ULID string
	 */
	public static String random(Random random) {
		byte[] entropy = new byte[10];
		random.nextBytes(entropy);
		return generate(System.currentTimeMillis(), entropy);
	}

	/**
	 * Generate ULID from Unix epoch timestamp in millisecond and entropy bytes.
	 * Throws {@link java.lang.IllegalArgumentException} if timestamp is less
	 * than {@value #MIN_TIME}, is more than {@value #MAX_TIME}, or entropy
	 * bytes is null or less than 10 bytes.
	 * 
	 * @param time
	 *            Unix epoch timestamp in millisecond
	 * @param entropy
	 *            Entropy bytes
	 * @return ULID string
	 */
	public static String generate(long time, byte[] entropy) {
		if (time < MIN_TIME || time > MAX_TIME || entropy == null || entropy.length < 10) {
			throw new IllegalArgumentException("Time is too long, or entropy is less than 10 bytes or null");
		}

		char[] chars = new char[26];

		// time
		chars[0] = C[((byte) (time >>> 45)) & 0x1f];
		chars[1] = C[((byte) (time >>> 40)) & 0x1f];
		chars[2] = C[((byte) (time >>> 35)) & 0x1f];
		chars[3] = C[((byte) (time >>> 30)) & 0x1f];
		chars[4] = C[((byte) (time >>> 25)) & 0x1f];
		chars[5] = C[((byte) (time >>> 20)) & 0x1f];
		chars[6] = C[((byte) (time >>> 15)) & 0x1f];
		chars[7] = C[((byte) (time >>> 10)) & 0x1f];
		chars[8] = C[((byte) (time >>> 5)) & 0x1f];
		chars[9] = C[((byte) (time)) & 0x1f];

		// entropy
		chars[10] = C[(byte) ((entropy[0] & 0xff) >>> 3)];
		chars[11] = C[(byte) (((entropy[0] << 2) | ((entropy[1] & 0xff) >>> 6)) & 0x1f)];
		chars[12] = C[(byte) (((entropy[1] & 0xff) >>> 1) & 0x1f)];
		chars[13] = C[(byte) (((entropy[1] << 4) | ((entropy[2] & 0xff) >>> 4)) & 0x1f)];
		chars[14] = C[(byte) (((entropy[2] << 5) | ((entropy[3] & 0xff) >>> 7)) & 0x1f)];
		chars[15] = C[(byte) (((entropy[3] & 0xff) >>> 2) & 0x1f)];
		chars[16] = C[(byte) (((entropy[3] << 3) | ((entropy[4] & 0xff) >>> 5)) & 0x1f)];
		chars[17] = C[(byte) (entropy[4] & 0x1f)];
		chars[18] = C[(byte) ((entropy[5] & 0xff) >>> 3)];
		chars[19] = C[(byte) (((entropy[5] << 2) | ((entropy[6] & 0xff) >>> 6)) & 0x1f)];
		chars[20] = C[(byte) (((entropy[6] & 0xff) >>> 1) & 0x1f)];
		chars[21] = C[(byte) (((entropy[6] << 4) | ((entropy[7] & 0xff) >>> 4)) & 0x1f)];
		chars[22] = C[(byte) (((entropy[7] << 5) | ((entropy[8] & 0xff) >>> 7)) & 0x1f)];
		chars[23] = C[(byte) (((entropy[8] & 0xff) >>> 2) & 0x1f)];
		chars[24] = C[(byte) (((entropy[8] << 3) | ((entropy[9] & 0xff) >>> 5)) & 0x1f)];
		chars[25] = C[(byte) (entropy[9] & 0x1f)];

		return new String(chars);
	}

	/**
	 * Checks ULID string validity.
	 * 
	 * @param ulid
	 *            ULID string
	 * @return true if ULID string is valid
	 */
	public static boolean isValid(CharSequence ulid) {
		if (ulid == null || ulid.length() != ULID_LENGTH) {
			return false;
		}
		for (int i = 0; i < ULID_LENGTH; i++) {
			/** We only care for chars between 0x00 and 0xff. */
			char c = ulid.charAt(i);
			if (c < 0 || c > V.length || V[c] == (byte) 0xff) {
				return false;
			}
		}
		return true;
	}

	/**
	 * Extract and return the timestamp part from ULID. Expects a valid ULID
	 * string. Call {@link io.azam.ulidj.ULID#isValid(CharSequence)} and check
	 * validity before calling this method if you do not trust the origin of the
	 * ULID string.
	 * 
	 * @param ulid
	 *            ULID string
	 * @return Unix epoch timestamp in millisecond
	 */
	public static long getTimestamp(CharSequence ulid) {
		return (long) V[ulid.charAt(0)] << 45 //
				| (long) V[ulid.charAt(1)] << 40 //
				| (long) V[ulid.charAt(2)] << 35 //
				| (long) V[ulid.charAt(3)] << 30 //
				| (long) V[ulid.charAt(4)] << 25 //
				| (long) V[ulid.charAt(5)] << 20 //
				| (long) V[ulid.charAt(6)] << 15 //
				| (long) V[ulid.charAt(7)] << 10 //
				| (long) V[ulid.charAt(8)] << 5 //
				| (long) V[ulid.charAt(9)];
	}

	/**
	 * Extract and return the entropy part from ULID. Expects a valid ULID
	 * string. Call {@link io.azam.ulidj.ULID#isValid(CharSequence)} and check
	 * validity before calling this method if you do not trust the origin of the
	 * ULID string.
	 * 
	 * @param ulid
	 *            ULID string
	 * @return Entropy bytes
	 */
	public static byte[] getEntropy(CharSequence ulid) {
		byte[] bytes = new byte[10];
		bytes[0] = (byte) ((V[ulid.charAt(10)] << 3) //
				| (V[ulid.charAt(11)] & 0xff) >>> 2);
		bytes[1] = (byte) ((V[ulid.charAt(11)] << 6) //
				| V[ulid.charAt(12)] << 1 //
				| (V[ulid.charAt(13)] & 0xff) >>> 4);
		bytes[2] = (byte) ((V[ulid.charAt(13)] << 4) //
				| (V[ulid.charAt(14)] & 0xff) >>> 1);
		bytes[3] = (byte) ((V[ulid.charAt(14)] << 7) //
				| V[ulid.charAt(15)] << 2 //
				| (V[ulid.charAt(16)] & 0xff) >>> 3);
		bytes[4] = (byte) ((V[ulid.charAt(16)] << 5) //
				| V[ulid.charAt(17)]);
		bytes[5] = (byte) ((V[ulid.charAt(18)] << 3) //
				| (V[ulid.charAt(19)] & 0xff) >>> 2);
		bytes[6] = (byte) ((V[ulid.charAt(19)] << 6) //
				| V[ulid.charAt(20)] << 1 //
				| (V[ulid.charAt(21)] & 0xff) >>> 4);
		bytes[7] = (byte) ((V[ulid.charAt(21)] << 4) //
				| (V[ulid.charAt(22)] & 0xff) >>> 1);
		bytes[8] = (byte) ((V[ulid.charAt(22)] << 7) //
				| V[ulid.charAt(23)] << 2 //
				| (V[ulid.charAt(24)] & 0xff) >>> 3);
		bytes[9] = (byte) ((V[ulid.charAt(24)] << 5) //
				| V[ulid.charAt(25)]);
		return bytes;
	}
}