ObjectId.cs 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539
  1. using System;
  2. using System.Diagnostics;
  3. using System.Linq;
  4. using System.Runtime.CompilerServices;
  5. using System.Security.Cryptography;
  6. using System.Text;
  7. using System.Threading;
  8. namespace Houdar.Core.Util.Common
  9. {
  10. /// <summary>Represents an ObjectId
  11. /// </summary>
  12. [Serializable]
  13. public struct ObjectId : IComparable<ObjectId>, IEquatable<ObjectId>
  14. {
  15. // private static fields
  16. private static readonly DateTime __unixEpoch;
  17. private static readonly long __dateTimeMaxValueMillisecondsSinceEpoch;
  18. private static readonly long __dateTimeMinValueMillisecondsSinceEpoch;
  19. private static ObjectId __emptyInstance = default(ObjectId);
  20. private static int __staticMachine;
  21. private static short __staticPid;
  22. private static int __staticIncrement; // high byte will be masked out when generating new ObjectId
  23. private static uint[] _lookup32 = Enumerable.Range(0, 256).Select(i =>
  24. {
  25. string s = i.ToString("x2");
  26. return ((uint)s[0]) + ((uint)s[1] << 16);
  27. }).ToArray();
  28. // we're using 14 bytes instead of 12 to hold the ObjectId in memory but unlike a byte[] there is no additional object on the heap
  29. // the extra two bytes are not visible to anyone outside of this class and they buy us considerable simplification
  30. // an additional advantage of this representation is that it will serialize to JSON without any 64 bit overflow problems
  31. private int _timestamp;
  32. private int _machine;
  33. private short _pid;
  34. private int _increment;
  35. // static constructor
  36. static ObjectId()
  37. {
  38. __unixEpoch = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc);
  39. __dateTimeMaxValueMillisecondsSinceEpoch = (DateTime.MaxValue - __unixEpoch).Ticks / 10000;
  40. __dateTimeMinValueMillisecondsSinceEpoch = (DateTime.MinValue - __unixEpoch).Ticks / 10000;
  41. __staticMachine = GetMachineHash();
  42. __staticIncrement = (new Random()).Next();
  43. __staticPid = (short)GetCurrentProcessId();
  44. }
  45. // constructors
  46. /// <summary>
  47. /// Initializes a new instance of the ObjectId class.
  48. /// </summary>
  49. /// <param name="bytes">The bytes.</param>
  50. public ObjectId(byte[] bytes)
  51. {
  52. if (bytes == null)
  53. {
  54. throw new ArgumentNullException("bytes");
  55. }
  56. Unpack(bytes, out _timestamp, out _machine, out _pid, out _increment);
  57. }
  58. /// <summary>
  59. /// Initializes a new instance of the ObjectId class.
  60. /// </summary>
  61. /// <param name="timestamp">The timestamp (expressed as a DateTime).</param>
  62. /// <param name="machine">The machine hash.</param>
  63. /// <param name="pid">The PID.</param>
  64. /// <param name="increment">The increment.</param>
  65. public ObjectId(DateTime timestamp, int machine, short pid, int increment)
  66. : this(GetTimestampFromDateTime(timestamp), machine, pid, increment)
  67. {
  68. }
  69. /// <summary>
  70. /// Initializes a new instance of the ObjectId class.
  71. /// </summary>
  72. /// <param name="timestamp">The timestamp.</param>
  73. /// <param name="machine">The machine hash.</param>
  74. /// <param name="pid">The PID.</param>
  75. /// <param name="increment">The increment.</param>
  76. public ObjectId(int timestamp, int machine, short pid, int increment)
  77. {
  78. if ((machine & 0xff000000) != 0)
  79. {
  80. throw new ArgumentOutOfRangeException("machine", "The machine value must be between 0 and 16777215 (it must fit in 3 bytes).");
  81. }
  82. if ((increment & 0xff000000) != 0)
  83. {
  84. throw new ArgumentOutOfRangeException("increment", "The increment value must be between 0 and 16777215 (it must fit in 3 bytes).");
  85. }
  86. _timestamp = timestamp;
  87. _machine = machine;
  88. _pid = pid;
  89. _increment = increment;
  90. }
  91. /// <summary>
  92. /// Initializes a new instance of the ObjectId class.
  93. /// </summary>
  94. /// <param name="value">The value.</param>
  95. public ObjectId(string value)
  96. {
  97. if (value == null)
  98. {
  99. throw new ArgumentNullException("value");
  100. }
  101. Unpack(ParseHexString(value), out _timestamp, out _machine, out _pid, out _increment);
  102. }
  103. // public static properties
  104. /// <summary>
  105. /// Gets an instance of ObjectId where the value is empty.
  106. /// </summary>
  107. public static ObjectId Empty
  108. {
  109. get { return __emptyInstance; }
  110. }
  111. // public properties
  112. /// <summary>
  113. /// Gets the timestamp.
  114. /// </summary>
  115. public int Timestamp
  116. {
  117. get { return _timestamp; }
  118. }
  119. /// <summary>
  120. /// Gets the machine.
  121. /// </summary>
  122. public int Machine
  123. {
  124. get { return _machine; }
  125. }
  126. /// <summary>
  127. /// Gets the PID.
  128. /// </summary>
  129. public short Pid
  130. {
  131. get { return _pid; }
  132. }
  133. /// <summary>
  134. /// Gets the increment.
  135. /// </summary>
  136. public int Increment
  137. {
  138. get { return _increment; }
  139. }
  140. /// <summary>
  141. /// Gets the creation time (derived from the timestamp).
  142. /// </summary>
  143. public DateTime CreationTime
  144. {
  145. get { return __unixEpoch.AddSeconds(_timestamp); }
  146. }
  147. // public operators
  148. /// <summary>
  149. /// Compares two ObjectIds.
  150. /// </summary>
  151. /// <param name="lhs">The first ObjectId.</param>
  152. /// <param name="rhs">The other ObjectId</param>
  153. /// <returns>True if the first ObjectId is less than the second ObjectId.</returns>
  154. public static bool operator <(ObjectId lhs, ObjectId rhs)
  155. {
  156. return lhs.CompareTo(rhs) < 0;
  157. }
  158. /// <summary>
  159. /// Compares two ObjectIds.
  160. /// </summary>
  161. /// <param name="lhs">The first ObjectId.</param>
  162. /// <param name="rhs">The other ObjectId</param>
  163. /// <returns>True if the first ObjectId is less than or equal to the second ObjectId.</returns>
  164. public static bool operator <=(ObjectId lhs, ObjectId rhs)
  165. {
  166. return lhs.CompareTo(rhs) <= 0;
  167. }
  168. /// <summary>
  169. /// Compares two ObjectIds.
  170. /// </summary>
  171. /// <param name="lhs">The first ObjectId.</param>
  172. /// <param name="rhs">The other ObjectId.</param>
  173. /// <returns>True if the two ObjectIds are equal.</returns>
  174. public static bool operator ==(ObjectId lhs, ObjectId rhs)
  175. {
  176. return lhs.Equals(rhs);
  177. }
  178. /// <summary>
  179. /// Compares two ObjectIds.
  180. /// </summary>
  181. /// <param name="lhs">The first ObjectId.</param>
  182. /// <param name="rhs">The other ObjectId.</param>
  183. /// <returns>True if the two ObjectIds are not equal.</returns>
  184. public static bool operator !=(ObjectId lhs, ObjectId rhs)
  185. {
  186. return !(lhs == rhs);
  187. }
  188. /// <summary>
  189. /// Compares two ObjectIds.
  190. /// </summary>
  191. /// <param name="lhs">The first ObjectId.</param>
  192. /// <param name="rhs">The other ObjectId</param>
  193. /// <returns>True if the first ObjectId is greather than or equal to the second ObjectId.</returns>
  194. public static bool operator >=(ObjectId lhs, ObjectId rhs)
  195. {
  196. return lhs.CompareTo(rhs) >= 0;
  197. }
  198. /// <summary>
  199. /// Compares two ObjectIds.
  200. /// </summary>
  201. /// <param name="lhs">The first ObjectId.</param>
  202. /// <param name="rhs">The other ObjectId</param>
  203. /// <returns>True if the first ObjectId is greather than the second ObjectId.</returns>
  204. public static bool operator >(ObjectId lhs, ObjectId rhs)
  205. {
  206. return lhs.CompareTo(rhs) > 0;
  207. }
  208. // public static methods
  209. /// <summary>
  210. /// Generates a new ObjectId with a unique value.
  211. /// </summary>
  212. /// <returns>An ObjectId.</returns>
  213. public static ObjectId GenerateNewId()
  214. {
  215. return GenerateNewId(GetTimestampFromDateTime(DateTime.UtcNow));
  216. }
  217. /// <summary>
  218. /// Generates a new ObjectId with a unique value (with the timestamp component based on a given DateTime).
  219. /// </summary>
  220. /// <param name="timestamp">The timestamp component (expressed as a DateTime).</param>
  221. /// <returns>An ObjectId.</returns>
  222. public static ObjectId GenerateNewId(DateTime timestamp)
  223. {
  224. return GenerateNewId(GetTimestampFromDateTime(timestamp));
  225. }
  226. /// <summary>
  227. /// Generates a new ObjectId with a unique value (with the given timestamp).
  228. /// </summary>
  229. /// <param name="timestamp">The timestamp component.</param>
  230. /// <returns>An ObjectId.</returns>
  231. public static ObjectId GenerateNewId(int timestamp)
  232. {
  233. int increment = Interlocked.Increment(ref __staticIncrement) & 0x00ffffff; // only use low order 3 bytes
  234. return new ObjectId(timestamp, __staticMachine, __staticPid, increment);
  235. }
  236. /// <summary>
  237. /// Generates a new ObjectId string with a unique value.
  238. /// </summary>
  239. /// <returns>The string value of the new generated ObjectId.</returns>
  240. public static string GenerateNewStringId()
  241. {
  242. return GenerateNewId().ToString();
  243. }
  244. /// <summary>
  245. /// Packs the components of an ObjectId into a byte array.
  246. /// </summary>
  247. /// <param name="timestamp">The timestamp.</param>
  248. /// <param name="machine">The machine hash.</param>
  249. /// <param name="pid">The PID.</param>
  250. /// <param name="increment">The increment.</param>
  251. /// <returns>A byte array.</returns>
  252. public static byte[] Pack(int timestamp, int machine, short pid, int increment)
  253. {
  254. if ((machine & 0xff000000) != 0)
  255. {
  256. throw new ArgumentOutOfRangeException("machine", "The machine value must be between 0 and 16777215 (it must fit in 3 bytes).");
  257. }
  258. if ((increment & 0xff000000) != 0)
  259. {
  260. throw new ArgumentOutOfRangeException("increment", "The increment value must be between 0 and 16777215 (it must fit in 3 bytes).");
  261. }
  262. byte[] bytes = new byte[12];
  263. bytes[0] = (byte)(timestamp >> 24);
  264. bytes[1] = (byte)(timestamp >> 16);
  265. bytes[2] = (byte)(timestamp >> 8);
  266. bytes[3] = (byte)(timestamp);
  267. bytes[4] = (byte)(machine >> 16);
  268. bytes[5] = (byte)(machine >> 8);
  269. bytes[6] = (byte)(machine);
  270. bytes[7] = (byte)(pid >> 8);
  271. bytes[8] = (byte)(pid);
  272. bytes[9] = (byte)(increment >> 16);
  273. bytes[10] = (byte)(increment >> 8);
  274. bytes[11] = (byte)(increment);
  275. return bytes;
  276. }
  277. /// <summary>
  278. /// Parses a string and creates a new ObjectId.
  279. /// </summary>
  280. /// <param name="s">The string value.</param>
  281. /// <returns>A ObjectId.</returns>
  282. public static ObjectId Parse(string s)
  283. {
  284. if (s == null)
  285. {
  286. throw new ArgumentNullException("s");
  287. }
  288. if (s.Length != 24)
  289. {
  290. throw new ArgumentOutOfRangeException("s", "ObjectId string value must be 24 characters.");
  291. }
  292. return new ObjectId(ParseHexString(s));
  293. }
  294. /// <summary>
  295. /// Unpacks a byte array into the components of an ObjectId.
  296. /// </summary>
  297. /// <param name="bytes">A byte array.</param>
  298. /// <param name="timestamp">The timestamp.</param>
  299. /// <param name="machine">The machine hash.</param>
  300. /// <param name="pid">The PID.</param>
  301. /// <param name="increment">The increment.</param>
  302. public static void Unpack(byte[] bytes, out int timestamp, out int machine, out short pid, out int increment)
  303. {
  304. if (bytes == null)
  305. {
  306. throw new ArgumentNullException("bytes");
  307. }
  308. if (bytes.Length != 12)
  309. {
  310. throw new ArgumentOutOfRangeException("bytes", "Byte array must be 12 bytes long.");
  311. }
  312. timestamp = (bytes[0] << 24) + (bytes[1] << 16) + (bytes[2] << 8) + bytes[3];
  313. machine = (bytes[4] << 16) + (bytes[5] << 8) + bytes[6];
  314. pid = (short)((bytes[7] << 8) + bytes[8]);
  315. increment = (bytes[9] << 16) + (bytes[10] << 8) + bytes[11];
  316. }
  317. // private static methods
  318. /// <summary>
  319. /// Gets the current process id. This method exists because of how CAS operates on the call stack, checking
  320. /// for permissions before executing the method. Hence, if we inlined this call, the calling method would not execute
  321. /// before throwing an exception requiring the try/catch at an even higher level that we don't necessarily control.
  322. /// </summary>
  323. [MethodImpl(MethodImplOptions.NoInlining)]
  324. private static int GetCurrentProcessId()
  325. {
  326. return Process.GetCurrentProcess().Id;
  327. }
  328. private static int GetMachineHash()
  329. {
  330. var hostName = Environment.MachineName; // use instead of Dns.HostName so it will work offline
  331. var md5 = MD5.Create();
  332. var hash = md5.ComputeHash(Encoding.UTF8.GetBytes(hostName));
  333. return (hash[0] << 16) + (hash[1] << 8) + hash[2]; // use first 3 bytes of hash
  334. }
  335. private static int GetTimestampFromDateTime(DateTime timestamp)
  336. {
  337. return (int)Math.Floor((ToUniversalTime(timestamp) - __unixEpoch).TotalSeconds);
  338. }
  339. // public methods
  340. /// <summary>
  341. /// Compares this ObjectId to another ObjectId.
  342. /// </summary>
  343. /// <param name="other">The other ObjectId.</param>
  344. /// <returns>A 32-bit signed integer that indicates whether this ObjectId is less than, equal to, or greather than the other.</returns>
  345. public int CompareTo(ObjectId other)
  346. {
  347. int r = _timestamp.CompareTo(other._timestamp);
  348. if (r != 0) { return r; }
  349. r = _machine.CompareTo(other._machine);
  350. if (r != 0) { return r; }
  351. r = _pid.CompareTo(other._pid);
  352. if (r != 0) { return r; }
  353. return _increment.CompareTo(other._increment);
  354. }
  355. /// <summary>
  356. /// Compares this ObjectId to another ObjectId.
  357. /// </summary>
  358. /// <param name="rhs">The other ObjectId.</param>
  359. /// <returns>True if the two ObjectIds are equal.</returns>
  360. public bool Equals(ObjectId rhs)
  361. {
  362. return
  363. _timestamp == rhs._timestamp &&
  364. _machine == rhs._machine &&
  365. _pid == rhs._pid &&
  366. _increment == rhs._increment;
  367. }
  368. /// <summary>
  369. /// Compares this ObjectId to another object.
  370. /// </summary>
  371. /// <param name="obj">The other object.</param>
  372. /// <returns>True if the other object is an ObjectId and equal to this one.</returns>
  373. public override bool Equals(object obj)
  374. {
  375. if (obj is ObjectId)
  376. {
  377. return Equals((ObjectId)obj);
  378. }
  379. else
  380. {
  381. return false;
  382. }
  383. }
  384. /// <summary>
  385. /// Gets the hash code.
  386. /// </summary>
  387. /// <returns>The hash code.</returns>
  388. public override int GetHashCode()
  389. {
  390. int hash = 17;
  391. hash = 37 * hash + _timestamp.GetHashCode();
  392. hash = 37 * hash + _machine.GetHashCode();
  393. hash = 37 * hash + _pid.GetHashCode();
  394. hash = 37 * hash + _increment.GetHashCode();
  395. return hash;
  396. }
  397. /// <summary>
  398. /// Converts the ObjectId to a byte array.
  399. /// </summary>
  400. /// <returns>A byte array.</returns>
  401. public byte[] ToByteArray()
  402. {
  403. return Pack(_timestamp, _machine, _pid, _increment);
  404. }
  405. /// <summary>
  406. /// Returns a string representation of the value.
  407. /// </summary>
  408. /// <returns>A string representation of the value.</returns>
  409. public override string ToString()
  410. {
  411. return ToHexString(ToByteArray());
  412. }
  413. /// <summary>
  414. /// Parses a hex string into its equivalent byte array.
  415. /// </summary>
  416. /// <param name="s">The hex string to parse.</param>
  417. /// <returns>The byte equivalent of the hex string.</returns>
  418. public static byte[] ParseHexString(string s)
  419. {
  420. if (s == null)
  421. {
  422. throw new ArgumentNullException("s");
  423. }
  424. if (s.Length % 2 == 1)
  425. {
  426. throw new Exception("The binary key cannot have an odd number of digits");
  427. }
  428. byte[] arr = new byte[s.Length >> 1];
  429. for (int i = 0; i < (s.Length >> 1); ++i)
  430. {
  431. arr[i] = (byte)((GetHexVal(s[i << 1]) << 4) + (GetHexVal(s[(i << 1) + 1])));
  432. }
  433. return arr;
  434. }
  435. /// <summary>
  436. /// Converts a byte array to a hex string.
  437. /// </summary>
  438. /// <param name="bytes">The byte array.</param>
  439. /// <returns>A hex string.</returns>
  440. public static string ToHexString(byte[] bytes)
  441. {
  442. if (bytes == null)
  443. {
  444. throw new ArgumentNullException("bytes");
  445. }
  446. var result = new char[bytes.Length * 2];
  447. for (int i = 0; i < bytes.Length; i++)
  448. {
  449. var val = _lookup32[bytes[i]];
  450. result[2 * i] = (char)val;
  451. result[2 * i + 1] = (char)(val >> 16);
  452. }
  453. return new string(result);
  454. }
  455. /// <summary>
  456. /// Converts a DateTime to number of milliseconds since Unix epoch.
  457. /// </summary>
  458. /// <param name="dateTime">A DateTime.</param>
  459. /// <returns>Number of seconds since Unix epoch.</returns>
  460. public static long ToMillisecondsSinceEpoch(DateTime dateTime)
  461. {
  462. var utcDateTime = ToUniversalTime(dateTime);
  463. return (utcDateTime - __unixEpoch).Ticks / 10000;
  464. }
  465. /// <summary>
  466. /// Converts a DateTime to UTC (with special handling for MinValue and MaxValue).
  467. /// </summary>
  468. /// <param name="dateTime">A DateTime.</param>
  469. /// <returns>The DateTime in UTC.</returns>
  470. public static DateTime ToUniversalTime(DateTime dateTime)
  471. {
  472. if (dateTime == DateTime.MinValue)
  473. {
  474. return DateTime.SpecifyKind(DateTime.MinValue, DateTimeKind.Utc);
  475. }
  476. else if (dateTime == DateTime.MaxValue)
  477. {
  478. return DateTime.SpecifyKind(DateTime.MaxValue, DateTimeKind.Utc);
  479. }
  480. else
  481. {
  482. return dateTime.ToUniversalTime();
  483. }
  484. }
  485. private static int GetHexVal(char hex)
  486. {
  487. int val = (int)hex;
  488. //For uppercase A-F letters:
  489. //return val - (val < 58 ? 48 : 55);
  490. //For lowercase a-f letters:
  491. //return val - (val < 58 ? 48 : 87);
  492. //Or the two combined, but a bit slower:
  493. return val - (val < 58 ? 48 : (val < 97 ? 55 : 87));
  494. }
  495. }
  496. }