summaryrefslogtreecommitdiff
path: root/qpid/cpp/rubygen/amqpgen.rb
diff options
context:
space:
mode:
Diffstat (limited to 'qpid/cpp/rubygen/amqpgen.rb')
-rwxr-xr-xqpid/cpp/rubygen/amqpgen.rb550
1 files changed, 550 insertions, 0 deletions
diff --git a/qpid/cpp/rubygen/amqpgen.rb b/qpid/cpp/rubygen/amqpgen.rb
new file mode 100755
index 0000000000..20aac35194
--- /dev/null
+++ b/qpid/cpp/rubygen/amqpgen.rb
@@ -0,0 +1,550 @@
+# 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.
+#
+# Generic AMQP code generation library.
+#
+# TODO aconway 2008-02-21:
+#
+# The amqp_attr_reader and amqp_child_reader for each Amqp* class
+# should correspond exactly to ampq.dtd. Currently they are more
+# permissive so we can parse 0-10 preview and 0-10 final XML.
+#
+# Code marked with "# preview" should be removed/modified when final 0-10
+# is complete and we are ready to remove preview-related code.
+#
+
+require 'delegate'
+require 'rexml/document'
+require 'pathname'
+require 'set'
+include REXML
+
+# Handy String functions for converting names.
+class String
+ # Convert to CapitalizedForm.
+ def caps() gsub( /(^|\W)(\w)/ ) { |m| $2.upcase } end
+
+ # Convert to underbar_separated_form.
+ def bars() tr('- .','_'); end
+
+ # Convert to ALL_UPPERCASE_FORM
+ def shout() bars.upcase; end
+
+ # Convert to lowerCaseCapitalizedForm
+ def lcaps() gsub( /\W(\w)/ ) { |m| $1.upcase } end
+
+ def plural() self + (/[xs]$/ === self ? 'es' : 's'); end
+end
+
+# Sort an array by name.
+module Enumerable
+ def sort_by_name() sort { |a,b| a.name <=> b.name }; end
+end
+
+# Add functions similar to attr_reader for AMQP attributes/children.
+# Symbols that are ruby Object function names (e.g. class) get
+# an "_" suffix.
+class Module
+ # Add trailing _ to avoid conflict with Object methods.
+ def mangle(sym)
+ sym = (sym.to_s+"_").to_sym if (Object.method_defined?(sym) or sym == :type)
+ sym
+ end
+
+ # Add attribute reader for XML attribute.
+ def amqp_attr_reader(*attrs)
+ attrs.each { |a|
+ case a
+ when Symbol
+ define_method(mangle(a)) {
+ @amqp_attr_reader||={ }
+ @amqp_attr_reader[a] ||= xml.attributes[a.to_s]
+ }
+ when Hash
+ a.each { |attr, default|
+ define_method(mangle(attr)) {
+ @amqp_attr_reader||={ }
+ value = xml.attributes[attr.to_s]
+ if value
+ @amqp_attr_reader[attr] ||= value
+ else
+ @amqp_attr_reader[attr] ||= default
+ end
+ }
+ }
+ end
+ }
+ end
+
+ # Add 2 child readers:
+ # elname(name) == child('elname',name)
+ # elnames() == children('elname')
+ def amqp_child_reader(*element_names)
+ element_names.each { |e|
+ define_method(mangle(e)) { |name| child(e.to_s, name) }
+ define_method(mangle(e.to_s.plural)) { children(e.to_s) } }
+ end
+
+ # When there can only be one child instance
+ def amqp_single_child_reader(*element_names)
+ element_names.each { |e|
+ define_method(mangle(e)) { children(e.to_s)[0] } }
+ end
+end
+
+# An AmqpElement contains an XML element and provides a convenient
+# API to access AMQP data.
+#
+# NB: AmqpElements cache values from XML, they assume that
+# the XML model does not change after the AmqpElement has
+# been created.
+class AmqpElement
+
+ def wrap(xml)
+ return nil if ["assert","rule"].include? xml.name
+ eval("Amqp"+xml.name.caps).new(xml, self) or raise "nil wrapper"
+ end
+
+ public
+
+ def initialize(xml, parent)
+ @xml, @parent=xml, parent
+ @children=xml.elements.map { |e| wrap e }.compact
+ @cache_child={}
+ @cache_child_named={}
+ @cache_children={}
+ @cache_children[nil]=@children
+ end
+
+ attr_reader :parent, :xml, :children, :doc
+ amqp_attr_reader :name, :label
+
+ # List of children of type elname, or all children if elname
+ # not specified.
+ def children(elname=nil)
+ if elname
+ @cache_children[elname] ||= @children.select { |c| elname==c.xml.name }
+ else
+ @children
+ end
+ end
+
+ def each_descendant(&block)
+ yield self
+ @children.each { |c| c.each_descendant(&block) }
+ end
+
+ def collect_all(amqp_type)
+ collect=[]
+ each_descendant { |d| collect << d if d.is_a? amqp_type }
+ collect
+ end
+
+ # Look up child of type elname with attribute name.
+ def child(elname, name)
+ @cache_child[[elname,name]] ||= children(elname).find { |c| c.name==name }
+ end
+
+ # Look up any child with name
+ def child_named(name)
+ @cache_child_named[name] ||= @children.find { |c| c.name==name }
+ end
+
+ # The root <amqp> element.
+ def root() @root ||=parent ? parent.root : self; end
+
+ def to_s() "#<#{self.class}(#{fqname})>"; end
+ def inspect() to_s; end
+
+ # Text of doc child if there is one.
+ def doc() d=xml.elements["doc"]; d and d.text; end
+
+ def fqname()
+ throw "fqname: #{self} #{parent.fqname} has no name" unless name
+ p=parent && parent.fqname
+ p ? p+"."+name : name;
+ end
+
+ def containing_class()
+ return self if is_a? AmqpClass
+ return parent && parent.containing_class
+ end
+
+ # 0-10 array domains are missing element type information, add it here.
+ ArrayTypes={
+ "str16-array" => "str-16",
+ "amqp-host-array" => "connection.amqp-host-url",
+ "command-fragments" => "session.command-fragment",
+ "in-doubt" => "dtx.xid",
+ "tx-publish" => "str-8",
+ "queues" => "str-8"
+ }
+
+ def array_type(name)
+ return ArrayTypes[name] if ArrayTypes[name]
+ raise "Missing ArrayType entry for " + name
+ end
+
+end
+
+class AmqpResponse < AmqpElement
+ def initialize(xml, parent) super; end
+ def fqname() (parent ? parent.dotted_name+"." : "") + "response"; end
+end
+
+class AmqpDoc < AmqpElement
+ def initialize(xml,parent) super; end
+ def text() @xml.text end
+end
+
+class AmqpChoice < AmqpElement
+ def initialize(xml,parent) super; end
+ amqp_attr_reader :name, :value
+end
+
+class AmqpEnum < AmqpElement
+ def initialize(xml,parent) super; end
+ amqp_child_reader :choice
+end
+
+class AmqpDomain < AmqpElement
+ def initialize(xml, parent)
+ super
+ root.used_by[uses].push(fqname) if uses and uses.index('.')
+ end
+
+ amqp_attr_reader :type
+ amqp_single_child_reader :struct # preview
+ amqp_single_child_reader :enum
+
+ def uses() type_=="array" ? ArrayTypes[name] : type_; end
+end
+
+class AmqpException < AmqpElement
+ def initialize(xml, amqp) super; end;
+ amqp_attr_reader :error_code
+end
+
+class AmqpField < AmqpElement
+ def initialize(xml, amqp)
+ super;
+ root.used_by[type_].push(parent.fqname) if type_ and type_.index('.')
+ end
+ amqp_single_child_reader :struct # preview
+ amqp_child_reader :exception
+ amqp_attr_reader :type, :default, :code, :required
+end
+
+class AmqpChassis < AmqpElement # preview
+ def initialize(xml, parent) super; end
+ amqp_attr_reader :implement
+end
+
+class AmqpConstant < AmqpElement
+ def initialize(xml, parent) super; end
+ amqp_attr_reader :value, :class
+end
+
+class AmqpResult < AmqpElement
+ def initialize(xml, parent) super; end
+ amqp_single_child_reader :struct # preview
+ amqp_attr_reader :type
+ def name() "result"; end
+end
+
+class AmqpEntry < AmqpElement
+ def initialize(xml,parent) super; end
+ amqp_attr_reader :type
+end
+
+class AmqpHeader < AmqpElement
+ def initialize(xml,parent) super; end
+ amqp_child_reader :entry
+ amqp_attr_reader :required
+end
+
+class AmqpBody < AmqpElement
+ def initialize(xml,parent) super; end
+ amqp_attr_reader :required
+end
+
+class AmqpSegments < AmqpElement
+ def initialize(xml,parent) super; end
+ amqp_child_reader :header, :body
+end
+
+class AmqpStruct < AmqpElement
+ def initialize(xml, parent) super; end
+ amqp_attr_reader :type # preview
+ amqp_attr_reader :size, :code, :pack
+ amqp_child_reader :field
+
+ def result?() parent.xml.name == "result"; end
+ def domain?() parent.xml.name == "domain"; end
+end
+
+class AmqpMethod < AmqpElement
+ def initialize(xml, parent) super; end
+
+ amqp_attr_reader :content, :index, :synchronous
+ amqp_child_reader :field, :chassis,:response
+ amqp_single_child_reader :result
+
+ def on_chassis?(chassis) child("chassis", chassis); end
+ def on_client?() on_chassis? "client"; end
+ def on_server?() on_chassis? "server"; end
+end
+
+# preview: Map command/control to preview method.
+class AmqpFakeMethod < AmqpMethod
+ def initialize(action)
+ super(action.xml, action.parent);
+ @action=action
+ end
+
+ def content() return "1" if @action.is_a? AmqpCommand and @action.segments end
+ def index() @action.code end
+ def code() @action.code end
+ def synchronous() end
+ def on_chassis?(chassis)
+ @action.received_by?(chassis)
+ end
+ def pack() "2" end # Encode pack=2, size=4 struct
+ def size() "4" end
+end
+
+class AmqpImplement < AmqpElement
+ def initialize(xml,amqp) super; end
+ amqp_attr_reader :handle, :send
+end
+
+class AmqpRole < AmqpElement
+ def initialize(xml,amqp) super; end
+ amqp_attr_reader :implement
+end
+
+# Base class for command and control.
+class AmqpAction < AmqpElement
+ def initialize(xml,amqp) super; end
+ amqp_child_reader :implement, :field, :response
+ amqp_attr_reader :code
+ def implement?(role)
+ # we can't use xpath for this because it triggers a bug in some
+ # versions of ruby, including version 1.8.6.110
+ xml.elements.each {|el|
+ return true if el.name == "implement" and el.attributes["role"] == role
+ }
+ return false
+ end
+ def received_by?(client_or_server)
+ return (implement?(client_or_server) or implement?("sender") or implement?("receiver"))
+ end
+ def pack() "2" end
+ def size() "4" end # Encoded as a size 4 Struct
+end
+
+class AmqpControl < AmqpAction
+ def initialize(xml,amqp) super; end
+end
+
+class AmqpCommand < AmqpAction
+ def initialize(xml,amqp) super; end
+ amqp_child_reader :exception
+ amqp_single_child_reader :result, :segments
+end
+
+class AmqpClass < AmqpElement
+ def initialize(xml,amqp) super; end
+
+ amqp_attr_reader :index # preview
+
+ amqp_child_reader :struct, :domain, :control, :command, :role, :method
+ amqp_attr_reader :code
+
+ def actions() controls+commands; end
+
+ # preview - command/control as methods
+ def methods_()
+ return (controls + commands).map { |a| AmqpFakeMethod.new(a) }
+ end
+
+ def method(name)
+ a = (command(name) or control(name))
+ return AmqpFakeMethod.new(a)
+ end
+
+ # chassis should be "client" or "server"
+ def methods_on(chassis) # preview
+ @methods_on ||= { }
+ @methods_on[chassis] ||= methods_.select { |m| m.on_chassis? chassis }
+ end
+
+ # FIXME aconway 2008-04-11:
+ def l4?() # preview
+ !["connection", "session", "execution"].include?(name) && !control?
+ end
+
+ # FIXME aconway 2008-04-11:
+ def control?()
+ ["connection", "session"].include?(name)
+ end
+end
+
+class AmqpType < AmqpElement
+ def initialize(xml,amqp) super; end
+ amqp_attr_reader :code, :fixed_width, :variable_width
+end
+
+class AmqpXref < AmqpElement
+ def initialize(xml,amqp) super; end
+end
+
+# AMQP root element.
+class AmqpRoot < AmqpElement
+ amqp_attr_reader :major, :minor, :port, :comment
+ amqp_child_reader :doc, :type, :struct, :domain, :constant, :class
+
+ def get_root(x)
+ case x
+ when Element then x
+ when Document then x.root
+ else Document.new(x).root
+ end
+ end
+
+ # Initialize with output directory and spec files from ARGV.
+ def initialize(*specs)
+ raise "No XML spec files." if specs.empty?
+ xml=get_root(specs.shift)
+ specs.each { |s| xml_merge(xml, get_root(s)) }
+ @used_by=Hash.new{ |h,k| h[k]=[] }
+ super(xml, nil)
+ end
+
+ attr_reader :used_by
+
+ def merge(root) xml_merge(xml, root.xml); end
+
+ def version() major + "-" + minor; end
+
+ def methods_() classes.map { |c| c.methods_ }.flatten; end
+
+ #preview
+ # Return all methods on chassis for all classes.
+ def methods_on(chassis)
+ @methods_on ||= { }
+ @methods_on[chassis] ||= classes.map { |c| c.methods_on(chassis) }.flatten
+ end
+
+ def fqname() nil; end
+
+ private
+
+ # Merge contents of elements.
+ def xml_merge(to,from)
+ from.elements.each { |from_child|
+ tag,name = from_child.name, from_child.attributes["name"]
+ to_child=to.elements["./#{tag}[@name='#{name}']"]
+ to_child ? xml_merge(to_child, from_child) : to.add(from_child.deep_clone) }
+ end
+end
+
+# Collect information about generated files.
+class GenFiles
+ @@files = Set.new
+ @@public_api = []
+ def GenFiles.add(f) @@files.add(f); end
+ def GenFiles.get() @@files; end
+ def GenFiles.public_api(file) @@public_api << file; end
+ def GenFiles.public_api?(file) @@public_api.find { |f| f == file }; end
+end
+
+# Base class for code generators.
+# Supports setting a per-line prefix, useful for e.g. indenting code.
+#
+class Generator
+ # Takes directory for output or "-", meaning print file names that
+ # would be generated.
+ def initialize (outdir, amqp)
+ @outdir=outdir[0]
+ @apidir=outdir[1]
+ @amqp=amqp
+ raise "outdir is not an array" unless outdir.class == Array
+ @prefix=[''] # For indentation or comments.
+ @indentstr=' ' # One indent level.
+ @outdent=2
+ end
+
+ # Declare next file to be public API
+ def public_api(file) GenFiles.public_api(file); end
+
+ # Create a new file, set @out.
+ def file(file, &block)
+ GenFiles.add(file)
+ dir = GenFiles.public_api?(file) ? @apidir : @outdir
+ if (dir != "-")
+ @path=Pathname.new "#{dir}/#{file}"
+ @path.parent.mkpath
+ @out=String.new # Generate in memory first
+ yield if block
+ if @path.exist? and @path.read == @out
+ puts "Skipped #{@path} - unchanged" # Dont generate if unchanged
+ else
+ @path.open('w') { |f| f << @out }
+ puts "Generated #{@path}"
+ end
+ end
+ end
+
+ # Append multi-line string to generated code, prefixing each line.
+ def gen(str)
+ str.each_line { |line|
+ @out << @prefix.last unless @midline
+ @out << line
+ @midline = nil
+ }
+ # Note if we stopped mid-line
+ @midline = /[^\n]\z/ === str
+ end
+
+ # Append str + '\n' to generated code.
+ def genl(str="") gen str+"\n"; end
+
+ # Generate code with added prefix.
+ def prefix(add, &block)
+ @prefix.push @prefix.last+add
+ if block then yield; endprefix; end
+ end
+
+ def endprefix()
+ @prefix.pop
+ end
+
+ # Generate indented code
+ def indent(n=1,&block) prefix(@indentstr * n,&block); end
+ alias :endindent :endprefix
+
+ # Generate outdented code
+ def outdent(&block)
+ @prefix.push @prefix.last[0...-2]
+ if block then yield; endprefix; end
+ end
+ alias :endoutdent :endprefix
+
+ attr_accessor :out
+end
+