Skip to content

Commit b7d9986

Browse files
committed
[JAVA-85]: refactoring of ObjectId, to store values native, and encode/decode big endian. Fixes some flaws with Increment value failing comparison.
1 parent 2ee21e9 commit b7d9986

File tree

6 files changed

+118
-56
lines changed

6 files changed

+118
-56
lines changed

src/main/org/bson/BSONDecoder.java

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -141,7 +141,8 @@ boolean decodeElement()
141141
break;
142142

143143
case OID:
144-
_callback.gotObjectId( name , new ObjectId( _in.readInt() , _in.readInt() , _in.readInt() ) );
144+
// OID is stored as big endian
145+
_callback.gotObjectId( name , new ObjectId( _in.readIntBE() , _in.readIntBE() , _in.readIntBE() ) );
145146
break;
146147

147148
case REF:
@@ -325,6 +326,11 @@ int readInt()
325326
return Bits.readInt( _inputBuffer , _need(4) );
326327
}
327328

329+
int readIntBE()
330+
throws IOException {
331+
return Bits.readIntBE( _inputBuffer , _need(4) );
332+
}
333+
328334
long readLong()
329335
throws IOException {
330336
return Bits.readLong( _inputBuffer , _need(8) );

src/main/org/bson/BSONEncoder.java

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -352,9 +352,10 @@ private void _putString( String name , String s, byte type ){
352352

353353
protected void putObjectId( String name , ObjectId oid ){
354354
_put( OID , name );
355-
_buf.writeInt( oid._time() );
356-
_buf.writeInt( oid._machine() );
357-
_buf.writeInt( oid._inc() );
355+
// according to spec, values should be stored big endian
356+
_buf.writeIntBE( oid._time() );
357+
_buf.writeIntBE( oid._machine() );
358+
_buf.writeIntBE( oid._inc() );
358359
}
359360

360361
private void putPattern( String name, Pattern p ) {

src/main/org/bson/io/Bits.java

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,15 @@ public static int readInt( byte[] data , int offset ) {
6363
return x;
6464
}
6565

66+
public static int readIntBE( byte[] data , int offset ) {
67+
int x = 0;
68+
x |= ( 0xFF & data[offset+0] ) << 24;
69+
x |= ( 0xFF & data[offset+1] ) << 16;
70+
x |= ( 0xFF & data[offset+2] ) << 8;
71+
x |= ( 0xFF & data[offset+3] ) << 0;
72+
return x;
73+
}
74+
6675
public static long readLong( InputStream in )
6776
throws IOException {
6877
return readLong( in , new byte[8] );

src/main/org/bson/io/OutputBuffer.java

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -108,6 +108,13 @@ public void writeInt( int x ){
108108
write( x >> 24 );
109109
}
110110

111+
public void writeIntBE( int x ){
112+
write( x >> 24 );
113+
write( x >> 16 );
114+
write( x >> 8 );
115+
write( x );
116+
}
117+
111118
public void writeInt( int pos , int x ){
112119
final int save = getPosition();
113120
setPosition( pos );

src/main/org/bson/types/ObjectId.java

Lines changed: 72 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -100,24 +100,20 @@ public static ObjectId massageToObjectId( Object o ){
100100
}
101101

102102
public ObjectId( Date time ){
103-
_time = _flip( (int)(time.getTime() / 1000) );
104-
_machine = _genmachine;
105-
_inc = _nextInc.getAndIncrement();
106-
_new = false;
103+
this(time, _genmachine, _nextInc.getAndIncrement());
107104
}
108105

109106
public ObjectId( Date time , int inc ){
110107
this( time , _genmachine , inc );
111108
}
112109

113110
public ObjectId( Date time , int machine , int inc ){
114-
_time = _flip( (int)(time.getTime() / 1000) );
111+
_time = (int)(time.getTime() / 1000);
115112
_machine = machine;
116113
_inc = inc;
117114
_new = false;
118115
}
119116

120-
121117
/** Creates a new instance from a string.
122118
* @param s the string to convert
123119
* @throws IllegalArgumentException if the string is not a valid id
@@ -133,44 +129,45 @@ public ObjectId( String s , boolean babble ){
133129

134130
if ( babble )
135131
s = babbleToMongod( s );
136-
132+
137133
byte b[] = new byte[12];
138134
for ( int i=0; i<b.length; i++ ){
139-
b[b.length-(i+1)] = (byte)Integer.parseInt( s.substring( i*2 , i*2 + 2) , 16 );
135+
b[i] = (byte)Integer.parseInt( s.substring( i*2 , i*2 + 2) , 16 );
140136
}
141137
ByteBuffer bb = ByteBuffer.wrap( b );
142-
143-
_inc = bb.getInt();
144-
_machine = bb.getInt();
145138
_time = bb.getInt();
146-
139+
_machine = bb.getInt();
140+
_inc = bb.getInt();
147141
_new = false;
148142
}
149143

150144
public ObjectId( byte[] b ){
151145
if ( b.length != 12 )
152146
throw new IllegalArgumentException( "need 12 bytes" );
153-
reverse( b );
154147
ByteBuffer bb = ByteBuffer.wrap( b );
155-
156-
_inc = bb.getInt();
157-
_machine = bb.getInt();
158148
_time = bb.getInt();
149+
_machine = bb.getInt();
150+
_inc = bb.getInt();
151+
_new = false;
159152
}
160153

161-
154+
/**
155+
* Creates an ObjectId
156+
* @param time time in seconds
157+
* @param machine machine ID
158+
* @param inc incremental value
159+
*/
162160
public ObjectId( int time , int machine , int inc ){
163161
_time = time;
164162
_machine = machine;
165163
_inc = inc;
166-
167164
_new = false;
168165
}
169166

170167
/** Create a new object id.
171168
*/
172169
public ObjectId(){
173-
_time = _curtime();
170+
_time = (int) (System.currentTimeMillis() / 1000);
174171
_machine = _genmachine;
175172
_inc = _nextInc.getAndIncrement();
176173
_new = true;
@@ -221,20 +218,12 @@ public String toStringMongod(){
221218
public byte[] toByteArray(){
222219
byte b[] = new byte[12];
223220
ByteBuffer bb = ByteBuffer.wrap( b );
224-
bb.putInt( _inc );
225-
bb.putInt( _machine );
221+
// by default BB is big endian like we need
226222
bb.putInt( _time );
227-
reverse( b );
223+
bb.putInt( _machine );
224+
bb.putInt( _inc );
228225
return b;
229226
}
230-
231-
static void reverse( byte[] b ){
232-
for ( int i=0; i<b.length/2; i++ ){
233-
byte t = b[i];
234-
b[i] = b[ b.length-(i+1) ];
235-
b[b.length-(i+1)] = t;
236-
}
237-
}
238227

239228
static String _pos( String s , int p ){
240229
return s.substring( p * 2 , ( p * 2 ) + 2 );
@@ -257,41 +246,61 @@ public String toString(){
257246
return toStringMongod();
258247
}
259248

260-
int _compare( int i , int j ){
261-
i = _flip(i);
262-
j = _flip(j);
263-
264-
final int diff = j - i;
265-
266-
if ( i >= 0 ){
267-
return j >= 0 ? -diff : -1;
268-
}
269-
270-
return j < 0 ? -diff : 1;
249+
int _compareUnsigned( int i , int j ){
250+
long li = 0xFFFFFFFFL;
251+
li = i & li;
252+
long lj = 0xFFFFFFFFL;
253+
lj = j & lj;
254+
long diff = li - lj;
255+
if (diff < Integer.MIN_VALUE)
256+
return Integer.MIN_VALUE;
257+
if (diff > Integer.MAX_VALUE)
258+
return Integer.MAX_VALUE;
259+
return (int) diff;
271260
}
272261

273262
public int compareTo( ObjectId id ){
274263
if ( id == null )
275264
return -1;
276265

277-
int x = _compare( _time , id._time );
266+
int x = _compareUnsigned( _time , id._time );
278267
if ( x != 0 )
279268
return x;
280269

281-
x = _compare( _machine , id._machine );
270+
x = _compareUnsigned( _machine , id._machine );
282271
if ( x != 0 )
283272
return x;
284273

285-
return _compare( _inc , id._inc );
274+
x = _compareUnsigned( _inc , id._inc );
275+
if (Math.abs(x) > Integer.MAX_VALUE / 2) {
276+
// this means that for same second and process more than (max int)/2 were generated
277+
// highly unlikely, most likely the counter wrapped
278+
if (x < 0)
279+
return 1;
280+
else
281+
return -1;
282+
}
283+
return x;
286284
}
287285

288286
public int getMachine(){
289287
return _machine;
290288
}
291-
289+
290+
/**
291+
* Gets the time of this ID, in milliseconds
292+
* @return
293+
*/
292294
public long getTime(){
293-
long z = _flip( _time );
294-
return z * 1000;
295+
return _time * 1000L;
296+
}
297+
298+
/**
299+
* Gets the time of this ID, in seconds
300+
* @return
301+
*/
302+
public int getTimeSecond(){
303+
return _time;
295304
}
296305

297306
public int getInc(){
@@ -316,6 +325,22 @@ public void notNew(){
316325
_new = false;
317326
}
318327

328+
/**
329+
* Gets the generated machine ID, identifying the machine / process / class loader
330+
* @return
331+
*/
332+
public static int getGenMachineId() {
333+
return _genmachine;
334+
}
335+
336+
/**
337+
* Gets the current value of the auto increment
338+
* @return
339+
*/
340+
public static int getCurrentInc() {
341+
return _nextInc.get();
342+
}
343+
319344
final int _time;
320345
final int _machine;
321346
final int _inc;
@@ -331,12 +356,7 @@ public static int _flip( int x ){
331356
return z;
332357
}
333358

334-
private static int _curtime(){
335-
return _flip( (int)(System.currentTimeMillis()/1000) );
336-
}
337-
338359
private static AtomicInteger _nextInc = new AtomicInteger( (new java.util.Random()).nextInt() );
339-
private static final String _incLock = new String( "ObjectId._incLock" );
340360

341361
private static final int _genmachine;
342362
static {

src/test/com/mongodb/ObjectIdTest.java

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -129,6 +129,25 @@ public void testFlip(){
129129
}
130130

131131
}
132+
133+
/**
134+
* Test that within same second, increment value correctly generates ordered ids
135+
*/
136+
@Test
137+
public void testInc() {
138+
ObjectId prev = null;
139+
Date now = new Date();
140+
// need to loop more than value of byte, to check that endianness is correct
141+
for (int i = 0; i < 1000; ++i) {
142+
ObjectId id = new ObjectId(now);
143+
assertEquals(id.getTime() / 1000, now.getTime() / 1000);
144+
if (prev != null) {
145+
assertTrue(prev.compareTo(id) < 0, "Wrong comparison for ids " + prev + " and " + id);
146+
}
147+
prev = id;
148+
}
149+
}
150+
132151
public static void main( String args[] )
133152
throws Exception {
134153
(new ObjectIdTest()).runConsole();

0 commit comments

Comments
 (0)