package co.codewizards.cloudstore.core;
import static co.codewizards.cloudstore.core.util.AssertUtil.*;
import java.io.Serializable;
import java.lang.ref.WeakReference;
import java.security.SecureRandom;
import java.util.UUID;
import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter;
import co.codewizards.cloudstore.core.dto.jaxb.UidXmlAdapter;
import co.codewizards.cloudstore.core.util.Base64Url;
/**
* Universal identifier similar to a {@link UUID}.
* <p>
* The main difference is the encoding which is optimized for brevity. In contrast to the hex-encoding used
* by {@code UUID}, the {@link #toString()} method uses standard
* <a href="http://en.wikipedia.org/wiki/Base64">base64url</a>
* (<a href="http://en.wikipedia.org/wiki/Base64#RFC_4648">RFC 4648</a>). The difference to normal base64 is
* that base64url replaces '+' by '-' and '/' by '_' in order to make the encoded string usable in URLs
* without any escaping.
* <p>
* <b>Important:</b> The string-representation is <b>case-sensitive</b>!
* <p>
* Examples showing the difference of {@code UUID} vs. {@code Uid}:
* <pre> WQL8yMHUQ4FhZrB0cLux5g
* WCRAGMeiz-2PPaKdmn-iww
* Jd6_KRqpMivfuxXO4JmwtQ
* 284tn0-92bIMRNV_4M53Tg
* 8b726260-f9f3-439b-bf21-615bb4b6731d
* 34fadc2b-5a58-4de8-b04c-6f315a6598cd
* 15c3f6cb-6275-4557-b24c-2cd57cd07a6d
* 11934d8c-d201-4a95-a714-e03ff48f5053
* 46875d87-01ef-4ece-98cf-5a96a5946ef7</pre>
* <p>
* A string-encoded {@code UUID} always has a length 36 characters, while a {@code Uid} always has a length
* of 22 characters. In other words, the strings are 38.89% shorter.
* <p>
* Instances of this class are immutable.
*
* @author Marco หงุ่ยตระกูล-Schulze - marco at codewizards dot co
*/
@XmlJavaTypeAdapter(type=Uid.class, value=UidXmlAdapter.class)
public class Uid implements Comparable<Uid>, Serializable {
/**
* Gets the length of an {@code Uid} in {@link #toBytes() bytes}.
*/
public static final int LENGTH_BYTES = 16;
/**
* Gets the length of an {@code Uid} in its {@link #toString() String representation}.
*/
public static final int LENGTH_STRING = 22;
private static final long serialVersionUID = 1L;
private final long hi;
private final long lo;
private transient WeakReference<String> toString;
private static class RandomHolder {
static final SecureRandom random = new SecureRandom();
static final byte[] next16Bytes() {
final byte[] bytes = new byte[16];
random.nextBytes(bytes);
return bytes;
}
}
/**
* Creates a new random {@code Uid}.
*/
public Uid() {
this(RandomHolder.next16Bytes());
}
/**
* Creates a new {@code Uid} with the given value.
* <p>
* This constructor is equivalent to {@link UUID#UUID(long, long) UUID(long, long)}.
* @param hi the most significant bits of the new {@code Uid}.
* @param lo the least significant bits of the new {@code Uid}.
*/
public Uid(final long hi, final long lo) {
this.hi = hi;
this.lo = lo;
}
public Uid(final byte[] bytes) {
if (assertNotNull(bytes, "bytes").length != LENGTH_BYTES)
throw new IllegalArgumentException("bytes.length != " + LENGTH_BYTES);
long hi = 0;
long lo = 0;
for (int i = 0; i < Math.min(8, bytes.length); ++i)
hi = (hi << 8) | (bytes[i] & 0xff);
for (int i = 8; i < Math.min(16, bytes.length); ++i)
lo = (lo << 8) | (bytes[i] & 0xff);
this.hi = hi;
this.lo = lo;
}
private static final String assertValidUidString(final String uidString) {
if (assertNotNull(uidString, "uidString").length() != LENGTH_STRING)
throw new IllegalArgumentException("uidString.length != " + LENGTH_STRING + " :: '" + uidString + "'");
return uidString;
}
/**
* Creates a new {@code Uid} instance from the encoded value in {@code uidString}.
* <p>
* This constructor is symmetric to the {@link #toString()} method: The output of {@code toString()} can
* be passed to this constructor to create a new instance with the same value as (and thus being
* {@linkplain #equals(Object) equal} to) the first instance.
*
* @param uidString the string-encoded value of the Uid.
* @see #toString()
*/
public Uid(final String uidString) {
this(uidStringToByteArray(uidString));
}
private static byte[] uidStringToByteArray(final String uidString) {
return Base64Url.decodeBase64FromString(assertValidUidString(uidString));
}
public byte[] toBytes() {
final byte[] bytes = new byte[LENGTH_BYTES];
int idx = -1;
for (int i = 7; i >= 0; --i)
bytes[++idx] = (byte) (hi >> (8 * i));
for (int i = 7; i >= 0; --i)
bytes[++idx] = (byte) (lo >> (8 * i));
return bytes;
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + (int) (hi ^ (hi >>> 32));
result = prime * result + (int) (lo ^ (lo >>> 32));
return result;
}
@Override
public boolean equals(final Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
final Uid other = (Uid) obj;
if (hi != other.hi)
return false;
if (lo != other.lo)
return false;
return true;
}
/**
* Gets a base64url-encoded string-representation of this {@code Uid}.
* <p>
* The string returned by this method can be passed to {@link #Uid(String)} to create a new equal
* {@code Uid} instance.
* <p>
* <b>Important:</b> The string-representation is <b>case-sensitive</b>!
* <p>
* <b><u>Inherited documentation:</u></b><br/>
* {@inheritDoc}
*/
@Override
public String toString() {
String s = toString == null ? null : toString.get();
if (s != null)
return s;
s = Base64Url.encodeBase64ToString(toBytes());
if (s.length() != LENGTH_STRING) // sanity check
throw new IllegalStateException("uidString.length != " + LENGTH_STRING);
toString = new WeakReference<String>(s);
return s;
}
@Override
public int compareTo(final Uid other) {
assertNotNull(other, "other");
// Same semantics as for normal numbers.
return (this.hi < other.hi ? -1 :
(this.hi > other.hi ? 1 :
(this.lo < other.lo ? -1 :
(this.lo > other.lo ? 1 :
0))));
}
}