diff options
Diffstat (limited to 'qpid/ruby/lib/qpid/spec010.rb')
-rw-r--r-- | qpid/ruby/lib/qpid/spec010.rb | 485 |
1 files changed, 485 insertions, 0 deletions
diff --git a/qpid/ruby/lib/qpid/spec010.rb b/qpid/ruby/lib/qpid/spec010.rb new file mode 100644 index 0000000000..3e54115087 --- /dev/null +++ b/qpid/ruby/lib/qpid/spec010.rb @@ -0,0 +1,485 @@ +# +# 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 |