module Hessian2::Writer

Public Instance Methods

call(method, args) click to toggle source
# File lib/hessian2/writer.rb, line 8
def call(method, args)
  refs, crefs, trefs = {}, {}, {}
  out = [ 'H', '2', '0', 'C' ].pack('ahha')
  out << write_string(method)
  out << write_int(args.size)
  args.each{|arg| out << write(arg, refs, crefs, trefs) }

  out
end
print_string(str) click to toggle source
reply(val) click to toggle source
# File lib/hessian2/writer.rb, line 19
def reply(val)
  [ 'H', '2', '0', 'R' ].pack('ahha') << write(val)
end
write(val, refs = {}, crefs = {}, trefs = {}) click to toggle source
# File lib/hessian2/writer.rb, line 33
def write(val, refs = {}, crefs = {}, trefs = {})
  case val
  when StructWrapper # object to values-array
    write_struct_wrapper(val, refs, crefs, trefs)
  when ClassWrapper # class definition for statically typed languages
    write_class_wrapper(val, refs, crefs, trefs)
  when TypeWrapper
    write_type_wrapper(val, refs, crefs, trefs)
  when TrueClass
    [ BC_TRUE ].pack('C')
  when FalseClass
    [ BC_FALSE ].pack('C')
  when Time
    if val.usec == 0 && val.sec == 0 # date in minutes
      [ BC_DATE_MINUTE, val.to_i / 60 ].pack('CL>')
    else
      [ BC_DATE, val.to_i * 1000 + val.usec / 1000 ].pack('CQ>') # date
    end
  when Float, BigDecimal
    write_float(val)
  when Fixnum
    write_int(val)
  when Array
    write_array(val, refs, crefs, trefs)
  when Bignum
    if val >= -0x80_000_000 && val <= 0x7f_fff_fff # four octet longs
      [ BC_LONG_INT, val ].pack('Cl>')
    else # long
      [ BC_LONG, val ].pack('Cq>')
    end
  when Integer
    write_int(val)
  when Hash
    write_hash(val, refs, crefs, trefs)
  when NilClass
    write_nil
  when String
    write_string(val)
  when Symbol
    write_string(val.to_s)
  else
    write_object(val, refs, crefs, trefs)
  end
end
write_array(arr, refs = {}, crefs = {}, trefs = {}) click to toggle source
# File lib/hessian2/writer.rb, line 101
def write_array(arr, refs = {}, crefs = {}, trefs = {})
  idx = refs[arr.object_id]
  return write_ref(idx) if idx

  refs[arr.object_id] = refs.size
  len = arr.count
  if len <= LIST_DIRECT_MAX # [x78-7f] value*
    str = [ BC_LIST_DIRECT_UNTYPED + len ].pack('C')
  else  # x58 int value*
    str = [ BC_LIST_FIXED_UNTYPED ].pack('C') << write_int(len)
  end

  arr.each do |ele|
    str << write(ele, refs, crefs, trefs)
  end

  str
end
write_binary(str) click to toggle source
# File lib/hessian2/writer.rb, line 121
def write_binary(str)
  chunks, i, len = [], 0, str.size
  while len > 0x8000
    chunks << [ BC_BINARY_CHUNK, 0x8000 ].pack('Cn') << str[i...(i += 0x8000)]
    len -= 0x8000
  end

  final = str[i..-1]
  if len <= BINARY_DIRECT_MAX
    chunks << [ BC_BINARY_DIRECT + len ].pack('C') << final
  elsif len <= BINARY_SHORT_MAX
    chunks << [ BC_BINARY_SHORT + (len >> 8), len ].pack('CC') << final
  else
    chunks << [ BC_BINARY, len ].pack('Cn') << final
  end

  chunks.join
