/*
* Copyright (c) 2015-2016, Christoph Engelbert (aka noctarius) and
* contributors. All rights reserved.
*
* 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 com.noctarius.tengi.core.model;
import com.noctarius.tengi.core.impl.Validate;
import java.util.Arrays;
import java.util.Random;
import java.util.concurrent.atomic.AtomicLong;
/**
* <p>The <tt>Identifier</tt> is a 128bit unique identifier
* which can be used to uniquely identify resources like
* connections, messages or objects.</p>
* <p>Identifiers should be created using the static methods
* of this class and can probably be pooled for resource
* efficient programming models.</p>
*/
public final class Identifier {
private static final ThreadLocal<Random> RANDOMIZERS = new ThreadLocal<Random>() {
@Override
protected Random initialValue() {
// Using the same way as the OpenJDK version just to
// make sure this happens on every JDK implementation
// since there are some out there that just use System.currentTimeMillis()
return new Random(seedUniquifier() ^ System.nanoTime());
}
};
private static final AtomicLong SEED_UNIQUIFIER = new AtomicLong(8682522807148012L);
private static final long MOTHER_OF_MAGIC_NUMBERS = 181783497276652981L;
private static final char[] CHARS = {48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 65, 66, 67, 68, 69, 70};
private final byte[] data;
private Identifier(byte[] data) {
this.data = data;
}
/**
* {@inheritDoc}
*/
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + Arrays.hashCode(data);
return result;
}
/**
* {@inheritDoc}
*/
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (obj == null) {
return false;
}
if (getClass() != obj.getClass()) {
return false;
}
Identifier other = (Identifier) obj;
if (!Arrays.equals(data, other.data)) {
return false;
}
return true;
}
/**
* {@inheritDoc}
*/
@Override
public String toString() {
char[] chars = new char[36];
int index = 0;
for (int i = 0; i < 16; i++) {
if (i == 4 || i == 6 || i == 8 || i == 10) {
chars[index++] = '-';
}
chars[index++] = CHARS[(data[i] & 0xF0) >>> 4];
chars[index++] = CHARS[(data[i] & 0x0F)];
}
return new String(chars);
}
/**
* Generates and returns a new unique identifier consisting of 16 random bytes. The
* bytes content corresponds to the v4 UUID specification but has a special serialization
* strategy for higher speed and is optimized for lower overhead.
*
* @return a new Identifier instance with random bytes
*/
public static Identifier randomIdentifier() {
return new Identifier(randomBytes());
}
/**
* <p>Creates an Identifier instance using the given byte-array. The array must consist of
* exactly 16 bytes building the 128 bit UUID content.</p>
* <p>For speed reasons the byte-array is not copied, therefore the byte-array must not be
* changed after passing it into the method, otherwise it will break the immutability contract
* of the Identifier. This method is meant to be used for deserialization of an Identifier in
* custom protocols.</p>
*
* @param data 16 bytes UUID content
* @return an Identifier instance based on the given byte-array
*/
public static Identifier fromBytes(byte[] data) {
Validate.notNull("data", data);
Validate.equals("data.length", 16, data.length);
return new Identifier(data);
}
private static long seedUniquifier() {
// L'Ecuyer, "Tables of Linear Congruential Generators of
// Different Sizes and Good Lattice Structure", 1999
for (; ; ) {
long current = SEED_UNIQUIFIER.get();
long next = current * MOTHER_OF_MAGIC_NUMBERS;
if (SEED_UNIQUIFIER.compareAndSet(current, next)) {
return next;
}
}
}
private static byte[] randomBytes() {
byte[] data = new byte[16];
RANDOMIZERS.get().nextBytes(data);
/* clear version */
data[6] &= 0x0f;
/* set to version 4 */
data[6] |= 0x40;
/* clear variant */
data[8] &= 0x3f;
/* set to IETF variant */
data[8] |= 0x80;
return data;
}
}