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}