end
write_class_wrapper(val, refs, crefs, trefs) click to toggle source
# File lib/hessian2/writer.rb, line 141
def write_class_wrapper(val, refs, crefs, trefs)
  return write_nil unless val.values

  idx = refs[val.object_id]
  return write_ref(idx) if idx

  refs[val.object_id] = refs.size

  if val.is_multi?
    type = '[' << val.klass
    if trefs.include?(type)
      tstr = write_int(trefs[type])
    else
      trefs[type] = trefs.size # store a type
      tstr = write_string(type)
    end
    return [ BC_LIST_DIRECT ].pack('C') << tstr if val.values.size == 0
  end

  cref = crefs[val.klass]
  if cref
    cidx = cref.first
    fields = cref.last
    str = ''
  else
    fstr = val.fields.map{|f| write_string(f) }.join
    
    str = [ BC_OBJECT_DEF ].pack('C') << write_string(val.klass) << write_int(val.fields.size) << fstr
    cidx = crefs.size
    crefs[val.klass] = [cidx, val.fields] # store a class definition
  end

  if cidx <= OBJECT_DIRECT_MAX
    cstr = [ BC_OBJECT_DIRECT + cidx ].pack('C')
  else
    cstr = [ BC_OBJECT ].pack('C') << write_int(cidx)
  end

  if val.is_multi?
    len = val.values.size
    if len <= LIST_DIRECT_MAX # [x70-77] type value*
      str << [ BC_LIST_DIRECT + len ].pack('C') << tstr
    else  # 'V' type int value*
      str << [ BC_LIST_FIXED ].pack('C') << tstr << write_int(len)
    end

    val.values.each do |ele|
      if ele
        ele_idx = refs[ele.object_id]
        if ele_idx
          str << (cstr + write_ref(ele_idx))
        else
          refs[ele.object_id] = refs.size
          str << (cstr + ele.map{|v| write(v, refs, crefs, trefs)}.join)
        end
      else
        str << write_nil
      end
    end
  else
    str << (cstr + val.values.map{|v| write(v, refs, crefs, trefs)}.join)
  end

  str
end
write_fault(e) click to toggle source
# File lib/hessian2/writer.rb, line 24
def write_fault(e)
  val = {
    code: e.class.to_s,
    message: e.message,
    detail: e.backtrace }
  [ 'F' ].pack('a') << write_hash(val)
end
write_float(val) click to toggle source
# File lib/hessian2/writer.rb, line 208
def write_float(val)
  case val.infinite?
  when 1
    return [ BC_DOUBLE, Float::INFINITY ].pack('CG')
  when -1
    return [ BC_DOUBLE, -Float::INFINITY ].pack('CG')
  else
    return [ BC_DOUBLE, Float::NAN ].pack('CG') if val.nan?
    return [ BC_DOUBLE_ZERO ].pack('C') if val.zero? # double zero
    return [ BC_DOUBLE_ONE ].pack('C') if val == 1 # double one

    ival = val.to_i
    if ival == val
      return [ BC_DOUBLE_BYTE, ival ].pack('Cc') if ival >= -0x80 && ival <= 0x7f # double octet
      return [ BC_DOUBLE_SHORT, (ival >> 8), ival ].pack('Ccc') if ival >= -0x8000 && ival <= 0x7fff # double short
    end

    mval = val * 1000
    if mval.finite?
      mills = mval.to_i
      if mills >= -0x80_000_000 && mills <= 0x7f_fff_fff && 0.001 * mills == val
        return [ BC_DOUBLE_MILL, mills ].pack('Cl>') # double mill
      end
    end

    [ BC_DOUBLE, val ].pack('CG') # double
  end
end
write_hash(hash, refs = {}, crefs = {}, trefs = {}) click to toggle source
# File lib/hessian2/writer.rb, line 238
def write_hash(hash, refs = {}, crefs = {}, trefs = {})
  idx = refs[hash.object_id]
  return write_ref(idx) if idx

  refs[hash.object_id] = refs.size
  str = [ BC_MAP_UNTYPED ].pack('C')
  hash.each do |k, v|
    str << write(k, refs, crefs, trefs)
    str << write(v, refs, crefs, trefs)
  end

  str << [ BC_END ].pack('C')
end
write_int(val) click to toggle source
# File lib/hessian2/writer.rb, line 253
def write_int(val)
  if val >= INT_DIRECT_MIN && val <= INT_DIRECT_MAX # single octet integers
    [ BC_INT_ZERO + val ].pack('c')
  elsif val >= INT_BYTE_MIN && val <= INT_BYTE_MAX # two octet integers
    [ BC_INT_BYTE_ZERO + (val >> 8), val ].pack('cc')
  elsif val >= INT_SHORT_MIN && val <= INT_SHORT_MAX # three octet integers
    [ BC_INT_SHORT_ZERO + (val >> 16), (val >> 8), val].pack('ccc')
  elsif val >= -0x80_000_000 && val <= 0x7f_fff_fff # integer
    [ BC_INT, val ].pack('Cl>')
  else
    [ BC_LONG, val ].pack('Cq>')
  end
