package ddth.dasp.common.id; import java.math.BigInteger; import java.net.InetAddress; import java.net.NetworkInterface; import java.util.HashMap; import java.util.Map; import java.util.concurrent.atomic.AtomicLong; public class IdGenerator { private final static Map<Long, IdGenerator> cache = new HashMap<Long, IdGenerator>(); private static long macAddr = 0; private final static char[] digits = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z' }; /** * Max radix: 62 */ public final static int MAX_RADIX = digits.length; /* copy from OpenJDK source code, modified by Thanh Nguyen */ /** * Returns the numeric value of the character <code>ch</code> in the * specified radix. * * @param ch * @param radix * @return * @see #MAX_RADIX */ public static int digit(char ch, int radix) { for (int i = 0; i < MAX_RADIX && i < radix; i++) { if (digits[i] == ch) { return i; } } return -1; } /* copy from OpenJDK source code, modified by Thanh Nguyen */ /** * Parses a long value from a string. * * @param s * @param radix * @return * @throws NumberFormatException * @see #MAX_RADIX */ public static long parseLong(String s, int radix) throws NumberFormatException { if (s == null) { throw new NumberFormatException("null"); } if (radix < Character.MIN_RADIX) { throw new NumberFormatException("radix " + radix + " less than " + Character.MIN_RADIX); } if (radix > MAX_RADIX) { throw new NumberFormatException("radix " + radix + " greater than " + MAX_RADIX); } long result = 0; boolean negative = false; int i = 0, len = s.length(); long limit = -Long.MAX_VALUE; long multmin; int digit; if (len > 0) { char firstChar = s.charAt(0); if (firstChar < '0') { // Possible leading "-" if (firstChar == '-') { negative = true; limit = Long.MIN_VALUE; } else { throw new NumberFormatException(s); } if (len == 1) {// Cannot have lone "-" throw new NumberFormatException(s); } i++; } multmin = limit / radix; while (i < len) { // Accumulating negatively avoids surprises near MAX_VALUE digit = digit(s.charAt(i++), radix); if (digit < 0) { throw new NumberFormatException(s); } if (result < multmin) { throw new NumberFormatException(s); } result *= radix; if (result < limit + digit) { throw new NumberFormatException(s); } result -= digit; } } else { throw new NumberFormatException(s); } return negative ? result : -result; } /* copy from OpenJDK source code, modified by Thanh Nguyen */ /** * Converts a long to string. * * @param l * @param radix * @return * @see #MAX_RADIX */ public static String toString(long l, int radix) { if (radix < Character.MIN_RADIX || radix > MAX_RADIX) { radix = 10; } if (radix == 10) { return Long.toString(l); } char[] buf = new char[65]; int charPos = 64; boolean negative = (l < 0); if (!negative) { l = -l; } while (l <= -radix) { buf[charPos--] = digits[(int) (-(l % radix))]; l = l / radix; } buf[charPos] = digits[(int) (-l)]; if (negative) { buf[--charPos] = '-'; } return new String(buf, charPos, (65 - charPos)); } /* copy from OpenJDK source code, modified by Thanh Nguyen */ /** * Converts a long to unsigned string. * * @param i * @param shift * @return * @see #MAX_RADIX */ public static String toUnsignedString(long i, int shift) { char[] buf = new char[64]; int charPos = 64; int radix = 1 << shift; long mask = radix - 1; do { buf[--charPos] = digits[(int) (i & mask)]; i >>>= shift; } while (i != 0); return new String(buf, charPos, (64 - charPos)); } public static long getMacAddr() { if (macAddr == 0) { try { InetAddress ip = InetAddress.getLocalHost(); NetworkInterface network = NetworkInterface.getByInetAddress(ip); byte[] mac = network.getHardwareAddress(); for (byte temp : mac) { macAddr = (macAddr << 8) | ((int) temp & 0xFF); } } catch (Exception e) { macAddr = System.currentTimeMillis(); } } return macAddr; } /** * Gets an {@link IdGenerator} instance for a node. * * @param nodeId * @return */ public static IdGenerator getInstance(long nodeId) { IdGenerator idGen = null; synchronized (cache) { idGen = cache.get(nodeId); if (idGen == null) { idGen = new IdGenerator(nodeId); idGen.init(); cache.put(nodeId, idGen); } } return idGen; } /** * Disposes an unused {@link IdGenerator}. * * @param idGen */ public static void disposeInstance(IdGenerator idGen) { if (idGen != null) { synchronized (cache) { // idGen.destroy(); long nodeId = idGen.nodeId; IdGenerator temp = cache.get(nodeId); if (temp != null) { // if (temp != idGen) { temp.destroy(); // } cache.remove(nodeId); } } } } private final static long MASK_TIMESTAMP_MINI = 0x1FFFFFFFFFFL; // 41 bits private final static long MASK_NODE_ID_MINI = 0x0L; // 0 bits private final static long MASK_SEQUENCE_MINI = 0x7FL; // 7 bits private final static long MAX_SEQUENCE_MINI = 0x7FL; // 7 bits private final static long SHIFT_TIMESTAMP_MINI = 7L; private final static long SHIFT_NODE_ID_MINI = 7L; private final static long MASK_TIMESTAMP_48 = 0xFFFFFFFFL; // 32 bits private final static long MASK_NODE_ID_48 = 0x7L; // 3 bits private final static long MASK_SEQUENCE_48 = 0x1FFFL; // 13 bits private final static long MAX_SEQUENCE_48 = 0x1FFFL; // 13 bits private final static long SHIFT_TIMESTAMP_48 = 16L; private final static long SHIFT_NODE_ID_48 = 13L; private final static long MASK_TIMESTAMP_64 = 0x1FFFFFFFFFFL; // 41 bits private final static long MASK_NODE_ID_64 = 0x3FFL; // 10 bits private final static long MASK_SEQUENCE_64 = 0x1FFFL; // 13 bits private final static long MAX_SEQUENCE_64 = 0x1FFFL; // 13 bits private final static long SHIFT_TIMESTAMP_64 = 23L; private final static long SHIFT_NODE_ID_64 = 13L; // private final static long TIMESTAMP_EPOCH = 1330534800000L; // 1-Mar-2012 public final static long TIMESTAMP_EPOCH = 1362070800000L; // 1-Mar-2013 private final static long MASK_NODE_ID_128 = 0xFFFFFFFFFFFFL; // 48 bits private final static long MASK_SEQUENCE_128 = 0xFFFF; // 16 bits private final static long MAX_SEQUENCE_128 = 0xFFFF; // 16 bits private final static long SHIFT_TIMESTAMP_128 = 64L; private final static long SHIFT_NODE_ID_128 = 16L; private long nodeId; private long template48, template64, templateMini; private BigInteger template128; private AtomicLong sequenceMillisec = new AtomicLong(); private AtomicLong sequenceSecond = new AtomicLong(); private AtomicLong lastTimestampMillisec = new AtomicLong(); private AtomicLong lastTimestampSecond = new AtomicLong(); protected IdGenerator(long nodeId) { this.nodeId = nodeId; } protected void init() { this.templateMini = (this.nodeId & MASK_NODE_ID_MINI) << SHIFT_NODE_ID_MINI; this.template64 = (this.nodeId & MASK_NODE_ID_64) << SHIFT_NODE_ID_64; this.template48 = (this.nodeId & MASK_NODE_ID_48) << SHIFT_NODE_ID_48; this.template128 = BigInteger .valueOf((this.nodeId & MASK_NODE_ID_128) << SHIFT_NODE_ID_128); } protected void destroy() { // EMPTY } public static long waitTillNextMillisec(long currentMillisec) { long nextMillisec = System.currentTimeMillis(); for (; nextMillisec <= currentMillisec; nextMillisec = System.currentTimeMillis()) { Thread.yield(); } return nextMillisec; } public static long waitTillNextSecond(long currentSecond) { long nextSecond = System.currentTimeMillis() / 1000L; for (; nextSecond <= currentSecond; nextSecond = System.currentTimeMillis() / 1000) { try { Thread.sleep(100); } catch (InterruptedException e) { } } return nextSecond; } public static long waitTillNextBlock(long currentBlock, long blockSize) { long nextBlock = System.currentTimeMillis() / blockSize; for (; nextBlock <= currentBlock; nextBlock = System.currentTimeMillis() / blockSize) { try { Thread.sleep(1000); } catch (InterruptedException e) { } } return nextBlock; } /* tiny id */ /** * Extracts the (UNIX) timestamp from a tiny id. * * @param idTiny * @return the UNIX timestamp (milliseconds) */ public static long extractTimestampTiny(long idTiny) { final long division = 10000L; final long seqBits = 16L; final long threshold = 0x800000000L; long timestamp = idTiny > threshold ? idTiny >> seqBits : idTiny; return timestamp * division + TIMESTAMP_EPOCH; } /** * Generates a tiny id (various bit long). * * Format: <41-bit: timestamp><0-7bit:sequence number> * * Where timestamp is in second, minus the epoch. * * Note: the generated id is NOT in order! * * @return */ synchronized public long generateIdTiny() { final long division = 10000L; final long seqBits = 16L; final long maxSeqTiny = 0xFFFFL; // 16 bits long timestamp = System.currentTimeMillis() / division; long sequence = 0; if (timestamp == this.lastTimestampSecond.get()) { // increase sequence sequence = this.sequenceSecond.incrementAndGet(); if (sequence > maxSeqTiny) { // reset sequence this.sequenceSecond.set(0); waitTillNextBlock(timestamp, division); return generateIdTiny(); } } else { // reset sequence this.sequenceSecond.set(sequence); this.lastTimestampSecond.set(timestamp); } timestamp -= TIMESTAMP_EPOCH / division; long result = timestamp; if (sequence != 0) { result = (result << seqBits) | sequence; } return result; } /* tiny id */ /* 48-bit id */ /** * Extracts the (UNIX) timestamp from a 48-bit id. * * @param id48 * @return the UNIX timestamp (milliseconds) */ public static long extractTimestamp48(long id48) { long timestamp = (id48 >> SHIFT_TIMESTAMP_48) + TIMESTAMP_EPOCH; return timestamp; } /** * Extracts the (UNIX) timestamp from a 48-bit hex id. * * @param id48hex * @return the UNIX timestamp (milliseconds) */ public static long extractTimestamp48(String id48hex) { long id48 = Long.parseLong(id48hex, 16); return extractTimestamp64(id48); } /** * Extracts the (UNIX) timestamp from a 48-bit ASCII id (radix 36). * * @param id48ascii * @return the UNIX timestamp (milliseconds) */ public static long extractTimestamp48Ascii(String id48ascii) { long id48 = Long.parseLong(id48ascii, Character.MAX_RADIX); return extractTimestamp64(id48); } /** * Generates a 48-bit id. * * Format: <32-bit: timestamp><3-bit: node id><13 bit: sequence number> * * Where timestamp is in millisec, minus the epoch. * * @return */ synchronized public long generateId48() { long timestamp = System.currentTimeMillis(); long sequence = 0; if (timestamp == this.lastTimestampMillisec.get()) { // increase sequence sequence = this.sequenceMillisec.incrementAndGet(); if (sequence > MAX_SEQUENCE_48) { // reset sequence this.sequenceMillisec.set(0); waitTillNextMillisec(timestamp); return generateId48(); } } else { // reset sequence this.sequenceMillisec.set(sequence); this.lastTimestampMillisec.set(timestamp); } timestamp = (timestamp - TIMESTAMP_EPOCH) & MASK_TIMESTAMP_48; long result = timestamp << SHIFT_TIMESTAMP_48 | template48 | (sequence & MASK_SEQUENCE_48); return result; } /** * Generate a 48-bit id as hex string. * * @return */ public String generateId48Hex() { long id = generateId48(); return Long.toHexString(id); } /** * Generate a 48-bit id as ASCII string (radix 36). * * @return */ public String generateId48Ascii() { long id = generateId48(); return Long.toString(id, Character.MAX_RADIX); } /* 48-bit id */ /* mini id */ /** * Extracts the (UNIX) timestamp from a mini id. * * @param idMini * @return the UNIX timestamp (milliseconds) */ public static long extractTimestampMini(long idMini) { long timestamp = (idMini >> SHIFT_TIMESTAMP_MINI) + TIMESTAMP_EPOCH; return timestamp; } /** * Extracts the (UNIX) timestamp from a mini hex id. * * @param idMinihex * @return the UNIX timestamp (milliseconds) */ public static long extractTimestampMini(String idMinihex) { long idMini = Long.parseLong(idMinihex, 16); return extractTimestampMini(idMini); } /** * Extracts the (UNIX) timestamp from a mini ASCII id (radix 36). * * @param idMiniascii * @return the UNIX timestamp (milliseconds) */ public static long extractTimestampMiniAscii(String idMiniAscii) { long idMini = Long.parseLong(idMiniAscii, Character.MAX_RADIX); return extractTimestampMini(idMini); } /** * Generates a mini id (48 bit long). * * Format: <41-bit: timestamp><0-bit: node id><7 bit: sequence number> * * Where timestamp is in millisec, minus the epoch. * * @return */ synchronized public long generateIdMini() { long timestamp = System.currentTimeMillis(); long sequence = 0; if (timestamp == this.lastTimestampMillisec.get()) { // increase sequence sequence = this.sequenceMillisec.incrementAndGet(); if (sequence > MAX_SEQUENCE_MINI) { // reset sequence this.sequenceMillisec.set(0); waitTillNextMillisec(timestamp); return generateIdMini(); } } else { // reset sequence this.sequenceMillisec.set(sequence); this.lastTimestampMillisec.set(timestamp); } timestamp = (timestamp - TIMESTAMP_EPOCH) & MASK_TIMESTAMP_MINI; long result = timestamp << SHIFT_TIMESTAMP_MINI | templateMini | (sequence & MASK_SEQUENCE_MINI); return result; } /** * Generate a mini id as hex string. * * @return */ public String generateIdMiniHex() { long id = generateIdMini(); return Long.toHexString(id); } /** * Generate a mini id as ASCII string (radix 36). * * @return */ public String generateIdMiniAscii() { long id = generateIdMini(); return Long.toString(id, Character.MAX_RADIX); } /* mini id */ /* 64-bit id */ /** * Extracts the (UNIX) timestamp from a 64-bit id. * * @param id64 * @return the UNIX timestamp (milliseconds) */ public static long extractTimestamp64(long id64) { long timestamp = (id64 >> SHIFT_TIMESTAMP_64) + TIMESTAMP_EPOCH; return timestamp; } /** * Extracts the (UNIX) timestamp from a 64-bit hex id. * * @param id64hex * @return the UNIX timestamp (milliseconds) */ public static long extractTimestamp64(String id64hex) { long id64 = Long.parseLong(id64hex, 16); return extractTimestamp64(id64); } /** * Extracts the (UNIX) timestamp from a 64-bit ASCII id (radix 36). * * @param id64ascii * @return the UNIX timestamp (milliseconds) */ public static long extractTimestamp64Ascii(String id64ascii) { long id64 = Long.parseLong(id64ascii, Character.MAX_RADIX); return extractTimestamp64(id64); } /** * Generates a 64-bit id. * * Format: <41-bit: timestamp><10-bit: node id><13 bit: sequence number> * * Where timestamp is in millisec, minus the epoch. * * @return */ synchronized public long generateId64() { long timestamp = System.currentTimeMillis(); long sequence = 0; if (timestamp == this.lastTimestampMillisec.get()) { // increase sequence sequence = this.sequenceMillisec.incrementAndGet(); if (sequence > MAX_SEQUENCE_64) { // reset sequence this.sequenceMillisec.set(0); waitTillNextMillisec(timestamp); return generateId64(); } } else { // reset sequence this.sequenceMillisec.set(sequence); this.lastTimestampMillisec.set(timestamp); } timestamp = (timestamp - TIMESTAMP_EPOCH) & MASK_TIMESTAMP_64; long result = timestamp << SHIFT_TIMESTAMP_64 | template64 | (sequence & MASK_SEQUENCE_64); return result; } /** * Generate a 64-bit id as hex string. * * @return */ public String generateId64Hex() { long id = generateId64(); return Long.toHexString(id); } /** * Generate a 64-bit id as ASCII string (radix 36). * * @return */ public String generateId64Ascii() { long id = generateId64(); return Long.toString(id, Character.MAX_RADIX); } /* 64-bit id */ /* 128-bit id */ /** * Generates a 128-bit id. * * Format: <64-bit: timestamp><48-bit: node id><16 bit: sequence number> * * Where timestamp is in millisec. * * @return */ synchronized public BigInteger generateId128() { long timestamp = System.currentTimeMillis(); long sequence = 0; if (timestamp == this.lastTimestampMillisec.get()) { // increase sequence sequence = this.sequenceMillisec.incrementAndGet(); if (sequence > MAX_SEQUENCE_128) { // reset sequence this.sequenceMillisec.set(0); waitTillNextMillisec(timestamp); return generateId128(); } } else { // reset sequence this.sequenceMillisec.set(sequence); this.lastTimestampMillisec.set(timestamp); } BigInteger biSequence = BigInteger.valueOf(sequence & MASK_SEQUENCE_128); BigInteger biResult = BigInteger.valueOf(timestamp); biResult = biResult.shiftLeft((int) SHIFT_TIMESTAMP_128); biResult = biResult.or(template128).or(biSequence); return biResult; } /** * Generate a 128-bit id as hex string. * * @return */ public String generateId128Hex() { BigInteger id = generateId128(); return id.toString(16); } /** * Generate a 128-bit id as ASCII string (radix 36). * * @return */ public String generateId128Ascii() { BigInteger id = generateId128(); return id.toString(Character.MAX_RADIX); } /** * Extracts the (UNIX) timestamp from a 128-bit id. * * @param id128 * @return the UNIX timestamp (milliseconds) */ public static long extractTimestamp128(BigInteger id128) { BigInteger result = id128.shiftRight((int) SHIFT_TIMESTAMP_128); return result.longValue(); } /** * Extracts the (UNIX) timestamp from a 128-bit hex id. * * @param id128hex * @return the UNIX timestamp (milliseconds) */ public static long extractTimestamp128(String id128hex) { BigInteger id128 = new BigInteger(id128hex, 16); return extractTimestamp128(id128); } /** * Extracts the (UNIX) timestamp from a 128-bit ASCII id (radix 36). * * @param id128ascii * @return the UNIX timestamp (milliseconds) */ public static long extractTimestamp128Ascii(String id128ascii) { BigInteger id128 = new BigInteger(id128ascii, Character.MAX_RADIX); return extractTimestamp128(id128); } /* 128-bit id */ public static void main(String... args) throws InterruptedException { System.out.println('a' < 'A'); // IdGenerator idGen = IdGenerator.getInstance(getMacAddr()); // int COUNT = 100000; // long[] TEST_DATA = new long[COUNT]; // long id; // long time1 = System.currentTimeMillis(); // for (int i = 0; i < COUNT; i++) { // id = idGen.generateId64(); // TEST_DATA[i] = id; // } // long time2 = System.currentTimeMillis(); // System.out.println(time2 - time1); // // for (int i = 1; i < COUNT; i++) { // if (TEST_DATA[i] == TEST_DATA[i - 1]) { // System.out.println("Error: DATA[" + i + "] vs DATA[" + (i - 1) + // "]: " // + TEST_DATA[i]); // } // } // Calendar cal = Calendar.getInstance(); // cal.set(Calendar.MILLISECOND, 0); // cal.set(Calendar.SECOND, 0); // cal.set(Calendar.MINUTE, 0); // cal.set(Calendar.HOUR_OF_DAY, 0); // cal.set(Calendar.DAY_OF_MONTH, 29); // cal.set(Calendar.MONTH, Calendar.APRIL); // cal.set(Calendar.YEAR, 2012); // System.out.println(cal.getTimeInMillis()); // // System.out.println(new Date(1330534800000L)); // // InetAddress ip; // try { // // ip = InetAddress.getLocalHost(); // System.out.println("Current IP address : " + ip.getHostAddress()); // // NetworkInterface network = NetworkInterface.getByInetAddress(ip); // // byte[] mac = network.getHardwareAddress(); // // System.out.print("Current MAC address : "); // // StringBuilder sb = new StringBuilder(); // for (int i = 0; i < mac.length; i++) { // sb.append(String.format("%02X%s", mac[i], // (i < mac.length - 1) ? "-" : "")); // } // System.out.println(sb.toString()); // // } catch (UnknownHostException e) { // // e.printStackTrace(); // // } catch (SocketException e) { // // e.printStackTrace(); // // } } }