using concurrent::AtomicInt
** (BSON Type) -
** A globally unique identifier for MongoDB objects.
**
** The ObjectID BSON type is a 12-byte value consisting of three different portions (fields):
** - a 4-byte value representing the seconds since the Unix epoch in the highest order bytes
** - a 5-byte random number unique to a machine and process
** - a 3-byte counter, starting with a random value
**
** pre>
** 4 byte timestamp 5 byte process unique 3 byte counter
** |<----------------->|<---------------------->|<------------>|
** [----|----|----|----|----|----|----|----|----|----|----|----]
** 0 4 8 12
** <pre
**
** @See
** - `https://github.com/mongodb/specifications/blob/master/source/objectid.rst`
**
@Serializable { simple = true }
final const class ObjectId {
private static const AtomicInt counterRef := AtomicInt(Int.random)
// one cannot get the ProcessId in Java - http://fantom.org/sidewalk/topic/856
// Even the Java impl of ObjectId generates a random Int
private static const Int thisPid := Int.random.and(0xFF_FF_FF_FF_FF)
private static const Int epochOffset := DateTime.fromJava(1).ticks / 1sec.ticks
** The creation timestamp with a 1 second accuracy.
const Int ts
** A 5-byte process id that this instance was created under.
const Int pid
** A 3-byte counter value.
const Int inc
@NoDoc
override const Int hash
** Creates a new 'ObjectId'.
new make() {
this.ts = (DateTime.nowTicks / 1sec.ticks) - epochOffset
this.pid = thisPid
this.inc = counterRef.incrementAndGet.and(0xFF_FF_FF)
this.hash = ts.shiftl(32) + pid.and(0xFF).shiftl(24) + inc
}
** Useful for testing.
@NoDoc
new makeAll(Int ts, Int pid, Int inc) {
this.ts = ts .and(0xFF_FF_FF_FF)
this.pid = pid.and(0xFF_FF_FF_FF_FF)
this.inc = inc.and(0xFF_FF_FF)
this.hash = ts .shiftl(32) + pid.and(0xFF).shiftl(24) + inc
}
** Create an 'ObjectId' from a hex string.
static new fromStr(Str hex, Bool checked := true) {
if (hex.size != 24)
return null ?: (checked ? throw ParseErr("Could not parse ObjectId: ${hex}") : null)
try {
ts := hex[ 0..< 8].toInt(16)
pid := hex[ 8..<18].toInt(16)
inc := hex[18..<24].toInt(16)
return ObjectId(ts, pid, inc)
} catch (Err e)
return null ?: (checked ? throw ParseErr("Could not parse ObjectId: ${hex}", e) : null)
}
** Reads an 'ObjectId' from the given stream.
**
** Note the stream is **not** closed.
static new fromStream(InStream in) {
origEndian := in.endian
in.endian = Endian.big
ts := in.readU4
pid := in.readU4.shiftl(8) + in.read
inc := in.readU2.shiftl(8) + in.read
in.endian = origEndian
return ObjectId(ts, pid, inc)
}
** Converts the 'ts' field into a Fantom 'DateTime' instance.
DateTime timestamp(TimeZone tz := TimeZone.cur) {
DateTime.fromJava(ts * 1000, tz, true)
}
** Converts this instance into a 24 character hexadecimal string representation.
Str toHex() {
toBuf.toHex
}
** Encodes this 'ObjectId' into an 12 byte buffer.
** The returned buffer is positioned at the start and is ready to read.
Buf toBuf() {
buf := Buf(12)
writeToStream(buf.out)
return buf.flip
}
** Writes this 'ObjectId' to the given 'OutStream'.
OutStream writeToStream(OutStream out) {
origEndian := out.endian
out.endian = Endian.big
out.writeI4(ts)
out.writeI4(pid.shiftr(8)).write(pid)
out.writeI2(inc.shiftr(8)).write(inc)
out.endian = origEndian
return out
}
** Returns a Mongo Shell compliant, JavaScript representation of the 'ObjectId'. Example:
**
** syntax: fantom
** objectId.toJs // --> ObjectId("57fe499fa81320d933000001")
**
** See [MongoDB Extended JSON]`https://docs.mongodb.com/manual/reference/mongodb-extended-json/#oid`.
Str toJs() {
"ObjectId(${toHex.toCode})"
}
** Returns this 'ObjectId' as a 24 character hexadecimal string.
override Str toStr() {
toHex
}
@NoDoc
override Bool equals(Obj? obj) {
that := obj as ObjectId
if (that == null) return false
if (that.inc != this.inc) return false
if (that.pid != this.pid) return false
if (that.ts != this.ts ) return false
return true
}
}