/* * Copyright (c) 2011-2015 Jeppetto and Jonathan Thompson * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.iternine.jeppetto.dao.id; import java.util.Arrays; import java.util.concurrent.ThreadLocalRandom; /** * This class produces randomly generated numbers and then encodes them using the characters provided in the * constructor. The length of the character array corresponds to the base of the encoding (i.e. an array containing * the characters 0-9 and a-f would produce a hexadecimal representation). * * The number of possible identifiers is configured by the bitsPerId, the number of bits that make up each random * identifier. The higher this number, the less likely an identifier is to have a collision. The downside is that * more bits correspond to longer identifiers. To minimize the size, identifiers can be encoded into a higher base. * The more characters available to encode with, the shorter an identifier can be for a given number of bits. * * Here are examples of identifiers with two different bit lengths and the size of the generated strings for * different bases. As a note, UUIDs use 128 bits and are generally represented in hexadecimal. * * Exponent: 64 (2^64 possible identifiers) * Base 10 = 81559628491067131560 (length = 20) * Base 16 = 857dbd8d413736a5 (length = 16) * Base 36 = 0uykurg7cfhd1 (length = 13) * Base 62 = IJkOeiKp8L7 (length = 11) * * Exponent: 128 (2^128 possible identifiers) * Base 10 = 34391415693059914568711289921679647638 (length = 39) * Base 16 = fc45accd3d5d9ca5c7239174a693f147 (length = 32) * Base 36 = f4nz9v3p3bpd1sftuut9hglkr1 (length = 26) * Base 62 = RJJ4WidpgN7yib4ltd67Y9 (length = 22) * * Note that for a given exponent and character set, the generated values will be padded to ensure they are the * same length. Also, this class uses ThreadLocalRandom to produce random numbers, so it should not have contention * issues at higher loads. */ public class BaseNIdGenerator implements IdGenerator<String> { //------------------------------------------------------------- // Constants //------------------------------------------------------------- public static final char[] BASE62_CHARACTERS = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ".toCharArray(); public static final char[] BASE36_CHARACTERS = Arrays.copyOfRange(BASE62_CHARACTERS, 0, 36); public static final char[] BASE16_CHARACTERS = Arrays.copyOfRange(BASE62_CHARACTERS, 0, 16); public static final char[] BASE10_CHARACTERS = Arrays.copyOfRange(BASE62_CHARACTERS, 0, 10); private static final int BITS_PER_LONG = 64; //------------------------------------------------------------- // Variables - Private //------------------------------------------------------------- private final int bitsPerId; private final char[] characters; private final int base; private final int encodedIdLength; //------------------------------------------------------------- // Constructors //------------------------------------------------------------- /** * Construct a new EncodedIdGenerator that randomly produces ids whose representation is encoded using the provided * characters. * * @param bitsPerId The number of bits produced for each identifier (UUIDs, for example, have 128 bits per id). * @param characters The characters used to encode the generated id. */ public BaseNIdGenerator(int bitsPerId, char[] characters) { if (bitsPerId < 1) { throw new IllegalArgumentException("bitsPerId must be >= 1"); } if (characters == null || characters.length < 2) { throw new IllegalArgumentException("characters must be non-null and have at least two values"); } this.bitsPerId = bitsPerId; this.characters = characters; this.base = characters.length; this.encodedIdLength = (int) Math.ceil(bitsPerId / (Math.log(base) / Math.log(2))); } //------------------------------------------------------------- // Implementation - IdGenerator //------------------------------------------------------------- @Override public String generateId() { StringBuilder sb = new StringBuilder(); int bitsLeft = bitsPerId; while (bitsLeft > 0) { long random = ThreadLocalRandom.current().nextLong(); if (bitsLeft < BITS_PER_LONG) { random >>>= (BITS_PER_LONG - bitsLeft); } encode(random, sb); bitsLeft -= BITS_PER_LONG; } while (sb.length() < encodedIdLength) { sb.append(characters[0]); } // It's possible we could generate a string larger than we expect. Cut it down if needed. return sb.length() == encodedIdLength ? sb.toString() : sb.substring(0, encodedIdLength); } //------------------------------------------------------------- // Methods - Private //------------------------------------------------------------- private void encode(long bits, StringBuilder sb) { if (bits < 0) { sb.append(characters[(int) ((bits % base + base) % base)]); bits /= -base; } while (bits > 0) { sb.append(characters[(int) (bits % base)]); bits /= base; } } }