001package co.codewizards.cloudstore.core; 002 003import static java.util.Objects.*; 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 /** 092 * Creates a new {@code Uid} instance from the binary data in {@code bytes}. 093 * <p> 094 * If the given {@code bytes} is <code>null</code> or empty (array-length 0), this method returns <code>null</code>. 095 * Otherwise, it creates an instance using the {@link #Uid(byte[])} constructor. 096 * @param bytes the raw binary data of the Uid. May be <code>null</code>. If not <code>null</code>, 097 * its length must be exactly 0 (treated the same a <code>null</code>) or {@value #LENGTH_BYTES}. 098 * @return an {@code Uid} instance created from the given {@code bytes} or <code>null</code>, if the input 099 * is <code>null</code> or empty (array-length 0). 100 */ 101 public static final Uid valueOf(final byte[] bytes) { 102 return bytes == null || bytes.length == 0 ? null : new Uid(bytes); 103 } 104 105 public Uid(final byte[] bytes) { 106 if (requireNonNull(bytes, "bytes").length != LENGTH_BYTES) 107 throw new IllegalArgumentException("bytes.length != " + LENGTH_BYTES); 108 109 long hi = 0; 110 long lo = 0; 111 112 for (int i = 0; i < Math.min(8, bytes.length); ++i) 113 hi = (hi << 8) | (bytes[i] & 0xff); 114 115 for (int i = 8; i < Math.min(16, bytes.length); ++i) 116 lo = (lo << 8) | (bytes[i] & 0xff); 117 118 this.hi = hi; 119 this.lo = lo; 120 } 121 122 private static final String assertValidUidString(final String uidString) { 123 if (requireNonNull(uidString, "uidString").length() != LENGTH_STRING) 124 throw new IllegalArgumentException("uidString.length != " + LENGTH_STRING + " :: '" + uidString + "'"); 125 126 return uidString; 127 } 128 129 /** 130 * Creates a new {@code Uid} instance from the encoded value in {@code uidString}. 131 * <p> 132 * If the given {@code uidString} is <code>null</code> or empty, this method returns <code>null</code>. 133 * Otherwise, it creates an instance using the {@link #Uid(String)} constructor. 134 * @param uidString the string-encoded value of the Uid. May be <code>null</code>. 135 * @return an {@code Uid} instance created from the given {@code uidString} or <code>null</code>, if the input 136 * is <code>null</code> or empty. 137 */ 138 public static final Uid valueOf(final String uidString) { 139 return uidString == null || uidString.isEmpty() ? null : new Uid(uidString); 140 } 141 142 /** 143 * Creates a new {@code Uid} instance from the encoded value in {@code uidString}. 144 * <p> 145 * This constructor is symmetric to the {@link #toString()} method: The output of {@code toString()} can 146 * be passed to this constructor to create a new instance with the same value as (and thus being 147 * {@linkplain #equals(Object) equal} to) the first instance. 148 * 149 * @param uidString the string-encoded value of the Uid. Must not be <code>null</code>. 150 * @see #toString() 151 */ 152 public Uid(final String uidString) { 153 this(uidStringToByteArray(uidString)); 154 } 155 156 private static byte[] uidStringToByteArray(final String uidString) { 157 return Base64Url.decodeBase64FromString(assertValidUidString(uidString)); 158 } 159 160 public byte[] toBytes() { 161 final byte[] bytes = new byte[LENGTH_BYTES]; 162 163 int idx = -1; 164 for (int i = 7; i >= 0; --i) 165 bytes[++idx] = (byte) (hi >> (8 * i)); 166 167 for (int i = 7; i >= 0; --i) 168 bytes[++idx] = (byte) (lo >> (8 * i)); 169 170 return bytes; 171 } 172 173 @Override 174 public int hashCode() { 175 final int prime = 31; 176 int result = 1; 177 result = prime * result + (int) (hi ^ (hi >>> 32)); 178 result = prime * result + (int) (lo ^ (lo >>> 32)); 179 return result; 180 } 181 182 @Override 183 public boolean equals(final Object obj) { 184 if (this == obj) 185 return true; 186 if (obj == null) 187 return false; 188 if (getClass() != obj.getClass()) 189 return false; 190 final Uid other = (Uid) obj; 191 if (hi != other.hi) 192 return false; 193 if (lo != other.lo) 194 return false; 195 return true; 196 } 197 198 /** 199 * Gets a base64url-encoded string-representation of this {@code Uid}. 200 * <p> 201 * The string returned by this method can be passed to {@link #Uid(String)} to create a new equal 202 * {@code Uid} instance. 203 * <p> 204 * <b>Important:</b> The string-representation is <b>case-sensitive</b>! 205 * <p> 206 * <b><u>Inherited documentation:</u></b><br/> 207 * {@inheritDoc} 208 */ 209 @Override 210 public String toString() { 211 String s = toString == null ? null : toString.get(); 212 if (s != null) 213 return s; 214 215 s = Base64Url.encodeBase64ToString(toBytes()); 216 217 if (s.length() != LENGTH_STRING) // sanity check 218 throw new IllegalStateException("uidString.length != " + LENGTH_STRING); 219 220 toString = new WeakReference<String>(s); 221 return s; 222 } 223 224 @Override 225 public int compareTo(final Uid other) { 226 requireNonNull(other, "other"); 227 // Same semantics as for normal numbers. 228 return (this.hi < other.hi ? -1 : 229 (this.hi > other.hi ? 1 : 230 (this.lo < other.lo ? -1 : 231 (this.lo > other.lo ? 1 : 232 0)))); 233 } 234}