// ObjectId.java
/**
* Copyright (C) 2008 10gen Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.bson.types;
import java.util.*;
import java.nio.*;
import java.net.*;
/**
* A globally unique identifier for objects.
* <p>Consists of 12 bytes, divided as follows:
* <blockquote><pre>
* <table border="1">
* <tr><td>0</td><td>1</td><td>2</td><td>3</td><td>4</td><td>5</td><td>6</td>
* <td>7</td><td>8</td><td>9</td><td>10</td><td>11</td></tr>
* <tr><td colspan="4">time</td><td colspan="3">machine</td>
* <td colspan="2">pid</td><td colspan="3">inc</td></tr>
* </table>
* </pre></blockquote>
*
* @dochub objectids
*/
public class ObjectId implements Comparable<ObjectId> , java.io.Serializable {
static final boolean D = false;
/** Gets a new object id.
* @return the new id
*/
public static ObjectId get(){
return new ObjectId();
}
/** Checks if a string could be an <code>ObjectId</code>.
* @return whether the string could be an object id
*/
public static boolean isValid( String s ){
if ( s == null )
return false;
if ( s.length() < 18 )
return false;
for ( int i=0; i<s.length(); i++ ){
char c = s.charAt( i );
if ( c >= '0' && c <= '9' )
continue;
if ( c >= 'a' && c <= 'f' )
continue;
if ( c >= 'A' && c <= 'F' )
continue;
return false;
}
return true;
}
/** Turn an object into an <code>ObjectId</code>, if possible.
* Strings will be converted into <code>ObjectId</code>s, if possible, and <code>ObjectId</code>s will
* be cast and returned. Passing in <code>null</code> returns <code>null</code>.
* @param o the object to convert
* @return an <code>ObjectId</code> if it can be massaged, null otherwise
*/
public static ObjectId massageToObjectId( Object o ){
if ( o == null )
return null;
if ( o instanceof ObjectId )
return (ObjectId)o;
if ( o instanceof String ){
String s = o.toString();
if ( isValid( s ) )
return new ObjectId( s );
}
return null;
}
public ObjectId( Date time ){
_time = _flip( (int)(time.getTime() / 1000) );
_machine = _genmachine;
synchronized ( _incLock ){
_inc = _nextInc++;
}
_new = false;
}
public ObjectId( Date time , int inc ){
_time = _flip( (int)(time.getTime() / 1000) );
_machine = _genmachine;
_inc = inc;
_new = false;
}
/** Creates a new instance from a string.
* @param s the string to convert
* @throws IllegalArgumentException if the string is not a valid id
*/
public ObjectId( String s ){
this( s , false );
}
public ObjectId( String s , boolean babble ){
if ( ! isValid( s ) )
throw new IllegalArgumentException( "invalid ObjectId [" + s + "]" );
if ( babble )
s = babbleToMongod( s );
byte b[] = new byte[12];
for ( int i=0; i<b.length; i++ ){
b[b.length-(i+1)] = (byte)Integer.parseInt( s.substring( i*2 , i*2 + 2) , 16 );
}
ByteBuffer bb = ByteBuffer.wrap( b );
_inc = bb.getInt();
_machine = bb.getInt();
_time = bb.getInt();
_new = false;
}
public ObjectId( byte[] b ){
if ( b.length != 12 )
throw new IllegalArgumentException( "need 12 bytes" );
reverse( b );
ByteBuffer bb = ByteBuffer.wrap( b );
_inc = bb.getInt();
_machine = bb.getInt();
_time = bb.getInt();
}
public ObjectId( int time , int machine , int inc ){
_time = time;
_machine = machine;
_inc = inc;
_new = false;
}
/** Create a new object id.
*/
public ObjectId(){
_time = _gentime;
_machine = _genmachine;
synchronized ( _incLock ){
_inc = _nextInc++;
}
_new = true;
}
public int hashCode(){
return _inc;
}
public boolean equals( Object o ){
if ( this == o )
return true;
ObjectId other = massageToObjectId( o );
if ( other == null )
return false;
return
_time == other._time &&
_machine == other._machine &&
_inc == other._inc;
}
public String toStringBabble(){
return babbleToMongod( toStringMongod() );
}
public String toStringMongod(){
byte b[] = toByteArray();
StringBuilder buf = new StringBuilder(24);
for ( int i=0; i<b.length; i++ ){
int x = b[i] & 0xFF;
String s = Integer.toHexString( x );
if ( s.length() == 1 )
buf.append( "0" );
buf.append( s );
}
return buf.toString();
}
public byte[] toByteArray(){
byte b[] = new byte[12];
ByteBuffer bb = ByteBuffer.wrap( b );
bb.putInt( _inc );
bb.putInt( _machine );
bb.putInt( _time );
reverse( b );
return b;
}
static void reverse( byte[] b ){
for ( int i=0; i<b.length/2; i++ ){
byte t = b[i];
b[i] = b[ b.length-(i+1) ];
b[b.length-(i+1)] = t;
}
}
static String _pos( String s , int p ){
return s.substring( p * 2 , ( p * 2 ) + 2 );
}
public static String babbleToMongod( String b ){
if ( ! isValid( b ) )
throw new IllegalArgumentException( "invalid object id: " + b );
StringBuilder buf = new StringBuilder( 24 );
for ( int i=7; i>=0; i-- )
buf.append( _pos( b , i ) );
for ( int i=11; i>=8; i-- )
buf.append( _pos( b , i ) );
return buf.toString();
}
public String toString(){
return toStringMongod();
}
public int compareTo( ObjectId id ){
if ( id == null )
return -1;
long xx = id.getTime() - getTime();
if ( xx > 0 )
return -1;
else if ( xx < 0 )
return 1;
int x = id._machine - _machine;
if ( x != 0 )
return -x;
x = id._inc - _inc;
if ( x != 0 )
return -x;
return 0;
}
public int getMachine(){
return _machine;
}
public long getTime(){
long z = _flip( _time );
return z * 1000;
}
public int getInc(){
return _inc;
}
public int _time(){
return _time;
}
public int _machine(){
return _machine;
}
public int _inc(){
return _inc;
}
public boolean isNew(){
return _new;
}
public void notNew(){
_new = false;
}
final int _time;
final int _machine;
final int _inc;
boolean _new;
static int _flip( int x ){
if ( true ){
byte b[] = new byte[4];
ByteBuffer bb = ByteBuffer.wrap( b );
bb.order( ByteOrder.LITTLE_ENDIAN );
bb.putInt( x );
bb.flip();
bb.order( ByteOrder.BIG_ENDIAN );
return bb.getInt();
}
int z = 0;
z |= ( x & 0xFF ) << 24;
z |= ( x & 0xFF00 ) << 8;
z |= ( x & 0xFF00000 ) >> 8;
z |= ( x & 0xFF000000 ) >> 24;
return z;
}
private static int _nextInc = (new java.util.Random()).nextInt();
private static final String _incLock = new String( "ObjectId._incLock" );
private static int _gentime = _flip( (int)(System.currentTimeMillis()/1000) );
static final Thread _timeFixer;
private static final int _genmachine;
static {
try {
final int machinePiece;
{
StringBuilder sb = new StringBuilder();
Enumeration<NetworkInterface> e = NetworkInterface.getNetworkInterfaces();
while ( e.hasMoreElements() ){
NetworkInterface ni = e.nextElement();
sb.append( ni.toString() );
}
machinePiece = sb.toString().hashCode() << 16;
if ( D ) System.out.println( "machine piece post: " + Integer.toHexString( machinePiece ) );
}
final int processPiece = java.lang.management.ManagementFactory.getRuntimeMXBean().getName().hashCode() & 0xFFFF;
if ( D ) System.out.println( "process piece: " + Integer.toHexString( processPiece ) );
_genmachine = machinePiece | processPiece;
if ( D ) System.out.println( "machine : " + Integer.toHexString( _genmachine ) );
}
catch ( java.io.IOException ioe ){
throw new RuntimeException( ioe );
}
_timeFixer = new Thread("ObjectId-TimeFixer"){
public void run(){
while ( true ){
try {
Thread.sleep( 499 );
}
catch ( Exception e ){}
_gentime = _flip( (int)(System.currentTimeMillis()/1000) );
}
}
};
_timeFixer.setDaemon( true );
_timeFixer.start();
}
public static void main( String args[] ){
if ( true ){
int z = _nextInc;
System.out.println( Integer.toHexString( z ) );
System.out.println( Integer.toHexString( _flip( z ) ) );
System.out.println( Integer.toHexString( _flip( _flip( z ) ) ) );
return;
}
ObjectId x = new ObjectId();
double num = 5000000.0;
long start = System.currentTimeMillis();
for ( double i=0; i<num; i++ ){
ObjectId id = get();
}
long end = System.currentTimeMillis();
System.out.println( ( ( num * 1000.0 ) / ( end - start ) ) + " oid/sec" );
Set<ObjectId> s = new HashSet<ObjectId>();
for ( double i=0; i<num/10; i++ ){
ObjectId id = get();
if ( s.contains( id ) )
throw new RuntimeException( "ObjectId() generated a repeat" );
s.add( id );
ObjectId o = new ObjectId( id.toString() );
if ( ! id.equals( o ) )
throw new RuntimeException( o.toString() + " != " + id.toString() );
}
}
}