83b85d77b55a5e392be0ffa2de7bc761f888f30e
[aai/esr-gui.git] /
1 /**
2  * Machine id.
3  *
4  * Create a random 3-byte value (i.e. unique for this
5  * process). Other drivers use a md5 of the machine id here, but
6  * that would mean an asyc call to gethostname, so we don't bother.
7  * @ignore
8  */
9 var MACHINE_ID = parseInt(Math.random() * 0xFFFFFF, 10);
10
11 // Regular expression that checks for hex value
12 var checkForHexRegExp = new RegExp("^[0-9a-fA-F]{24}$");
13
14 /**
15 * Create a new ObjectID instance
16 *
17 * @class
18 * @param {(string|number)} id Can be a 24 byte hex string, 12 byte binary string or a Number.
19 * @property {number} generationTime The generation time of this ObjectId instance
20 * @return {ObjectID} instance of ObjectID.
21 */
22 var ObjectID = function ObjectID(id) {
23   // Duck-typing to support ObjectId from different npm packages
24   if(id instanceof ObjectID) return id;
25   if(!(this instanceof ObjectID)) return new ObjectID(id);
26
27   this._bsontype = 'ObjectID';
28
29   var __id = null;
30   var valid = ObjectID.isValid(id);
31
32   // Throw an error if it's not a valid setup
33   if(!valid && id != null){
34     throw new Error("Argument passed in must be a single String of 12 bytes or a string of 24 hex characters");
35   } else if(valid && typeof id == 'string' && id.length == 24) {
36     return ObjectID.createFromHexString(id);
37   } else if(id == null || typeof id == 'number') {
38     // convert to 12 byte binary string
39     this.id = this.generate(id);
40   } else if(id != null && id.length === 12) {
41     // assume 12 byte string
42     this.id = id;
43   } else if(id != null && id.toHexString) {
44     // Duck-typing to support ObjectId from different npm packages
45     return id;
46   } else {
47     throw new Error("Argument passed in must be a single String of 12 bytes or a string of 24 hex characters");
48   }
49
50   if(ObjectID.cacheHexString) this.__id = this.toHexString();
51 };
52
53 // Allow usage of ObjectId as well as ObjectID
54 var ObjectId = ObjectID;
55
56 // Precomputed hex table enables speedy hex string conversion
57 var hexTable = [];
58 for (var i = 0; i < 256; i++) {
59   hexTable[i] = (i <= 15 ? '0' : '') + i.toString(16);
60 }
61
62 /**
63 * Return the ObjectID id as a 24 byte hex string representation
64 *
65 * @method
66 * @return {string} return the 24 byte hex string representation.
67 */
68 ObjectID.prototype.toHexString = function() {
69   if(ObjectID.cacheHexString && this.__id) return this.__id;
70
71   var hexString = '';
72   if(!this.id || !this.id.length) {
73     throw new Error('invalid ObjectId, ObjectId.id must be either a string or a Buffer, but is [' + JSON.stringify(this.id) + ']');
74   }
75
76   if(this.id instanceof _Buffer) {
77     hexString = convertToHex(this.id);
78     if(ObjectID.cacheHexString) this.__id = hexString;
79     return hexString;
80   }
81
82   for (var i = 0; i < this.id.length; i++) {
83     hexString += hexTable[this.id.charCodeAt(i)];
84   }
85
86   if(ObjectID.cacheHexString) this.__id = hexString;
87   return hexString;
88 };
89
90 /**
91 * Update the ObjectID index used in generating new ObjectID's on the driver
92 *
93 * @method
94 * @return {number} returns next index value.
95 * @ignore
96 */
97 ObjectID.prototype.get_inc = function() {
98   return ObjectID.index = (ObjectID.index + 1) % 0xFFFFFF;
99 };
100
101 /**
102 * Update the ObjectID index used in generating new ObjectID's on the driver
103 *
104 * @method
105 * @return {number} returns next index value.
106 * @ignore
107 */
108 ObjectID.prototype.getInc = function() {
109   return this.get_inc();
110 };
111
112 /**
113 * Generate a 12 byte id buffer used in ObjectID's
114 *
115 * @method
116 * @param {number} [time] optional parameter allowing to pass in a second based timestamp.
117 * @return {Buffer} return the 12 byte id buffer string.
118 */
119 ObjectID.prototype.generate = function(time) {
120   if ('number' != typeof time) {
121     time = ~~(Date.now()/1000);
122   }
123
124   // Use pid
125   var pid = (typeof process === 'undefined' ? Math.floor(Math.random() * 100000) : process.pid) % 0xFFFF;
126   var inc = this.get_inc();
127   // Buffer used
128   var buffer = new Buffer(12);
129   // Encode time
130   buffer[3] = time & 0xff;
131   buffer[2] = (time >> 8) & 0xff;
132   buffer[1] = (time >> 16) & 0xff;
133   buffer[0] = (time >> 24) & 0xff;
134   // Encode machine
135   buffer[6] = MACHINE_ID & 0xff;
136   buffer[5] = (MACHINE_ID >> 8) & 0xff;
137   buffer[4] = (MACHINE_ID >> 16) & 0xff;
138   // Encode pid
139   buffer[8] = pid & 0xff;
140   buffer[7] = (pid >> 8) & 0xff;
141   // Encode index
142   buffer[11] = inc & 0xff;
143   buffer[10] = (inc >> 8) & 0xff;
144   buffer[9] = (inc >> 16) & 0xff;
145   // Return the buffer
146   return buffer;
147 };
148
149 /**
150 * Converts the id into a 24 byte hex string for printing
151 *
152 * @return {String} return the 24 byte hex string representation.
153 * @ignore
154 */
155 ObjectID.prototype.toString = function() {
156   return this.toHexString();
157 };
158
159 /**
160 * Converts to a string representation of this Id.
161 *
162 * @return {String} return the 24 byte hex string representation.
163 * @ignore
164 */
165 ObjectID.prototype.inspect = ObjectID.prototype.toString;
166
167 /**
168 * Converts to its JSON representation.
169 *
170 * @return {String} return the 24 byte hex string representation.
171 * @ignore
172 */
173 ObjectID.prototype.toJSON = function() {
174   return this.toHexString();
175 };
176
177 /**
178 * Compares the equality of this ObjectID with `otherID`.
179 *
180 * @method
181 * @param {object} otherID ObjectID instance to compare against.
182 * @return {boolean} the result of comparing two ObjectID's
183 */
184 ObjectID.prototype.equals = function equals (otherId) {
185   var id;
186
187   if(otherId instanceof ObjectID) {
188     return this.toString() == otherId.toString();
189   } else if(typeof otherId == 'string' && ObjectID.isValid(otherId) && otherId.length == 12 && this.id instanceof _Buffer) {
190     return otherId === this.id.toString('binary');
191   } else if(typeof otherId == 'string' && ObjectID.isValid(otherId) && otherId.length == 24) {
192     return otherId === this.toHexString();
193   } else if(typeof otherId == 'string' && ObjectID.isValid(otherId) && otherId.length == 12) {
194     return otherId === this.id;
195   } else if(otherId != null && (otherId instanceof ObjectID || otherId.toHexString)) {
196     return otherId.toHexString() === this.toHexString();
197   } else {
198     return false;
199   }
200 }
201
202 /**
203 * Returns the generation date (accurate up to the second) that this ID was generated.
204 *
205 * @method
206 * @return {date} the generation date
207 */
208 ObjectID.prototype.getTimestamp = function() {
209   var timestamp = new Date();
210   var time = this.id[3] | this.id[2] << 8 | this.id[1] << 16 | this.id[0] << 24;
211   timestamp.setTime(Math.floor(time) * 1000);
212   return timestamp;
213 }
214
215 /**
216 * @ignore
217 */
218 ObjectID.index = ~~(Math.random() * 0xFFFFFF);
219
220 /**
221 * @ignore
222 */
223 ObjectID.createPk = function createPk () {
224   return new ObjectID();
225 };
226
227 /**
228 * Creates an ObjectID from a second based number, with the rest of the ObjectID zeroed out. Used for comparisons or sorting the ObjectID.
229 *
230 * @method
231 * @param {number} time an integer number representing a number of seconds.
232 * @return {ObjectID} return the created ObjectID
233 */
234 ObjectID.createFromTime = function createFromTime (time) {
235   var buffer = new Buffer([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]);
236   // Encode time into first 4 bytes
237   buffer[3] = time & 0xff;
238   buffer[2] = (time >> 8) & 0xff;
239   buffer[1] = (time >> 16) & 0xff;
240   buffer[0] = (time >> 24) & 0xff;
241   // Return the new objectId
242   return new ObjectID(buffer);
243 };
244
245 // Lookup tables
246 var encodeLookup = '0123456789abcdef'.split('')
247 var decodeLookup = []
248 var i = 0
249 while (i < 10) decodeLookup[0x30 + i] = i++
250 while (i < 16) decodeLookup[0x41 - 10 + i] = decodeLookup[0x61 - 10 + i] = i++
251
252 var _Buffer = Buffer;
253 var convertToHex = function(bytes) {
254   return bytes.toString('hex');
255 }
256
257 /**
258 * Creates an ObjectID from a hex string representation of an ObjectID.
259 *
260 * @method
261 * @param {string} hexString create a ObjectID from a passed in 24 byte hexstring.
262 * @return {ObjectID} return the created ObjectID
263 */
264 ObjectID.createFromHexString = function createFromHexString (string) {
265   // Throw an error if it's not a valid setup
266   if(typeof string === 'undefined' || string != null && string.length != 24)
267     throw new Error("Argument passed in must be a single String of 12 bytes or a string of 24 hex characters");
268
269   var length = string.length;
270
271   if(length > 12*2) {
272     throw new Error('Id cannot be longer than 12 bytes');
273   }
274
275   // Calculate lengths
276   var sizeof = length >> 1;
277   var array = new _Buffer(sizeof);
278   var n = 0;
279   var i = 0;
280
281   while (i < length) {
282     array[n++] = decodeLookup[string.charCodeAt(i++)] << 4 | decodeLookup[string.charCodeAt(i++)]
283   }
284
285   return new ObjectID(array);
286 };
287
288 /**
289 * Checks if a value is a valid bson ObjectId
290 *
291 * @method
292 * @return {boolean} return true if the value is a valid bson ObjectId, return false otherwise.
293 */
294 ObjectID.isValid = function isValid(id) {
295   if(id == null) return false;
296
297   if(typeof id == 'number') {
298     return true;
299   }
300
301   if(typeof id == 'string') {
302     return id.length == 12 || (id.length == 24 && checkForHexRegExp.test(id));
303   }
304
305   if(id instanceof ObjectID) {
306     return true;
307   }
308
309   if(id instanceof _Buffer) {
310     return true;
311   }
312
313   // Duck-Typing detection of ObjectId like objects
314   if(id.toHexString) {
315     return id.id.length == 12 || (id.id.length == 24 && checkForHexRegExp.test(id.id));
316   }
317
318   return false;
319 };
320
321 /**
322 * @ignore
323 */
324 Object.defineProperty(ObjectID.prototype, "generationTime", {
325    enumerable: true
326  , get: function () {
327      return this.id[3] | this.id[2] << 8 | this.id[1] << 16 | this.id[0] << 24;
328    }
329  , set: function (value) {
330      // Encode time into first 4 bytes
331      this.id[3] = value & 0xff;
332      this.id[2] = (value >> 8) & 0xff;
333      this.id[1] = (value >> 16) & 0xff;
334      this.id[0] = (value >> 24) & 0xff;
335    }
336 });
337
338 /**
339  * Expose.
340  */
341 module.exports = ObjectID;
342 module.exports.ObjectID = ObjectID;
343 module.exports.ObjectId = ObjectID;