Source

fantomongo / fan / ObjectID.fan

Full commit
////////////////////////////////////////////////////////////////////////////////
//
//  Copyright 2010 Liam Staskawicz
//
//  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.
//
////////////////////////////////////////////////////////////////////////////////

using inet
using concurrent

**
**  ObjectID
**
@Serializable { simple = true }
const class ObjectID 
{
  static const Int SIZE := 12
  private static const AtomicInt counter := AtomicInt(0)
  private const Int inc
  private const Int time
  private const Int machine
  
  new make()
  { 
    this.inc = counter.incrementAndGet().and(0xFFFFFFFF)
    this.time = DateTime.nowUnique.and(0xFFFFFFFF)
    this.machine = IpAddr.local.bytes.toDigest("MD5").readS4.and(0xFFFFFFFF)
  }

  new makeAll(Int time, Int machine, Int inc)
  { 
    this.time = time
    this.machine = machine
    this.inc = inc
  }
  
  new fromStream(InStream ins)
  {
    endian := ins.endian
    ins.endian = Endian.big
    this.time = ins.readS4.and(0xFFFFFFFF)
    this.machine = ins.readS4.and(0xFFFFFFFF)
    this.inc = ins.readS4.and(0xFFFFFFFF)
    ins.endian = endian
  }
  
  ** Parse ObjectID from according to the string format defined in mongodb documentation
  ** If invalid format and checked is false
  ** return null, otherwise throw Err.
  static new fromStr(Str s, Bool checked := true)
  {
    if (!isValid(s)) {
      if (checked)
        throw Err("invalid ObjectID format")
      return null
    }

    return makeAll(s[0..7].toInt(16).and(0xFFFFFFFF),
                   s[8..15].toInt(16).and(0xFFFFFFFF),
                   s[16..23].toInt(16).and(0xFFFFFFFF))
  }
  
  override Str toStr()
  {
    s := StrBuf()
    [time, machine, inc].each |val, i| {
      s.add(val.toHex(8))
    }
    return s.toStr()
  }
  
  override Bool equals(Obj? o)
  {
    if (o isnot ObjectID)
      return false
    
    return ((o as ObjectID).inc == this.inc && 
            (o as ObjectID).machine == this.machine && 
            (o as ObjectID).time == this.time)
  }
  
  override Int hash() {
    return 31 * (inc.hash.xor(machine.hash) + time.hash)
  }
  
  OutStream write(OutStream o)
  {
    endian := o.endian
    o.endian = Endian.big
    o = o.writeI4(time).writeI4(machine).writeI4(inc)
    o.endian = endian
    return o
  }
  
  static Bool isValid(Str s)
  {
    if (s.size != 24)
      return false;
    
    // make sure it's all hex digits so we can convert to Ints
    valid := true
    s.each |Int c| {
      if (c >= '0' && c <= '9')
        return                 
      if (c >= 'a' && c <= 'f')
        return                 
      if (c >= 'A' && c <= 'F')
        return
      valid = false
    }
    return valid
  }
  
}