/** This file is part of Waarp Project. Copyright 2009, Frederic Bregier, and individual contributors by the @author tags. See the COPYRIGHT.txt in the distribution for a full listing of individual contributors. All Waarp Project is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. Waarp is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with Waarp . If not, see <http://www.gnu.org/licenses/>. */ package org.waarp.common.utility; import java.lang.management.ManagementFactory; import java.util.Arrays; import java.util.Date; import java.util.concurrent.atomic.AtomicInteger; /** * UUID Generator (also Global UUID Generator) but limited to 1 Long (64 bits) <br> * <br> * Inspired from com.groupon locality-uuid which used combination of internal counter value - process id - * and Timestamp. see https://github.com/groupon/locality-uuid.java <br> * <br> * But force sequence and take care of errors and improves some performance issues * * @author "Frederic Bregier" * */ public final class LongUuid { /** * Random Generator */ private static final ThreadLocalRandom RANDOM = ThreadLocalRandom.current(); /** * So MAX value on 2 bytes */ private static final int MAX_PID = 65536; /** * 2 bytes value maximum */ private static final int JVMPID = jvmProcessId(); /** * Counter part */ private static final AtomicInteger COUNTER = new AtomicInteger(RANDOM.nextInt()); /** * Byte size of UUID */ private static final int UUIDSIZE = 8; /** * real UUID */ private final byte[] uuid; /** * Constructor that generates a new UUID using the current process id, MAC address, and timestamp */ public LongUuid() { final long time = System.currentTimeMillis(); uuid = new byte[UUIDSIZE]; // atomically final int count = COUNTER.incrementAndGet(); // copy pid to uuid uuid[0] = (byte) (JVMPID >> 8); uuid[1] = (byte) (JVMPID); // copy timestamp into uuid (up to 2^36 s = 2 years rolling) uuid[2] = (byte) (time >> 28); uuid[3] = (byte) (time >> 20); uuid[4] = (byte) (time >> 12); // Keep 4 first bytes, 4 bytes coming from Timestamp => 2^20 (at most 1M / 1/2s) uuid[5] = (byte) (((count >> 16) & 0x0F) | ((time >> 4) & 0xF0)); uuid[6] = (byte) (count >> 8); uuid[7] = (byte) (count); } /** * Constructor that takes a byte array as this UUID's content * * @param bytes * UUID content */ public LongUuid(final byte[] bytes) { if (bytes.length != UUIDSIZE) throw new RuntimeException("Attempted to parse malformed UUID: " + Arrays.toString(bytes)); uuid = Arrays.copyOf(bytes, UUIDSIZE); } public LongUuid(final long value) { uuid = new byte[UUIDSIZE]; uuid[0] = (byte) (value >> 56); uuid[1] = (byte) (value >> 48); uuid[2] = (byte) (value >> 40); uuid[3] = (byte) (value >> 32); uuid[4] = (byte) (value >> 24); uuid[5] = (byte) (value >> 16); uuid[6] = (byte) (value >> 8); uuid[7] = (byte) (value); } public LongUuid(final String idsource) { final String id = idsource.trim(); if (id.length() != UUIDSIZE * 2) throw new RuntimeException("Attempted to parse malformed UUID: " + id); uuid = Hexa.fromHex(id); } @Override public String toString() { return Hexa.toHex(uuid); } /** * copy the uuid of this UUID, so that it can't be changed, and return it * * @return raw byte array of UUID */ public byte[] getBytes() { return Arrays.copyOf(uuid, UUIDSIZE); } /** * extract process id from raw UUID bytes and return as int * * @return id of process that generated the UUID */ public int getProcessId() { return ((uuid[0] & 0xFF) << 8) | (uuid[1] & 0xFF); } /** * extract timestamp from raw UUID bytes and return as int * * @return millisecond UTC timestamp from generation of the UUID */ public long getTimestamp() { long time; time = ((long) uuid[2] & 0xFF) << 28; time |= ((long) uuid[3] & 0xFF) << 20; time |= ((long) uuid[4] & 0xFF) << 12; time |= ((long) uuid[5] & 0xF0) << 4; return time; } @Override public boolean equals(Object o) { if (o == null || !(o instanceof LongUuid)) return false; return (this == o) || Arrays.equals(this.uuid, ((LongUuid) o).uuid); } @Override public int hashCode() { return Arrays.hashCode(uuid); } /** * * @return the equivalent UUID as long */ public long getLong() { long value = ((long) uuid[0] & 0xFF) << 56; value |= ((long) uuid[1] & 0xFF) << 48; value |= ((long) uuid[2] & 0xFF) << 40; value |= ((long) uuid[3] & 0xFF) << 32; value |= ((long) uuid[4] & 0xFF) << 24; value |= ((long) uuid[5] & 0xFF) << 16; value |= ((long) uuid[6] & 0xFF) << 8; value |= ((long) uuid[7] & 0xFF); return value; } /** * * @param length * @return a byte array with random values */ public static final byte[] getRandom(final int length) { final byte[] result = new byte[length]; RANDOM.nextBytes(result); return result; } // pulled from http://stackoverflow.com/questions/35842/how-can-a-java-program-get-its-own-process-id public static int jvmProcessId() { // Note: may fail in some JVM implementations // something like '<pid>@<hostname>', at least in SUN / Oracle JVMs final String jvmName = ManagementFactory.getRuntimeMXBean().getName(); final int index = jvmName.indexOf('@'); if (index < 1) { System.err.println("Could not get JVMPID"); return RANDOM.nextInt(MAX_PID); } try { return Integer.parseInt(jvmName.substring(0, index)) % MAX_PID; } catch (NumberFormatException e) { System.err.println("Could not get JVMPID"); e.printStackTrace(); return RANDOM.nextInt(MAX_PID); } } @SuppressWarnings("unused") public static void main(String[] args) { long pseudoMax = Long.MAX_VALUE >> 16; System.out.println(new Date(pseudoMax)); System.out.println(new LongUuid().toString()); for (int i = 0; i < 10; i++) { LongUuid uuid = new LongUuid(); System.out.println(System.currentTimeMillis() + " " + uuid + "=" + uuid.getLong() + ":" + uuid.getProcessId() + ":" + uuid.getTimestamp()); } for (int i = 0; i < 10; i++) { LongUuid uuid = new LongUuid(); System.out.println(System.currentTimeMillis() + " " + uuid + "=" + uuid.getLong() + ":" + uuid.getProcessId() + ":" + uuid.getTimestamp()); try { Thread.sleep(500); } catch (InterruptedException e) { } } final int n = 100000000; for (int i = 0; i < n; i++) { LongUuid uuid = new LongUuid(); } long start = System.currentTimeMillis(); for (int i = 0; i < n; i++) { LongUuid uuid = new LongUuid(); } long stop = System.currentTimeMillis(); System.out.println("TimeW = " + (stop - start) + " so " + (n * 1000 / (stop - start)) + " Uuids/s"); start = System.currentTimeMillis(); for (int i = 0; i < n; i++) { LongUuid uuid = new LongUuid(); uuid.getLong(); } stop = System.currentTimeMillis(); System.out.println("TimeW+getLong = " + (stop - start) + " so " + (n * 1000 / (stop - start)) + " Uuids/s"); start = System.currentTimeMillis(); for (int i = 0; i < n; i++) { LongUuid uuid = new LongUuid(); uuid = new LongUuid(uuid.getLong()); } stop = System.currentTimeMillis(); System.out.println("TimeW+reloadFromgetLong = " + (stop - start) + " so " + (n * 1000 / (stop - start)) + " Uuids/s"); int count = 0; start = System.currentTimeMillis(); for (int i = 0; i < n; i++) { LongUuid uuid = new LongUuid(); LongUuid uuid2 = new LongUuid(uuid.getLong()); if (uuid2.equals(uuid)) count++; } stop = System.currentTimeMillis(); System.out.println("TimeWAndTest = " + (stop - start) + " so " + (n * 1000 / (stop - start)) + " Uuids/s " + count); start = System.currentTimeMillis(); for (int i = 0; i < n; i++) { LongUuid uuid = new LongUuid(); uuid.getBytes(); } stop = System.currentTimeMillis(); System.out.println("TimeW+getBytes = " + (stop - start) + " so " + (n * 1000 / (stop - start)) + " Uuids/s"); start = System.currentTimeMillis(); for (int i = 0; i < n; i++) { LongUuid uuid = new LongUuid(); uuid = new LongUuid(uuid.getBytes()); } stop = System.currentTimeMillis(); System.out.println("TimeW+reloadFromgetBytes = " + (stop - start) + " so " + (n * 1000 / (stop - start)) + " Uuids/s"); count = 0; start = System.currentTimeMillis(); for (int i = 0; i < n; i++) { LongUuid uuid = new LongUuid(); LongUuid uuid2 = new LongUuid(uuid.getBytes()); if (uuid2.equals(uuid)) count++; } stop = System.currentTimeMillis(); System.out.println("TimeWAndTest = " + (stop - start) + " so " + (n * 1000 / (stop - start)) + " Uuids/s " + count); } }