Source

fantomongo / fan / bson / BsonWriter.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.
//
////////////////////////////////////////////////////////////////////////////////


**
**  BsonWriter
**
internal class BsonWriter 
{
  
  internal static Void write(OutStream out, Str:Obj? obj)
  {
    buf := Buf() { endian = Endian.little }
    // because we need to know the length of the object before we can 
    // write it out, we need to assemble it in a Buf first...boo
    out.writeBuf(writeObject(buf, obj).seek(0))
  }
  
  private static Buf writeObject(Buf b, Str:Obj? obj)
  {
    lenpos := b.pos
    b.writeI4(0) // placeholder for len
    obj.each |val, key| { writeKeyVal(b, key, val) }
    b.write(Bson.EOO) // end of object
    // write the len back at the front, then seek to the end
    return b.seek(lenpos).writeI4(b.size - lenpos).seek(b.size)
  }
  
  private static Void writeKeyVal(Buf b, Str key, Obj? val)
  {
    if (val == null) writeNull(b, key)
    else if (val is Str) writeStr(b, key, val)
    // implicit conversion to DateTime
    else if (val is Date || val is DateTime) writeDateTime(b, key, val)
    else if (val is Buf) writeBinary(b, key, val)
    else if (val is Int) writeInt(b, key, val)
    // implicit conversion to Float - maybe maintain Decimal somehow?
    else if (val is Float || val is Decimal) writeFloat(b, key, val)
    else if (val is ObjectID) writeOID(b, key, val)
    else if (val is List) writeArray(b, key, val)
    else if (val is Bool) writeBool(b, key, val)
    else if (val is Map) writeMap(b, key, val)
    else if (val is Code) writeCode(b, key, val)
    // else if (val is Regex) writeRegex(out, key, val)
    else throw Err("unknown BSON type - can't serialize ${Type.of(val).qname}")
  }
  
  internal static Void writeCStr(OutStream out, Str str)
  {
    out.writeChars(str).write(0)
  }
  
  private static Void writeStr(Buf b, Str str, Str val)
  {
    b.write(Bson.STRING)
    writeCStr(b.out, str)
    b.writeI4(val.size + 1) // account for null terminator
    writeCStr(b.out, val)
  }
  
  private static Void writeNull(Buf b, Str key)
  {
    b.write(Bson.NULL)
    writeCStr(b.out, key)
  }
  
  private static Void writeBool(Buf b, Str key, Bool tf)
  {
    b.write(Bson.BOOLEAN)
    writeCStr(b.out, key)
    b.write(tf ? 0x01 : 0x00)
  }
  
  private static Void writeInt(Buf b, Str key, Int i)
  {
    b.write(Bson.NUMBER_LONG)
    writeCStr(b.out, key)
    b.writeI8(i)
  }
  
  private static Void writeFloat(Buf b, Str key, Float f)
  {
    b.write(Bson.NUMBER)
    writeCStr(b.out, key)
    b.writeF8(f)
  }
  
  private static Void writeDateTime(Buf b, Str key, DateTime d)
  {
    b.write(Bson.DATE)
    writeCStr(b.out, key)
    b.writeI8(d.toJava)
  }
  
  // bson array is really a map with stringified indexes
  private static Void writeArray(Buf b, Str key, List list)
  {
    b.write(Bson.ARRAY)
    writeCStr(b.out, key)
    m := Str:Obj?[:] { ordered = true }
    m.addList(list) |v, i->Str| { return i.toStr }
    writeObject(b, m)
  }
  
  private static Void writeMap(Buf b, Str key, Map m)
  {
    b.write(Bson.OBJECT)
    writeCStr(b.out, key)
    writeObject(b, m)
  }
  
  private static Void writeBinary(Buf b, Str key, Buf bin, Int bbt := Bson.BIN_LENGTH)
  {
    b.write(Bson.BINARY)
    writeCStr(b.out, key)
    switch (bbt) {
      case Bson.BIN_LENGTH:
        b.writeI4(bin.size + 4).write(bbt).writeI4(bin.size)
      default:
        b.writeI4(bin.size).write(bbt)
    }
    // don't disturb the pos
    p := bin.pos
    b.writeBuf(bin)
    bin.seek(p)
  }
  
  private static Void writeOID(Buf b, Str key, ObjectID oid)
  {
    b.write(Bson.OID)
    writeCStr(b.out, key)
    oid.write(b.out)
  }
  
  private static Void writeCode(Buf b, Str key, Code cws)
  {
    b.write(Bson.CODE_W_SCOPE)
    writeCStr(b.out, key)

    writeObject(b, cws.scope)
    b.writeI4(8 + cws.code.size + 1 + b.size)   // total size
    b.writeI4(cws.code.size + 1)                // size of code str
    writeCStr(b.out, cws.code)                  // code str
    b.writeBuf(b.seek(0))                       // scope
  }
  
  private static Void writeMinKey(Buf b, Str key)
  {
    b.write(Bson.MINKEY)
    writeCStr(b.out, key)
  }
  
  private static Void writeMaxKey(Buf b, Str key)
  {
    b.write(Bson.MAXKEY)
    writeCStr(b.out, key)
  }
  
}