1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20 package com.allanbank.mongodb.bson.element;
21
22 import java.io.Serializable;
23 import java.lang.management.ManagementFactory;
24 import java.lang.management.RuntimeMXBean;
25 import java.net.InetAddress;
26 import java.net.NetworkInterface;
27 import java.security.MessageDigest;
28 import java.security.SecureRandom;
29 import java.util.Enumeration;
30 import java.util.concurrent.TimeUnit;
31 import java.util.concurrent.atomic.AtomicLong;
32
33 import com.allanbank.mongodb.bson.io.EndianUtils;
34 import com.allanbank.mongodb.util.IOUtils;
35
36
37
38
39
40
41
42
43
44
45 public class ObjectId implements Serializable, Comparable<ObjectId> {
46
47
48 public static final long MACHINE_ID;
49
50
51 private static final AtomicLong COUNTER;
52
53
54 private static final long serialVersionUID = -3035334151717895487L;
55
56 static {
57 long value = 0;
58 final SecureRandom rand = new SecureRandom();
59 try {
60 boolean foundIface = true;
61 final MessageDigest md5 = MessageDigest.getInstance("MD5");
62
63 try {
64 final Enumeration<NetworkInterface> ifaces = NetworkInterface
65 .getNetworkInterfaces();
66 while (ifaces.hasMoreElements()) {
67 try {
68 final NetworkInterface iface = ifaces.nextElement();
69
70 if (!iface.isLoopback()) {
71 md5.update(iface.getHardwareAddress());
72 foundIface = true;
73 }
74 }
75 catch (final Throwable tryAnotherIface) {
76
77 tryAnotherIface.hashCode();
78 }
79 }
80 }
81 catch (final Throwable tryTheHostName) {
82
83 tryTheHostName.hashCode();
84 }
85
86 if (!foundIface) {
87 md5.update(InetAddress.getLocalHost().getHostName()
88 .getBytes("UTF8"));
89 }
90
91 final byte[] hash = md5.digest();
92 value += (hash[0] & 0xFF);
93 value <<= Byte.SIZE;
94 value += (hash[1] & 0xFF);
95 value <<= Byte.SIZE;
96 value += (hash[2] & 0xFF);
97 value <<= Byte.SIZE;
98 }
99 catch (final Throwable t) {
100
101 for (int i = 0; i < 3; ++i) {
102 value += rand.nextInt(256);
103 value <<= Byte.SIZE;
104 }
105 }
106
107
108 int processId;
109 try {
110 final RuntimeMXBean runtime = ManagementFactory.getRuntimeMXBean();
111 final String processName = runtime.getName();
112 final int atLoc = processName.indexOf('@');
113 if (atLoc >= 0) {
114 final String pidString = processName.substring(0, atLoc);
115 processId = Integer.parseInt(pidString);
116 }
117 else {
118
119 processId = rand.nextInt();
120 }
121
122 }
123 catch (final Throwable t) {
124
125 processId = rand.nextInt();
126 }
127
128 value += ((processId >> Byte.SIZE) & 0xFF);
129 value <<= Byte.SIZE;
130 value += (processId & 0xFF);
131
132 MACHINE_ID = (value << 24);
133 COUNTER = new AtomicLong(rand.nextLong() & 0xFFFFFFL);
134 }
135
136
137
138
139
140
141
142 private static int now() {
143 return (int) TimeUnit.MILLISECONDS
144 .toSeconds(System.currentTimeMillis());
145 }
146
147
148
149
150
151
152
153 private static long processId() {
154 return MACHINE_ID + (COUNTER.incrementAndGet() & 0xFFFFFFL);
155 }
156
157
158 private final long myMachineId;
159
160
161 private final int myTimestamp;
162
163
164
165
166 public ObjectId() {
167 this(now(), processId());
168 }
169
170
171
172
173
174
175
176
177
178 public ObjectId(final int timestamp, final long machineId) {
179 myTimestamp = timestamp;
180 myMachineId = machineId;
181 }
182
183
184
185
186
187
188
189
190
191 public ObjectId(final String hexBytes) throws IllegalArgumentException {
192
193 if (hexBytes.length() != 24) {
194 throw new IllegalArgumentException(
195 "Invalid ObjectId value. Must be a 24 character hex string.");
196 }
197
198 final byte[] bytes = IOUtils.hexToBytes(hexBytes);
199 int timestamp = 0;
200 for (int i = 0; i < 4; ++i) {
201 int value = (bytes[i] & 0xFF);
202 value <<= (Byte.SIZE * i);
203 timestamp += value;
204 }
205
206 long machineId = 0;
207 for (int i = 4; i < 12; ++i) {
208 long value = (bytes[i] & 0xFF);
209 value <<= (Byte.SIZE * (i - 4));
210 machineId += value;
211 }
212
213 myTimestamp = EndianUtils.swap(timestamp);
214 myMachineId = EndianUtils.swap(machineId);
215 }
216
217
218
219
220
221
222
223
224 @Override
225 public int compareTo(final ObjectId other) {
226 int result = myTimestamp - other.myTimestamp;
227 if (result == 0) {
228 result = (myMachineId < other.myMachineId) ? -1
229 : ((myMachineId == other.myMachineId) ? 0 : 1);
230 }
231 return result;
232 }
233
234
235
236
237
238
239
240
241
242
243 @Override
244 public boolean equals(final Object object) {
245 boolean result = false;
246 if (this == object) {
247 result = true;
248 }
249 else if ((object != null) && (getClass() == object.getClass())) {
250 final ObjectId other = (ObjectId) object;
251
252 result = (myMachineId == other.myMachineId)
253 && (myTimestamp == other.myTimestamp);
254 }
255 return result;
256 }
257
258
259
260
261
262
263 public int getCounterField() {
264 return (int) (myMachineId & 0xFFFFFFL);
265 }
266
267
268
269
270
271
272
273 public long getMachineId() {
274 return myMachineId;
275 }
276
277
278
279
280
281
282 public int getMachineIdentifier() {
283 return (int) ((myMachineId >> 40) & 0xFFFFFFL);
284 }
285
286
287
288
289
290
291 public int getPidField() {
292 return (int) ((myMachineId >> 24) & 0xFFFFL);
293 }
294
295
296
297
298
299
300
301 public int getTimestamp() {
302 return myTimestamp;
303 }
304
305
306
307
308
309
310 @Override
311 public int hashCode() {
312 int result = 0;
313 result += (int) ((myMachineId >> 32) & 0xFFFFFFFF);
314 result += (int) (myMachineId & 0xFFFFFFFF);
315 result += myTimestamp;
316 return result;
317 }
318
319
320
321
322
323
324 public String toHexString() {
325 final StringBuilder builder = new StringBuilder();
326 String hex = Integer.toHexString(myTimestamp);
327 builder.append("00000000".substring(hex.length()));
328 builder.append(hex);
329
330 hex = Long.toHexString(myMachineId);
331 builder.append("0000000000000000".substring(hex.length()));
332 builder.append(hex);
333 return builder.toString();
334 }
335
336
337
338
339
340
341
342
343 @Override
344 public String toString() {
345 final StringBuilder builder = new StringBuilder();
346
347 builder.append("ObjectId(");
348 builder.append(toHexString());
349 builder.append(")");
350
351 return builder.toString();
352 }
353 }