source: Dev/branches/rest-dojo-ui/client/dojox/uuid/generateTimeBasedUuid.js @ 256

Last change on this file since 256 was 256, checked in by hendrikvanantwerpen, 13 years ago

Reworked project structure based on REST interaction and Dojo library. As
soon as this is stable, the old jQueryUI branch can be removed (it's
kept for reference).

File size: 12.1 KB
Line 
1define([ 'dojo/_base/lang', './_base'], function(lang){
2
3dojox.uuid.generateTimeBasedUuid = function(/*String?*/ node){
4        // summary:
5        //              This function generates time-based UUIDs, meaning "version 1" UUIDs.
6        // description:
7        // For more info, see
8        //              http://www.webdav.org/specs/draft-leach-uuids-guids-01.txt
9        //              http://www.infonuovo.com/dma/csdocs/sketch/instidid.htm
10        //              http://kruithof.xs4all.nl/uuid/uuidgen
11        //              http://www.opengroup.org/onlinepubs/009629399/apdxa.htm#tagcjh_20
12        //              http://jakarta.apache.org/commons/sandbox/id/apidocs/org/apache/commons/id/uuid/clock/Clock.html
13        // node:
14        //              A 12-character hex string representing either a pseudo-node or
15        //              hardware-node (an IEEE 802.3 network node).  A hardware-node
16        //              will be something like "017bf397618a", always with the first bit
17        //              being 0.  A pseudo-node will be something like "f17bf397618a",
18        //              always with the first bit being 1.
19        // examples:
20        //              string = dojox.uuid.generateTimeBasedUuid();
21        //              string = dojox.uuid.generateTimeBasedUuid("017bf397618a");
22        //              dojox.uuid.generateTimeBasedUuid.setNode("017bf397618a");
23        //              string = dojox.uuid.generateTimeBasedUuid(); // the generated UUID has node == "017bf397618a"
24        var uuidString = dojox.uuid.generateTimeBasedUuid._generator.generateUuidString(node);
25        return uuidString; // String
26};
27
28dojox.uuid.generateTimeBasedUuid.isValidNode = function(/*String?*/ node){
29        var HEX_RADIX = 16;
30        var integer = parseInt(node, HEX_RADIX);
31        var valid = lang.isString(node) && node.length == 12 && isFinite(integer);
32        return valid; // Boolean
33};
34
35dojox.uuid.generateTimeBasedUuid.setNode = function(/*String?*/ node){
36        // summary:
37        //              Sets the 'node' value that will be included in generated UUIDs.
38        // node: A 12-character hex string representing a pseudoNode or hardwareNode.
39        dojox.uuid.assert((node === null) || this.isValidNode(node));
40        this._uniformNode = node;
41};
42
43dojox.uuid.generateTimeBasedUuid.getNode = function(){
44        // summary:
45        //              Returns the 'node' value that will be included in generated UUIDs.
46        return this._uniformNode; // String (a 12-character hex string representing a pseudoNode or hardwareNode)
47};
48
49       
50dojox.uuid.generateTimeBasedUuid._generator = new function(){
51        // Number of hours between October 15, 1582 and January 1, 1970:
52        this.GREGORIAN_CHANGE_OFFSET_IN_HOURS = 3394248;
53       
54        // Number of seconds between October 15, 1582 and January 1, 1970:
55        //   dojox.uuid.generateTimeBasedUuid.GREGORIAN_CHANGE_OFFSET_IN_SECONDS = 12219292800;
56       
57        // --------------------------------------------------
58        // Private variables:
59        var _uuidPseudoNodeString = null;
60        var _uuidClockSeqString = null;
61        var _dateValueOfPreviousUuid = null;
62        var _nextIntraMillisecondIncrement = 0;
63        var _cachedMillisecondsBetween1582and1970 = null;
64        var _cachedHundredNanosecondIntervalsPerMillisecond = null;
65       
66        // --------------------------------------------------
67        // Private constants:
68        var HEX_RADIX = 16;
69
70        function _carry(/* array */ arrayA){
71                // summary:
72                //              Given an array which holds a 64-bit number broken into 4 16-bit
73                //              elements, this method carries any excess bits (greater than 16-bits)
74                //              from each array element into the next.
75                // arrayA: An array with 4 elements, each of which is a 16-bit number.
76                arrayA[2] += arrayA[3] >>> 16;
77                arrayA[3] &= 0xFFFF;
78                arrayA[1] += arrayA[2] >>> 16;
79                arrayA[2] &= 0xFFFF;
80                arrayA[0] += arrayA[1] >>> 16;
81                arrayA[1] &= 0xFFFF;
82                dojox.uuid.assert((arrayA[0] >>> 16) === 0);
83        }
84
85        function _get64bitArrayFromFloat(/* float */ x){
86                // summary:
87                //              Given a floating point number, this method returns an array which
88                //              holds a 64-bit number broken into 4 16-bit elements.
89                var result = new Array(0, 0, 0, 0);
90                result[3] = x % 0x10000;
91                x -= result[3];
92                x /= 0x10000;
93                result[2] = x % 0x10000;
94                x -= result[2];
95                x /= 0x10000;
96                result[1] = x % 0x10000;
97                x -= result[1];
98                x /= 0x10000;
99                result[0] = x;
100                return result; // Array with 4 elements, each of which is a 16-bit number.
101        }
102
103        function _addTwo64bitArrays(/* array */ arrayA, /* array */ arrayB){
104                // summary:
105                //              Takes two arrays, each of which holds a 64-bit number broken into 4
106                //              16-bit elements, and returns a new array that holds a 64-bit number
107                //              that is the sum of the two original numbers.
108                // arrayA: An array with 4 elements, each of which is a 16-bit number.
109                // arrayB: An array with 4 elements, each of which is a 16-bit number.
110                dojox.uuid.assert(lang.isArray(arrayA));
111                dojox.uuid.assert(lang.isArray(arrayB));
112                dojox.uuid.assert(arrayA.length == 4);
113                dojox.uuid.assert(arrayB.length == 4);
114       
115                var result = new Array(0, 0, 0, 0);
116                result[3] = arrayA[3] + arrayB[3];
117                result[2] = arrayA[2] + arrayB[2];
118                result[1] = arrayA[1] + arrayB[1];
119                result[0] = arrayA[0] + arrayB[0];
120                _carry(result);
121                return result; // Array with 4 elements, each of which is a 16-bit number.
122        }
123
124        function _multiplyTwo64bitArrays(/* array */ arrayA, /* array */ arrayB){
125                // summary:
126                //              Takes two arrays, each of which holds a 64-bit number broken into 4
127                //              16-bit elements, and returns a new array that holds a 64-bit number
128                //              that is the product of the two original numbers.
129                // arrayA: An array with 4 elements, each of which is a 16-bit number.
130                // arrayB: An array with 4 elements, each of which is a 16-bit number.
131                dojox.uuid.assert(lang.isArray(arrayA));
132                dojox.uuid.assert(lang.isArray(arrayB));
133                dojox.uuid.assert(arrayA.length == 4);
134                dojox.uuid.assert(arrayB.length == 4);
135       
136                var overflow = false;
137                if(arrayA[0] * arrayB[0] !== 0){ overflow = true; }
138                if(arrayA[0] * arrayB[1] !== 0){ overflow = true; }
139                if(arrayA[0] * arrayB[2] !== 0){ overflow = true; }
140                if(arrayA[1] * arrayB[0] !== 0){ overflow = true; }
141                if(arrayA[1] * arrayB[1] !== 0){ overflow = true; }
142                if(arrayA[2] * arrayB[0] !== 0){ overflow = true; }
143                dojox.uuid.assert(!overflow);
144       
145                var result = new Array(0, 0, 0, 0);
146                result[0] += arrayA[0] * arrayB[3];
147                _carry(result);
148                result[0] += arrayA[1] * arrayB[2];
149                _carry(result);
150                result[0] += arrayA[2] * arrayB[1];
151                _carry(result);
152                result[0] += arrayA[3] * arrayB[0];
153                _carry(result);
154                result[1] += arrayA[1] * arrayB[3];
155                _carry(result);
156                result[1] += arrayA[2] * arrayB[2];
157                _carry(result);
158                result[1] += arrayA[3] * arrayB[1];
159                _carry(result);
160                result[2] += arrayA[2] * arrayB[3];
161                _carry(result);
162                result[2] += arrayA[3] * arrayB[2];
163                _carry(result);
164                result[3] += arrayA[3] * arrayB[3];
165                _carry(result);
166                return result; // Array with 4 elements, each of which is a 16-bit number.
167        }
168
169        function _padWithLeadingZeros(/* string */ string, /* int */ desiredLength){
170                // summary:
171                //              Pads a string with leading zeros and returns the result.
172                // string: A string to add padding to.
173                // desiredLength: The number of characters the return string should have.
174
175                // examples:
176                //              result = _padWithLeadingZeros("abc", 6);
177                //              dojox.uuid.assert(result == "000abc");
178                while(string.length < desiredLength){
179                        string = "0" + string;
180                }
181                return string; // string
182        }
183
184        function _generateRandomEightCharacterHexString() {
185                // summary:
186                //              Returns a randomly generated 8-character string of hex digits.
187
188                // FIXME: This probably isn't a very high quality random number.
189       
190                // Make random32bitNumber be a randomly generated floating point number
191                // between 0 and (4,294,967,296 - 1), inclusive.
192                var random32bitNumber = Math.floor( (Math.random() % 1) * Math.pow(2, 32) );
193       
194                var eightCharacterString = random32bitNumber.toString(HEX_RADIX);
195                while(eightCharacterString.length < 8){
196                        eightCharacterString = "0" + eightCharacterString;
197                }
198                return eightCharacterString; // String (an 8-character hex string)
199        }
200       
201        this.generateUuidString = function(/*String?*/ node){
202                // summary:
203                //              Generates a time-based UUID, meaning a version 1 UUID.
204                // description:
205                //              JavaScript code running in a browser doesn't have access to the
206                //              IEEE 802.3 address of the computer, so if a node value isn't
207                //              supplied, we generate a random pseudonode value instead.
208                // node: An optional 12-character string to use as the node in the new UUID.
209                if(node){
210                        dojox.uuid.assert(dojox.uuid.generateTimeBasedUuid.isValidNode(node));
211                }else{
212                        if(dojox.uuid.generateTimeBasedUuid._uniformNode){
213                                node = dojox.uuid.generateTimeBasedUuid._uniformNode;
214                        }else{
215                                if(!_uuidPseudoNodeString){
216                                        var pseudoNodeIndicatorBit = 0x8000;
217                                        var random15bitNumber = Math.floor( (Math.random() % 1) * Math.pow(2, 15) );
218                                        var leftmost4HexCharacters = (pseudoNodeIndicatorBit | random15bitNumber).toString(HEX_RADIX);
219                                        _uuidPseudoNodeString = leftmost4HexCharacters + _generateRandomEightCharacterHexString();
220                                }
221                                node = _uuidPseudoNodeString;
222                        }
223                }
224                if(!_uuidClockSeqString){
225                        var variantCodeForDCEUuids = 0x8000; // 10--------------, i.e. uses only first two of 16 bits.
226                        var random14bitNumber = Math.floor( (Math.random() % 1) * Math.pow(2, 14) );
227                        _uuidClockSeqString = (variantCodeForDCEUuids | random14bitNumber).toString(HEX_RADIX);
228                }
229       
230                // Maybe we should think about trying to make the code more readable to
231                // newcomers by creating a class called "WholeNumber" that encapsulates
232                // the methods and data structures for working with these arrays that
233                // hold 4 16-bit numbers?  And then these variables below have names
234                // like "wholeSecondsPerHour" rather than "arraySecondsPerHour"?
235                var now = new Date();
236                var millisecondsSince1970 = now.valueOf(); // milliseconds since midnight 01 January, 1970 UTC.
237                var nowArray = _get64bitArrayFromFloat(millisecondsSince1970);
238                if(!_cachedMillisecondsBetween1582and1970){
239                        var arraySecondsPerHour = _get64bitArrayFromFloat(60 * 60);
240                        var arrayHoursBetween1582and1970 = _get64bitArrayFromFloat(dojox.uuid.generateTimeBasedUuid._generator.GREGORIAN_CHANGE_OFFSET_IN_HOURS);
241                        var arraySecondsBetween1582and1970 = _multiplyTwo64bitArrays(arrayHoursBetween1582and1970, arraySecondsPerHour);
242                        var arrayMillisecondsPerSecond = _get64bitArrayFromFloat(1000);
243                        _cachedMillisecondsBetween1582and1970 = _multiplyTwo64bitArrays(arraySecondsBetween1582and1970, arrayMillisecondsPerSecond);
244                        _cachedHundredNanosecondIntervalsPerMillisecond = _get64bitArrayFromFloat(10000);
245                }
246                var arrayMillisecondsSince1970 = nowArray;
247                var arrayMillisecondsSince1582 = _addTwo64bitArrays(_cachedMillisecondsBetween1582and1970, arrayMillisecondsSince1970);
248                var arrayHundredNanosecondIntervalsSince1582 = _multiplyTwo64bitArrays(arrayMillisecondsSince1582, _cachedHundredNanosecondIntervalsPerMillisecond);
249       
250                if(now.valueOf() == _dateValueOfPreviousUuid){
251                        arrayHundredNanosecondIntervalsSince1582[3] += _nextIntraMillisecondIncrement;
252                        _carry(arrayHundredNanosecondIntervalsSince1582);
253                        _nextIntraMillisecondIncrement += 1;
254                        if (_nextIntraMillisecondIncrement == 10000) {
255                                // If we've gotten to here, it means we've already generated 10,000
256                                // UUIDs in this single millisecond, which is the most that the UUID
257                                // timestamp field allows for.  So now we'll just sit here and wait
258                                // for a fraction of a millisecond, so as to ensure that the next
259                                // time this method is called there will be a different millisecond
260                                // value in the timestamp field.
261                                while (now.valueOf() == _dateValueOfPreviousUuid) {
262                                        now = new Date();
263                                }
264                        }
265                }else{
266                        _dateValueOfPreviousUuid = now.valueOf();
267                        _nextIntraMillisecondIncrement = 1;
268                }
269       
270                var hexTimeLowLeftHalf  = arrayHundredNanosecondIntervalsSince1582[2].toString(HEX_RADIX);
271                var hexTimeLowRightHalf = arrayHundredNanosecondIntervalsSince1582[3].toString(HEX_RADIX);
272                var hexTimeLow = _padWithLeadingZeros(hexTimeLowLeftHalf, 4) + _padWithLeadingZeros(hexTimeLowRightHalf, 4);
273                var hexTimeMid = arrayHundredNanosecondIntervalsSince1582[1].toString(HEX_RADIX);
274                hexTimeMid = _padWithLeadingZeros(hexTimeMid, 4);
275                var hexTimeHigh = arrayHundredNanosecondIntervalsSince1582[0].toString(HEX_RADIX);
276                hexTimeHigh = _padWithLeadingZeros(hexTimeHigh, 3);
277                var hyphen = "-";
278                var versionCodeForTimeBasedUuids = "1"; // binary2hex("0001")
279                var resultUuid = hexTimeLow + hyphen + hexTimeMid + hyphen +
280                                        versionCodeForTimeBasedUuids + hexTimeHigh + hyphen +
281                                        _uuidClockSeqString + hyphen + node;
282                resultUuid = resultUuid.toLowerCase();
283                return resultUuid; // String (a 36 character string, which will look something like "b4308fb0-86cd-11da-a72b-0800200c9a66")
284        }
285
286}();
287
288return dojox.uuid.generateTimeBasedUuid;
289
290});
Note: See TracBrowser for help on using the repository browser.