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}