001package co.codewizards.cloudstore.core; 002 003import static co.codewizards.cloudstore.core.util.AssertUtil.*; 004 005import java.io.Serializable; 006import java.lang.ref.WeakReference; 007import java.security.SecureRandom; 008import java.util.UUID; 009 010import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter; 011 012import co.codewizards.cloudstore.core.dto.jaxb.UidXmlAdapter; 013import co.codewizards.cloudstore.core.util.Base64Url; 014 015/** 016 * Universal identifier similar to a {@link UUID}. 017 * <p> 018 * The main difference is the encoding which is optimized for brevity. In contrast to the hex-encoding used 019 * by {@code UUID}, the {@link #toString()} method uses standard 020 * <a href="http://en.wikipedia.org/wiki/Base64">base64url</a> 021 * (<a href="http://en.wikipedia.org/wiki/Base64#RFC_4648">RFC 4648</a>). The difference to normal base64 is 022 * that base64url replaces '+' by '-' and '/' by '_' in order to make the encoded string usable in URLs 023 * without any escaping. 024 * <p> 025 * <b>Important:</b> The string-representation is <b>case-sensitive</b>! 026 * <p> 027 * Examples showing the difference of {@code UUID} vs. {@code Uid}: 028 * <pre> WQL8yMHUQ4FhZrB0cLux5g 029 * WCRAGMeiz-2PPaKdmn-iww 030 * Jd6_KRqpMivfuxXO4JmwtQ 031 * 284tn0-92bIMRNV_4M53Tg 032 * 8b726260-f9f3-439b-bf21-615bb4b6731d 033 * 34fadc2b-5a58-4de8-b04c-6f315a6598cd 034 * 15c3f6cb-6275-4557-b24c-2cd57cd07a6d 035 * 11934d8c-d201-4a95-a714-e03ff48f5053 036 * 46875d87-01ef-4ece-98cf-5a96a5946ef7</pre> 037 * <p> 038 * A string-encoded {@code UUID} always has a length 36 characters, while a {@code Uid} always has a length 039 * of 22 characters. In other words, the strings are 38.89% shorter. 040 * <p> 041 * Instances of this class are immutable. 042 * 043 * @author Marco หงุ่ยตระกูล-Schulze - marco at codewizards dot co 044 */ 045@XmlJavaTypeAdapter(type=Uid.class, value=UidXmlAdapter.class) 046public class Uid implements Comparable<Uid>, Serializable { 047 /** 048 * Gets the length of an {@code Uid} in {@link #toBytes() bytes}. 049 */ 050 public static final int LENGTH_BYTES = 16; 051 052 /** 053 * Gets the length of an {@code Uid} in its {@link #toString() String representation}. 054 */ 055 public static final int LENGTH_STRING = 22; 056 057 private static final long serialVersionUID = 1L; 058 059 private final long hi; 060 private final long lo; 061 private transient WeakReference<String> toString; 062 063 private static class RandomHolder { 064 static final SecureRandom random = new SecureRandom(); 065 static final byte[] next16Bytes() { 066 final byte[] bytes = new byte[16]; 067 random.nextBytes(bytes); 068 return bytes; 069 } 070 } 071 072 /** 073 * Creates a new random {@code Uid}. 074 */ 075 public Uid() { 076 this(RandomHolder.next16Bytes()); 077 } 078 079 /** 080 * Creates a new {@code Uid} with the given value. 081 * <p> 082 * This constructor is equivalent to {@link UUID#UUID(long, long) UUID(long, long)}. 083 * @param hi the most significant bits of the new {@code Uid}. 084 * @param lo the least significant bits of the new {@code Uid}. 085 */ 086 public Uid(final long hi, final long lo) { 087 this.hi = hi; 088 this.lo = lo; 089 } 090 091 public Uid(final byte[] bytes) { 092 if (assertNotNull(bytes, "bytes").length != LENGTH_BYTES) 093 throw new IllegalArgumentException("bytes.length != " + LENGTH_BYTES); 094 095 long hi = 0; 096 long lo = 0; 097 098 for (int i = 0; i < Math.min(8, bytes.length); ++i) 099 hi = (hi << 8) | (bytes[i] & 0xff); 100 101 for (int i = 8; i < Math.min(16, bytes.length); ++i) 102 lo = (lo << 8) | (bytes[i] & 0xff); 103 104 this.hi = hi; 105 this.lo = lo; 106 } 107 108 private static final String assertValidUidString(final String uidString) { 109 if (assertNotNull(uidString, "uidString").length() != LENGTH_STRING) 110 throw new IllegalArgumentException("uidString.length != " + LENGTH_STRING + " :: '" + uidString + "'"); 111 112 return uidString; 113 } 114 115 /** 116 * Creates a new {@code Uid} instance from the encoded value in {@code uidString}. 117 * <p> 118 * This constructor is symmetric to the {@link #toString()} method: The output of {@code toString()} can 119 * be passed to this constructor to create a new instance with the same value as (and thus being 120 * {@linkplain #equals(Object) equal} to) the first instance. 121 * 122 * @param uidString the string-encoded value of the Uid. 123 * @see #toString() 124 */ 125 public Uid(final String uidString) { 126 this(uidStringToByteArray(uidString)); 127 } 128 129 private static byte[] uidStringToByteArray(final String uidString) { 130 return Base64Url.decodeBase64FromString(assertValidUidString(uidString)); 131 } 132 133 public byte[] toBytes() { 134 final byte[] bytes = new byte[LENGTH_BYTES]; 135 136 int idx = -1; 137 for (int i = 7; i >= 0; --i) 138 bytes[++idx] = (byte) (hi >> (8 * i)); 139 140 for (int i = 7; i >= 0; --i) 141 bytes[++idx] = (byte) (lo >> (8 * i)); 142 143 return bytes; 144 } 145 146 @Override 147 public int hashCode() { 148 final int prime = 31; 149 int result = 1; 150 result = prime * result + (int) (hi ^ (hi >>> 32)); 151 result = prime * result + (int) (lo ^ (lo >>> 32)); 152 return result; 153 } 154 155 @Override 156 public boolean equals(final Object obj) { 157 if (this == obj) 158 return true; 159 if (obj == null) 160 return false; 161 if (getClass() != obj.getClass()) 162 return false; 163 final Uid other = (Uid) obj; 164 if (hi != other.hi) 165 return false; 166 if (lo != other.lo) 167 return false; 168 return true; 169 } 170 171 /** 172 * Gets a base64url-encoded string-representation of this {@code Uid}. 173 * <p> 174 * The string returned by this method can be passed to {@link #Uid(String)} to create a new equal 175 * {@code Uid} instance. 176 * <p> 177 * <b>Important:</b> The string-representation is <b>case-sensitive</b>! 178 * <p> 179 * <b><u>Inherited documentation:</u></b><br/> 180 * {@inheritDoc} 181 */ 182 @Override 183 public String toString() { 184 String s = toString == null ? null : toString.get(); 185 if (s != null) 186 return s; 187 188 s = Base64Url.encodeBase64ToString(toBytes()); 189 190 if (s.length() != LENGTH_STRING) // sanity check 191 throw new IllegalStateException("uidString.length != " + LENGTH_STRING); 192 193 toString = new WeakReference<String>(s); 194 return s; 195 } 196 197 @Override 198 public int compareTo(final Uid other) { 199 assertNotNull(other, "other"); 200 // Same semantics as for normal numbers. 201 return (this.hi < other.hi ? -1 : 202 (this.hi > other.hi ? 1 : 203 (this.lo < other.lo ? -1 : 204 (this.lo > other.lo ? 1 : 205 0)))); 206 } 207}