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