/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You 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.apache.jackrabbit.core.id;
import java.util.Random;
import java.util.UUID;
/**
* Node identifier, i.e. an immutable 128 bit UUID.
*/
public class NodeId implements ItemId, Comparable<NodeId> {
/**
* The serial version UID.
*/
private static final long serialVersionUID = 5773949574212570258L;
/**
* Chars in a UUID String.
*/
public static final int UUID_FORMATTED_LENGTH = 36;
/**
* Number of bytes in a UUID (16).
*/
public static final int UUID_BYTE_LENGTH = 16;
/**
* Returns a node identifier that is represented by the given UUID string.
*
* @param uuid the UUID string
* @return the node identifier
* @throws IllegalArgumentException if the given string is <code>null</code>
* or not a valid UUID
*/
public static NodeId valueOf(String uuid) throws IllegalArgumentException {
if (uuid != null) {
return new NodeId(uuid);
} else {
throw new IllegalArgumentException("NodeId.valueOf(null)");
}
}
/**
* The most significant 64 bits (bytes 0-7) of the UUID.
*/
private final long msb;
/**
* The least significant 64 bits (bytes 8-15) of the UUID.
*/
private final long lsb;
/**
* Creates a node identifier from the given 128 bits.
*
* @param msb most significant 64 bits
* @param lsb least significant 64 bits
*/
public NodeId(long msb, long lsb) {
this.msb = msb;
this.lsb = lsb;
}
/**
* Creates a node identifier from the given 16 bytes.
*
* @param bytes array of 16 bytes
* @throws NullPointerException if the given array is <code>null</code>
* @throws ArrayIndexOutOfBoundsException
* if the given array is less than 16 bytes long
*/
public NodeId(byte[] bytes)
throws NullPointerException, ArrayIndexOutOfBoundsException {
this( // Most significant 64 bits
((((long) bytes[0]) & 0xFF) << 56)
+ ((((long) bytes[1]) & 0xFF) << 48)
+ ((((long) bytes[2]) & 0xFF) << 40)
+ ((((long) bytes[3]) & 0xFF) << 32)
+ ((((long) bytes[4]) & 0xFF) << 24)
+ ((((long) bytes[5]) & 0xFF) << 16)
+ ((((long) bytes[6]) & 0xFF) << 8)
+ ((((long) bytes[7]) & 0xFF)),
// Least significant 64 bits
((((long) bytes[8]) & 0xFF) << 56)
+ ((((long) bytes[9]) & 0xFF) << 48)
+ ((((long) bytes[10]) & 0xFF) << 40)
+ ((((long) bytes[11]) & 0xFF) << 32)
+ ((((long) bytes[12]) & 0xFF) << 24)
+ ((((long) bytes[13]) & 0xFF) << 16)
+ ((((long) bytes[14]) & 0xFF) << 8)
+ ((((long) bytes[15]) & 0xFF)));
}
/**
* Creates a node identifier from the given UUID.
*
* @param uuid UUID
*/
public NodeId(UUID uuid) {
this(uuid.getMostSignificantBits(), uuid.getLeastSignificantBits());
}
/**
* Creates a node identifier from the given UUID string.
*
* @param uuidString UUID string
* @throws IllegalArgumentException if the UUID string is invalid
*/
public NodeId(String uuidString) throws IllegalArgumentException {
// e.g. f81d4fae-7dec-11d0-a765-00a0c91e6bf6
// 012345678901234567890123456789012345
if (uuidString.length() != UUID_FORMATTED_LENGTH) {
throw new IllegalArgumentException(uuidString);
}
long m = 0, x = 0;
for (int i = 0; i < UUID_FORMATTED_LENGTH; i++) {
int c = uuidString.charAt(i);
switch (i) {
case 18:
m = x;
x = 0;
// fall through
case 8:
case 13:
case 23:
if (c != '-') {
throw new IllegalArgumentException(uuidString);
}
break;
default:
if (c >= '0' && c <= '9') {
x = (x << 4) | (c - '0');
} else if (c >= 'a' && c <= 'f') {
x = (x << 4) | (c - 'a' + 0xa);
} else if (c >= 'A' && c <= 'F') {
x = (x << 4) | (c - 'A' + 0xa);
} else {
throw new IllegalArgumentException(uuidString);
}
}
}
this.msb = m;
this.lsb = x;
}
/**
* Creates a random node identifier using a secure random number generator.
*/
public static NodeId randomId() {
Random random = SeededSecureRandom.getInstance();
return new NodeId(
// Most significant 64 bits, with version field set to 4
random.nextLong() & 0xFfffFfffFfff0fffL | 0x0000000000004000L,
// Least significant 64 bits, with variant field set to IETF
random.nextLong() & 0x3fffFfffFfffFfffL | 0x8000000000000000L
);
}
/**
* Returns the 64 most significant bits of this identifier.
*
* @return 64 most significant bits
*/
public long getMostSignificantBits() {
return msb;
}
/**
* Returns the 64 least significant bits of this identifier.
*
* @return 64 least significant bits
*/
public long getLeastSignificantBits() {
return lsb;
}
/**
* Returns the 16 bytes of this identifier.
*
* @return newly allocated array of 16 bytes
*/
public byte[] getRawBytes() {
return new byte[] {
(byte) (msb >> 56), (byte) (msb >> 48), (byte) (msb >> 40),
(byte) (msb >> 32), (byte) (msb >> 24), (byte) (msb >> 16),
(byte) (msb >> 8), (byte) msb,
(byte) (lsb >> 56), (byte) (lsb >> 48), (byte) (lsb >> 40),
(byte) (lsb >> 32), (byte) (lsb >> 24), (byte) (lsb >> 16),
(byte) (lsb >> 8), (byte) lsb
};
}
//--------------------------------------------------------------< ItemId >
/**
* Returns <code>true</code> to indicate that this is a node identifier.
*
* @return always <code>true</code>
*/
public boolean denotesNode() {
return true;
}
//----------------------------------------------------------< Comparable >
/**
* Compares this identifier to the given other one.
*
* @param that other identifier
* @return -1, 0 or +1 if this identifier is less than, equal to,
* or greater than the given other identifier
*/
public int compareTo(NodeId that) {
// This is not a 128 bit integer comparison! See also JCR-687.
if (msb < that.msb) {
return -1;
} else if (msb > that.msb) {
return 1;
} else if (lsb < that.lsb) {
return -1;
} else if (lsb > that.lsb) {
return 1;
} else {
return 0;
}
}
//--------------------------------------------------------------< Object >
/**
* Returns the UUID string representation of this identifier.
*
* @see UUID#toString()
* @return UUID string
*/
public String toString() {
char[] retval = new char[36];
hex4(retval, 0, msb >>> 48);
hex4(retval, 4, msb >>> 32);
retval[8] = '-';
hex4(retval, 9, msb >>> 16);
retval[13] = '-';
hex4(retval, 14, msb);
retval[18] = '-';
hex4(retval, 19, lsb >>> 48);
retval[23] = '-';
hex4(retval, 24, lsb >>> 32);
hex4(retval, 28, lsb >>> 16);
hex4(retval, 32, lsb);
return new String(retval);
}
private static final void hex4(char[] c, int index, long value) {
for (int i = 0; i < 4; i++) {
long v = (value >>> (12 - i * 4)) & 0xf;
if (v < 10) {
c[index + i] = (char) (v + '0');
} else {
c[index + i] = (char) (v - 10 + 'a');
}
}
}
/**
* Compares two UUID for equality.
*
* @see Object#equals(Object)
*/
public boolean equals(Object that) {
return that instanceof NodeId
&& msb == ((NodeId) that).msb
&& lsb == ((NodeId) that).lsb;
}
/**
* Returns a hash code of this identifier.
*
* @return hash code
*/
public int hashCode() {
return (int) ((msb >>> 32) ^ msb ^ (lsb >>> 32) ^ lsb);
}
}