# # Licensed to the Apache Software Foundation (ASF) under one # or more contributor license agreements. See the NOTICE file # distributed with this work for additional information # regarding copyright ownership. The ASF licenses this file # to you 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. # require "qpid/spec" require 'pathname' require 'fileutils' module Qpid::Spec010 include Qpid::Spec # XXX: workaround for ruby bug/missfeature Reference = Reference Loader = Loader class Spec ENCODINGS = { String => "str16", Fixnum => "int64", Bignum => "int64", Float => "float", NilClass => "void", Array => "list", Hash => "map" } fields(:major, :minor, :port, :children) def init() @controls = {} @commands = {} @structs = {} @types = {} children.each {|c| case c when Control @controls[c.code] = c when Command @commands[c.code] = c when Struct @structs[c.code] = c when Type @types[c.code] = c unless c.code.nil? end } end attr_reader :controls, :commands, :structs, :types def [](key) return @children[key] end def encoding(klass) if ENCODINGS.has_key?(klass) return self[ENCODINGS[klass]] end for base in klass.__bases__ result = encoding(base) return result unless result.nil? end end def inspect; "spec"; end end class Constant fields(:name, :value) attr :parent, true end class Type fields(:name, :code, :fixed, :variable) attr :parent, true def present?(value) if @fixed == 0 return value else return !value.nil? end end def encode(codec, value) codec.send("write_#{name}", value) end def decode(codec) return codec.send("read_#{name}") end def inspect; name; end end class Domain < Type fields(:name, :type, :enum) attr :parent, true def encode(codec, value) @type.encode(codec, value) end def decode(codec) return @type.decode(codec) end end class Enum fields(:choices) def [](choice) case choice when String choice = choice.to_sym return choices.find { |c| c.name == choice } when Symbol return choices.find { |c| c.name == choice } else return choices.find { |c| c.value == choice } end end def method_missing(name, *args) raise ArgumentError.new("wrong number of arguments") unless args.empty? return self[name].value end end class Choice fields(:name, :value) end class Composite fields(:name, :code, :size, :pack, :fields) attr :parent, true # Python calls this 'new', but that has special meaning in Ruby def create(*args) return Qpid::struct(self, *args) end def decode(codec) codec.read_size(@size) codec.read_uint16() unless @code.nil? return Qpid::struct(self, self.decode_fields(codec)) end def decode_fields(codec) flags = 0 pack.times {|i| flags |= (codec.read_uint8() << 8*i)} result = {} fields.each_index {|i| f = @fields[i] if flags & (0x1 << i) != 0 result[f.name] = f.type.decode(codec) else result[f.name] = nil end } return result end def encode(codec, value) sc = Qpid::StringCodec.new(@spec) sc.write_uint16(@code) unless @code.nil? encode_fields(sc, value) codec.write_size(@size, sc.encoded.size) codec.write(sc.encoded) end def encode_fields(codec, values) # FIXME: This could be written cleaner using select # instead of flags flags = 0 fields.each_index do |i| f = fields[i] flags |= (0x1 << i) if f.type.present?(values[f.name]) end pack.times { |i| codec.write_uint8((flags >> 8*i) & 0xFF) } fields.each_index do |i| f = fields[i] f.type.encode(codec, values[f.name]) if flags & (0x1 << i) != 0 end end def inspect; name; end end class Field fields(:name, :type, :exceptions) def default() return nil end end class Struct < Composite def present?(value) return !value.nil? end end class Action < Composite; end class Control < Action def segment_type @parent[:segment_type].enum[:control].value end def track @parent[:track].enum[:control].value end end class Command < Action attr_accessor :payload, :result def segment_type @parent["segment_type"].enum["command"].value end def track @parent["track"].enum["command"].value end end class Doc fields(:type, :title, :text) end class Loader010 < Loader def initialize() super() end def klass cls = element until cls.nil? break if cls.name == "class" cls = cls.parent end return cls end def scope if element.name == "struct" return nil else return class_name end end def class_name cls = klass if cls.nil? return nil else return parse_name(cls.attributes["name"].strip) end end def class_code cls = klass if cls.nil? return 0 else return parse_int(cls.attributes["code"].strip) end end def parse_decl(value) name = parse_name(value) s = scope if s.nil? return name else return :"#{s}_#{name}" end end def parse_code(value) c = parse_int(value) if c.nil? return nil else return c | (class_code << 8) end end def parse_type(value) name = parse_name(value.sub(".", "_")) cls = class_name return Reference.new {|spec| candidates = [name] candidates << :"#{cls}_#{name}" unless cls.nil? for c in candidates child = spec[c] break unless child.nil? end if child.nil? raise Exception.new("unresolved type: #{name}") else child end } end def load_amqp() children = nil for s in ["constant", "type", "domain", "struct", "control", "command"] ch = load(s) if children.nil? children = ch else children += ch end children += load("class/#{s}") end children += load("class/command/result/struct") Spec.new(attr("major", :int), attr("minor", :int), attr("port", :int), children) end def load_constant() Constant.new(attr("name", :decl), attr("value", :int)) end def load_type() Type.new(attr("name", :decl), attr("code", :code), attr("fixed-width", :int), attr("variable-width", :int)) end def load_domain() Domain.new(attr("name", :decl), attr("type", :type), load("enum").first) end def load_enum() Enum.new(load("choice")) end def load_choice() Choice.new(attr("name", :name), attr("value", :int)) end def load_field() Field.new(attr("name", :name), attr("type", :type)) end def load_struct() Struct.new(attr("name", :decl), attr("code", :code), attr("size", :int), attr("pack", :int), load("field")) end def load_action(cls) cls.new(attr("name", :decl), attr("code", :code), 0, 2, load("field")) end def load_control() load_action(Control) end def load_command() result = attr("type", :type, nil, "result") result = attr("name", :type, nil, "result/struct") if result.nil? segs = load("segments") cmd = load_action(Command) cmd.result = result cmd.payload = !segs.empty? return cmd end def load_result() true end def load_segments() true end end def self.spec_cache(specfile) File::join(File::dirname(__FILE__), "spec_cache", File::basename(specfile, ".xml") + ".rb_marshal") end # XXX: could be shared def self.load(spec = nil) return spec if spec.is_a?(Qpid::Spec010::Spec) if spec.nil? # FIXME: Need to add a packaging setup in here so we know where # the installed spec is going to be. specfile = nil if ENV['AMQP_SPEC'] specfile = ENV['AMQP_SPEC'] else require "qpid/config" specfile = Qpid::Config.amqp_spec end else specfile = spec end specfile_cache = spec_cache(specfile) # FIXME: Check that cache is newer than specfile if File::exist?(specfile_cache) begin spec = File::open(specfile_cache, "r") do |f| Marshal::load(f) end return spec rescue # Ignore, will load from XML end end doc = File::open(specfile, "r") { |f| Document.new(f) } spec = Loader010.new().load(doc.root) spec.traverse! do |o| if o.is_a?(Reference) o.resolve(spec) else o end end spec.children.each { |c| c.parent = spec } begin FileUtils::mkdir_p(File::dirname(specfile_cache)) File::open(specfile_cache, "w") { |f| Marshal::dump(spec, f) } rescue # Ignore, we are fine without the cached spec end return spec end end