end
write_long(val) click to toggle source
# File lib/hessian2/writer.rb, line 268
def write_long(val)
  if val >= LONG_DIRECT_MIN && val <= LONG_DIRECT_MAX # single octet longs
    [ BC_LONG_ZERO + val ].pack('c')
  elsif val >= LONG_BYTE_MIN && val <= LONG_BYTE_MAX # two octet longs
    [ BC_LONG_BYTE_ZERO + (val >> 8), val ].pack('cc')
  elsif val >= LONG_SHORT_MIN && val <= LONG_SHORT_MAX # three octet longs
    [ BC_LONG_SHORT_ZERO + (val >> 16), (val >> 8), val ].pack('ccc')
  elsif val >= -0x80_000_000 && val <= 0x7f_fff_fff # four octet longs
    [ BC_LONG_INT, val ].pack('Cl>')
  else
    [ BC_LONG, val ].pack('Cq>')
  end
end
write_nil() click to toggle source
# File lib/hessian2/writer.rb, line 283
def write_nil
  [ BC_NULL ].pack('C')
end
write_object(object, refs = {}, crefs = {}, trefs = {}) click to toggle source
# File lib/hessian2/writer.rb, line 288
def write_object(object, refs = {}, crefs = {}, trefs = {})
  return write_nil unless object

  return write_array(object, refs, crefs, trefs) if object.respond_to?(:count) && (object.respond_to?(:sql) || object.respond_to?(:to_sql))

  idx = refs[object.object_id]
  return write_ref(idx) if idx

  refs[object.object_id] = refs.size

  klass = object.class.to_s
  cref = crefs[klass]
  if cref
    cidx = cref.first
    fields = cref.last
    str = ''
  else
    fields = get_fields(object)
    fstr = fields.map{|f| write_string(f) }.join
    cidx = crefs.size
    crefs[klass] = [cidx, fields]

    str = [ BC_OBJECT_DEF ].pack('C') << write_string(klass) << write_int(fields.size) << fstr
  end
  
  if cidx <= OBJECT_DIRECT_MAX
    cstr = [ BC_OBJECT_DIRECT + cidx ].pack('C')
  else
    cstr = [ BC_OBJECT ].pack('C') << write_int(cidx)
  end

  str << write_values(object, cstr, fields, refs, crefs, trefs)

  str
end
write_ref(val) click to toggle source
# File lib/hessian2/writer.rb, line 325
def write_ref(val)
  [ BC_REF ].pack('C') << write_int(val)
end
write_string(str) click to toggle source
# File lib/hessian2/writer.rb, line 342
def write_string(str)
  chunks, i, len = '', 0, str.size
  while len > 0x8000
    chunks << [ BC_STRING_CHUNK, 0x8000 ].pack('Cn') << print_string(str[i...(i += 0x8000)])
    len -= 0x8000
  end

  final = str[i..-1]
  chunks << if len <= STRING_DIRECT_MAX
    [ BC_STRING_DIRECT + len ].pack('C')
  elsif len <= STRING_SHORT_MAX
    [ BC_STRING_SHORT + (len >> 8), len ].pack('CC')
  else
    [ BC_STRING, len ].pack('Cn')
  end

  chunks << print_string(final)
end
write_struct_wrapper(val, refs, crefs, trefs) click to toggle source
# File lib/hessian2/writer.rb, line 330
def write_struct_wrapper(val, refs, crefs, trefs)
  return write_nil unless val.values

  idx = refs[val.object_id]
  return write_ref(idx) if idx

  refs[val.object_id] = refs.size

  write_array(val.values, refs, crefs, trefs)
end
write_type_wrapped_array(arr, tstr, eletype, refs = {}, crefs = {}, trefs = {}) click to toggle source
# File lib/hessian2/writer.rb, line 362
def write_type_wrapped_array(arr, tstr, eletype, refs = {}, crefs = {}, trefs = {})
  len = arr.size
  return [ BC_LIST_DIRECT ].pack('C') << tstr if len == 0

  if len <= LIST_DIRECT_MAX # [x70-77] type value*
    str = [ BC_LIST_DIRECT + len ].pack('C') << tstr
  else  # 'V' type int value*
    str = [ BC_LIST_FIXED ].pack('C') << tstr << write_int(len)
  end

  case eletype
  when 'L'
    arr.each do |ele|
      str << write_long(Integer(ele))
    end
  when 'I'
    arr.each do |ele|
      str << write_int(Integer(ele))
    end
  when 'B'
    arr.each do |ele|
      str << write_binary(ele)
    end
  else
    arr.each do |ele|
      idx = refs[ele.object_id]
      if idx
        str << write_ref(idx)
      else
        refs[ele.object_id] = refs.size
        str << write_type_wrapped_object(ele, tstr, refs, crefs, trefs)
      end
    end
  end
  
  str
