Classes in this File | Line Coverage | Branch Coverage | Complexity | ||||
UUID |
|
| 2.5;2.5 |
1 | /* | |
2 | * Licensed to the Apache Software Foundation (ASF) under one or more | |
3 | * contributor license agreements. See the NOTICE file distributed with | |
4 | * this work for additional information regarding copyright ownership. | |
5 | * The ASF licenses this file to You under the Apache License, Version 2.0 | |
6 | * (the "License"); you may not use this file except in compliance with | |
7 | * the License. You may obtain a copy of the License at | |
8 | * | |
9 | * http://www.apache.org/licenses/LICENSE-2.0 | |
10 | * | |
11 | * Unless required by applicable law or agreed to in writing, software | |
12 | * distributed under the License is distributed on an "AS IS" BASIS, | |
13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
14 | * See the License for the specific language governing permissions and | |
15 | * limitations under the License. | |
16 | */ | |
17 | package org.apache.commons.id.uuid; | |
18 | ||
19 | import org.apache.commons.id.DecoderException; | |
20 | import org.apache.commons.id.DigestUtils; | |
21 | import org.apache.commons.id.Hex; | |
22 | ||
23 | import java.io.DataInput; | |
24 | import java.io.IOException; | |
25 | import java.io.Serializable; | |
26 | import java.util.StringTokenizer; | |
27 | ||
28 | ||
29 | /** | |
30 | * <p><code>UUID</code> represents a Universally Unique Identifier per RFC 4122. | |
31 | * See the <a href="ftp://ftp.rfc-editor.org/in-notes/rfc4122.txt">RFC 4122: | |
32 | * A Universally Unique IDentifier (UUID) URN Namespace</a> | |
33 | * for more information.</p> | |
34 | * | |
35 | * @author Commons-Id Team | |
36 | * @version $Revision: 480488 $ $Date: 2006-11-29 08:57:26 +0000 (Wed, 29 Nov 2006) $ | |
37 | * | |
38 | */ | |
39 | ||
40 | public class UUID implements Constants, Serializable, Comparable { | |
41 | ||
42 | /** byte array to store 128-bits composing this UUID */ | |
43 | 107 | private byte[] rawBytes = new byte[UUID_BYTE_LENGTH]; |
44 | ||
45 | /** Holds node identifier for this UUID */ | |
46 | 107 | private Long node = null; |
47 | ||
48 | /** Holds timestamp for this UUID */ | |
49 | 107 | private long timestamp = -1; |
50 | ||
51 | /** Holds the clock sequence field */ | |
52 | 107 | private Short clockSq = null; |
53 | ||
54 | /** Holds the version field of this UUID */ | |
55 | 107 | private int version = -1; |
56 | ||
57 | /** Holds the variant field of this UUID */ | |
58 | 107 | private int variant = -1; |
59 | ||
60 | /** Holds the internal string value of the UUID */ | |
61 | 107 | private String stringValue = null; |
62 | ||
63 | /** Constructs a nil UUID */ | |
64 | public UUID() { | |
65 | 3 | super(); |
66 | 3 | } |
67 | ||
68 | /** | |
69 | * <p>Constructs a UUID from a 128 bit java.math.BigInteger.</p> | |
70 | * <p>Method is protected as their is no standard as to the internal representation of a UUID. | |
71 | * In this case a BigInteger is used with signum always positive.</p> | |
72 | * | |
73 | * @param bigIntValue the 128 bit BigInteger to construct this UUID from. | |
74 | * @throws IllegalArgumentException argument must be 128 bit | |
75 | */ | |
76 | /* protected UUID(BigInteger bigIntValue) throws IllegalArgumentException { | |
77 | super(); | |
78 | if (bigIntValue.bitLength() > UUID.UUID_BIT_LENGTH) { | |
79 | throw new IllegalArgumentException("UUID must be contructed using a 128 bit BigInteger"); | |
80 | } | |
81 | numberValue = bigIntValue; | |
82 | } */ | |
83 | ||
84 | /** | |
85 | * <p>Copy constructor.</p> | |
86 | * | |
87 | * @param copyFrom the UUID to copy to create this UUID. | |
88 | */ | |
89 | public UUID(UUID copyFrom) { | |
90 | 34 | super(); |
91 | 34 | rawBytes = copyFrom.getRawBytes(); |
92 | 34 | } |
93 | ||
94 | /** | |
95 | * <p>Constructs a UUID from a 16 byte array.</p> | |
96 | * | |
97 | * @param byteArray the 16 byte array to construct this UUID from. | |
98 | * @throws IllegalArgumentException argument must be 16 bytes | |
99 | */ | |
100 | public UUID(byte[] byteArray) throws IllegalArgumentException { | |
101 | 67 | super(); |
102 | 67 | if (byteArray.length != UUID_BYTE_LENGTH) { |
103 | 0 | throw new IllegalArgumentException("UUID must be contructed using a 16 byte array."); |
104 | } | |
105 | // UUID must be immutable so a copy is used. | |
106 | 67 | System.arraycopy(byteArray, 0, rawBytes, 0, UUID_BYTE_LENGTH); |
107 | 67 | } |
108 | ||
109 | /** | |
110 | * <p>Constructs a UUID from a DataInput. Note if 16 bytes are not available this method will block.</p> | |
111 | * | |
112 | * @param input the datainput with 16 bytes to read in from. | |
113 | * @throws IOException exception if there is an IO problem also argument must contain 16 bytes. | |
114 | */ | |
115 | public UUID(DataInput input) throws IOException { | |
116 | 2 | super(); |
117 | 2 | input.readFully(rawBytes, 0, UUID_BYTE_LENGTH); |
118 | 1 | } |
119 | ||
120 | /** | |
121 | * <p>Constructs a UUID from two long values in most significant byte, and least significant bytes order.</p> | |
122 | * | |
123 | * @param mostSignificant - the most significant 8 bytes of the uuid to be constructed. | |
124 | * @param leastSignificant - the least significant 8 bytes of the uuid to be constructed. | |
125 | */ | |
126 | 1 | public UUID(long mostSignificant, long leastSignificant) { |
127 | 1 | rawBytes = Bytes.append(Bytes.toBytes(mostSignificant), Bytes.toBytes(leastSignificant)); |
128 | 1 | } |
129 | ||
130 | /** | |
131 | * <p>Constructs a UUID from a UUID formatted String.</p> | |
132 | * | |
133 | * @param uuidString the String representing a UUID to construct this UUID | |
134 | * @throws UUIDFormatException String must be a properly formatted UUID string | |
135 | */ | |
136 | public UUID(String uuidString) throws UUIDFormatException { | |
137 | //Calls the copy constructor | |
138 | 37 | this(UUID.fromString(uuidString)); |
139 | 33 | } |
140 | ||
141 | /** | |
142 | * <p>Parses a string for a UUID.</p> | |
143 | * | |
144 | * @param uuidString the UUID formatted String to parse. | |
145 | * @throws UUIDFormatException the String must be a properly formatted UUID String. | |
146 | * @return Returns a UUID or null if the formatted string could not be parsed. | |
147 | */ | |
148 | public static UUID fromString(String uuidString) | |
149 | throws UUIDFormatException { | |
150 | 48 | String leanString = uuidString.toLowerCase(); |
151 | 48 | UUID tmpUUID = null; |
152 | ||
153 | //Handle prefixed UUIDs | |
154 | // e.g. urn:uuid:f81d4fae-7dec-11d0-a765-00a0c91e6bf6 | |
155 | 48 | int pos = uuidString.lastIndexOf(":"); |
156 | 48 | if (pos > 1) { |
157 | 6 | leanString = uuidString.substring(++pos, uuidString.length()); |
158 | } | |
159 | ||
160 | //Check for 36 char length | |
161 | 48 | if (leanString.length() != UUID_FORMATTED_LENGTH) { |
162 | 4 | throw new UUIDFormatException(uuidString); |
163 | } | |
164 | ||
165 | //Check for 5 fields | |
166 | 44 | StringTokenizer tok = new StringTokenizer(leanString, "-"); |
167 | 44 | if ( tok.countTokens() != TOKENS_IN_UUID ) { |
168 | 0 | throw new UUIDFormatException(uuidString); |
169 | } | |
170 | ||
171 | //Remove the "-" from the formatted string and test token sizes | |
172 | 44 | StringBuffer buf = new StringBuffer(UUID_UNFORMATTED_LENGTH); |
173 | 44 | String token = null; |
174 | 44 | int count = 0; |
175 | 260 | while (tok.hasMoreTokens()) { |
176 | 218 | token = tok.nextToken(); |
177 | 218 | if (token.length() != TOKEN_LENGTHS[count++]) { |
178 | 2 | throw new UUIDFormatException(uuidString); |
179 | } | |
180 | 216 | buf.append(token); |
181 | 216 | } |
182 | ||
183 | //Create from the hex value | |
184 | try { | |
185 | 42 | char[] chars = buf.toString().toCharArray(); |
186 | 42 | tmpUUID = new UUID(Hex.decodeHex(chars)); |
187 | 2 | } catch (DecoderException de) { |
188 | 2 | throw new UUIDFormatException(uuidString + ": " + de.getMessage()); |
189 | 40 | } |
190 | 40 | return tmpUUID; |
191 | } | |
192 | ||
193 | /** | |
194 | * <p>Returns a string representation of the UUID.</p> | |
195 | * | |
196 | * @return a string representation of the UUID formatted according to the specification. | |
197 | */ | |
198 | public String toString() { | |
199 | //set string value if not set | |
200 | 19 | if (stringValue == null) { |
201 | 19 | StringBuffer buf = new StringBuffer(new String(Hex.encodeHex(rawBytes))); |
202 | 19 | while (buf.length() != UUID_UNFORMATTED_LENGTH) { |
203 | 0 | buf.insert(0, "0"); |
204 | 0 | } |
205 | 19 | buf.ensureCapacity(UUID_FORMATTED_LENGTH); |
206 | 19 | buf.insert(FORMAT_POSITION1, '-'); |
207 | 19 | buf.insert(FORMAT_POSITION2, '-'); |
208 | 19 | buf.insert(FORMAT_POSITION3, '-'); |
209 | 19 | buf.insert(FORMAT_POSITION4, '-'); |
210 | 19 | stringValue = buf.toString(); |
211 | } | |
212 | 19 | return stringValue; |
213 | } | |
214 | ||
215 | /** | |
216 | * <p>Returns a urn representation of the UUID. This is same as the | |
217 | * toString() value prefixed with <code>urn:uuid:</code></p> | |
218 | * | |
219 | * @return Returns the urn string representation of the UUID | |
220 | */ | |
221 | public String toUrn() { | |
222 | 3 | return URN_PREFIX + this.toString(); |
223 | } | |
224 | ||
225 | /** | |
226 | * <p>Compares two UUID for equality.</p> | |
227 | * | |
228 | * @see java.lang.Object#equals(Object) | |
229 | */ | |
230 | ||
231 | public boolean equals(Object obj) { | |
232 | 19 | if (!(obj instanceof UUID)) { |
233 | 0 | return false; |
234 | } | |
235 | 19 | return Bytes.areEqual( ((UUID) obj).getRawBytes(), rawBytes); |
236 | } | |
237 | ||
238 | /** | |
239 | * <p>Returns a hash code value for the object.</p> | |
240 | * | |
241 | * @see java.lang.Object#hashCode() | |
242 | */ | |
243 | public int hashCode() { | |
244 | 14 | int iConstant = 37; |
245 | 14 | int iTotal = 17; |
246 | 238 | for (int i = 0; i < rawBytes.length; i++) { |
247 | 224 | iTotal = iTotal * iConstant + rawBytes[i]; |
248 | } | |
249 | 14 | return iTotal; |
250 | } | |
251 | ||
252 | /** | |
253 | * <p>Compares two UUID's for equality.</p> | |
254 | * | |
255 | * @see Comparable#compareTo(Object) | |
256 | */ | |
257 | public int compareTo(Object compareTo) throws ClassCastException { | |
258 | 3 | if (!(compareTo instanceof UUID)) { |
259 | 0 | throw new ClassCastException(); |
260 | } | |
261 | 3 | return (Bytes.compareTo(rawBytes, ((UUID) compareTo).getRawBytes())); |
262 | } | |
263 | ||
264 | /** | |
265 | * <p>Returns the clock sequence value in the UUID. The clock sequence is a random assigned to a particular clock instance that | |
266 | * generated the time in the timestamp of a time based UUID.</p> | |
267 | * | |
268 | * @return the clock sequence value in the UUID. | |
269 | * @throws UnsupportedOperationException thrown if this is not a IETF variant or not a time-based UUID. | |
270 | */ | |
271 | public int clockSequence() throws UnsupportedOperationException { | |
272 | //if variant is not mealling leach salz throw unsupported operation exception | |
273 | 1 | if (variant() != VARIANT_IETF_DRAFT || version() != VERSION_ONE) { |
274 | 0 | throw new UnsupportedOperationException(WRONG_VAR_VER_MSG); |
275 | } | |
276 | 1 | if (clockSq == null) { |
277 | 1 | byte[] b = {((byte) (rawBytes[8] & 0x3F)), rawBytes[9]}; |
278 | 1 | clockSq = new Short(Bytes.toShort(b)); |
279 | } | |
280 | 1 | return clockSq.intValue(); |
281 | } | |
282 | ||
283 | /** | |
284 | * <p>Returns the version of the UUID. | |
285 | * <ul> | |
286 | * <li>VERSION_ONE - The time-based version</li> | |
287 | * <li>VERSION_TWO - DCE Security version, with embedded POSIX UIDs.</li> | |
288 | * <li>VERSION_THREE - Name based UUID with MD5 hashing.</li> | |
289 | * <li>VERSION_FOUR - Random based UUID.</li> | |
290 | * <li>VERSION_FIVE - Name based UUID with SHA-1 hashing.</li> | |
291 | * </ul> | |
292 | * </p> | |
293 | * @return the version of the UUID. | |
294 | */ | |
295 | public int version() { | |
296 | 14 | if (version == -1) { |
297 | 14 | version = ((rawBytes[6] >>> 4) & 0x0F); |
298 | } | |
299 | 14 | return version; |
300 | } | |
301 | ||
302 | /** | |
303 | * <p>Returns the variant field of the UUID.</p> | |
304 | * | |
305 | * @return Returns the variant field of the UUID. | |
306 | * @see UUID#VARIANT_NCS_COMPAT | |
307 | * @see UUID#VARIANT_IETF_DRAFT | |
308 | * @see UUID#VARIANT_MS | |
309 | * @see UUID#VARIANT_FUTURE | |
310 | */ | |
311 | public int variant() { | |
312 | 9 | if (variant == -1) { |
313 | 9 | if ((rawBytes[8] & 0x80) == 0x0) { |
314 | 1 | variant = VARIANT_NCS_COMPAT; |
315 | 1 | } else if ((rawBytes[8] & 0x40) == 0x0) { |
316 | 6 | variant = VARIANT_IETF_DRAFT; |
317 | 6 | } else if ((rawBytes[8] & 0x20) == 0x0) { |
318 | 1 | variant = VARIANT_MS; |
319 | 1 | } else { |
320 | 1 | variant = VARIANT_FUTURE; |
321 | } | |
322 | } | |
323 | 9 | return variant; |
324 | } | |
325 | ||
326 | /** | |
327 | * <p>Returns the node identifier found in this UUID. The specification was written such that this value holds the IEEE 802 MAC | |
328 | * address. The specification permits this value to be calculated from other sources other than the MAC.</p> | |
329 | * | |
330 | * @return the node identifier found in this UUID. | |
331 | * @throws UnsupportedOperationException thrown if this is not a IETF variant or not a time-based UUID. | |
332 | */ | |
333 | public long node() throws UnsupportedOperationException { | |
334 | //if variant is not mealling leach salz throw unsupported operation exception | |
335 | 1 | if (variant() != VARIANT_IETF_DRAFT || version() != VERSION_ONE) { |
336 | 0 | throw new UnsupportedOperationException(WRONG_VAR_VER_MSG); |
337 | } | |
338 | 1 | if (node == null) { |
339 | 1 | byte[] b = new byte[8]; |
340 | 1 | System.arraycopy(rawBytes, 10, b, 2, 6); |
341 | 1 | node = new Long((Bytes.toLong(b) & 0xFFFFFFFFFFFFL)); |
342 | } | |
343 | 1 | return node.longValue(); |
344 | } | |
345 | ||
346 | /** | |
347 | * <p>Returns the timestamp value of the UUID as 100-nano second intervals since the Gregorian change offset (00:00:00.00, 15 | |
348 | * October 1582 ).</p> | |
349 | * | |
350 | * @return the timestamp value of the UUID as 100-nano second intervals since the Gregorian change offset. | |
351 | * @throws UnsupportedOperationException thrown if this is not a IETF variant or not a time-based UUID. | |
352 | */ | |
353 | public long timestamp() throws UnsupportedOperationException { | |
354 | //if variant is not mealling leach salz throw unsupported operation exception | |
355 | 1 | if (variant() != VARIANT_IETF_DRAFT || version() != VERSION_ONE) { |
356 | 0 | throw new UnsupportedOperationException(WRONG_VAR_VER_MSG); |
357 | } | |
358 | 1 | if (timestamp == -1) { |
359 | 1 | byte[] longVal = new byte[8]; |
360 | 1 | System.arraycopy(rawBytes, TIME_HI_START_POS, longVal, TIME_HI_TS_POS, TIME_HI_BYTE_LEN); |
361 | 1 | System.arraycopy(rawBytes, TIME_MID_START_POS, longVal, TIME_MID_TS_POS, TIME_MID_BYTE_LEN); |
362 | 1 | System.arraycopy(rawBytes, TIME_LOW_START_POS, longVal, TIME_LOW_TS_POS, TIME_LOW_BYTE_LEN); |
363 | 1 | longVal[TIME_HI_TS_POS] &= 0x0F; |
364 | 1 | timestamp = Bytes.toLong(longVal); |
365 | } | |
366 | 1 | return timestamp; |
367 | } | |
368 | ||
369 | /** | |
370 | * <p>Returns the least significant bits stored in the uuid's internal structure.</p> | |
371 | * | |
372 | * @return the least significant bits stored in the uuid's internal structure. | |
373 | */ | |
374 | long getLeastSignificantBits() { | |
375 | 0 | byte[] lsb = new byte[8]; |
376 | 0 | System.arraycopy(rawBytes, 8, lsb, 0, 8); |
377 | 0 | return Bytes.toLong(lsb); |
378 | } | |
379 | ||
380 | /** | |
381 | * <p>Returns the least significant bits stored in the uuid's internal structure.</p> | |
382 | * | |
383 | * @return the least significant bits stored in the uuid's internal structure. | |
384 | */ | |
385 | long getMostSignificantBits() { | |
386 | 0 | byte[] msb = new byte[8]; |
387 | 0 | System.arraycopy(rawBytes, 0, msb, 0, 8); |
388 | 0 | return Bytes.toLong(msb); |
389 | } | |
390 | ||
391 | /** | |
392 | * <p>Returns a copy of the byte values contained in this UUID. | |
393 | * | |
394 | * @return a copy of the byte values contained in this UUID. | |
395 | */ | |
396 | public byte[] getRawBytes() { | |
397 | 76 | byte[] ret = new byte[UUID_BYTE_LENGTH]; |
398 | 76 | System.arraycopy(rawBytes, 0, ret, 0, UUID_BYTE_LENGTH); |
399 | 76 | return ret; |
400 | } | |
401 | ||
402 | /** | |
403 | * <p>Returns a new version 4 UUID, based upon Random bits.</p> | |
404 | * | |
405 | * @return a new version 4 UUID, based upon Random bits. | |
406 | */ | |
407 | public static UUID randomUUID() { | |
408 | 1 | return VersionFourGenerator.getInstance().nextUUID(); |
409 | } | |
410 | ||
411 | /** | |
412 | * <p>Returns a new version 1 UUID, based upon node identifier and time stamp.</p> | |
413 | * | |
414 | * @return a new version 1 UUID, based upon node identifier and time stamp. | |
415 | */ | |
416 | public static UUID timeUUID() { | |
417 | 1 | return VersionOneGenerator.getInstance().nextUUID(); |
418 | } | |
419 | ||
420 | /** | |
421 | * <p>Returns a new version three (MD5) or five (SHA-1) UUID, using the specified encoding | |
422 | * given a name and the namespace's UUID.</p> | |
423 | * | |
424 | * @param name String the name to calculate the UUID for. | |
425 | * @param namespace UUID assigned to this namespace. | |
426 | * @param encoding The encoding to use, either #{link UUID.MD5_ENCODING} or #{link UUID.SHA1_ENCODING} | |
427 | * @return a new version three UUID given a name and the namespace's UUID. | |
428 | */ | |
429 | public static UUID nameUUIDFromString(String name, UUID namespace, String encoding) { | |
430 | 13 | byte[] nameAsBytes = name.getBytes(); |
431 | 13 | byte[] concat = new byte[UUID_BYTE_LENGTH + nameAsBytes.length]; |
432 | 13 | System.arraycopy(namespace.getRawBytes(), 0, concat, 0, UUID_BYTE_LENGTH); |
433 | 13 | System.arraycopy(nameAsBytes, 0, concat, UUID_BYTE_LENGTH, nameAsBytes.length); |
434 | ||
435 | 13 | byte[] raw = null; |
436 | ||
437 | 13 | if(encoding.equals(UUID.MD5_ENCODING)) { |
438 | 7 | raw = DigestUtils.md5(concat); |
439 | 7 | } |
440 | 6 | else if(encoding.equals(UUID.SHA1_ENCODING)) { |
441 | 6 | byte[] shaDigest = DigestUtils.sha(concat); |
442 | // Truncate digest to 16 bytes (SHA-1 returns a 20-byte digest) | |
443 | 6 | raw = new byte[16]; |
444 | 6 | System.arraycopy(shaDigest, 0, raw, 0, 16); |
445 | 6 | } |
446 | else { | |
447 | 0 | throw new RuntimeException("Unsupported encoding " + encoding); |
448 | } | |
449 | ||
450 | ||
451 | //Set version (version 3 and version 5 are identical on a bit-level, | |
452 | //thus we only need ever set one of them | |
453 | 13 | raw[TIME_HI_AND_VERSION_BYTE_6] &= 0x0F; |
454 | 13 | raw[TIME_HI_AND_VERSION_BYTE_6] |= (UUID.VERSION_THREE << 4); |
455 | ||
456 | //Set variant | |
457 | 13 | raw[CLOCK_SEQ_HI_AND_RESERVED_BYTE_8] &= 0x3F; //0011 1111 |
458 | 13 | raw[CLOCK_SEQ_HI_AND_RESERVED_BYTE_8] |= 0x80; //1000 0000 |
459 | ||
460 | 13 | return new UUID(raw); |
461 | } | |
462 | ||
463 | /** | |
464 | * <p>Returns a new version three UUID given a name and the namespace's UUID.</p> | |
465 | * | |
466 | * @param name String the name to calculate the UUID for. | |
467 | * @param namespace UUID assigned to this namespace. | |
468 | * @return a new version three UUID given a name and the namespace's UUID. | |
469 | * | |
470 | */ | |
471 | public static UUID nameUUIDFromString(String name, UUID namespace) { | |
472 | 5 | return nameUUIDFromString(name, namespace, UUID.MD5_ENCODING); |
473 | } | |
474 | ||
475 | } |