/** 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.io.IOException; import java.lang.management.ManagementFactory; import java.net.InetAddress; import java.net.NetworkInterface; import java.net.SocketException; import java.net.UnknownHostException; import java.util.Arrays; import java.util.Enumeration; import java.util.LinkedHashMap; import java.util.Map; import java.util.Map.Entry; import java.util.regex.Pattern; /** * UUID Generator (also Global UUID Generator) <br> * <br> * Inspired from com.groupon locality-uuid which used combination of internal counter value - process id - * fragment of MAC address 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 UUID implements Comparable<UUID> { /** * Native size of the UUID */ public static final int KEYSIZE = 20; private static final int KEYB64SIZE = 28; private static final int KEYB16SIZE = KEYSIZE * 2; private static final int UTILUUIDKEYSIZE = 16; /** * Random Generator */ private static final ThreadLocalRandom RANDOM = ThreadLocalRandom.current(); /** * So MAX value on 3 bytes (64 system use 2^22 id) */ private static final int MAX_PID = 4194304; /** * Version to store (to check correctness if future algorithm) */ private static final int VERSION = (1 & 0x0F); private static final Pattern MACHINE_ID_PATTERN = Pattern.compile("^(?:[0-9a-fA-F][:-]?){6,8}$"); private static final int MACHINE_ID_LEN = 6; /** * 2 bytes value maximum */ private static final int JVMPID = jvmProcessId(); /** * Try to get Mac Address but could be also changed dynamically */ private static byte[] MAC = macAddress(); /** * Counter part */ private static final int MAXSTART = Integer.MAX_VALUE >> 4; private static volatile int counter = RANDOM.nextInt(MAXSTART); /** * Counter reset */ private static volatile long lastTimeStamp = 0; /** * real UUID */ private final byte[] uuid; /** * Up to the 6 first bytes will be used. If Null or less than 6 bytes, extra bytes will * be randomly generated. * * @param mac * the MAC address in byte format (up to the 6 first bytes will be used) */ public static synchronized void setMAC(final byte[] mac) { if (mac == null) { MAC = getRandom(MACHINE_ID_LEN); } else { MAC = Arrays.copyOf(mac, MACHINE_ID_LEN); for (int i = mac.length; i < MACHINE_ID_LEN; i++) { MAC[i] = (byte) RANDOM.nextInt(256); } } } /** * Constructor that generates a new UUID using the current process id, MAC address, and timestamp */ public UUID() { this((byte) 0); } /** * Constructor that generates a new UUID using the current process id, MAC address, and timestamp * @param id the id of the subset of UUID from which it belongs to (default being 0, else between 1 and 255) */ public UUID(int id) { if (id < 0 || id > 255) { throw new IllegalArgumentException("ID must be between 0 and 255"); } // atomically final long time; final int count; synchronized (MACHINE_ID_PATTERN) { time = System.currentTimeMillis(); if (lastTimeStamp != time) { counter = RANDOM.nextInt(MAXSTART); lastTimeStamp = time; } count = ++counter; } uuid = new byte[KEYSIZE]; // UUID cycle default to 0 uuid[0] = (byte) (id & 0xFF); // switch the order of the count in 4 byte segments and place into uuid uuid[1] = (byte) (count & 0xFF); uuid[2] = (byte) ((count >> 8) & 0xFF); uuid[3] = (byte) ((count >> 16) & 0xFF); // place UUID version (value between 0 and 3) in first 2 bits // copy pid to uuid uuid[4] = (byte) ((VERSION << 6) | (JVMPID & 0x3F0000) >> 16); uuid[5] = (byte) (JVMPID >> 8); uuid[6] = (byte) (JVMPID); // copy rest of mac address into uuid uuid[7] = MAC[0]; uuid[8] = MAC[1]; uuid[9] = MAC[2]; uuid[10] = MAC[3]; uuid[11] = MAC[4]; uuid[12] = MAC[5]; // copy timestamp into uuid (up to 56 bits so up to 2 200 000 years after Time 0) uuid[13] = (byte) (time >> 48); uuid[14] = (byte) (time >> 40); uuid[15] = (byte) (time >> 32); uuid[16] = (byte) (time >> 24); uuid[17] = (byte) (time >> 16); uuid[18] = (byte) (time >> 8); uuid[19] = (byte) (time); } /** * Create a UUID immediately compatible with a standard UUID implementation * @param on128bits True for 128 bits limitation (16 Bytes instead of 20 Bytes) */ public UUID(boolean on128bits) { this(); if (on128bits) { uuid[0] = 0; uuid[4] = VERSION; // Special Version 0 for compatibility with standard UUID uuid[7] = 0; uuid[8] = 0; } } /** * Create a UUID immediately compatible with a standard UUID implementation * @param mostSigBits * @param leastSigBits */ public UUID(long mostSigBits, long leastSigBits) { uuid = new byte[KEYSIZE]; uuid[0] = 0; uuid[1] = (byte) (mostSigBits >> 56); uuid[2] = (byte) (mostSigBits >> 48); uuid[3] = (byte) (mostSigBits >> 40); uuid[4] = VERSION; // Special Version 0 for compatibility with standard UUID uuid[5] = (byte) (mostSigBits >> 32); uuid[6] = (byte) (mostSigBits >> 24); uuid[7] = 0; uuid[8] = 0; uuid[9] = (byte) (mostSigBits >> 16); uuid[10] = (byte) (mostSigBits >> 8); uuid[11] = (byte) (mostSigBits); uuid[12] = (byte) (leastSigBits >> 56); uuid[13] = (byte) (leastSigBits >> 48); uuid[14] = (byte) (leastSigBits >> 40); uuid[15] = (byte) (leastSigBits >> 32); uuid[16] = (byte) (leastSigBits >> 24); uuid[17] = (byte) (leastSigBits >> 16); uuid[18] = (byte) (leastSigBits >> 8); uuid[19] = (byte) (leastSigBits); } /** * Create a UUID immediately compatible with a standard UUID implementation * @param uuid */ public UUID(java.util.UUID uuid) { this(uuid.getMostSignificantBits(), uuid.getLeastSignificantBits()); } /** * Constructor that takes a byte array as this UUID's content * * @param bytes * UUID content * @throws RuntimeException */ public UUID(final byte[] bytes) throws RuntimeException { uuid = new byte[KEYSIZE]; setBytes(bytes); } /** * Internal function * @param bytes * @return * @throws RuntimeException */ protected UUID setBytes(final byte[] bytes) throws RuntimeException { if (bytes.length != KEYSIZE && bytes.length != UTILUUIDKEYSIZE) { throw new RuntimeException("Attempted to parse malformed UUID: (" + bytes.length + ") " + Arrays.toString(bytes)); } if (bytes.length == UTILUUIDKEYSIZE) { uuid[0] = 0; System.arraycopy(bytes, 0, uuid, 1, 3); uuid[4] = VERSION; System.arraycopy(bytes, 3, uuid, 5, 2); uuid[7] = 0; uuid[8] = 0; System.arraycopy(bytes, 5, uuid, 9, 11); } else { System.arraycopy(bytes, 0, uuid, 0, KEYSIZE); } return this; } /** * Build from String key * * @param idsource * @throws RuntimeException */ public UUID(final String idsource) throws RuntimeException { final String id = idsource.trim(); int len = id.length(); if (len == KEYB16SIZE) { // HEXA uuid = Hexa.fromHex(id); } else if (len == KEYB64SIZE || len == KEYB64SIZE + 1) { // BASE64 try { uuid = Base64.decode(id, Base64.URL_SAFE | Base64.DONT_GUNZIP); } catch (IOException e) { throw new RuntimeException("Attempted to parse malformed UUID: " + id, e); } } else { throw new RuntimeException("Attempted to parse malformed UUID: (" + len + ") " + id); } } public final String toBase64() { try { return Base64.encodeBytes(uuid, Base64.URL_SAFE); } catch (IOException e) { return Base64.encodeBytes(uuid); } } public final String toHex() { return Hexa.toHex(uuid); } @Override public String toString() { return toBase64(); } /** * 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, KEYSIZE); } /** * extract version field as a hex char from raw UUID bytes * * @return version char */ public final int getVersion() { return (uuid[4] & 0xC0) >> 6; } /** * @return the id of the subset of UUID from which it belongs to (default being 0) */ public final int getId() { return uuid[0] & 0xFF; } /** * extract process id from raw UUID bytes and return as int * * @return id of process that generated the UUID, or -1 for unrecognized format */ public final int getProcessId() { if (getVersion() != VERSION) { return -1; } return ((uuid[4] & 0x3F) << 16) | (uuid[5] & 0xFF) << 8 | (uuid[6] & 0xFF); } /** * @return the associated counter value */ public final int getCounter() { int count = (uuid[3] & 0xFF) << 16; count |= (uuid[2] & 0xFF) << 8; count |= uuid[1] & 0xFF; return count; } /** * extract timestamp from raw UUID bytes and return as int * * @return millisecond UTC timestamp from generation of the UUID, or -1 for unrecognized format */ public final long getTimestamp() { if (getVersion() != VERSION) { return -1; } long time; time = ((long) uuid[13] & 0xFF) << 48; time |= ((long) uuid[14] & 0xFF) << 40; time |= ((long) uuid[15] & 0xFF) << 32; time |= ((long) uuid[16] & 0xFF) << 24; time |= ((long) uuid[17] & 0xFF) << 16; time |= ((long) uuid[18] & 0xFF) << 8; time |= ((long) uuid[19] & 0xFF); return time; } /** * extract MAC address fragment from raw UUID bytes, setting missing values to 0, * from the active MAC address when the UUID was generated * * @return byte array of UUID fragment, or null for unrecognized format */ public final byte[] getMacFragment() { if (getVersion() != VERSION) { return null; } final byte[] x = new byte[6]; x[0] = uuid[7]; x[1] = uuid[8]; x[2] = uuid[9]; x[3] = uuid[10]; x[4] = uuid[11]; x[5] = uuid[12]; return x; } /** * * @return the least significant bits (as in standard UUID implementation) */ public long getLeastSignificantBits() { long least; least = ((long) uuid[12] & 0xFF) << 56; least |= ((long) uuid[13] & 0xFF) << 48; least |= ((long) uuid[14] & 0xFF) << 40; least |= ((long) uuid[15] & 0xFF) << 32; least |= ((long) uuid[16] & 0xFF) << 24; least |= ((long) uuid[17] & 0xFF) << 16; least |= ((long) uuid[18] & 0xFF) << 8; least |= ((long) uuid[19] & 0xFF); return least; } /** * * @return the most significant bits (as in standard UUID implementation) */ public long getMostSignificantBits() { long most; most = ((long) uuid[1] & 0xFF) << 56; most |= ((long) uuid[2] & 0xFF) << 48; most |= ((long) uuid[3] & 0xFF) << 40; most |= ((long) uuid[5] & 0xFF) << 32; most |= ((long) uuid[6] & 0xFF) << 24; most |= ((long) uuid[9] & 0xFF) << 16; most |= ((long) uuid[10] & 0xFF) << 8; most |= ((long) uuid[11] & 0xFF); return most; } /** * * @return a UUID compatible with Java.Util package implementation */ public java.util.UUID getJavaUuid() { return new java.util.UUID(getMostSignificantBits(), getLeastSignificantBits()); } /** * copy the uuid of this UUID, so that it can't be changed, and return it * * @return raw byte array of UUID for a 16 Bytes UUID */ public byte[] javaUuidGetBytes() { byte []newUuid = new byte[UTILUUIDKEYSIZE]; System.arraycopy(uuid, 1, newUuid, 0, 3); System.arraycopy(uuid, 5, newUuid, 3, 2); System.arraycopy(uuid, 9, newUuid, 5, 11); return newUuid; } @Override public boolean equals(Object o) { if (o == null || !(o instanceof UUID)) return false; return (this == o) || Arrays.equals(this.uuid, ((UUID) o).uuid); } @Override public int hashCode() { return Arrays.hashCode(uuid); } /** * * @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; } /** * * @return the mac address if possible, else random values */ private static byte[] macAddress() { try { byte[] machineId = null; String customMachineId = SystemPropertyUtil.get("org.waarp.machineId"); if (customMachineId != null) { if (MACHINE_ID_PATTERN.matcher(customMachineId).matches()) { machineId = parseMachineId(customMachineId); } } if (machineId == null) { machineId = defaultMachineId(); } return machineId; } catch (Exception e) { //System.err.println("Could not get MAC address"); //e.printStackTrace(); return getRandom(MACHINE_ID_LEN); } } private static byte[] parseMachineId(String value) { // Strip separators. value = value.replaceAll("[:-]", ""); byte[] machineId = new byte[MACHINE_ID_LEN]; for (int i = 0; i < value.length() && i < MACHINE_ID_LEN; i += 2) { machineId[i] = (byte) Integer.parseInt(value.substring(i, i + 2), 16); } return machineId; } private static byte[] defaultMachineId() { // Find the best MAC address available. final byte[] NOT_FOUND = { -1 }; byte[] bestMacAddr = NOT_FOUND; InetAddress bestInetAddr = null; try { bestInetAddr = InetAddress.getByAddress(new byte[] { 127, 0, 0, 1 }); } catch (UnknownHostException e) { // Never happens. throw new IllegalArgumentException(e); } // Retrieve the list of available network interfaces. Map<NetworkInterface, InetAddress> ifaces = new LinkedHashMap<NetworkInterface, InetAddress>(); try { for (Enumeration<NetworkInterface> i = NetworkInterface.getNetworkInterfaces(); i.hasMoreElements();) { NetworkInterface iface = i.nextElement(); // Use the interface with proper INET addresses only. Enumeration<InetAddress> addrs = iface.getInetAddresses(); if (addrs.hasMoreElements()) { InetAddress a = addrs.nextElement(); if (!a.isLoopbackAddress()) { ifaces.put(iface, a); } } } } catch (SocketException e) { } for (Entry<NetworkInterface, InetAddress> entry : ifaces.entrySet()) { NetworkInterface iface = entry.getKey(); InetAddress inetAddr = entry.getValue(); if (iface.isVirtual()) { continue; } byte[] macAddr; try { macAddr = iface.getHardwareAddress(); } catch (SocketException e) { continue; } boolean replace = false; int res = compareAddresses(bestMacAddr, macAddr); if (res < 0) { // Found a better MAC address. replace = true; } else if (res == 0) { // Two MAC addresses are of pretty much same quality. res = compareAddresses(bestInetAddr, inetAddr); if (res < 0) { // Found a MAC address with better INET address. replace = true; } else if (res == 0) { // Cannot tell the difference. Choose the longer one. if (bestMacAddr.length < macAddr.length) { replace = true; } } } if (replace) { bestMacAddr = macAddr; bestInetAddr = inetAddr; } } if (bestMacAddr == NOT_FOUND) { bestMacAddr = getRandom(MACHINE_ID_LEN); } return bestMacAddr; } /** * @return positive - current is better, 0 - cannot tell from MAC addr, negative - candidate is better. */ private static int compareAddresses(byte[] current, byte[] candidate) { if (candidate == null) { return 1; } // Must be EUI-48 or longer. if (candidate.length < 6) { return 1; } // Must not be filled with only 0 and 1. boolean onlyZeroAndOne = true; for (byte b : candidate) { if (b != 0 && b != 1) { onlyZeroAndOne = false; break; } } if (onlyZeroAndOne) { return 1; } // Must not be a multicast address if ((candidate[0] & 1) != 0) { return 1; } // Prefer globally unique address. if ((current[0] & 2) == 0) { if ((candidate[0] & 2) == 0) { // Both current and candidate are globally unique addresses. return 0; } else { // Only current is globally unique. return 1; } } else { if ((candidate[0] & 2) == 0) { // Only candidate is globally unique. return -1; } else { // Both current and candidate are non-unique. return 0; } } } /** * @return positive - current is better, 0 - cannot tell, negative - candidate is better */ private static int compareAddresses(InetAddress current, InetAddress candidate) { return scoreAddress(current) - scoreAddress(candidate); } private static int scoreAddress(InetAddress addr) { if (addr.isAnyLocalAddress()) { return 0; } if (addr.isMulticastAddress()) { return 1; } if (addr.isLinkLocalAddress()) { return 2; } if (addr.isSiteLocalAddress()) { return 3; } return 4; } // pulled from http://stackoverflow.com/questions/35842/how-can-a-java-program-get-its-own-process-id /** * @return the JVM Process ID */ public static int jvmProcessId() { // Note: may fail in some JVM implementations // something like '<pid>@<hostname>', at least in SUN / Oracle JVMs try { 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); } } catch (Exception e) { e.printStackTrace(); return RANDOM.nextInt(MAX_PID); } } @Override public int compareTo(UUID arg0) { if (getId() != arg0.getId()) { return getId() < arg0.getId() ? -1 : 1; } long ts = getTimestamp(); long ts2 = arg0.getTimestamp(); if (ts == ts2) { int ct = getCounter(); int ct2 = arg0.getCounter(); if (ct == ct2) { // then all must be equals, else whatever return Arrays.equals(uuid, arg0.uuid) ? 0 : -1; } // Cannot be equal return ct < ct2 ? -1 : 1; } // others as ProcessId or MacFragment is unimportant in comparison return ts < ts2 ? -1 : 1; } /** * Simply return a new UUID. If an argument is provided, 2 cases occur:</br> * - length <= 3 = same as UUID(int) * - length > 3 = same as checking the given UUID in String format * @param args */ public static void main(String[] args) { if (args.length == 0) { UUID uuid = new UUID(); System.out.println(uuid.toString()); System.exit(0); } if (args[0].length() <= 3) { int val = Integer.parseInt(args[0]); UUID uuid = new UUID(val); System.out.println(uuid.toString()); System.exit(0); } try { new UUID(args[0]); } catch (RuntimeException e) { System.out.println(e.getMessage()); System.exit(2); } System.out.println("ok"); System.exit(0); } }