All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.fintx.util.UniqueId Maven / Gradle / Ivy

There is a newer version: 0.3.0.0
Show newest version
/**
 *  Copyright 2017 FinTx
 *
 * 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.fintx.util;

import java.io.Serializable;
import java.net.NetworkInterface;
import java.security.SecureRandom;
import java.util.Arrays;
import java.util.Base64;
import java.util.Date;
import java.util.Enumeration;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicLong;

/**
 * 

* A globally unique identifier for objects. *

* *

* Consists of 12 bytes, divided as follows: *

* * * * * * * * * * * * * * * * * * * * * * * * * *
ObjectID layout
01234567891011121314
timemachinepidinc
* *

* Instances of this class are immutable. *

*

* Limitations: *

*

* ProcessId on os could not bigger then 65535. Only in one bundle of same JVM when using OSGI. Generated id number could not more then about 16777215 per * second per JVM. Id maybe (hardly) generate the same one every 69 years. *

* */ public final class UniqueId implements Comparable, Serializable { private static final long serialVersionUID = 3670079982654483072L; private static final int LOW_ORDER_THREE_BYTES = 0x00ffffff; private static final long MACHINE_IDENTIFIER; private static final short PROCESS_IDENTIFIER; private static final AtomicInteger NEXT_COUNTER = new AtomicInteger(new SecureRandom().nextInt()); // to prevent time change back maybe when use time server to correct the machine time. private static volatile AtomicLong LAST_TIMESTAMP = new AtomicLong(0); private static final char[] HEX_CHARS = new char[] { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f' }; private final int timestamp; private final long machineIdentifier; private final short processIdentifier; private final int counter; /** * Gets a new object id. * * @return the new https://oss.sonatype.org/content/repositories/snapshots */ public static UniqueId get() { return new UniqueId(dateToTimestampSeconds(new Date()), MACHINE_IDENTIFIER, PROCESS_IDENTIFIER, NEXT_COUNTER.getAndIncrement(), false); } /** * Checks if a string could be an {@code UniqueId}. * * @param idString hexString (base16) or base64String, a potential UniqueId as a String. * @return whether the string could be an object id * @throws IllegalArgumentException if hexString is null */ public static boolean isValid(final String idString) { if (idString == null) { throw new IllegalArgumentException(); } int len = idString.length(); if (len == 30) { for (int i = 0; i < len; i++) { char c = idString.charAt(i); if (c >= '0' && c <= '9') { continue; } if (c >= 'a' && c <= 'f') { continue; } if (c >= 'A' && c <= 'F') { continue; } return false; } return true; } else if (len == 20) { for (int i = 0; i < len; i++) { char c = idString.charAt(i); if (c >= '0' && c <= '9') { continue; } if (c >= 'a' && c <= 'z') { continue; } if (c >= 'A' && c <= 'Z') { continue; } if (c == '_' || c == '-') { continue; } return false; } return true; } else { return false; } } /** * Gets the generated machine identifier. * * @return an int representing the machine identifier */ public static long getGeneratedMachineIdentifier() { return MACHINE_IDENTIFIER; } /** * Gets the generated process identifier. * * @return the process id */ public static int getGeneratedProcessIdentifier() { return PROCESS_IDENTIFIER; } /** * Gets the current value of the auto-incrementing counter. * * @return the current counter value. */ public static int getCurrentCounter() { return NEXT_COUNTER.get(); } /** * Constructs a new instance from the timestamp, * * @param timestamp of second * @param machineIdentifier the machine identifier * @Param processIdentifier the process identifier * @counter the counter in this jvm */ private UniqueId(final int timestamp, final long machineIdentifier, final short processIdentifier, final int counter, final boolean checkCounter) { long current = LAST_TIMESTAMP.get(); if (((timestamp & 0xffffffffL) == (current & 0xffffffffL))) { // @formatter:off // mostly // @formatter:on // Do nothing } else if (((timestamp & 0xffffffffL) > (current & 0xffffffffL))) { // @formatter:off // once per second or less // @formatter:on synchronized (LAST_TIMESTAMP) { if ((timestamp & 0xffffffffL) > (LAST_TIMESTAMP.get() & 0xffffffffL)) { current = LAST_TIMESTAMP.addAndGet((timestamp & 0xffffffffL) - (LAST_TIMESTAMP.get() & 0xffffffffL)); } } } else { // @formatter:off //((timestamp & 0xffffffffL) < (current & 0xffffffffL)) // hardly // @formatter:on synchronized (LAST_TIMESTAMP) { if (((LAST_TIMESTAMP.get() & 0xffffffffL) - (timestamp & 0xffffffffL)) == 1L) { // @formatter:off // LAST_TIMESTAMP increased after timestamp generated // @formatter:on // Do nothing } else if (((LAST_TIMESTAMP.get() & 0xffffffffL) - (timestamp & 0xffffffffL)) >= 0x7fffffffL) { // timestamp is in the new round of zero to 0xffffffffL. 0x7fffffffL is half of 0xffffffffL. // A round is about 69 years, so the gap between last timestamp in the last round and new timestamp // in this round will not less then 34 years LAST_TIMESTAMP.set(timestamp & 0xffffffffL); current = (timestamp & 0xffffffffL); } else { System.err.println(((LAST_TIMESTAMP.get() & 0xffffffffL) - (timestamp & 0xffffffffL))); System.err.println(timestamp); System.err.println(current); System.err.println(LAST_TIMESTAMP.get()); throw new IllegalArgumentException( "The timestamp must not be less then the timestamp last time. (Maybe the machine correct time using time server)."); } } } if (((machineIdentifier >> 32) & 0xff000000) != 0) { throw new IllegalArgumentException("The machine identifier must be between 0 and 28139015110655 (it must fit in six bytes)."); } if (checkCounter && ((counter & 0xff000000) != 0)) { throw new IllegalArgumentException("The counter must be between 0 and 16777215 (it must fit in three bytes)."); } this.timestamp = (int) current; this.machineIdentifier = machineIdentifier; this.processIdentifier = processIdentifier; this.counter = counter & LOW_ORDER_THREE_BYTES; } /** * Constructs a new instance from a 30-byte hexadecimal (base16 encoding) string representation. * * @param hexString the string to convert * @return new UniqueId instance * @throws IllegalArgumentException if the string is not a valid hex string representation of an UniqueId */ public static UniqueId fromHexString(final String hexString) { return new UniqueId(parseHexString(hexString)); } /** * Constructs a new instance from a 20-byte base64 encoding string representation. * * @param base64String the string to convert * @return new UniqueId instance * @throws IllegalArgumentException if the string is not a valid hex string representation of an UniqueId */ public static UniqueId fromBase64String(final String base64String) { return new UniqueId(parseBase64String(base64String)); } /** * Constructs a new instance from the given byte array * * @param bytes the byte array * @throws IllegalArgumentException if array is null or not of length 15 */ public UniqueId(final byte[] bytes) { if (bytes == null) { throw new IllegalArgumentException(); } if (bytes.length != 15) { throw new IllegalArgumentException("need 15 bytes"); } timestamp = bytes2int(Arrays.copyOfRange(bytes, 0, 4)); machineIdentifier = bytes2long(Arrays.copyOfRange(bytes, 4, 10)); processIdentifier = (short) bytes2int(Arrays.copyOfRange(bytes, 10, 12)); counter = bytes2int(Arrays.copyOfRange(bytes, 12, 15)); } /** * Convert to a byte array. * * @return the byte array */ public byte[] toByteArray() { byte[] bytes = new byte[15]; byte[] temp = int2bytes(timestamp); bytes[0] = temp[0]; bytes[1] = temp[1]; bytes[2] = temp[2]; bytes[3] = temp[3]; temp = long2bytes(machineIdentifier); bytes[4] = temp[2]; bytes[5] = temp[3]; bytes[6] = temp[4]; bytes[7] = temp[5]; bytes[8] = temp[6]; bytes[9] = temp[7]; temp = int2bytes(processIdentifier); bytes[10] = temp[2]; bytes[11] = temp[3]; temp = int2bytes(counter); bytes[12] = temp[1]; bytes[13] = temp[2]; bytes[14] = temp[3]; return bytes; } /** * Gets the timestamp (number of seconds since the Unix epoch). * * @return the timestamp of second */ public long getTimestamp() { // To unsigned int return timestamp & 0x0ffffffffL; } /** * Gets the machine identifier (physical MAC address). * * @return the machine identifier */ public long getMachineIdentifier() { return machineIdentifier; } /** * Gets the process identifier. * * @return the process identifier */ public int getProcessIdentifier() { return processIdentifier & 0x0ffff; } /** * Gets the counter. * * @return the counter */ public int getCounter() { return counter; } /** * Gets the timestamp as a {@code Date} instance. * * @return the Date */ public Date getDate() { return getDate(new Date().getTime()); } /** * Gets the timestamp as a {@code Date} instance. * * @param now the timestamp in millisecond * @return the Date */ private Date getDate(long now) { // Timestamp is in this round of zero to 0xffffffffL scope. if ((timestamp & 0xffffffffL) <= (now / 1000L % 0xffffffffL)) { return new Date((((now / 1000L / 0xffffffffL) * 0xffffffffL + (timestamp & 0xffffffffL)) + now / 1000L / 0xffffffffL) * 1000L); // @formatter:off // Timestamp is in last round of zero to 0xffffffffL scope. // @formatter:on } else if (((timestamp & 0xffffffffL) + now / 1000L / 0xffffffffL) - (now / 1000L % 0xffffffffL) >= 0x7fffffffL) { return new Date((((now / 1000L / 0xffffffffL) - 1) * 0xffffffffL + (timestamp & 0xffffffffL) + (now / 1000L / 0xffffffffL) - 1) * 1000L); // @formatter:off // Timestamp is in this round of zero to 0xffffffffL scope but bigger then now // @formatter:on } else { throw new IllegalArgumentException("The timestamp must not be less then the timestamp now. (Maybe the machine correct time using time server)."); } } /** * Converts this instance into a 30-byte hexadecimal string representation. * * @return a string representation of the UniqueId in hexadecimal format */ public String toHexString() { return toHexString(toByteArray()); } /** * Converts byte array into a hexadecimal string representation. * * @return a string */ private static String toHexString(byte[] bytes) { char[] chars = new char[bytes.length * 2]; int i = 0; for (byte b : bytes) { chars[i++] = HEX_CHARS[b >> 4 & 0xf]; chars[i++] = HEX_CHARS[b & 0xf]; } return new String(chars); } /** * Converts this instance into a 20-byte base64 string representation. * * @return a string representation of the UniqueId in base64 format */ public String toBase64String() { return Base64.getUrlEncoder().encodeToString(toByteArray()); } @Override public boolean equals(final Object o) { if (this == o) { return true; } if (o == null || getClass() != o.getClass()) { return false; } UniqueId UniqueId = (UniqueId) o; if (counter != UniqueId.counter) { return false; } if (machineIdentifier != UniqueId.machineIdentifier) { return false; } if (processIdentifier != UniqueId.processIdentifier) { return false; } if (timestamp != UniqueId.timestamp) { return false; } return true; } @Override public int hashCode() { int result = timestamp; result = 31 * result + (int) machineIdentifier; result = 31 * result + (int) processIdentifier; result = 31 * result + counter; return result; } @Override public int compareTo(final UniqueId other) { if (other == null) { throw new NullPointerException(); } byte[] byteArray = toByteArray(); byte[] otherByteArray = other.toByteArray(); for (int i = 0; i < 12; i++) { if (byteArray[i] != otherByteArray[i]) { return ((byteArray[i] & 0xff) < (otherByteArray[i] & 0xff)) ? -1 : 1; } } return 0; } @Override public String toString() { return toBase64String(); } static { try { MACHINE_IDENTIFIER = createMachineIdentifier(); PROCESS_IDENTIFIER = createProcessIdentifier(); } catch (Exception e) { throw new RuntimeException(e); } } /* * Creates the machine identifier from the physical MAC address. */ private static long createMachineIdentifier() { byte[] mac = null; try { Enumeration e = NetworkInterface.getNetworkInterfaces(); while (e.hasMoreElements()) { NetworkInterface ni = e.nextElement(); if (!ni.isLoopback()) { mac = ni.getHardwareAddress(); } ; // ?? mac[1] != (byte) 0xff it is from http://johannburkard.de/software/uuid/ if (mac != null && mac.length == 6 && mac[1] != (byte) 0xff) { break; } else { continue; } } } catch (Throwable t) { } if (mac != null && mac.length == 6 && mac[1] != (byte) 0xff) { return bytes2long(mac); } else { throw new RuntimeException("MAC address is not correct:" + toHexString(mac)); } } /* * Creates the process identifier. */ private static short createProcessIdentifier() { short processId; try { String processName = java.lang.management.ManagementFactory.getRuntimeMXBean().getName(); if (processName.contains("@")) { processId = (short) Integer.parseInt(processName.substring(0, processName.indexOf('@'))); } else { throw new Throwable("Process name:'" + processName + "' is invalid!"); } } catch (Throwable t) { throw new RuntimeException(t); } return processId; } /* * Parse the hexadecimal string (base16 encoding) to byte array * * @param s the hexadecimal string * */ private static byte[] parseHexString(final String s) { if (!isValid(s)) { throw new IllegalArgumentException("invalid hexadecimal representation of an UniqueId: [" + s + "]"); } byte[] b = new byte[15]; for (int i = 0; i < b.length; i++) { b[i] = (byte) Integer.parseInt(s.substring(i * 2, i * 2 + 2), 16); } return b; } /* * Parse the base64 String to byte array * * @param s the base64 string */ private static byte[] parseBase64String(final String s) { if (!isValid(s)) { throw new IllegalArgumentException("invalid hexadecimal representation of an UniqueId: [" + s + "]"); } return Base64.getUrlDecoder().decode(s); } /* * Get the timestamp of second from Date instance */ private static int dateToTimestampSeconds(final Date time) { return (int) ((time.getTime() / 1000L) & 0xffffffffL); } /* * Convert from integer to byte array */ private static byte[] int2bytes(int num) { byte[] bytes = new byte[4]; for (int ix = 0; ix < 4; ++ix) { int offset = 32 - (ix + 1) * 8; bytes[ix] = (byte) ((num >> offset) & 0xff); } return bytes; } /* * Convert from byte array to integer */ private static int bytes2int(byte[] bytes) { if (bytes.length > 4) { throw new RuntimeException("byteNum is too long for a int type:" + bytes.length); } int num = 0; for (int ix = 0; ix < bytes.length; ++ix) { num <<= 8; num |= (bytes[ix] & 0xff); } return num; } /* * Convert from long to bytes array */ private static byte[] long2bytes(long num) { byte[] bytes = new byte[8]; for (int ix = 0; ix < 8; ++ix) { int offset = 64 - (ix + 1) * 8; bytes[ix] = (byte) ((num >> offset) & 0xff); } return bytes; } /* * Convert from bytes array to long type */ private static long bytes2long(byte[] bytes) { if (bytes.length > 8) { throw new RuntimeException("byteNum is too long for a long type:" + bytes.length); } long num = 0; for (int ix = 0; ix < bytes.length; ++ix) { num <<= 8; num |= (bytes[ix] & 0xff);// byte become 64bit since the index 1 with higher bit 1? & 0xff make higher bit // 0 } return num; } // public static void main(String[] args) { // //Full test!!!! // UniqueId uniqueId = null; // for (long lo = new Date().getTime(); lo > 0 && lo < Long.MAX_VALUE; lo += 100) { // uniqueId = // new UniqueId(dateToTimestampSeconds(new Date(lo)), MACHINE_IDENTIFIER, PROCESS_IDENTIFIER, // NEXT_COUNTER.getAndIncrement(), false); // if (!uniqueId.getDate(lo).toString().equals(new Date(lo / 1000L * 1000L).toString())) { // System.err.println("XXXXXXXXXXXXXXXXXXXXXXXXXXLo:" + lo); // System.err.println(uniqueId.getDate(lo).toString()); // System.err.println(new Date(lo / 1000L * 1000L).toString()); // throw new RuntimeException(); // } // if (lo / 100 % 1000000 == 0) { // System.err.println(lo); // System.err.println(uniqueId.getDate(lo).toString()); // } // } // //Function test!!!!!! // System.err.println("-------------------------------1"); // long l = 12345678901223322L; // System.err.println(Long.toBinaryString(l)); // byte[] bytes = long2bytes(l); // l = bytes2long(bytes); // System.err.println(Long.toBinaryString(l)); // int i = 1500617485; // System.err.println(Integer.toBinaryString(i)); // bytes = int2bytes(i); // l = bytes2int(bytes); // System.err.println(Long.toBinaryString(i)); // //Extreme condition test!!!!!!! // System.err.println("-------------------------------2"); // UniqueId uid = new UniqueId(dateToTimestampSeconds(new Date((Integer.MAX_VALUE - 1) * 1000L)), // MACHINE_IDENTIFIER, PROCESS_IDENTIFIER, // NEXT_COUNTER.getAndIncrement(), false); // System.err.println(uid.getDate((Integer.MAX_VALUE - 1) * 1000L).toString()); // System.err.println(uid.getTimestamp()); // System.err.println(new Date((Integer.MAX_VALUE - 1) * 1000L).toString()); // System.err.println("-------------------------------3"); // uid = new UniqueId(dateToTimestampSeconds(new Date(Integer.MAX_VALUE * 1000L)), MACHINE_IDENTIFIER, // PROCESS_IDENTIFIER, // NEXT_COUNTER.getAndIncrement(), false); // System.err.println(uid.getDate(Integer.MAX_VALUE * 1000L).toString()); // System.err.println(uid.getTimestamp()); // System.err.println(new Date(Integer.MAX_VALUE * 1000L).toString()); // System.err.println("-------------------------------4"); // uid = new UniqueId(dateToTimestampSeconds(new Date((Integer.MAX_VALUE + 1L) * 1000L)), MACHINE_IDENTIFIER, // PROCESS_IDENTIFIER, // NEXT_COUNTER.getAndIncrement(), false); // System.err.println((Integer.MAX_VALUE + 1L) * 1000L); // System.err.println(uid.getDate((Integer.MAX_VALUE + 1L) * 1000L).toString()); // System.err.println(uid.getTimestamp()); // System.err.println(new Date((Integer.MAX_VALUE + 1L) * 1000L).toString()); // System.err.println("-------------------------------5"); // uid = new UniqueId(dateToTimestampSeconds(new Date((Integer.MAX_VALUE + 2L) * 1000L)), MACHINE_IDENTIFIER, // PROCESS_IDENTIFIER, // NEXT_COUNTER.getAndIncrement(), false); // System.err.println((Integer.MAX_VALUE + 2L) * 1000L); // System.err.println(uid.getDate((Integer.MAX_VALUE + 2L) * 1000L).toString()); // System.err.println(uid.getTimestamp()); // System.err.println(new Date((Integer.MAX_VALUE + 2L) * 1000L).toString()); // System.err.println("-------------------------------6"); // System.err.println((new Date(0xfffffffeL * 1000L).getTime() / 1000L) & 0xffffffffL); // System.err.println(dateToTimestampSeconds(new Date(0xfffffffeL * 1000L))); // System.err.println(Long.toBinaryString(dateToTimestampSeconds(new Date(0xfffffffeL * 1000L)))); // System.err.println(Integer.toBinaryString((int) dateToTimestampSeconds(new Date(0xfffffffeL * 1000L)))); // uid = new UniqueId(dateToTimestampSeconds(new Date(0xfffffffeL * 1000L)), MACHINE_IDENTIFIER, // PROCESS_IDENTIFIER, // NEXT_COUNTER.getAndIncrement(), false); // System.err.println(uid.getDate(0xfffffffeL * 1000L).toString()); // System.err.println(uid.getTimestamp()); // System.err.println(new Date(0xfffffffeL * 1000L).toString()); // System.err.println("-------------------------------7"); // System.err.println((new Date(0xfffffffeL * 1000L).getTime() / 1000L) & 0xffffffffL); // System.err.println(dateToTimestampSeconds(new Date(0xffffffffL * 1000L))); // System.err.println(Long.toBinaryString(dateToTimestampSeconds(new Date(0xffffffffL * 1000L)))); // System.err.println(Integer.toBinaryString((int) dateToTimestampSeconds(new Date(0xffffffffL * 1000L)))); // uid = new UniqueId(dateToTimestampSeconds(new Date(0xffffffffL * 1000L)), MACHINE_IDENTIFIER, // PROCESS_IDENTIFIER, // NEXT_COUNTER.getAndIncrement(), false); // System.err.println(uid.getDate(0xffffffffL * 1000L).toString()); // System.err.println(uid.getTimestamp()); // System.err.println(new Date(0xffffffffL * 1000L).toString()); // System.err.println("-------------------------------8"); // uid = new UniqueId(dateToTimestampSeconds(new Date(0xffffffffL * 1000L)), MACHINE_IDENTIFIER, // PROCESS_IDENTIFIER, // NEXT_COUNTER.getAndIncrement(), false); // System.err.println(uid.getDate(0x100000000L * 1000L).toString()); // System.err.println(uid.getDate(0x100000001L * 1000L).toString()); // System.err.println(uid.getTimestamp()); // System.err.println(new Date(0xffffffffL * 1000L).toString()); // System.err.println("-------------------------------9"); // uid = new UniqueId(dateToTimestampSeconds(new Date(0x100000000L * 1000L)), MACHINE_IDENTIFIER, // PROCESS_IDENTIFIER, // NEXT_COUNTER.getAndIncrement(), false); // System.err.println(0x100000000L * 1000L); // System.err.println(uid.getDate(0x100000000L * 1000L).toString()); // System.err.println(uid.getTimestamp()); // System.err.println(new Date(0x100000000L * 1000L).getTime()); // System.err.println(new Date(0x100000000L * 1000L).toString()); // System.err.println("-------------------------------a"); // uid = new UniqueId(dateToTimestampSeconds(new Date(0x100000001L * 1000L)), MACHINE_IDENTIFIER, // PROCESS_IDENTIFIER, // NEXT_COUNTER.getAndIncrement(), false); // System.err.println(0x100000001L * 1000L); // System.err.println(uid.getDate(0x100000001L * 1000L).toString()); // System.err.println(uid.getTimestamp()); // System.err.println(new Date(0x100000001L * 1000L).getTime()); // System.err.println(new Date(0x100000001L * 1000L).toString()); // System.err.println("-------------------------------b"); // uid = new UniqueId(dateToTimestampSeconds(new Date(0x1fffffff1L * 1000L)), MACHINE_IDENTIFIER, // PROCESS_IDENTIFIER, // NEXT_COUNTER.getAndIncrement(), false); // System.err.println(0x100000001L * 1000L); // System.err.println(uid.getDate(0x200000000L * 1000L).toString()); // System.err.println(uid.getTimestamp()); // System.err.println(new Date(0x1fffffff1L * 1000L).toString()); // System.err.println(new Date(0x200000000L * 1000L).toString()); // } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy