View Javadoc

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  	private byte[] rawBytes = new byte[UUID_BYTE_LENGTH];
44  	
45  	/** Holds node identifier for this UUID */
46  	private Long node = null;
47  	
48  	/** Holds timestamp for this UUID */
49  	private long timestamp = -1;
50  	
51  	/** Holds the clock sequence field */
52  	private Short clockSq = null;
53  	
54  	/** Holds the version field of this UUID */
55  	private int version = -1;
56  	
57  	/** Holds the variant field of this UUID */
58  	private int variant = -1;
59  	
60  	/** Holds the internal string value of the UUID */
61  	private String stringValue = null;
62  	
63  	/** Constructs a nil UUID */
64  	public UUID() {
65  		super();
66  	}
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  		super();
91  		rawBytes = copyFrom.getRawBytes();
92  	}
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 		super();
102 		if (byteArray.length != UUID_BYTE_LENGTH) {
103 			throw new IllegalArgumentException("UUID must be contructed using a 16 byte array.");
104 		}
105 		// UUID must be immutable so a copy is used.
106 		System.arraycopy(byteArray, 0, rawBytes, 0, UUID_BYTE_LENGTH);
107 	}
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 		super();
117 		input.readFully(rawBytes, 0, UUID_BYTE_LENGTH);
118 	}
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 	public UUID(long mostSignificant, long leastSignificant) {
127 		rawBytes = Bytes.append(Bytes.toBytes(mostSignificant), Bytes.toBytes(leastSignificant));
128 	}
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 		this(UUID.fromString(uuidString));
139 	}
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 		String leanString = uuidString.toLowerCase();
151 		UUID tmpUUID = null;
152 		
153 		//Handle prefixed UUIDs
154 		// e.g. urn:uuid:f81d4fae-7dec-11d0-a765-00a0c91e6bf6
155 		int pos = uuidString.lastIndexOf(":");
156 		if (pos > 1) {
157 			leanString = uuidString.substring(++pos, uuidString.length());
158 		}
159 		
160 		//Check for 36 char length
161 		if (leanString.length() != UUID_FORMATTED_LENGTH) {
162 			throw new UUIDFormatException(uuidString);
163 		}
164 		
165 		//Check for 5 fields
166 		StringTokenizer tok = new StringTokenizer(leanString, "-");
167 		if ( tok.countTokens() != TOKENS_IN_UUID ) {
168 			throw new UUIDFormatException(uuidString);
169 		}
170 		
171 		//Remove the "-" from the formatted string and test token sizes
172 		StringBuffer buf = new StringBuffer(UUID_UNFORMATTED_LENGTH);
173 		String token = null;
174 		int count = 0;
175 		while (tok.hasMoreTokens()) {
176 			token = tok.nextToken();
177 			if (token.length() != TOKEN_LENGTHS[count++]) {
178 				throw new UUIDFormatException(uuidString);
179 			}
180 			buf.append(token);
181 		}
182 		
183 		//Create from the hex value
184 		try {
185 			char[] chars = buf.toString().toCharArray();
186 			tmpUUID = new UUID(Hex.decodeHex(chars));
187 		} catch (DecoderException de) {
188 			throw new UUIDFormatException(uuidString + ": " + de.getMessage());
189 		}
190 		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 		if (stringValue == null) {
201 			StringBuffer buf = new StringBuffer(new String(Hex.encodeHex(rawBytes)));
202 			while (buf.length() != UUID_UNFORMATTED_LENGTH) {
203 				buf.insert(0, "0");
204 			}
205 			buf.ensureCapacity(UUID_FORMATTED_LENGTH);
206 			buf.insert(FORMAT_POSITION1, '-');
207 			buf.insert(FORMAT_POSITION2, '-');
208 			buf.insert(FORMAT_POSITION3, '-');
209 			buf.insert(FORMAT_POSITION4, '-');
210 			stringValue = buf.toString();
211 		}
212 		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 		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 		if (!(obj instanceof UUID)) {
233 			return false;
234 		}
235 		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 		int iConstant = 37;
245 		int iTotal = 17;
246 		for (int i = 0; i < rawBytes.length; i++) {
247 			iTotal = iTotal * iConstant + rawBytes[i];
248 		}
249 		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 		if (!(compareTo instanceof UUID)) {
259 			throw new ClassCastException();
260 		}
261 		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 		if (variant() != VARIANT_IETF_DRAFT || version() != VERSION_ONE) {
274 			throw new UnsupportedOperationException(WRONG_VAR_VER_MSG);
275 		}
276 		if (clockSq == null) {
277 			byte[] b = {((byte) (rawBytes[8] & 0x3F)), rawBytes[9]};
278 			clockSq = new Short(Bytes.toShort(b));
279 		}
280 		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 		if (version == -1) {
297 			version = ((rawBytes[6] >>> 4) & 0x0F);
298 		}
299 		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 		if (variant == -1) {
313 			if ((rawBytes[8] & 0x80) == 0x0) {
314 				variant = VARIANT_NCS_COMPAT;
315 			} else if ((rawBytes[8] & 0x40) == 0x0) {
316 				variant = VARIANT_IETF_DRAFT;
317 			} else if ((rawBytes[8] & 0x20) == 0x0) {
318 				variant = VARIANT_MS;
319 			} else {
320 				variant = VARIANT_FUTURE;
321 			}
322 		}
323 		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 		if (variant() != VARIANT_IETF_DRAFT || version() != VERSION_ONE) {
336 			throw new UnsupportedOperationException(WRONG_VAR_VER_MSG);
337 		}
338 		if (node == null) {
339 			byte[] b = new byte[8];
340 			System.arraycopy(rawBytes, 10, b, 2, 6);
341 			node = new Long((Bytes.toLong(b) & 0xFFFFFFFFFFFFL));
342 		}
343 		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 		if (variant() != VARIANT_IETF_DRAFT || version() != VERSION_ONE) {
356 			throw new UnsupportedOperationException(WRONG_VAR_VER_MSG);
357 		}
358 		if (timestamp == -1) {
359 			byte[] longVal = new byte[8];
360 			System.arraycopy(rawBytes, TIME_HI_START_POS, longVal, TIME_HI_TS_POS, TIME_HI_BYTE_LEN);
361 			System.arraycopy(rawBytes, TIME_MID_START_POS, longVal, TIME_MID_TS_POS, TIME_MID_BYTE_LEN);
362 			System.arraycopy(rawBytes, TIME_LOW_START_POS, longVal, TIME_LOW_TS_POS, TIME_LOW_BYTE_LEN);
363 			longVal[TIME_HI_TS_POS] &= 0x0F;
364 			timestamp = Bytes.toLong(longVal);
365 		}
366 		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 		byte[] lsb = new byte[8];
376 		System.arraycopy(rawBytes, 8, lsb, 0, 8);
377 		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 		byte[] msb = new byte[8];
387 		System.arraycopy(rawBytes, 0, msb, 0, 8);
388 		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 		byte[] ret = new byte[UUID_BYTE_LENGTH];
398 		System.arraycopy(rawBytes, 0, ret, 0, UUID_BYTE_LENGTH);
399 		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                 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 		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 		byte[] nameAsBytes = name.getBytes();
431 		byte[] concat = new byte[UUID_BYTE_LENGTH + nameAsBytes.length];
432 		System.arraycopy(namespace.getRawBytes(), 0, concat, 0, UUID_BYTE_LENGTH);
433 		System.arraycopy(nameAsBytes, 0, concat, UUID_BYTE_LENGTH, nameAsBytes.length);
434 		
435 		byte[] raw = null;
436 		
437 		if(encoding.equals(UUID.MD5_ENCODING)) {
438 			raw = DigestUtils.md5(concat);
439 		}
440 		else if(encoding.equals(UUID.SHA1_ENCODING)) {
441 			byte[] shaDigest = DigestUtils.sha(concat);
442 			// Truncate digest to 16 bytes (SHA-1 returns a 20-byte digest)
443 			raw = new byte[16];
444 			System.arraycopy(shaDigest, 0, raw, 0, 16); 
445 		}
446 		else {
447 			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 		raw[TIME_HI_AND_VERSION_BYTE_6] &= 0x0F;
454 		raw[TIME_HI_AND_VERSION_BYTE_6] |= (UUID.VERSION_THREE << 4);
455 		
456 		//Set variant
457 		raw[CLOCK_SEQ_HI_AND_RESERVED_BYTE_8] &= 0x3F; //0011 1111
458 		raw[CLOCK_SEQ_HI_AND_RESERVED_BYTE_8] |= 0x80; //1000 0000
459 		
460 		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 		return nameUUIDFromString(name, namespace, UUID.MD5_ENCODING);
473 	}
474 	
475 }