summaryrefslogtreecommitdiff
path: root/qpid/ruby/lib/qpid/spec010.rb
diff options
context:
space:
mode:
Diffstat (limited to 'qpid/ruby/lib/qpid/spec010.rb')
-rw-r--r--qpid/ruby/lib/qpid/spec010.rb485
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