end
write_type_wrapped_object(object, tstr, refs = {}, crefs = {}, trefs = {}) click to toggle source
# File lib/hessian2/writer.rb, line 401
def write_type_wrapped_object(object, tstr, refs = {}, crefs = {}, trefs = {})
  return write_nil unless object

  str = [ BC_MAP ].pack('C') << tstr

  if object.is_a?(Hash)
    object.each do |k, v|
      str << write(k, refs, crefs, trefs)
      str << write(v, refs, crefs, trefs)
    end
  elsif object.instance_variable_get(:@attributes).is_a?(Hash)
    object.attributes.each do |k, v|
      str << write(k, refs, crefs, trefs)
      str << write(v, refs, crefs, trefs)
    end
  elsif object.is_a?(ClassWrapper)
    object.fields.each_with_index do |f, i|
      str << write(f, refs, crefs, trefs)
      str << write(object.values[i], refs, crefs, trefs)
    end
  elsif object.is_a?(TypeWrapper)
    object.object.each do |k, v|
      str << write(k, refs, crefs, trefs)
      str << write(v, refs, crefs, trefs)
    end
  else
    object.instance_variables.each do |var| 
      str << write(var[1..-1], refs, crefs, trefs)
      str << write(object.instance_variable_get(var), refs, crefs, trefs)
    end
  end

  str << [ BC_END ].pack('C')
end
write_type_wrapper(val, refs, crefs, trefs) click to toggle source
# File lib/hessian2/writer.rb, line 437
def write_type_wrapper(val, refs, crefs, trefs)
  return write_nil unless val.object

  idx = refs[val.object_id]
  return write_ref(idx) if idx

  refs[val.object_id] = refs.size

  type = val.is_multi? ? ('[' << val.hessian_type) : val.hessian_type
  if trefs.include?(type)
    tstr = write_int(trefs[type])
  else
    trefs[type] = trefs.size
    tstr = write_string(type)
  end

  if val.is_multi?
    write_type_wrapped_array(val.object, tstr, val.hessian_type, refs, crefs, trefs)
  else
    case val.hessian_type
    when 'L'
      write_long(Integer(val.object))
    when 'I'
      write_int(Integer(val.object))
    when 'B'
      write_binary(val.object)
    else
      write_type_wrapped_object(val.object, tstr, refs, crefs, trefs)
    end
  end
end

Private Instance Methods

get_fields(object) click to toggle source
# File lib/hessian2/writer.rb, line 472
def get_fields(object)
  fields = if object.is_a?(Hash)
    object.keys.map{|k| k.to_sym }
  elsif object.instance_variable_get(:@values).is_a?(Hash)
    object.values.keys
  elsif object.instance_variable_get(:@attributes).is_a?(Hash)
    object.attributes.keys
  else
    object.instance_variables.map{|k| k[1..-1].to_sym }
  end

  raise "fields should not be empty: #{object.inspect}" if fields.empty?

  fields
end
write_values(object, cstr, fields, refs, crefs, trefs) click to toggle source
# File lib/hessian2/writer.rb, line 489
def write_values(object, cstr, fields, refs, crefs, trefs)
  return write_nil unless object
  
  vstr = if object.is_a?(Hash)
    fields.map{|f| write(object[f] || object[f.to_s], refs, crefs, trefs) }.join
  elsif object.instance_variable_get(:@values).is_a?(Hash)
    fields.map{|f| write(object.values[f], refs, crefs, trefs) }.join
  elsif object.instance_variable_get(:@attributes).is_a?(Hash)
    fields.map{|f| write(object.attributes[f], refs, crefs, trefs) }.join
  else
    fields.map{|f| write(object.instance_variable_get(f.to_s.prepend('@')), refs, crefs, trefs) }.join
  end

  cstr + vstr
end