001: /* Copyright 2002 The JA-SIG Collaborative. All rights reserved.
002: * See license distributed with this file and
003: * available online at http://www.uportal.org/license.html
004: */
005:
006: package org.jasig.portal.utils;
007:
008: import java.net.InetAddress;
009: import java.net.UnknownHostException;
010: import java.util.Random;
011:
012: /**
013: * Creates a Global/Universal Unique ID, per DCE RPC specification.
014: * Requires seed of MAC address/node identifier (12-digit hex string) for uniqueness.
015: * Specification found at the OpenGroup.Org web site.
016: * @author Michael Ivanov
017: * @version $Revision: 35432 $
018: */
019: public class GuidGenerator {
020: /**
021: * CLOCKMOD constitutes the net value range for the clock counter per RFC.
022: *
023: * @see GuidGenerator#setClockSeq
024: */
025: private final static short CLOCKMOD = 16384;
026:
027: /**
028: * NANOS is the conversion from millisecond timer to 100 nanoseconds per RFC.
029: *
030: * @see GuidGenerator#getMilliTime
031: */
032: private final static short NANOS = 10000;
033:
034: /**
035: * nanoCounter holds static iteration value for unique nanosecond value when
036: * two Guid values requested within clock resolution (one millisecond).
037: *
038: * @see GuidGenerator#getNano
039: *
040: * @see GuidGenerator#nanoBump
041: *
042: * @see GuidGenerator#nanoReset
043: *
044: * @see GuidGenerator#set
045: */
046: private static long nanoCounter = 0;
047:
048: /**
049: * clockSeq holds the static clock sequence string used in fourth element of Guid per RFC.
050: *
051: * @see GuidGenerator#setClockSeq
052: */
053: private static String clockSeq;
054:
055: /**
056: * lastMilliTime holds the static last millisecond time requested in order to provide determination
057: * if nanoCounter needs to be adjusted.
058: *
059: * @see GuidGenerator#getLastTime
060: *
061: * @see GuidGenerator#setLastTime
062: *
063: * @see GuidGenerator#set
064: */
065: private static long lastMilliTime;
066:
067: // =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
068: //
069: // class instance vars
070: //
071: // =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
072: /**
073: * macbase holds the instance initialization string denoting fifth element of Guid per RFC
074: *
075: * @see GuidGenerator#GuidGenerator(String)
076: *
077: * @see GuidGenerator#toString
078: */
079: private String macbase;
080:
081: /**
082: * timebase holds the instance string denoting elements one thru four of Guid per RFC
083: *
084: * @see GuidGenerator#GuidGenerator(String)
085: *
086: * @see GuidGenerator#set()
087: */
088: private String timebase;
089:
090: // Random generator
091: private static Random random = new Random();
092:
093: /**
094: * Returns the hexidecimal representation of byte values in the array.
095: * Uses Integer.toHexString on each byte, so is relatively expensive.
096: * This could easily be ameliorated with some trivial byte-packing
097: * but for now I'm just going to do the Simplest Thing Possible.
098: */
099: private static final void bytesToHex(byte[] bytes,
100: StringBuffer buffer) {
101: for (int i = 0; i < bytes.length; i++) {
102: String s = Integer.toHexString(0x00ff & bytes[i]);
103: if (s.length() < 2) {
104: buffer.append('0');
105: }
106: buffer.append(s);
107: }
108: }
109:
110: // =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
111: //
112: // methods
113: //
114: // =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
115: /**
116: * GuidGenerator() - default constructor
117: * @throws UnknownHostException
118: */
119: public GuidGenerator() {
120:
121: InetAddress ip;
122: try {
123: ip = InetAddress.getByName(InetAddress.getLocalHost()
124: .getHostName());
125: } catch (UnknownHostException e) {
126: throw new RuntimeException(e);
127: }
128: byte[] bytes = ip.getAddress();
129:
130: StringBuffer buffer = new StringBuffer(12);
131: byte[] tailBytes = new byte[2];
132: random.nextBytes(tailBytes);
133:
134: bytesToHex(bytes, buffer);
135: bytesToHex(tailBytes, buffer);
136:
137: initGuid(buffer.toString());
138: }
139:
140: /**
141: * GuidGenerator(String) - seeded constructor
142: *
143: * @param newMAC as the seed value for fifth element of string GUID
144: *
145: * @exception IllegalArgumentException
146: */
147: public GuidGenerator(String newMAC) throws IllegalArgumentException {
148: initGuid(newMAC);
149: }
150:
151: // =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
152: //
153: // private routines to protect class static vars
154: //
155: // =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
156: /**
157: * getNano atomically increments and returns nanoCounter
158: *
159: */
160: private void initGuid(String newMAC)
161: throws IllegalArgumentException {
162: // validate newMAC length
163: if (newMAC.length() != 12 && newMAC.trim().length() != 12) {
164: throw new IllegalArgumentException(
165: "Guid(String) requires 12-digit string per RFC");
166: }
167:
168: // set statics
169: setLastTime(getMilliTime());
170: setClockSeq();
171: // set instance
172: macbase = newMAC;
173: // timebase
174: set();
175: }
176:
177: synchronized static long getNano() {
178: nanoBump();
179: return nanoCounter;
180: }
181:
182: /**
183: * nanoBump increments nanoCounter
184: */
185: synchronized static void nanoBump() {
186: nanoCounter++;
187: }
188:
189: /**
190: * nanoReset zeroes nanoCounter
191: */
192: synchronized static void nanoReset() {
193: nanoCounter = 0;
194: }
195:
196: /**
197: * getLastTime accesses lastMilliTime for return
198: *
199: * @return the current value of lastMilliTime
200: */
201: synchronized static long getLastTime() {
202: return lastMilliTime;
203: }
204:
205: /**
206: * setLastTime sets the new value of lastMilliTime
207: *
208: * @see GuidGenerator#set
209: *
210: * @param lastTime the value to which to set lastMilliTime
211: */
212: synchronized static void setLastTime(long lastTime) {
213: lastMilliTime = lastTime;
214: }
215:
216: /**
217: * getMilliTime accesses the system clock
218: *
219: * @return the last system time converted to 100 nanosecond intervals
220: */
221: synchronized static long getMilliTime() {
222: return System.currentTimeMillis() * NANOS;
223: }
224:
225: /**
226: * getClockSeq returns the RFC clock sequence string
227: *
228: * @return clockSeq value
229: */
230: synchronized static String getClockSeq() {
231: return clockSeq;
232: }
233:
234: /**
235: * setClockSeq sets the RFC clock sequence string
236: *
237: * @see GuidGenerator#GuidGenerator(String)
238: */
239: synchronized static void setClockSeq() {
240: int clockCounter;
241: final int x00FF = (new Integer(0x000000FF).shortValue());
242: final int x3F00 = (new Integer(0x00003F00).shortValue());
243: final int x0080 = (new Integer(0x00000080).shortValue());
244: int tlen, sbfr;
245: byte clock_seq_hi_res, clock_seq_low;
246: String tempclockseq = "";
247: //
248: // create clock sequence
249: //
250: clockCounter = (new Double(Math.random() * CLOCKMOD)
251: .shortValue());
252: // -- mask out low-order 8 bits into clock_seq_low
253: sbfr = (clockCounter & x00FF);
254: clock_seq_low = (new Short(new Integer(sbfr).shortValue())
255: .byteValue());
256: // -- mask out 6 least-significant high-order bits
257: // -- then shift right ten places for storage into low order clock_seq_hi_res
258: sbfr = (clockCounter & x3F00) >> 10;
259: clock_seq_hi_res = (new Short(new Integer(sbfr).shortValue())
260: .byteValue());
261: // -- mask in 0x2 as two hi-order bits of clock_seq_hi_res
262: clock_seq_hi_res |= (new Short(new Integer(x0080).shortValue())
263: .byteValue());
264: // -- reconstruct clock_seq_* into short by left-shifting clock_hi by eight
265: sbfr = clock_seq_hi_res;
266: sbfr = sbfr << 8;
267: // -- and then OR clock_low
268: sbfr |= clock_seq_low;
269: // -- sbfr contains combined clock_seq_hi_res and clock_seq_low;
270: // -- sbfr convers to Hex only as four right-most characters for 16-bit
271: tempclockseq = Integer.toHexString(sbfr);
272: tlen = tempclockseq.length();
273: clockSeq = tempclockseq.substring(Math.max(tlen - 4, 0), tlen);
274: }
275:
276: // =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
277: //
278: // public access and process methods
279: //
280: // =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
281: /**
282: * getNewGuid calls set to generate a new GUID
283: *
284: * @return the latest GUID
285: */
286: public String getNewGuid() {
287: set();
288: return toString();
289: }
290:
291: /**
292: * toString returns the current values as a single string per RFC
293: *
294: * @return the timebase from set() concatenated to the initialization string
295: */
296: public String toString() {
297: return timebase + "-" + macbase;
298: }
299:
300: /**
301: * set is where all the work is done
302: */
303: public void set() {
304: long millitime, lastmilli, nanomod = 0;
305: String temptimemid = "";
306: String temptimehi = "";
307: int time_low, time_hi, ibfr, tlen;
308: short time_mid, time_hi_ver;
309: long lbfr;
310: // get approximation of current 100-nanosecond offset
311: millitime = getMilliTime();
312: lastmilli = getLastTime();
313: // adjust millitime if retrieved within millisecond
314: if (millitime == lastmilli) {
315: nanomod = getNano();
316: millitime += nanomod;
317: } else {
318: nanoReset();
319: }
320: //
321: // convert millitime to string according to time-low, time-mid, time-hi specs:
322: //
323: // -- mask out low-order 32 bits into time_low
324: lbfr = (millitime & 0x00000000FFFFFFFFL);
325: time_low = (new Long(lbfr).intValue());
326: // -- mask out hi-order 32 bits into time_hi
327: lbfr = (millitime & 0xFFFFFFFF00000000L) >>> 32;
328: time_hi = (new Long(lbfr).intValue());
329: // -- mask out original 32-47 bits (0-15 now) to time_mid
330: ibfr = (time_hi & 0x0000FFFF);
331: time_mid = (new Integer(ibfr).shortValue());
332: // -- mask out original 48-59 bits (16-27 now) to time_hi_ver bits 0-12
333: ibfr = (time_hi & 0x0FFF0000) >>> 16;
334: time_hi_ver = (new Integer(ibfr).shortValue());
335: // -- mask in version (0x4) in bits 12-15 to time_hi_ver
336: time_hi_ver |= 0x4000;
337: //
338: // construct string per RFC:
339: // <time_low>-<time_mid>-<time_hi_ver>-<clock_seq_hi_res><clock_seq_low>-<macbase>
340: //
341: // -- time_low converts to Hex cleanly
342: // -- time_mid converts to Hex only as four right-most characters for 16-bit
343: temptimemid = Integer.toHexString(time_mid);
344: tlen = temptimemid.length();
345: temptimemid = temptimemid
346: .substring(Math.max(tlen - 4, 0), tlen);
347: // -- time_hi_ver converts to Hex only as four right-most characters for 16-bit
348: temptimehi = Integer.toHexString(time_hi_ver);
349: tlen = temptimehi.length();
350: temptimehi = temptimehi.substring(Math.max(tlen - 4, 0), tlen);
351: // -- construct entire string
352: timebase = Integer.toHexString(time_low) + "-" + temptimemid
353: + "-" + temptimehi + "-" + getClockSeq();
354: // reset lastMilliTime to be original millitime upon entry to this function
355: setLastTime(millitime - nanomod);
356: }
357:
358: /**
359: * main is the unit testing interface that creates a new Guid instance and prints result of getNewGuid to System.out
360: *
361: * @param args array for input arguments
362: */
363: public static void main(String args[]) {
364: try {
365: GuidGenerator g = new GuidGenerator("abcdefghijkl");
366: System.out.println("New GUID is " + g.getNewGuid());
367: g = new GuidGenerator();
368: System.out.println("New GUID is " + g.getNewGuid());
369: } catch (Exception e) {
370: System.out
371: .println("GuidGenerator::main threw "
372: + e.getClass() + " with message: "
373: + e.getMessage());
374: }
375: }
376: }
|