fantomongo / fan / bson / BsonReader.fan

////////////////////////////////////////////////////////////////////////////////
//
//  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.
//
////////////////////////////////////////////////////////////////////////////////


**
**  BsonReader
**
internal class BsonReader
{
  private static const Log log := Log.get("mongo")
  
  static internal Str:Obj? read(InStream ins)
  {
    bson := Str:Obj?[:] { ordered = true }
    readObject(ins, bson)
    return bson
  }
  
  static private Int readObject(InStream ins, Str:Obj? bson)
  {
    objectlen := ins.readS4
    remaining := objectlen - 4
    while (remaining > 0) {
      type := ins.read
      remaining--
      // Sys.out.printLine("type - ${type}, remaining - ${remaining}")
      switch (type)
      {
        case Bson.MINKEY:
          remaining -= readMinKey(ins, bson)
        case Bson.EOO:
          if (remaining > 0)
            log.warn("Bson.read() - got EOO with $remaining bytes remaining")
          break // end of object - return whatever we have
        case Bson.DOUBLE:
          remaining -= readFloat(ins, bson)
        case Bson.SYMBOL:
        case Bson.STRING:
          remaining -= readStr(ins, bson)
        case Bson.OBJECT:
          remaining -= readMap(ins, bson)
        case Bson.ARRAY:
          remaining -= readArray(ins, bson)
        case Bson.BINARY:
          remaining -= readBuf(ins, bson)
        case Bson.OID:
          remaining -= readOID(ins, bson)
        case Bson.BOOLEAN:
          remaining -= readBool(ins, bson)
        case Bson.TIMESTAMP: // both have the same format
        case Bson.DATE:
          remaining -= readDate(ins, bson)
        case Bson.UNDEFINED:
        case Bson.NULL:
          remaining -= readNull(ins, bson)
        // case Bson.REGEX:
          // remaining = readRegex(ins, bson)
        case Bson.CODE_W_SCOPE:
          remaining -= readCode(ins, bson)
        case Bson.NUMBER_LONG:
          remaining -= readLong(ins, bson)
        case Bson.NUMBER_INT:
          remaining -= readInt(ins, bson)
        case Bson.MAXKEY:
          remaining -= readMaxKey(ins, bson)
        default:
          throw Err("Unknown BSON type received - ${type}")
      }
    }
    return objectlen - remaining
  }
  
  static private Str readCStr(InStream ins)
  {
    str := ins.readStrToken(null, |i| { return i == 0 }) // read till we get a null
    ins.skip(1) // eat the null terminator...worth validating that this is 0?
    return str
  }
  
  static private Int readStr(InStream ins, Str:Obj? bson)
  {
    name := readCStr(ins)
    val := ins.readChars(ins.readU4 - 1)
    ins.skip(1) // eat the null terminator...worth validating that this is 0?
    bson[name] = val
    return (name.size + val.size + 6) // 6 == 1 null, 1 null, 4 strlen
  }
  
  static private Int readInt(InStream ins, Str:Obj? bson)
  {
    name := readCStr(ins)
    bson[name] = ins.readS4
    return (name.size + 5) // 5 == 1 null, 4 int len
  }
  
  static private Int readLong(InStream ins, Str:Obj? bson)
  {
    name := readCStr(ins)
    bson[name] = ins.readS8
    return (name.size + 9) // 9 == 1 null, 8 long len
  }
  
  static private Int readFloat(InStream ins, Str:Obj? bson)
  {
    name := readCStr(ins)
    bson[name] = ins.readF8
    return (name.size + 9) // 9 == 1 null, 8 floatlen
  }
  
  static private Int readBool(InStream ins, Str:Obj? bson)
  {
    name := readCStr(ins)
    bson[name] = (ins.read == 0x01) ? true : false
    return (name.size + 2) // 2 == 1 null, 1 bool
  }
  
  static private Int readBuf(InStream ins, Str:Obj? bson)
  {
    name := readCStr(ins)
    totallen := ins.readS4
    Buf? val
    switch (ins.read) { // type
      case Bson.BIN_LENGTH:
        len := ins.readS4
        if (len + 4 != totallen)
          throw MongoOpErr("BSON - bad data size got subtype 0x02 len: $len, totallen: $totallen")
        val = ins.readBufFully(null, len)
      default:
        val = ins.readBufFully(null, totallen)
    }
    bson[name] = val
    return (name.size + totallen + 6)  // 6 == 1 null, 4 len, 1 type
  }
  
  static private Int readDate(InStream ins, Str:Obj? bson)
  {
    name := readCStr(ins)
    bson[name] = DateTime.fromJava(ins.readS8)
    return (name.size + 9) // 9 == 1 null, 8 date len
  }
  
  static private Int readNull(InStream ins, Str:Obj? bson)
  {
    name := readCStr(ins)
    bson[name] = null
    return (name.size + 1)
  }
  
  static private Int readOID(InStream ins, Str:Obj? bson)
  {
    name := readCStr(ins)
    bson[name] = ObjectID.fromStream(ins)
    return (name.size + 1 + ObjectID.SIZE)
  }
  
  static private Int readArray(InStream ins, Str:Obj? bson)
  {
    name := readCStr(ins)
    arr := Str:Obj?[:] { ordered = true }
    arraylen := readObject(ins, arr)
    bson[name] = arr.vals
    return (name.size + 1 + arraylen)
  }
  
  static private Int readMap(InStream ins, Str:Obj? bson)
  {
    name := readCStr(ins)
    v := Str:Obj?[:] { ordered = true }
    objlen := readObject(ins, v)
    bson[name] = v
    return (name.size + 1 + objlen)
  }
  
  static private Int readCode(InStream ins, Str:Obj? bson)
  {
    name := readCStr(ins)
    totalsize := ins.readS4
    
    code := ins.readChars(ins.readS4 - 1)
    ins.skip(1) // eat null terminator
    
    scope := Str:Obj?[:] { ordered = true }
    readObject(ins, scope)
    bson[name] = Code(code, scope)
    return (name.size + 1 + totalsize + 4 + 4) // 4 - totalsize, 4 - strsize
  }
  
  static private Int readMinKey(InStream ins, Str:Obj? bson)
  {
    name := readCStr(ins)
    bson[name] = MinKey() // todo - figure this out
    return (name.size + 1)
  }
  
  static private Int readMaxKey(InStream ins, Str:Obj? bson)
  {
    name := readCStr(ins)
    bson[name] = MaxKey() // todo - figure this out
    return (name.size + 1)
  }
  
}
Tip: Filter by directory path e.g. /media app.js to search for public/media/app.js.
Tip: Use camelCasing e.g. ProjME to search for ProjectModifiedEvent.java.
Tip: Filter by extension type e.g. /repo .js to search for all .js files in the /repo directory.
Tip: Separate your search with spaces e.g. /ssh pom.xml to search for src/ssh/pom.xml.
Tip: Use ↑ and ↓ arrow keys to navigate and return to view the file.
Tip: You can also navigate files with Ctrl+j (next) and Ctrl+k (previous) and view the file with Ctrl+o.
Tip: You can also navigate files with Alt+j (next) and Alt+k (previous) and view the file with Alt+o.