diff options
author | Rajith Muditha Attapattu <rajith@apache.org> | 2011-05-27 15:44:23 +0000 |
---|---|---|
committer | Rajith Muditha Attapattu <rajith@apache.org> | 2011-05-27 15:44:23 +0000 |
commit | 66765100f4257159622cefe57bed50125a5ad017 (patch) | |
tree | a88ee23bb194eb91f0ebb2d9b23ff423e3ea8e37 /qpid/cpp/rubygen | |
parent | 1aeaa7b16e5ce54f10c901d75c4d40f9f88b9db6 (diff) | |
parent | 88b98b2f4152ef59a671fad55a0d08338b6b78ca (diff) | |
download | qpid-python-rajith_jms_client.tar.gz |
Creating a branch for experimenting with some ideas for JMS client.rajith_jms_client
git-svn-id: https://svn.apache.org/repos/asf/qpid/branches/rajith_jms_client@1128369 13f79535-47bb-0310-9956-ffa450edef68
Diffstat (limited to 'qpid/cpp/rubygen')
21 files changed, 3743 insertions, 0 deletions
diff --git a/qpid/cpp/rubygen/0-10/allsegmenttypes.rb b/qpid/cpp/rubygen/0-10/allsegmenttypes.rb new file mode 100755 index 0000000000..26363d6a1f --- /dev/null +++ b/qpid/cpp/rubygen/0-10/allsegmenttypes.rb @@ -0,0 +1,52 @@ +#!/usr/bin/env ruby +# +# 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. +# +$: << ".." # Include .. in load path +require 'cppgen' + +class GenAllSegmentTypes < CppGen + def initialize(outdir, amqp) + super(outdir, amqp) + end + + def generate + h_file("tests/allSegmentTypes.h") { + include "qpid/amqp_0_10/specification.h" + include "qpid/amqp_0_10/Header.h" + include "qpid/amqp_0_10/Body.h" + genl + genl "using namespace qpid::amqp_0_10;" + genl + scope("template <class Op> size_t allSegmentTypes(Op& op) {"){ + genl "op(Header());" + genl "op(Body());" + n = 2; + @amqp.classes.each { |c| + c.commands.each { |s| genl "op(CommandHolder(#{c.nsname}::#{s.classname}()));" } + c.controls.each { |s| genl "op(ControlHolder(#{c.nsname}::#{s.classname}()));" } + n += 2 + } + genl "return #{n};" + } + } + end +end + +GenAllSegmentTypes.new($outdir, $amqp).generate(); + diff --git a/qpid/cpp/rubygen/0-10/exceptions.rb b/qpid/cpp/rubygen/0-10/exceptions.rb new file mode 100755 index 0000000000..02e3a5d547 --- /dev/null +++ b/qpid/cpp/rubygen/0-10/exceptions.rb @@ -0,0 +1,73 @@ +#!/usr/bin/env ruby +# +# 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. +# +$: << ".." # Include .. in load path +require 'cppgen' + +class GenExceptions < CppGen + + def initialize(outdir, amqp) + super(outdir, amqp) + @ns="qpid::amqp_#{@amqp.version.bars}" + @dir="qpid/amqp_#{@amqp.version.bars}" + end + + def exceptions_for_enum(enum, base, ns, suffix="") + enum.choices.each { |c| + name=c.name.typename+suffix+"Exception" + genl + doxygen_comment { genl c.doc } + struct(name, "public #{base}") { + genl "#{name}(const std::string& msg=std::string())" + genl " : #{base}(#{ns}::#{c.name.shout}, msg) {}" + protected + genl "std::string getPrefix() const { return \"#{name}\"; }" + } + } + end + + def gen_exceptions() + h_file("#{@dir}/exceptions") { + include "qpid/amqp_0_10/Exception" + namespace("#{@ns}") { + error_code = @amqp.class_("execution").domain("error-code").enum + exceptions_for_enum(error_code, "SessionAbortedException", "execution") + genl + + detach_code = @amqp.class_("session").domain("detach-code").enum + exceptions_for_enum(detach_code, "SessionDetachedException", "session", "Detached") + + genl + exceptions_for_enum(detach_code, "SessionExpiredException", "session", "Expired") + genl + + close_code = @amqp.class_("connection").domain("close-code").enum + exceptions_for_enum(close_code, "ConnectionException", "connection") + } + } + end + + def generate() + gen_exceptions + end +end + +GenExceptions.new($outdir, $amqp).generate(); + + diff --git a/qpid/cpp/rubygen/0-10/handlers.rb b/qpid/cpp/rubygen/0-10/handlers.rb new file mode 100755 index 0000000000..981ea890e6 --- /dev/null +++ b/qpid/cpp/rubygen/0-10/handlers.rb @@ -0,0 +1,47 @@ +#!/usr/bin/env ruby +# +# 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. +# +$: << ".." # Include .. in load path +require 'cppgen' + +class GenHandlers < CppGen + def initialize(outdir, amqp) + super(outdir, amqp) + @ns="qpid::amqp_#{@amqp.version.bars}" + @dir="qpid/amqp_#{@amqp.version.bars}" + end + + def action_handler(type, actions) + genl + bases=actions.map { |a| "public #{a.fqclassname}::Handler" } + struct("#{type}Handler", *bases) { } + end + + def generate() + h_file("#{@dir}/handlers.h") { + include "#{@dir}/specification" + namespace("#{@ns}") { + action_handler "Command", @amqp.collect_all(AmqpCommand) + action_handler "Control", @amqp.collect_all(AmqpControl) + } + } + end +end + +GenHandlers.new($outdir, $amqp).generate() diff --git a/qpid/cpp/rubygen/0-10/specification.rb b/qpid/cpp/rubygen/0-10/specification.rb new file mode 100755 index 0000000000..ef193f5fd0 --- /dev/null +++ b/qpid/cpp/rubygen/0-10/specification.rb @@ -0,0 +1,389 @@ +#!/usr/bin/env ruby +# +# 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. +# +$: << ".." # Include .. in load path +require 'cppgen' + + +# Dummy element representing an unknown struct type. +class UnknownStruct + def visitable?() true end + def fqclassname() "UnknownStruct" end +end + +# Dummy element representing a session.header field +class SessionHeaderField + def amqp2cpp() "session::Header" end + def cppname() "sessionHeader" end + def name() "session-header" end +end + +class Specification < CppGen + def initialize(outdir, amqp) + super(outdir, amqp) + @ns="qpid::amqp_#{@amqp.version.bars}" + @dir="qpid/amqp_#{@amqp.version.bars}" + end + + # domains + + def domain_h(d) + genl + typename=d.name.typename + if d.enum + scope("enum #{typename} {", "};") { + genl d.enum.choices.map { |c| + "#{c.name.constname} = #{c.value}" }.join(",\n") + } + scope("inline SerializeAs<#{typename}, uint8_t> serializable(#{typename}& e) {") { + genl "return SerializeAs<#{typename}, uint8_t>(e);" + } + else + genl "typedef #{d.amqp2cpp} #{typename};" + end + end + + def visitable?(x) x.code and x.size=="4" end + + # Used by structs, commands and controls. + def action_struct_h(x, base, consts, &block) + genl + base = visitable?(x) ? ["public #{base}"] : [] + struct(x.classname, *base) { + x.fields.each { |f| genl "#{f.amqp2cpp} #{f.cppname};" } + genl + genl "static const char* NAME;" + consts.each { + |c| genl "static const uint8_t #{c.upcase}=#{(x.send c) or 0};" + } + genl "static const uint8_t CLASS_CODE=#{x.containing_class.nsname}::CODE;" + genl "static const char* CLASS_NAME;" + ctor_decl("explicit #{x.classname}", x.parameters(true)) + + if visitable? x + genl "void accept(Visitor&);" + genl "void accept(ConstVisitor&) const;" + end + + if (x.fields.empty?) + genl "template <class S> void serialize(S&) {}" + else + scope("template <class S> void serialize(S& s) {") { + gen "s"; x.fields.each { |f| gen "(#{f.cppname})"}; genl ";" + } + end + genl + yield if block + } + case x + when AmqpCommand then packer = "CommandPacker" + when AmqpControl then packer = "Packer" + when AmqpStruct then packer = "SizedPacker" + end + genl "inline #{packer}<#{x.classname}> serializable(#{x.classname}& x) { return #{packer}<#{x.classname}>(x); }" unless x.respond_to? :pack and x.pack == "0" + genl "std::ostream& operator << (std::ostream&, const #{x.classname}&);" + genl "bool operator==(const #{x.classname}&, const #{x.classname}&);" + end + + def action_struct_cpp(x, &block) + genl + genl "const char* #{x.classname}::NAME=\"#{x.fqname}\";" + genl "const char* #{x.classname}::CLASS_NAME=#{x.containing_class.nsname}::NAME;" + genl + ctor=x.classname+"::"+x.classname + ctor_defn(ctor, x.parameters, x.initializers) {} + + if visitable? x + genl "void #{x.classname}::accept(Visitor& v) { v.visit(*this); }" + genl "void #{x.classname}::accept(ConstVisitor& v) const { v.visit(*this); }" + end + genl + scope("std::ostream& operator << (std::ostream& o, const #{x.classname}&#{"x" unless x.fields.empty?}) {") { + genl "o << \"#{x.fqname}[\";"; + x.fields.each{ |f| genl "o << \" #{f.name}=\" << x.#{f.cppname};" } + genl "o << \"]\";" + genl "return o;" + } + yield if block + end + + # structs + + def struct_h(s) action_struct_h(s, "Struct", ["size","pack","code"]); end + def struct_cpp(s) action_struct_cpp(s) end + + # command and control + + def action_h(a) + action_struct_h(a, a.base, ["code"]) { + struct("Handler") { + scope("void #{a.funcname}(", ");") { + genl a.parameters.join(",\n") + } + } + function_defn("template <class T> void invoke", ["T& target"], "const") { + genl "target.#{a.funcname}(#{a.values.join(', ')} );" + } + } + end + + def action_cpp(a) + action_struct_cpp(a) { + scope("void #{a.classname}::Handler::#{a.funcname}(", ")") { + genl a.unused_parameters.join(",\n") + } + scope { + genl "assert(0);" + genl "throw NotImplementedException(QPID_MSG(\"#{a.fqname} not implemented.\"));" + } + } + end + + # Types that must be generated early because they are used by other types. + def pregenerate?(x) not @amqp.used_by[x.fqname].empty?; end + + def pregenerate_class?(c) + c.children.select{ |t| (t.is_a? AmqpStruct or t.is_a? AmqpDomain) and pregenerate? t} + end + + # Typedefs, enums and forward declarations for classes. + def gen_specification_fwd() + h_file("#{@dir}/specification_fwd") { + include "#{@dir}/built_in_types" + namespace(@ns) { + # Top level + @amqp.domains.each { |d| + # segment-type and track are are built in + domain_h d unless ["track","segment-type"].include?(d.name) + } + each_class_ns { |c| + genl "const uint8_t CODE=#{c.code};" # class code + genl "extern const char* NAME;" + c.each_descendant { |x| + case x + when AmqpDomain then domain_h x + when AmqpStruct then genl "class #{x.classname};" + when AmqpAction then genl "class #{x.classname};" + end + } + } + } + } + end + + # Generate struct definitions into a separate header file so the + # can be included by StructHolder.h without circularity. + def gen_structs() + h_file("#{@dir}/structs") { + include "#{@dir}/specification_fwd" + include "#{@dir}/Map.h" + include "#{@dir}/Array.h" + include "#{@dir}/Struct.h" + include "#{@dir}/UnknownStruct.h" + include "#{@dir}/Packer.h" + namespace(@ns) { + each_class_ns { |c| + c.collect_all(AmqpStruct).each { |s| struct_h s } + } + } + } + + cpp_file("#{@dir}/structs") { + include "#{@dir}/structs" + include "#{@dir}/StructHolder" + namespace(@ns) { + each_class_ns { |c| + c.collect_all(AmqpStruct).each { |s| struct_cpp(s) } + } + } + } + end + + # Generate the specification files + def gen_specification() + h_file("#{@dir}/specification") { + include "#{@dir}/specification_fwd.h" + include "#{@dir}/Map.h" + include "#{@dir}/Array.h" + include "#{@dir}/UnknownType.h" + include "#{@dir}/Struct32" + include "#{@dir}/Control.h" + include "#{@dir}/Command.h" + include "#{@dir}/Packer.h" + include "<iosfwd>" + namespace(@ns) { + each_class_ns { |c| + c.collect_all(AmqpAction).each { |a| action_h a } + } + }} + + cpp_file("#{@dir}/specification") { + include "#{@dir}/specification" + include "#{@dir}/exceptions" + include "<iostream>" + ["Command","Control", "Struct"].each { |x| include "#{@dir}/Apply#{x}" } + namespace(@ns) { + each_class_ns { |c| + genl "const char* NAME=\"#{c.fqname}\";" + c.actions.each { |a| action_cpp a} + } + } + } + end + + def gen_proxy() + h_file("#{@dir}/ProxyTemplate.h") { + include "#{@dir}/specification" + namespace(@ns) { + genl "template <class F, class R=typename F::result_type>" + cpp_extern_class("QPID_COMMON_CLASS_EXTERN", "ProxyTemplate") { + public + genl "ProxyTemplate(F f=F()) : functor(f) {}" + @amqp.classes.each { |c| + c.actions.each { |a| + genl + function_defn("R #{a.funcname}", a.parameters) { + var=a.name.funcname + args = a.arguments.empty? ? "" : "("+a.arguments.join(", ")+")" + genl("#{a.fqclassname} #{var}#{args};") + genl "return functor(#{var});" + } + } + } + private + genl "F functor;" + } + } + } + end + + def visitor_interface_h(base, subs, is_const) + name="#{is_const ? 'Const' : ''}#{base}Visitor" + const=is_const ? "const " : "" + struct(name) { + genl "virtual ~#{name}() {}" + genl "typedef #{const}#{base} BaseType;" + subs.each{ |s| + genl "virtual void visit(#{const}#{s.fqclassname}&) = 0;" + }} + end + + def visitor_impl(base, subs, is_const) + name="#{is_const ? 'Const' : ''}#{base}Visitor" + const=is_const ? "const " : "" + genl "template <class F>" + struct("ApplyVisitor<#{name}, F>", "public ApplyVisitorBase<#{name}, F>") { + subs.each{ |s| + genl "virtual void visit(#{const}#{s.fqclassname}& x) { this->invoke(x); }" + }} + end + + def gen_visitor(base, subs) + h_file("#{@dir}/#{base}Visitor.h") { + include base=="Struct" ? "#{@dir}/structs" : "#{@dir}/specification" + namespace("#{@ns}") { + visitor_interface_h(base, subs, false) + visitor_interface_h(base, subs, true) + }} + + h_file("#{@dir}/Apply#{base}.h") { + include "#{@dir}/#{base}Visitor.h" + include "#{@dir}/apply.h" + namespace("#{@ns}") { + visitor_impl(base, subs, false) + visitor_impl(base, subs, true) + } + } + end + + def gen_holder(base, subs) + name=base+"Holder" + h_file("#{@dir}/#{name}") { + include "#{@dir}/Apply#{base}" + include "#{@dir}/Holder" + include base=="Struct" ? "#{@dir}/structs" : "#{@dir}/specification" + namespace(@ns){ + namespace("#{base.downcase}_max") { + genl "static const size_t MAX000=0;" + last="MAX000" + subs.each { |s| + sizeof="sizeof(#{s.fqclassname})" + genl "static const size_t #{last.succ} = #{sizeof} > #{last} ? #{sizeof} : #{last};" + last.succ! + } + genl "static const int MAX=#{last};" + } + holder_base="amqp_0_10::Holder<#{name}, #{base}, #{base.downcase}_max::MAX>" + struct("#{name}", "public #{holder_base}") { + genl "#{name}() {}" + genl "template <class T> explicit #{name}(const T& t) : #{holder_base}(t) {}" + genl "using #{holder_base}::operator=;" + genl "void set(uint8_t classCode, uint8_t code);" + } + genl + genl "std::ostream& operator<<(std::ostream& o, const #{name}& h);" + } + } + + cpp_file("#{@dir}/#{name}") { + include "#{@dir}/#{name}" + include "#{@dir}/exceptions.h" + namespace(@ns) { + genl "using framing::in_place;" + genl + scope("void #{name}::set(uint8_t classCode, uint8_t code) {") { + genl "uint16_t key=(classCode<<8)+code;" + scope ("switch(key) {") { + subs.each { |s| + genl "case 0x#{s.full_code.to_s(16)}: *this=in_place<#{s.fqclassname}>(); break;" unless (s.is_a? UnknownStruct) + } + genl "default: " + indent { + if (base=="Struct") + genl "*this=in_place<UnknownStruct>(classCode, code);" + else + genl "throw CommandInvalidException(QPID_MSG(\"Invalid class-#{base.downcase} key \" << std::hex << key));" + end + } + } + } + genl + genl "std::ostream& operator<<(std::ostream& o, const #{name}& h) { return h.get() ? (o << *h.get()) : (o << \"<empty #{name}>\"); }" + } + } + end + + def gen_visitable(base, subs) + subs << UnknownStruct.new if base=="Struct" # Extra case for unknown structs. + gen_holder(base, subs) + gen_visitor(base, subs) + end + + def generate + gen_specification_fwd + gen_specification + gen_proxy + gen_structs + gen_visitable("Command", @amqp.collect_all(AmqpCommand)) + gen_visitable("Control", @amqp.collect_all(AmqpControl)) + gen_visitable("Struct", @amqp.collect_all(AmqpStruct).select { |s| s.code}) + end +end + +Specification.new($outdir, $amqp).generate(); diff --git a/qpid/cpp/rubygen/0-10/typecode.rb b/qpid/cpp/rubygen/0-10/typecode.rb new file mode 100755 index 0000000000..0ab9c4be5d --- /dev/null +++ b/qpid/cpp/rubygen/0-10/typecode.rb @@ -0,0 +1,117 @@ +#!/usr/bin/env ruby +# +# 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. +# +$: << ".." # Include .. in load path +require 'cppgen' + +class TypeCode < CppGen + def initialize(outdir, amqp) + super(outdir, amqp) + @ns="qpid::amqp_#{@amqp.version.bars}" + @dir="qpid/amqp_#{@amqp.version.bars}" + @types = @amqp.collect_all(AmqpType).select { |t| t.code } + + end + + def type_for_code_h() + h_file("#{@dir}/TypeForCode") { + include "#{@dir}/built_in_types.h" + include "#{@dir}/UnknownType.h" + namespace(@ns) { + genl + genl "template <uint8_t Code> struct TypeForCode;" + genl + @types.each { |t| + genl "template <> struct TypeForCode<#{t.code}> { typedef #{t.typename} type; };" + } + genl + genl "template <class V> typename V::result_type" + scope("apply_visitor(V& visitor, uint8_t code) {") { + scope("switch (code) {", "}") { + @types.each { |t| + genl "case #{t.code}: return visitor((#{t.typename}*)0);" + } + genl "default: return visitor((UnknownType*)0);" + } + } + genl + genl "std::string typeName(uint8_t code);" + } + } + end + + def type_for_code_cpp() + cpp_file("#{@dir}/TypeForCode") { + include "<string>" + include "<sstream>" + namespace(@ns) { + namespace("") { + struct("Names") { + scope("Names() {") { + scope("for (int i =0; i < 256; ++i) {") { + genl "std::ostringstream os;" + genl "os << \"UnknownType<\" << i << \">\";" + genl "names[i] = os.str();" + } + @types.each { |t| genl "names[#{t.code}] = \"#{t.name}\";" } + } + genl "std::string names[256];" + } + genl "Names names;" + } + genl "std::string typeName(uint8_t code) { return names.names[code]; }" + }} + end + + def code_for_type_h() + name="#{@dir}/CodeForType" + h_file(name) { + include "#{@dir}/built_in_types.h" + + namespace(@ns) { + genl + genl "template <class T> struct CodeForType;" + genl + @types.each { |t| + genl "template <> struct CodeForType<#{t.typename}> { static const uint8_t value; };" + } + genl + genl "template <class T> uint8_t codeFor(const T&) { return CodeForType<T>::value; }" + } + } + + cpp_file(name) { + include name + namespace(@ns) { + @types.each { |t| + genl "const uint8_t CodeForType<#{t.typename}>::value=#{t.code};" + } + } + } + end + + def generate + type_for_code_h + type_for_code_cpp + code_for_type_h + end +end + +TypeCode.new($outdir, $amqp).generate(); + diff --git a/qpid/cpp/rubygen/MethodBodyDefaultVisitor.rb b/qpid/cpp/rubygen/MethodBodyDefaultVisitor.rb new file mode 100755 index 0000000000..11dbcb8f83 --- /dev/null +++ b/qpid/cpp/rubygen/MethodBodyDefaultVisitor.rb @@ -0,0 +1,53 @@ +#!/usr/bin/env ruby +# +# 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. +# + +$: << ".." # Include .. in load path +require 'cppgen' + +class MethodBodyDefaultVisitorGen < CppGen + + def initialize(outdir, amqp) + super(outdir, amqp) + set_classname("qpid::framing::MethodBodyDefaultVisitor") + end + + def generate() + h_file(@filename) { + include "qpid/framing/MethodBodyConstVisitor" + namespace(@namespace) { + genl + cpp_extern_class("QPID_COMMON_CLASS_EXTERN", @classname, "public MethodBodyConstVisitor") { + genl "public:" + genl "virtual void defaultVisit() = 0;" + @amqp.methods_.each { |m| + genl "virtual void visit(const #{m.body_name}&);" } + }}} + + cpp_file(@filename) { + include(@filename) + namespace(@namespace) { + @amqp.methods_.each { |m| + genl "void #{@classname}::visit(const #{m.body_name}&) { defaultVisit(); }" + }}} + end +end + +MethodBodyDefaultVisitorGen.new($outdir, $amqp).generate(); + diff --git a/qpid/cpp/rubygen/README.txt b/qpid/cpp/rubygen/README.txt new file mode 100644 index 0000000000..a1fd6cfec8 --- /dev/null +++ b/qpid/cpp/rubygen/README.txt @@ -0,0 +1,17 @@ +RUBY CODE GENERATOR + +Run ./generate for usage. +Examples in samples/ + +For example: + ./generate . ../../specs/amqp.0-9.xml samples/Proxy.rb +will generate + + + + + + + + + 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 + diff --git a/qpid/cpp/rubygen/cppgen.rb b/qpid/cpp/rubygen/cppgen.rb new file mode 100755 index 0000000000..7dc21fe1bc --- /dev/null +++ b/qpid/cpp/rubygen/cppgen.rb @@ -0,0 +1,452 @@ +#!/usr/bin/ruby +# +# General purpose C++ code generation. +# +require 'amqpgen' +require 'set' + +Copyright=<<EOS +/* + * + * 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. + * + */ + +/// +/// This file was automatically generated from the AMQP specification. +/// Do not edit. +/// + + +EOS + +CppKeywords = Set.new(["and", "and_eq", "asm", "auto", "bitand", + "bitor", "bool", "break", "case", "catch", "char", + "class", "compl", "const", "const_cast", "continue", + "default", "delete", "do", "DomainInfo", "double", + "dynamic_cast", "else", "enum", "explicit", "extern", + "false", "float", "for", "friend", "goto", "if", + "inline", "int", "long", "mutable", "namespace", "new", + "not", "not_eq", "operator", "or", "or_eq", "private", + "protected", "public", "register", "reinterpret_cast", + "return", "short", "signed", "sizeof", "static", + "static_cast", "struct", "switch", "template", "this", + "throw", "true", "try", "typedef", "typeid", + "typename", "union", "unsigned", "using", "virtual", + "void", "volatile", "wchar_t", "while", "xor", + "xor_eq"]) +# Names that need a trailing "_" to avoid clashes. +CppMangle = CppKeywords+Set.new(["std::string"]) + +class String + def cppsafe() CppMangle.include?(self) ? self+"_" : self; end + + def amqp2cpp() + path=split(".") + name=path.pop + return name.typename if path.empty? + path.map! { |n| n.nsname } + return (path << name.caps.cppsafe).join("::") + end + + def typename() caps.cppsafe; end + def nsname() bars.cppsafe; end + def constname() shout.cppsafe; end + def funcname() lcaps.cppsafe; end + def varname() lcaps.cppsafe; end +end + +# preview: Hold information about a C++ type. +# +# new mapping does not use CppType, +# Each amqp type corresponds exactly by dotted name +# to a type, domain or struct, which in turns +# corresponds by name to a C++ type or typedef. +# (see String.amqp2cpp) +# +class CppType + def initialize(name) @name=@param=@ret=name; end + attr_reader :name, :param, :ret, :code + + def retref() @ret="#{name}&"; self; end + def retcref() @ret="const #{name}&"; self; end + def passcref() @param="const #{name}&"; self; end + def code(str) @code=str; self; end + def defval(str) @defval=str; self; end + def encoded() @code end + def ret_by_val() @name; end + + def encode(value, buffer) + @code ? "#{buffer}.put#{@code}(#{value});" : "#{value}.encode(#{buffer});" + end + + def decode(value,buffer) + if @code + if /&$/===param then + "#{buffer}.get#{@code}(#{value});" + else + "#{value} = #{buffer}.get#{@code}();" + end + else + "#{value}.decode(#{buffer});" + end + end + + def default_value() + return @defval ||= "#{name}()" + end + + def to_s() name; end; +end + +class AmqpRoot + # preview; map 0-10 types to preview code generator types + @@typemap = { + "bit"=> CppType.new("bool").code("Octet").defval("false"), + "boolean"=> CppType.new("bool").code("Octet").defval("false"), + "uint8"=>CppType.new("uint8_t").code("Octet").defval("0"), + "uint16"=>CppType.new("uint16_t").code("Short").defval("0"), + "uint32"=>CppType.new("uint32_t").code("Long").defval("0"), + "uint64"=>CppType.new("uint64_t").code("LongLong").defval("0"), + "datetime"=>CppType.new("uint64_t").code("LongLong").defval("0"), + "str8"=>CppType.new("std::string").passcref.retcref.code("ShortString"), + "str16"=>CppType.new("std::string").passcref.retcref.code("MediumString"), + "str32"=>CppType.new("std::string").passcref.retcref.code("LongString"), + "vbin8"=>CppType.new("std::string").passcref.retcref.code("ShortString"), + "vbin16"=>CppType.new("std::string").passcref.retcref.code("MediumString"), + "vbin32"=>CppType.new("std::string").passcref.retcref.code("LongString"), + "map"=>CppType.new("FieldTable").passcref.retcref, + "array"=>CppType.new("Array").passcref.retcref, + "sequence-no"=>CppType.new("SequenceNumber").passcref, + "sequence-set"=>CppType.new("SequenceSet").passcref.retcref, + "struct32"=>CppType.new("std::string").passcref.retcref.code("LongString"), + "uuid"=>CppType.new("Uuid").passcref.retcref, + "byte-ranges"=>CppType.new("ByteRanges").passcref.retcref + } + + # preview: map amqp types to preview cpp types. + def lookup_cpptype(t) t = @@typemap[t] and return t end +end + + +class AmqpElement + # convert my amqp type_ attribute to a C++ type. + def amqp2cpp() + return "ArrayDomain<#{array_type(name).amqp2cpp}> " if type_=="array" + return type_.amqp2cpp + end + + # Does this object have a type-like child named name? + def typechild(name) + child = domain(name) if respond_to? :domain + child = struct(name) if not child and respond_to? :struct + child = type_(name) if not child and respond_to? :type_ + child + end + + # dotted name to a type object + def dotted_typechild(name) + names=name.split('.') + context = self + while context and names.size > 1 + context = context.child_named(names.shift) + end + return context.typechild(names[0]) if context + end + + # preview mapping - type_ attribute to C++ type + def lookup_cpptype(name) + if t = root.lookup_cpptype(name) then return t + elsif c = containing_class.typechild(name) then return c.cpptype + elsif c= root.dotted_typechild(name) then return c.cpptype + else raise "Cannot resolve type-name #{name} from #{self}" + end + end + + def containing_class() + return self if is_a? AmqpClass + return parent && parent.containing_class + end +end + + +class AmqpField + def struct?() + c=containing_class + c.struct(type_) + end + def cpptype() lookup_cpptype(type_) or raise "no cpptype #{type_} for field #{self}" end + def cppname() name.lcaps.cppsafe; end + def bit?() type_ == "bit"; end + def signature() cpptype.param+" "+cppname; end + + def fqtypename() + unless type_.index(".") + c=containing_class + return c.domain(type_).fqtypename if c.domain(type_) + return c.struct(type_).fqclassname if c.struct(type_) + end + return amqp2cpp + end + def paramtype() + /^(int|uint|char|boolean|bit)/ === type_ ? fqtypename : "const #{fqtypename}&" + end + def param_default() "=#{fqtypename}()" end + + # Default value is normally the C++ default but over-ridden in specific cases + def default_value() + defval = cpptype.default_value; + if name == "accept-mode" and parent.name == "transfer" then defval = "1"; end + return defval + end +end + +class AmqpMethod + def cppname() name.lcaps.cppsafe; end + def param_names() fields.map { |f| f.cppname }; end + def signature() fields.map { |f| f.signature }; end + def classname() parent.name; end + def body_name() + classname().caps+name.caps+"Body" + end + def cpp_pack_type() root.lookup_cpptype("uint16") end +end + +module AmqpHasFields + def parameters(with_default=nil) + fields.map { |f| + "#{f.paramtype} #{f.cppname}_#{f.param_default if with_default}" + } + end + def unused_parameters() fields.map { |f| "#{f.paramtype} /*#{f.cppname}_*/"} end + def arguments() fields.map { |f| "#{f.cppname}_"} end + def values() fields.map { |f| "#{f.cppname}"} end + def initializers() fields.map { |f| "#{f.cppname}(#{f.cppname}_)"} end +end + +class AmqpAction + def classname() name.typename; end + def funcname() parent.name.funcname + name.caps; end + def fqclassname() parent.name+"::"+classname; end + def full_code() (containing_class.code.hex << 8)+code.hex; end + include AmqpHasFields +end + +class AmqpType + def cpptype() root.lookup_cpptype(name) end # preview + def typename() name.typename; end # new mapping + def fixed?() fixed_width; end +end + +class AmqpCommand < AmqpAction + def base() "Command"; end +end + +class AmqpControl < AmqpAction + def base() "Control"; end +end + +class AmqpClass + def cppname() name.caps; end # preview + def nsname() name.nsname; end +end + +class AmqpDomain + # preview + def cpptype() lookup_cpptype(type_) end + def cppname() name.caps; end + + # new mapping + def fqtypename() + return containing_class.nsname+"::"+name.typename if containing_class + name.typename + end +end + +class AmqpResult + # preview + def cpptype() + if type_ then lookup_cpptype(type_) + else CppType.new(parent.parent.name.caps+parent.name.caps+"Result").passcref + end + end +end + +class AmqpStruct + include AmqpHasFields + + @@pack_types={ "1"=>"uint8", "2"=>"uint16", "4"=>"uint32"} + def cpp_pack_type() # preview + root.lookup_cpptype(@@pack_types[pack]) + end + def cpptype() CppType.new(cppname).passcref.retcref end + #def cppname() containing_class.cppname+name.caps; end + def cppname() + if parent.kind_of? AmqpResult + parent.parent.parent.name.caps+parent.parent.name.caps+"Result" + else + name.caps + end + end + def fqclassname() containing_class.nsname+"::"+name.typename; end + def classname() name.typename; end + def full_code() (containing_class.code.hex << 8)+code.hex; end +end + +class CppGen < Generator + def initialize(outdir, *specs) + super(outdir,*specs) + # need to sort classes for dependencies + @actions=[] # Stack of end-scope actions + end + + # Write a header file. + def h_file(path, &block) + path = (/\.h$/ === path ? path : path+".h") + guard=path.upcase.tr('./-','_') + file(path) { + gen "#ifndef #{guard}\n" + gen "#define #{guard}\n" + gen Copyright + yield + gen "#endif /*!#{guard}*/\n" + } + end + + # Write a .cpp file. + def cpp_file(path, &block) + path = (/\.cpp$/ === path ? path : path+".cpp") + file(path) do + gen Copyright + yield + end + end + + def include(header) + header+=".h" unless /(\.h|[">])$/===header + header="\"#{header}\"" unless /(^<.*>$)|(^".*"$)/===header + genl "#include #{header}" + end + + def scope(open="{",close="}", &block) + genl open + indent &block + genl close + end + + def namespace(name, &block) + genl + names = name.split("::") + names.each { |n| genl "namespace #{n} {" } + genl "namespace {" if (names.empty?) + genl + yield + genl + genl('}'*([names.size, 1].max)+" // namespace "+name) + genl + end + + def struct_class(type, name, bases, &block) + gen "#{type} #{name}" + if (!bases.empty?) + genl ":" + indent { gen "#{bases.join(",\n")}" } + end + genl + scope("{","};", &block) + end + + def struct(name, *bases, &block) + struct_class("struct", name, bases, &block); + end + def cpp_class(name, *bases, &block) + struct_class("class", name, bases, &block); + end + def cpp_extern_class(scope, name, *bases, &block) + struct_class("class "+scope, name, bases, &block); + end + + def typedef(type, name) genl "typedef #{type} #{name};\n"; end + + def variant(types) "boost::variant<#{types.join(", ")}>"; end + def variantl(types) "boost::variant<#{types.join(", \n")}>"; end + def blank_variant(types) variant(["boost::blank"]+types); end + def tuple(types) "boost::tuple<#{types.join(', ')}>"; end + + def public() outdent { genl "public:" } end + def private() outdent { genl "private:" } end + def protected() outdent { genl "protected:" } end + + # Returns [namespace, classname, filename] + def parse_classname(full_cname) + names=full_cname.split("::") + return names[0..-2].join('::'), names[-1], names.join("/") + end + + def doxygen_comment(&block) + genl "/**" + prefix(" * ",&block) + genl " */" + end + + # Generate code in namespace for each class + def each_class_ns() + @amqp.classes.each { |c| namespace(c.nsname) { yield c } } + end + + def signature(ret_name, params, trailer="") + if params.size <= 1 + genl ret_name+"(#{params})"+trailer + else + scope(ret_name+"(",")"+trailer) { genl params.join(",\n") } + end + end + + def function_decl(ret_name, params=[], trailer="") + signature(ret_name, params, trailer+";") + end + + def function_defn(ret_name, params=[], trailer="") + genl + signature(ret_name, params, trailer) + scope() { yield } + end + + def ctor_decl(name, params=[]) function_decl(name, params); end + + def ctor_defn(name, params=[], inits=[]) + signature(name, params, inits.empty? ? "" : " :") + indent { gen inits.join(",\n") } if not inits.empty? + scope() { yield } + end + + def function_call(name, params=[], trailer="") + gen name + list "(",params, ")" + gen trailer + end +end + +# Fully-qualified class name +class FqClass < Struct.new(:fqname,:namespace,:name,:file) + def initialize(fqclass) + names=fqclass.split "::" + super(fqclass, names[0..-2].join('::'), names[-1], names.join("/")) + end +end + diff --git a/qpid/cpp/rubygen/framing.0-10/MethodBodyConstVisitor.rb b/qpid/cpp/rubygen/framing.0-10/MethodBodyConstVisitor.rb new file mode 100755 index 0000000000..d784e589df --- /dev/null +++ b/qpid/cpp/rubygen/framing.0-10/MethodBodyConstVisitor.rb @@ -0,0 +1,45 @@ +#!/usr/bin/env ruby +# +# 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. +# +$: << ".." # Include .. in load path +require 'cppgen' + +class MethodBodyConstVisitorGen < CppGen + + def initialize(outdir, amqp) + super(outdir, amqp) + @namespace="qpid::framing" + @classname="MethodBodyConstVisitor" + @filename="qpid/framing/MethodBodyConstVisitor" + end + + def generate() + h_file("#{@filename}") { + namespace(@namespace) { + @amqp.methods_.each { |m| genl "class #{m.body_name};" } + cpp_class("MethodBodyConstVisitor") { + genl "public:" + genl "virtual ~MethodBodyConstVisitor() {}" + @amqp.methods_.each { |m| genl "virtual void visit(const #{m.body_name}&) = 0;" } + }}} + end +end + +MethodBodyConstVisitorGen.new($outdir, $amqp).generate(); + diff --git a/qpid/cpp/rubygen/framing.0-10/MethodBodyDefaultVisitor.rb b/qpid/cpp/rubygen/framing.0-10/MethodBodyDefaultVisitor.rb new file mode 100755 index 0000000000..4c58ff2bbb --- /dev/null +++ b/qpid/cpp/rubygen/framing.0-10/MethodBodyDefaultVisitor.rb @@ -0,0 +1,54 @@ +#!/usr/bin/env ruby +# +# 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. +# +$: << ".." # Include .. in load path +require 'cppgen' + +class MethodBodyDefaultVisitorGen < CppGen + + def initialize(outdir, amqp) + super(outdir, amqp) + @namespace, @classname, @filename = parse_classname("qpid::framing::MethodBodyDefaultVisitor") + end + + def generate() + h_file(@filename) { + include "qpid/framing/MethodBodyConstVisitor" + include "qpid/CommonImportExport.h" + namespace(@namespace) { + genl "class AMQMethodBody;" + cpp_extern_class("QPID_COMMON_CLASS_EXTERN", @classname, "public MethodBodyConstVisitor") { + genl "public:" + genl "virtual void defaultVisit(const AMQMethodBody&) = 0;" + @amqp.methods_.each { |m| + genl "QPID_COMMON_EXTERN virtual void visit(const #{m.body_name}&);" } + }}} + + cpp_file(@filename) { + include(@filename) + include("qpid/framing/all_method_bodies.h") + namespace(@namespace) { + @amqp.methods_.each { |m| + genl "void #{@classname}::visit(const #{m.body_name}& b) { defaultVisit(b); }" + }}} + end +end + +MethodBodyDefaultVisitorGen.new($outdir, $amqp).generate(); + diff --git a/qpid/cpp/rubygen/framing.0-10/MethodBodyFactory.rb b/qpid/cpp/rubygen/framing.0-10/MethodBodyFactory.rb new file mode 100644 index 0000000000..28a5d94e32 --- /dev/null +++ b/qpid/cpp/rubygen/framing.0-10/MethodBodyFactory.rb @@ -0,0 +1,59 @@ +#!/usr/bin/env ruby +# +# 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. +# +$: << ".." # Include .. in load path +require 'cppgen' + +class MethodBodyFactoryGen < CppGen + + def initialize(outdir, amqp) + super(outdir, amqp) + @namespace="qpid::framing" + @classname="MethodBodyFactory" + @filename="qpid/framing/MethodBodyFactory" + end + + def generate() + cpp_file(@filename) { + include @filename + include "qpid/framing/BodyFactory" + @amqp.methods_.each { |m| include "qpid/framing/#{m.body_name}" } + include "qpid/Exception.h" + include "qpid/Msg.h" + genl + namespace(@namespace) { + scope("boost::intrusive_ptr<AMQMethodBody> #{@classname}::create(ClassId c, MethodId m) {") { + scope("switch (c) {") { + @amqp.classes.each { |c| + scope("case #{c.code}: switch(m) {") { + c.methods_.each { |m| + genl "case #{m.code}: return BodyFactory::create<#{m.body_name}>();" + } + genl "default: throw Exception(QPID_MSG(\"Invalid method id \" << int(m) << \" for class #{c.name} \"));" + } + genl "break;" + } + genl "default: throw Exception(QPID_MSG(\"Invalid class id \" << int(c)));" + } + } + }} + end +end + +MethodBodyFactoryGen.new($outdir, $amqp).generate(); diff --git a/qpid/cpp/rubygen/framing.0-10/Operations.rb b/qpid/cpp/rubygen/framing.0-10/Operations.rb new file mode 100755 index 0000000000..cd6a363c56 --- /dev/null +++ b/qpid/cpp/rubygen/framing.0-10/Operations.rb @@ -0,0 +1,121 @@ +#!/usr/bin/env ruby +# +# 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. +# +# Usage: output_directory xml_spec_file [xml_spec_file...] +# +$: << '..' +require 'cppgen' +require 'fileutils' +require 'etc' +require 'pathname' + +class OperationsGen < CppGen + + def initialize(chassis, outdir, amqp) + super(outdir, amqp) + @chassis=chassis + @classname="AMQP_#{@chassis.caps}Operations" + end + + def handler_method (m) + return_type = m.result ? m.result.cpptype.ret_by_val : "void" + gen "\nvirtual #{return_type} #{m.cppname}(" + gen m.signature.join(",\n") + gen ") = 0;\n" + end + + def handler_classname(c) c.name.caps+"Handler"; end + + def methods_on(parent, chassis) + chassis == "all" ? parent.methods_ : parent.methods_on(chassis) + end + + def handler_class(c) + m = methods_on(c,@chassis) + if (not m.empty?) + handlerclass=handler_classname c + gen <<EOS +// ==================== class #{handlerclass} ==================== +class #{handlerclass} { + // Constructors and destructors + public: + class Invoker; // Declared in #{@chassis.caps}Invoker + + #{handlerclass}(){}; + virtual ~#{handlerclass}() {} + // Protocol methods +EOS + m.each { |m| handler_method(m) if !m.content() } + gen <<EOS +}; // class #{handlerclass} + + +EOS + end + end + + def handler_get(c) + m = methods_on(c,@chassis) + if (not m.empty?) + handlerclass=handler_classname c + gen "virtual #{handlerclass}* get#{handlerclass}() = 0;\n" + end + end + + def generate() + h_file("qpid/framing/#{@classname}.h") { + gen <<EOS +#include <sstream> +#include "qpid/framing/ProtocolVersion.h" +#include "qpid/framing/amqp_structs.h" + +namespace qpid { +namespace framing { + +class AMQMethodBody; + +class #{@classname} { + public: + class Invoker; // Declared in #{@chassis.caps}Invoker + + virtual ~#{@classname}() {} + + virtual ProtocolVersion getVersion() const = 0; + + // Inner classes +EOS + indent { @amqp.classes.each { |c| handler_class(c) } } + gen <<EOS + + // Method handler get methods + +EOS + indent { @amqp.classes.each { |c| handler_get(c) } } + gen <<EOS +}; /* class #{@classname} */ +}} +EOS +} + end +end + +OperationsGen.new("client",$outdir, $amqp).generate() +OperationsGen.new("server",$outdir, $amqp).generate() +OperationsGen.new("all",$outdir, $amqp).generate() + diff --git a/qpid/cpp/rubygen/framing.0-10/OperationsInvoker.rb b/qpid/cpp/rubygen/framing.0-10/OperationsInvoker.rb new file mode 100755 index 0000000000..f9b5ce58d8 --- /dev/null +++ b/qpid/cpp/rubygen/framing.0-10/OperationsInvoker.rb @@ -0,0 +1,117 @@ +#!/usr/bin/env ruby +# +# 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. +# +# Usage: output_directory xml_spec_file [xml_spec_file...] +# +$: << '..' +require 'cppgen' + +class OperationsInvokerGen < CppGen + def initialize(chassis, outdir, amqp) + super(outdir, amqp) + @chassis=chassis + @ops="AMQP_#{@chassis.caps}Operations" + @classname="#{@ops}::Invoker" + @filename="qpid/framing/#{@chassis.caps}Invoker" + end + + def methods_on(parent, chassis) + chassis == "all" ? parent.methods_ : parent.methods_on(chassis) + end + + def handler(c) "#{@ops}::#{c.cppname}Handler"; end + def getter(c) "get#{c.cppname}Handler"; end + def invoker(c) "#{handler(c)}::Invoker"; end + def visit_methods(c) methods_on(c, @chassis).select { |m| !m.content } end + + + def handler_visits_cpp(c) + visit_methods(c).each { |m| + scope("void #{invoker(c)}::visit(const #{m.body_name}& body) {") { + if (m.result) + genl "this->encode(body.invoke(target), result.result);" + else + genl "body.invoke(target);" + end + genl "result.handled=true;" + } + } + end + + def ops_visits_cpp() + @amqp.classes.each { |c| + visit_methods(c).each { |m| + scope("void #{@classname}::visit(const #{m.body_name}& body) {") { + genl "#{handler(c)}::Invoker invoker(*target.#{getter(c)}());" + genl "body.accept(invoker);" + genl "result=invoker.getResult();" + } + } + } + end + + def invoker_h(invoker, target, methods) + return if methods.empty? + genl + cpp_extern_class("QPID_COMMON_CLASS_EXTERN", invoker, "public qpid::framing::Invoker") { + genl "#{target}& target;" + public + genl("Invoker(#{target}& target_) : target(target_) {}") + genl "using MethodBodyDefaultVisitor::visit;" + methods.each { |m| genl "QPID_COMMON_EXTERN void visit(const #{m.body_name}& body);" } + } + end + + def generate() + h_file(@filename) { + include "qpid/framing/#{@ops}" + include "qpid/framing/Invoker.h" + include "qpid/CommonImportExport.h" + namespace("qpid::framing") { + # AMQP_*Operations invoker. + methods=@amqp.classes.map { |c| visit_methods(c).to_a }.flatten + invoker_h(@classname, @ops, methods) + + # AMQP_*Operations::*Handler invokers. + @amqp.classes.each { |c| + invoker_h(invoker(c), handler(c), visit_methods(c)) + } + } + } + + cpp_file(@filename) { + include @filename + @amqp.classes.each { |c| + visit_methods(c).each { |m| + include "qpid/framing/#{m.body_name}" + }} + namespace("qpid::framing") { + ops_visits_cpp + @amqp.classes.each { |c| + next if visit_methods(c).empty? + handler_visits_cpp(c) + } + } + } + end +end + +OperationsInvokerGen.new("client",$outdir, $amqp).generate() +OperationsInvokerGen.new("server",$outdir, $amqp).generate() +OperationsInvokerGen.new("all",$outdir, $amqp).generate() diff --git a/qpid/cpp/rubygen/framing.0-10/Proxy.rb b/qpid/cpp/rubygen/framing.0-10/Proxy.rb new file mode 100755 index 0000000000..3325616754 --- /dev/null +++ b/qpid/cpp/rubygen/framing.0-10/Proxy.rb @@ -0,0 +1,109 @@ +#!/usr/bin/env ruby +# +# 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. +# +$: << ".." # Include .. in load path +require 'cppgen' + +class ProxyGen < CppGen + + def initialize(chassis, outdir, amqp) + super(outdir, amqp) + @chassis=chassis + @classname="AMQP_#{@chassis.caps}Proxy" + @filename="qpid/framing/#{@classname}" + end + + def methods_on(parent, chassis) + chassis == "all" ? parent.methods_ : parent.methods_on(chassis) + end + + def proxy_member(c) c.name.lcaps+"Proxy"; end + + def inner_class_decl(c) + cname=c.name.caps + cpp_extern_class("QPID_COMMON_CLASS_EXTERN", cname, "public Proxy") { + gen <<EOS +public: +#{cname}(FrameHandler& f) : Proxy(f) {} +static #{cname}& get(#{@classname}& proxy) { return proxy.get#{cname}(); } +EOS + methods_on(c, @chassis).each { |m| + genl "QPID_COMMON_EXTERN virtual void #{m.cppname}(#{m.signature.join(",\n ")});" + genl + }} + end + + def inner_class_defn(c) + cname=c.cppname + methods_on(c, @chassis).each { |m| + genl "void #{@classname}::#{cname}::#{m.cppname}(#{m.signature.join(", ")})" + scope { + params=(["getVersion()"]+m.param_names).join(", ") + genl "send(#{m.body_name}(#{params}));" + }} + end + + def generate + # .h file + h_file(@filename) { + include "qpid/framing/Proxy.h" + include "qpid/framing/Array.h" + include "qpid/framing/amqp_types.h" + include "qpid/framing/amqp_structs.h" + include "qpid/CommonImportExport.h" + + namespace("qpid::framing") { + cpp_extern_class("QPID_COMMON_CLASS_EXTERN", @classname, "public Proxy") { + public + genl "QPID_COMMON_EXTERN #{@classname}(FrameHandler& out);" + genl + @amqp.classes.each { |c| + inner_class_decl(c) + genl + genl "#{c.cppname}& get#{c.cppname}() { return #{proxy_member(c)}; }" + genl + } + private + @amqp.classes.each{ |c| gen c.cppname+" "+proxy_member(c)+";\n" } + }}} + + # .cpp file + cpp_file(@filename) { + include "<sstream>" + include "qpid/framing/#{@classname}.h" + include "qpid/framing/amqp_types_full.h" + methods_on(@amqp, @chassis).each { + |m| include "qpid/framing/"+m.body_name + } + genl + namespace("qpid::framing") { + genl "#{@classname}::#{@classname}(FrameHandler& f) :" + gen " Proxy(f)" + @amqp.classes.each { |c| gen ",\n "+proxy_member(c)+"(f)" } + genl "{}\n" + @amqp.classes.each { |c| inner_class_defn(c) } + }} + end +end + + +ProxyGen.new("client", $outdir, $amqp).generate; +ProxyGen.new("server", $outdir, $amqp).generate; +ProxyGen.new("all", $outdir, $amqp).generate; + diff --git a/qpid/cpp/rubygen/framing.0-10/Session.rb b/qpid/cpp/rubygen/framing.0-10/Session.rb new file mode 100755 index 0000000000..e800df9b2e --- /dev/null +++ b/qpid/cpp/rubygen/framing.0-10/Session.rb @@ -0,0 +1,417 @@ +#!/usr/bin/env ruby +# +# 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. +# +# Usage: output_directory xml_spec_file [xml_spec_file...] +# +$: << '..' +require 'cppgen' + +class CppGen + def session_methods(sync_default) + excludes = ["connection", "session", "file", "stream"] + gen_methods=@amqp.methods_on(@chassis).reject { |m| + excludes.include? m.parent.name or m.body_name.include?("010") + } + gen_methods.each { |m| m.set_sync_default(sync_default) } + end + + + # Generates a doxygen comment for AmqpMethod m. + def doxygen(m) + doxygen_comment { + genl m.doc + genl + m.fields_c.each { |f| + genl "@param #{f.cppname}" + genl f.doc if f.doc + genl + } + } + end +end + +# Sync vs. async APIs +module SyncAsync + def sync_prefix() @async ? "Async" : "" end + def sync_adjective() @async ? "asynchronous" : "synchronous" end + def sync_convert() @async ? "async" : "sync" end + + + def decl_ctor_opeq() + genl + genl "QPID_CLIENT_EXTERN #{@classname}();" + genl "QPID_CLIENT_INLINE_EXTERN #{@classname}(const #{@version_base}& other);" + genl "QPID_CLIENT_INLINE_EXTERN #{@classname}& operator=(const #{@version_base}& other);" + end + + def defn_ctor_opeq(inline="") + genl + genl "#{inline} #{@classname}::#{@classname}() {}" + scope("#{inline} #{@classname}::#{@classname}(const #{@version_base}& other) {") { + genl "*this = other;" + } + scope("#{inline} #{@classname}& #{@classname}::operator=(const #{@version_base}& other) {") { + genl "impl = static_cast<const #{@classname}&>(other).impl;" + genl "return *this;" + } + end + + def sync_default() !@async end +end + +class ContentField # For extra content parameters + def cppname() "content" end + def signature() "const Message& content" end + def sig_default() signature+"="+"Message(std::string())" end + def unpack() "p[arg::content|Message(std::string())]"; end + def doc() "Message content"; end +end + +class SyncField # For extra sync parameters + def initialize(default_value) @default_value=default_value ? "true" : "false" end + def cppname() "sync" end + def signature() "bool sync" end + def sig_default() signature+"="+@default_value end + def unpack() "p[arg::sync|#{@default_value}]"; end + def doc() "If true the broker will respond with completion status as soon as possible."; end +end + +class AmqpField + def unpack() "p[arg::#{cppname}|#{default_value}]"; end + def sig_default() signature+"="+default_value; end +end + +class AmqpMethod + def set_sync_default(sync_default) @sync_default=sync_default end + def fields_c() result = fields + (content ? [ContentField.new] : []) + [SyncField.new(@sync_default)] end + def param_names_c() fields_c.map { |f| f.cppname} end + def signature_c() fields_c.map { |f| f.signature }; end + def sig_c_default() fields_c.map { |f| f.sig_default }; end + def argpack_name() "#{parent.cppname}#{name.caps}Parameters"; end + def argpack_type() + "boost::parameter::parameters<" + + fields_c.map { |f| "arg::keyword_tags::"+f.cppname }.join(',') + + ">" + end + + def return_type(async) + if (async) + return "TypedResult<qpid::framing::#{result.cpptype.ret_by_val}>" if (result) + return "Completion" + else + return "qpid::framing::#{result.cpptype.ret_by_val}" if (result) + return "void" + end + end + + def session_function() "#{parent.name.lcaps}#{name.caps}"; end +end + +class SessionNoKeywordGen < CppGen + include SyncAsync + + def initialize(outdir, amqp, async) + super(outdir, amqp) + @async=async + @chassis="server" + @namespace,@classname,@file= + parse_classname "qpid::client::no_keyword::#{sync_prefix}Session_#{@amqp.version.bars}" + @version_base="SessionBase_#{@amqp.major}_#{@amqp.minor}" + end + + def generate() + public_api("#{@file}.h") + h_file(@file) { + include "qpid/client/#{@version_base}.h" + include "qpid/client/ClientImportExport.h" + namespace(@namespace) { + doxygen_comment { + genl "AMQP #{@amqp.version} #{sync_adjective} session API." + genl @amqp.class_("session").doc + # FIXME aconway 2008-05-23: additional doc on sync/async use. + } + cpp_class(@classname, "public #{@version_base}") { + public + decl_ctor_opeq() + session_methods(sync_default).each { |m| + genl + doxygen(m) + args=m.sig_c_default.join(", ") + genl "QPID_CLIENT_EXTERN #{m.return_type(@async)} #{m.session_function}(#{args});" + } + } + }} + + cpp_file(@file) { + include "qpid/client/#{@classname}" + include "qpid/framing/all_method_bodies.h" + include "qpid/client/SessionImpl.h" + include "qpid/client/MessageImpl.h" + include "qpid/client/PrivateImplRef.h" + include "qpid/client/CompletionImpl.h" + include "<boost/intrusive_ptr.hpp>" + namespace(@namespace) { + genl "using namespace framing;" + session_methods(sync_default).each { |m| + genl + sig=m.signature_c.join(", ") + func="#{@classname}::#{m.session_function}" + scope("#{m.return_type(@async)} #{func}(#{sig}) {") { + args=(["ProtocolVersion(#{@amqp.major},#{@amqp.minor})"]+m.param_names).join(", ") + genl "#{m.body_name} body(#{args});"; + genl "body.setSync(sync);" + sendargs="body" + sendargs << ", *MessageImpl::get(content)" if m.content + async_retval="#{m.return_type(true)}(new CompletionImpl(impl->send(#{sendargs}), impl))" + if @async then + genl "return #{async_retval};" + else + if m.result + genl "return #{async_retval}.get();" + else + genl "#{async_retval}.wait();" + end + end + }} + defn_ctor_opeq() + }} + end +end + +class SessionGen < CppGen + include SyncAsync + + def initialize(outdir, amqp, async) + super(outdir, amqp) + @async=async + @chassis="server" + session="#{sync_prefix}Session_#{@amqp.version.bars}" + @base="no_keyword::#{session}" + @fqclass=FqClass.new "qpid::client::#{session}" + @classname=@fqclass.name + @fqbase=FqClass.new("qpid::client::#{@base}") + @version_base="SessionBase_#{@amqp.major}_#{@amqp.minor}" + end + + def gen_keyword_decl(m) + return if m.fields_c.empty? # Inherited function will do. + scope("BOOST_PARAMETER_MEMFUN(#{m.return_type(@async)}, #{m.session_function}, 0, #{m.fields_c.size}, #{m.argpack_name}) {") { + scope("return #{@base}::#{m.session_function}(",");") { + gen m.fields_c.map { |f| f.unpack() }.join(",\n") + } + } + genl + end + + def generate() + keyword_methods=session_methods(sync_default).reject { |m| m.fields_c.empty? } + max_arity = keyword_methods.map{ |m| m.fields_c.size }.max + + public_api("qpid/client/arg.h") + h_file("qpid/client/arg.h") { + # Generate keyword tag declarations. + genl "#define BOOST_PARAMETER_MAX_ARITY #{max_arity}" + include "<boost/parameter.hpp>" + namespace("qpid::client::arg") { + keyword_methods.map{ |m| m.param_names_c }.flatten.uniq.each { |k| + genl "BOOST_PARAMETER_KEYWORD(keyword_tags, #{k})" + }} + } + public_api("#{@fqclass.file}.h") + h_file(@fqclass.file) { + include @fqbase.file + include "qpid/client/arg" + include "qpid/client/ClientImportExport" + namespace("qpid::client") { + # Doxygen comment. + doxygen_comment { + genl "AMQP #{@amqp.version} session API with keyword arguments." + genl <<EOS +This class provides the same set of functions as #{@base}, but also +allows parameters be passed using keywords. The keyword is the +parameter name in the namespace "arg". + +For example given the normal function "foo(int x=0, int y=0, int z=0)" +you could call it in either of the following ways: + +@code +session.foo(1,2,3); // Normal no keywords +session.foo(arg::z=3, arg::x=1); // Keywords and a default +@endcode + +The keyword functions are easy to use but their declarations are hard +to read. You may find it easier to read the documentation for #{@base} +which provides the same set of functions using normal non-keyword +declarations. + +\\ingroup clientapi + + +\\details + +<h2>Publishing Messages</h2> +<ul> +<li><p>messageTransfer()</p> +<pre>session.messageTransfer(arg::content=message, arg::destination="amq.topic");</pre></li> +<li><p>messageTransfer() — asynchronous</p> +<pre>#include <qpid/client/AsyncSession.h> + +for (int i=0; i<10; i++) { + message.setData(message_data.str()); + async(session).messageTransfer(arg::content=message, arg::destination="amq.direct"); +} + +session.sync(); +</pre> +</li> +</ul> + +<h2>Exchanges</h2> +<ul> +<li><p>exchangeBind()</p> +<pre>session.exchangeBind(arg::exchange="amq.topic", arg::queue=queue, arg::bindingKey=routing_key);</pre> +</li> +<li><p>exchangeUnbind()</p> +<pre>session.exchangeUnBind(arg::exchange="amq.topic", arg::queue=queue, arg::bindingKey=routing_key);</pre></li> +<li><p>exchangeBound()</p> +<pre>if (session.exchangeBound(arg::exchange="amq.topic", arg::queue=queue, arg::bindingKey=rk)){...}</pre> +<pre>if (session.exchangeBound(arg::exchange="amq.topic", arg::queue=queue)){...}</pre> +</li> +<li><p>exchangeDeclare()</p> +<pre>session.exchangeDeclare(arg::exchange="my.topic", arg::type="topic");</pre> +<pre>session.exchangeDeclare(arg::exchange="xml", arg::type="xml");</pre> +</li> +<li><p>exchangeDelete()</p> +<pre>session.exchangeDeclare(arg::exchange="my.topic");</pre> +<pre>session.exchangeDeclare(arg::exchange="xml", arg::ifUnused=true);</pre> +</li> +<li><p>exchangeQuery()</p> +<pre>ExchangeQueryResult eqr = session.exchangeQuery(arg::exchange="my.topic");</pre></li> +</ul> + + +<h2>Configuring exchanges in session.exchangeDeclare</h2> + +<pre>arg::durable=true</pre> +<p>Default: false.</p> +<p>If durable=true, an exchange remains active even if the server is restarted. If durable=false, an exchange is purged when a server restarts.</p> + +<pre>arg::autoDelete=true</pre> +<p>Default: false.</p> +<p>If autoDelete=true, deleting the last binding for an exchange also deletes the exchange.</p> + +<pre>arg::alternatExchange="my.exchange"</pre> +<p>Default: none.</p> +<p>If an alternate exchange is specified, messages that can not be delivered to any queue are sent to the alternate exchange.</p> + +<h2>Queues</h2> +<ul> +<li><p>queueDeclare()</p> +<pre>session.queueDeclare(arg::queue="message_queue");</pre> +</li> +<li><p>queueDelete()</p> +<pre>session.queueDelete(arg::queue="message_queue");</pre></li> +<li><p>queuePurge()</p> +<pre>session.queuePurge(arg::queue="message_queue");</pre></li> +<li><p>queueQuery()</p> +<pre>QueueQueryResult qqr = session.queueQuery(arg::queue="message_queue");</pre></li> +</ul> + + +<h2>Configuring queues with session.queueDeclare</h2> +<pre>arg::durable=true</pre> +<p>Default: false.</p> +<p>If durable=true, a queue remains active if the server is restarted. If durable=false, a queue and its contents are lost when a server restarts.</p> +<br/> + +<pre>arg::autoDelete=true</pre> +<p>Default: false.</p> +<p>If autoDelete=true, the queue is deleted when the last active Subscription to the Queue is canceled.</p> +<br/> + +<pre>arg::exclusive=true</pre> +<p>Default: false.</p> +<p>If exclusive=true, only the Session that created a queue can access it.</p> +<br/> + +<pre>arg::alternateExchange="my.exchange"</pre> +<p>Default: none. </p> +<p>If an alternate exchange is specified, messages are routed to it if (1) they are rejected by a client, or (2) they remain on the queue when it is deleted.</p> +<br/> + + +<h2>Accepting, Acquiring, Rejecting, or Releasing Messages</h2> +<ul> +<li><p>messageAccept() — acknowledges messages</p> +<pre>SequenceSet tobeAccepted; +toAccepted.add(msg.getId()); +session.messageAccept(toBeAccepted);</pre> +</li> +<li><p>messageAcquire()</p> +<pre>SequenceSet tobeAcquired; +toBeAcquired.add(msg.getId()); +session.messageAcquire(toBeAcquired);</pre> +</li> +<li><p>messageReject()</p> +<pre>SequenceSet tobeRejected; +toRejected.add(msg.getId()); +session.messageReject(toBeRejected);</pre> +</li> +<li><p>messageRelease()</p> +<pre>SequenceSet tobeReleased; +toReleased.add(msg.getId()); +session.messageRelease(toBeReleased);</pre></li> +</ul> + +<h2>Transactions</h2> +<ul> +<li><p>txSelect()</p> +<pre>session.txSelect();</pre> +</li> +<li><p>txCommit()</p> +<pre>session.txSelect();</pre></li> +<li><p>txRollback()</p> +<pre>session.txRollback();</pre></li> +</ul> + + +EOS + } + # Session class. + cpp_class(@classname,"public #{@base}") { + public + decl_ctor_opeq() + private + keyword_methods.each { |m| typedef m.argpack_type, m.argpack_name } + genl "friend class Connection;" + public + keyword_methods.each { |m| gen_keyword_decl(m) } + } + genl "/** Conversion to #{@classname} from another session type */" + genl "inline #{@classname} #{sync_convert}(const #{@version_base}& other) { return #{@clasname}(other); }" + defn_ctor_opeq("inline") + }} + end +end + +SessionNoKeywordGen.new($outdir, $amqp, true).generate() +SessionNoKeywordGen.new($outdir, $amqp, false).generate() +SessionGen.new($outdir, $amqp, true).generate() +SessionGen.new($outdir, $amqp, false).generate() + diff --git a/qpid/cpp/rubygen/framing.0-10/all_method_bodies.rb b/qpid/cpp/rubygen/framing.0-10/all_method_bodies.rb new file mode 100755 index 0000000000..4c7fccfff5 --- /dev/null +++ b/qpid/cpp/rubygen/framing.0-10/all_method_bodies.rb @@ -0,0 +1,39 @@ +#!/usr/bin/env ruby +# +# 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. +# +$: << ".." # Include .. in load path +require 'cppgen' + +class AllMethodBodiesGen < CppGen + + def initialize(outdir, amqp) + super(outdir, amqp) + @namespace="qpid::framing" + @filename="qpid/framing/all_method_bodies" + end + + def generate() + h_file(@filename) { + @amqp.methods_.each { |m| include "qpid/framing/"+m.body_name } + } + end +end + +AllMethodBodiesGen.new($outdir, $amqp).generate(); + diff --git a/qpid/cpp/rubygen/framing.0-10/constants.rb b/qpid/cpp/rubygen/framing.0-10/constants.rb new file mode 100755 index 0000000000..85bfb96ac0 --- /dev/null +++ b/qpid/cpp/rubygen/framing.0-10/constants.rb @@ -0,0 +1,208 @@ +#!/usr/bin/env ruby +# +# 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. +# +$: << ".." # Include .. in load path +require 'cppgen' + +class ConstantsGen < CppGen + + def initialize(outdir, amqp) + super(outdir, amqp) + @namespace="qpid::framing" + @dir="qpid/framing" + end + + def constants_h() + public_api("#{@dir}/constants.h") + h_file("#{@dir}/constants.h") { + namespace(@namespace) { + # Constants for class/method names. + scope("enum AmqpConstant {","};") { + l=[] + l.concat @amqp.constants.map { |c| "#{c.name.shout}=#{c.value}" } + @amqp.classes.each { |c| + l << "#{c.name.shout}_CLASS_ID=#{c.code}" + l.concat c.methods_.map { |m| + "#{c.name.shout}_#{m.name.shout}_METHOD_ID=#{m.code}" } + } + genl l.join(",\n") + } + } + } + end + + def typecode_enum(t) "TYPE_CODE_#{t.name.shout}" end + + def typecode_h_cpp + path="#{@dir}/TypeCode" + public_api(path+".h") + h_file(path) { + include("<iosfwd>") + include("\"qpid/sys/IntegerTypes.h\"") + namespace(@namespace) { + scope("enum TypeCode {", "};") { + genl @amqp.types.map { |t| "#{typecode_enum t} = #{t.code}" if t.code }.compact.join(",\n") + } + genl <<EOS + +/** True if t is a valid TypeCode value */ +bool isTypeCode(uint8_t t); + +/** Throw exception if not a valid TypeCode */ +TypeCode typeCode(uint8_t); + +/**@return 0 if t is not a valid enum TypeCode value. */ +const char* typeName(TypeCode t); + +std::ostream& operator<<(std::ostream&, TypeCode); +EOS + } + } + + cpp_file(path) { + include(path); + include("qpid/Exception.h") + include("qpid/Msg.h") + include("<ostream>") + namespace(@namespace) { + scope("const char* typeName(TypeCode t) {") { + scope("switch (t) {") { + @amqp.types.each { |t| genl "case #{typecode_enum t}: return \"#{t.name}\";" if t.code } + genl "default: break;" + } + genl "return 0;"; + } + genl <<EOS + +bool isTypeCode(uint8_t t) { return typeName(TypeCode(t)); } + +TypeCode typeCode(uint8_t t) { + if (!isTypeCode(t)) throw Exception(QPID_MSG("Invalid TypeCode " << t)); + return TypeCode(t); +} + +std::ostream& operator<<(std::ostream& o, TypeCode t) { + if (isTypeCode(t)) return o << typeName(t); + else return o << "Invalid TypeCode " << t; +} +EOS + } + } + end + + def enum_h() + public_api("#{@dir}/enum.h") + h_file("#{@dir}/enum.h") { + # Constants for enum domains. + namespace(@namespace) { + @amqp.domains.each { |d| declare_enum(d.enum) if d.enum } + @amqp.classes.each { |c| + enums=c.collect_all(AmqpEnum) + if !enums.empty? then + namespace(c.nsname) { enums.each { |e| declare_enum(e) } } + end + } + } + } + end + + def declare_enum(enum) + # Generated like this: enum containing_class::Foo { FOO_X, FOO_Y; } + name="#{enum.parent.name.caps}" + prefix=enum.parent.name.shout+"_" + scope("enum #{name} {","};") { + genl enum.choices.collect { |c| "#{prefix}#{c.name.shout}=#{c.value}" }.join(",\n") + } + end + + def declare_exception(c, base, package, enum) + name=c.name.caps+"Exception" + value="#{package}::#{enum.parent.name.shout}_#{c.name.shout}" + genl + doxygen_comment { genl c.doc } + struct(c.name.caps+"Exception", base) { + genl "std::string getPrefix() const { return \"#{c.name}\"; }" + genl "#{c.name.caps}Exception(const std::string& msg=std::string()) : #{base}(#{value}, \"\"+msg) {}" + } + end + + def declare_exceptions(class_name, domain_name, base) + enum = @amqp.class_(class_name).domain(domain_name).enum + enum.choices.each { |c| declare_exception(c, base, class_name, enum) unless c.name == "normal" } + genl + genl "QPID_COMMON_EXTERN sys::ExceptionHolder create#{base}(int code, const std::string& text);" + end + + def create_exception(class_name, domain_name, base, invalid) + scope("sys::ExceptionHolder create#{base}(int code, const std::string& text) {") { + genl "sys::ExceptionHolder holder;" + scope("switch (code) {") { + enum = @amqp.class_(class_name).domain(domain_name).enum + enum.choices.each { |c| + assign = "holder = new #{c.name.caps}Exception(text); " unless c.name == "normal" + genl "case #{c.value}: #{assign}break;" + } + genl "default: holder = new #{invalid}(QPID_MSG(\"Bad #{enum.parent.name}: \" << code << \": \" << text));" + } + genl "return holder;" + } + end + + def reply_exceptions_h() + public_api("#{@dir}/reply_exceptions.h") + h_file("#{@dir}/reply_exceptions.h") { + include "qpid/Exception" + include "qpid/sys/ExceptionHolder" + include "qpid/framing/enum" + include "qpid/CommonImportExport.h" + namespace(@namespace) { + declare_exceptions("execution", "error-code", "SessionException") + declare_exceptions("connection", "close-code", "ConnectionException") + declare_exceptions("session", "detach-code", "ChannelException") + } + } + end + + def reply_exceptions_cpp() + cpp_file("#{@dir}/reply_exceptions") { + include "#{@dir}/reply_exceptions" + include "qpid/Msg.h" + include "<sstream>" + include "<assert.h>" + namespace("qpid::framing") { + create_exception("execution", "error-code", "SessionException", "InvalidArgumentException") + # FIXME aconway 2008-10-07: there are no good exception codes in 0-10 for an invalid code. + # The following choices are arbitrary. + create_exception("connection", "close-code", "ConnectionException", "FramingErrorException") + create_exception("session", "detach-code", "ChannelException", "NotAttachedException") + } + } + end + + def generate() + constants_h + enum_h + reply_exceptions_h + reply_exceptions_cpp + typecode_h_cpp + end +end + +ConstantsGen.new($outdir, $amqp).generate(); + diff --git a/qpid/cpp/rubygen/framing.0-10/frame_body_lists.rb b/qpid/cpp/rubygen/framing.0-10/frame_body_lists.rb new file mode 100644 index 0000000000..4f1b976032 --- /dev/null +++ b/qpid/cpp/rubygen/framing.0-10/frame_body_lists.rb @@ -0,0 +1,49 @@ +# +# 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. +# +$: << ".." # Include .. in load path +require 'cppgen' + +class FrameBodyListsGen < CppGen + + def initialize(outdir, amqp) + super(outdir, amqp); + end + + def generate + h_file("qpid/framing/frame_body_lists.h") { + gen <<EOS +/**@file + * Macro lists of frame body classes, used to generate Visitors + */ +EOS + gen "#define METHOD_BODIES() " + @amqp.methods_.each { |m| gen "\\\n (#{m.body_name}) " } + gen <<EOS + + +#define OTHER_BODIES() (AMQContentBody)(AMQHeaderBody)(AMQHeartbeatBody)) + +EOS + } + end +end + +FrameBodyListsGen.new($outdir, $amqp).generate; + + diff --git a/qpid/cpp/rubygen/framing.0-10/structs.rb b/qpid/cpp/rubygen/framing.0-10/structs.rb new file mode 100755 index 0000000000..62b33ce773 --- /dev/null +++ b/qpid/cpp/rubygen/framing.0-10/structs.rb @@ -0,0 +1,615 @@ +#!/usr/bin/env ruby +# +# 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. +# +# Usage: output_directory xml_spec_file [xml_spec_file...] +# +$: << '..' +require 'cppgen' + +class StructGen < CppGen + + def initialize(outdir, amqp) + super(outdir, amqp) + end + + SizeMap={ + "Octet"=>1, + "Short"=>2, + "Long"=>4, + "LongLong"=>8, + "int8"=>1, + "int16"=>2, + "int32"=>4, + "int64"=>8, + "uint8"=>1, + "uint16"=>2, + "uint32"=>4, + "uint64"=>8, + "timestamp"=>8 + } + + StringSizeMap={ + "LongString"=>4, + "MediumString"=>2, + "ShortString"=>1 + } + + SizeType={ + 1=>"Octet", + 2=>"Short", + 4=>"Long", + 8=>"LongLong" + } + + ValueTypes=["uint8_t", "uint16_t", "uint32_t", "uint64_t"] + + def is_packed(s) s.pack and s.pack != "0" end + + def execution_header?(s) + s.is_a? AmqpMethod and not s.parent.control? + # s.kind_of? AmqpMethod and s.parent.name.include?("010") and not s.parent.control? + end + + def has_bitfields_only(s) + s.fields.select {|f| f.type_ != "bit"}.empty? + end + + def default_initialisation(s) + params = s.fields.select {|f| ValueTypes.include?(f.cpptype.name) || (!is_packed(s) && f.type_ == "bit")} + strings = params.collect {|f| "#{f.cppname}(#{f.default_value})"} + strings << "flags(0)" if (is_packed(s)) + if strings.empty? + return "" + else + return " : " + strings.join(", ") + end + end + + def printable_form(f) + if (f.cpptype.name == "uint8_t") + return "(int) " + f.cppname + elsif (f.type_ == "bit") + return "get#{f.name.caps}()" + else + return f.cppname + end + end + + def flag_mask(s, i) + pos = s.pack.to_i*8 - 8 - (i/8)*8 + (i % 8) + return "(1 << #{pos})" + end + + def encode_packed_struct(s) + genl s.cpp_pack_type.encode('flags', 'buffer') + process_packed_fields(s) { |f, i| encode_packed_field(s, f, i) unless f.type_ == "bit" } + end + + def decode_packed_struct(s) + genl "#{s.cpp_pack_type.decode('flags', 'buffer')}" + process_packed_fields(s) { |f, i| decode_packed_field(s, f, i) unless f.type_ == "bit" } + end + + def size_packed_struct(s) + genl "total += #{s.pack};" + process_packed_fields(s) { |f, i| size_packed_field(s, f, i) unless f.type_ == "bit" } + end + + def print_packed_struct(s) + process_packed_fields(s) { |f, i| print_packed_field(s, f, i) } + end + + def encode_packed_field(s, f, i) + genl "if (flags & #{flag_mask(s, i)})" + indent { genl f.cpptype.encode(f.cppname,"buffer") } + end + + def decode_packed_field(s, f, i) + genl "if (flags & #{flag_mask(s, i)})" + indent { genl f.cpptype.decode(f.cppname,"buffer") } + end + + def size_packed_field(s, f, i) + genl "if (flags & #{flag_mask(s, i)})" + indent { generate_size(f, []) } + end + + def print_packed_field(s, f, i) + classname = s.cppname + if (s.kind_of? AmqpMethod) + classname = s.body_name + end + genl "if (flags & #{flag_mask(s, i)})" + indent { + unless (classname == "ConnectionStartOkBody" && f.name == "response") + genl "out << \"#{f.name}=\" << #{printable_form(f)} << \"; \";" + else + genl "out << \"response=\" << \"xxxxxx\" << \"; \";" + end + } + end + + def generate_encode(f, combined) + if (f.type_ == "bit") + genl "uint8_t #{f.cppname}_bits = #{f.cppname};" + count = 0 + combined.each { |c| genl "#{f.cppname}_bits |= #{c.cppname} << #{count += 1};" } + genl "buffer.putOctet(#{f.cppname}_bits);" + else + genl f.cpptype.encode(f.cppname,"buffer") + end + end + + def generate_decode(f, combined) + if (f.type_ == "bit") + genl "uint8_t #{f.cppname}_bits = buffer.getOctet();" + genl "#{f.cppname} = 1 & #{f.cppname}_bits;" + count = 0 + combined.each { |c| genl "#{c.cppname} = (1 << #{count += 1}) & #{f.cppname}_bits;" } + else + genl f.cpptype.decode(f.cppname,"buffer") + end + end + + def generate_size(f, combined) + if (f.type_ == "bit") + names = ([f] + combined).collect {|g| g.cppname} + genl "total += 1;//#{names.join(", ")}" + else + size = SizeMap[f.cpptype.encoded] + if (size) + genl "total += #{size};//#{f.cppname}" + elsif (f.cpptype.name == "SequenceNumberSet") + genl "total += #{f.cppname}.encodedSize();" + elsif (size = StringSizeMap[f.cpptype.encoded]) + genl "total += #{size} + #{f.cppname}.size();" + else + genl "total += #{f.cppname}.encodedSize();" + end + end + end + + def process_packed_fields(s) + s.fields.each { |f| yield f, s.fields.index(f) } + end + + def process_fields(s) + last = nil + count = 0 + bits = [] + s.fields.each { + |f| if (last and last.bit? and f.bit? and count < 7) + count += 1 + bits << f + else + if (last and last.bit?) + yield last, bits + count = 0 + bits = [] + end + if (not f.bit?) + yield f + end + last = f + end + } + if (last and last.bit?) + yield last, bits + end + end + + def all_fields_via_accessors(s) + s.fields.collect { |f| "get#{f.name.caps}()" }.join(", ") + end + + def methodbody_extra_defs(s) + if (s.parent.control?) + genl "virtual uint8_t type() const { return 0;/*control segment*/ }" + end + + + gen <<EOS + typedef #{s.result ? s.result.cpptype.name : 'void'} ResultType; + + template <class T> ResultType invoke(T& invocable) const { + return invocable.#{s.cppname}(#{all_fields_via_accessors(s)}); + } + + using AMQMethodBody::accept; + void accept(MethodBodyConstVisitor& v) const { v.visit(*this); } + boost::intrusive_ptr<AMQBody> clone() const { return BodyFactory::copy(*this); } + + ClassId amqpClassId() const { return CLASS_ID; } + MethodId amqpMethodId() const { return METHOD_ID; } + bool isContentBearing() const { return #{s.content ? "true" : "false" }; } + bool resultExpected() const { return #{s.result ? "true" : "false"}; } + bool responseExpected() const { return #{s.responses().empty? ? "false" : "true"}; } +EOS + end + + def define_constructor(name, s) + if (s.fields.size > 0) + genl "#{name}(" + if (s.kind_of? AmqpMethod) + indent {gen "ProtocolVersion, "} + end + indent { gen s.fields.collect { |f| "#{f.cpptype.param} _#{f.cppname}" }.join(",\n") } + genl ") : " + if (is_packed(s)) + initialisers = s.fields.select { |f| f.type_ != "bit"}.collect { |f| "#{f.cppname}(_#{f.cppname})"} + + initialisers << "flags(0)" + indent { gen initialisers.join(",\n") } + genl "{" + indent { + process_packed_fields(s) { |f, i| genl "set#{f.name.caps}(_#{f.cppname});" if f.type_ == "bit"} + process_packed_fields(s) { |f, i| genl "flags |= #{flag_mask(s, i)};" unless f.type_ == "bit"} + } + genl "}" + else + indent { gen s.fields.collect { |f| " #{f.cppname}(_#{f.cppname})" }.join(",\n") } + genl "{}" + end + end + #default constructors: + if (s.kind_of? AmqpMethod) + genl "#{name}(ProtocolVersion=ProtocolVersion()) #{default_initialisation(s)} {}" + end + if (s.kind_of? AmqpStruct) + genl "#{name}() #{default_initialisation(s)} {}" + end + end + + def define_packed_field_accessors(s, f, i) + if (s.kind_of? AmqpMethod) + define_packed_field_accessors_for_method(s, f, i) + else + define_packed_field_accessors_for_struct(s, f, i) + end + end + + def define_packed_field_accessors_for_struct(s, f, i) + if (f.type_ == "bit") + genl "void #{s.cppname}::set#{f.name.caps}(#{f.cpptype.param} _#{f.cppname}) {" + indent { + genl "if (_#{f.cppname}) flags |= #{flag_mask(s, i)};" + genl "else flags &= ~#{flag_mask(s, i)};" + } + genl "}" + genl "#{f.cpptype.ret} #{s.cppname}::get#{f.name.caps}() const { return flags & #{flag_mask(s, i)}; }" + else + genl "void #{s.cppname}::set#{f.name.caps}(#{f.cpptype.param} _#{f.cppname}) {" + indent { + genl "#{f.cppname} = _#{f.cppname};" + genl "flags |= #{flag_mask(s, i)};" + } + genl "}" + genl "#{f.cpptype.ret} #{s.cppname}::get#{f.name.caps}() const { return #{f.cppname}; }" + if (f.cpptype.name == "FieldTable") + genl "#{f.cpptype.name}& #{s.cppname}::get#{f.name.caps}() {" + indent { + genl "flags |= #{flag_mask(s, i)};"#treat the field table as having been 'set' + genl "return #{f.cppname};" + } + genl "}" + end + genl "bool #{s.cppname}::has#{f.name.caps}() const { return flags & #{flag_mask(s, i)}; }" + genl "void #{s.cppname}::clear#{f.name.caps}Flag() { flags &= ~#{flag_mask(s, i)}; }" + end + genl "" + end + + def define_packed_field_accessors_for_method(s, f, i) + if (f.type_ == "bit") + genl "void #{s.body_name}::set#{f.name.caps}(#{f.cpptype.param} _#{f.cppname}) {" + indent { + genl "if (_#{f.cppname}) flags |= #{flag_mask(s, i)};" + genl "else flags &= ~#{flag_mask(s, i)};" + } + genl "}" + genl "#{f.cpptype.ret} #{s.body_name}::get#{f.name.caps}() const { return flags & #{flag_mask(s, i)}; }" + else + genl "void #{s.body_name}::set#{f.name.caps}(#{f.cpptype.param} _#{f.cppname}) {" + indent { + genl "#{f.cppname} = _#{f.cppname};" + genl "flags |= #{flag_mask(s, i)};" + } + genl "}" + genl "#{f.cpptype.ret} #{s.body_name}::get#{f.name.caps}() const { return #{f.cppname}; }" + if (f.cpptype.name == "FieldTable") + genl "#{f.cpptype.name}& #{s.body_name}::get#{f.name.caps}() {" + indent { + genl "flags |= #{flag_mask(s, i)};"#treat the field table as having been 'set' + genl "return #{f.cppname};" + } + genl "}" + end + genl "bool #{s.body_name}::has#{f.name.caps}() const { return flags & #{flag_mask(s, i)}; }" + genl "void #{s.body_name}::clear#{f.name.caps}Flag() { flags &= ~#{flag_mask(s, i)}; }" + end + genl "" + end + + def define_packed_accessors(s) + process_packed_fields(s) { |f, i| define_packed_field_accessors(s, f, i) } + end + + def declare_packed_accessors(f) + genl "QPID_COMMON_EXTERN void set#{f.name.caps}(#{f.cpptype.param} _#{f.cppname});"; + genl "QPID_COMMON_EXTERN #{f.cpptype.ret} get#{f.name.caps}() const;" + if (f.cpptype.name == "FieldTable") + genl "QPID_COMMON_EXTERN #{f.cpptype.name}& get#{f.name.caps}();" + end + if (f.type_ != "bit") + #extra 'accessors' for packed fields: + genl "QPID_COMMON_EXTERN bool has#{f.name.caps}() const;" + genl "QPID_COMMON_EXTERN void clear#{f.name.caps}Flag();" + end + end + + def define_accessors(f) + genl "void set#{f.name.caps}(#{f.cpptype.param} _#{f.cppname}) { #{f.cppname} = _#{f.cppname}; }" + genl "#{f.cpptype.ret} get#{f.name.caps}() const { return #{f.cppname}; }" + if (f.cpptype.name == "FieldTable") + genl "#{f.cpptype.name}& get#{f.name.caps}() { return #{f.cppname}; }" + end + end + + def define_struct(s) + classname = s.cppname + inheritance = "" + if (s.kind_of? AmqpMethod) + classname = s.body_name + if (execution_header?(s)) + inheritance = ": public ModelMethod" + else + inheritance = ": public AMQMethodBody" + end + else + public_api("qpid/framing/#{classname}.h") # Non-method structs are public + end + + h_file("qpid/framing/#{classname}.h") { + if (s.kind_of? AmqpMethod) + gen <<EOS +#include "qpid/framing/AMQMethodBody.h" +#include "qpid/framing/AMQP_ServerOperations.h" +#include "qpid/framing/MethodBodyConstVisitor.h" +EOS + end + include "qpid/framing/ModelMethod.h" if (execution_header?(s)) + + s.fields.each { |f| include "qpid/framing/#{f.cpptype.name}" if f.struct?} + + gen <<EOS + +#include <ostream> +#include "qpid/framing/amqp_types_full.h" +#include "qpid/CommonImportExport.h" + +namespace qpid { +namespace framing { + +class QPID_COMMON_CLASS_EXTERN #{classname} #{inheritance} { +EOS + if (is_packed(s)) + indent { s.fields.each { |f| genl "#{f.cpptype.name} #{f.cppname};" unless f.type_ == "bit"} } + indent { + genl "#{s.cpp_pack_type.name} flags;" + } + else + indent { s.fields.each { |f| genl "#{f.cpptype.name} #{f.cppname};" } } + end + genl "public:" + if (s.kind_of? AmqpMethod) + indent { genl "static const ClassId CLASS_ID = #{s.parent.code};" } + indent { genl "static const MethodId METHOD_ID = #{s.code};" } + end + + if (s.kind_of? AmqpStruct) + if (s.code) + indent { genl "static const uint16_t TYPE = #{s.full_code};" } + end + end + + indent { + define_constructor(classname, s) + genl "" + if (is_packed(s)) + s.fields.each { |f| declare_packed_accessors(f) } + else + s.fields.each { |f| define_accessors(f) } + end + } + if (s.kind_of? AmqpMethod) + methodbody_extra_defs(s) + end + if (s.kind_of? AmqpStruct) + indent {genl "QPID_COMMON_EXTERN friend std::ostream& operator<<(std::ostream&, const #{classname}&);" } + end + + gen <<EOS + QPID_COMMON_EXTERN void encode(Buffer&) const; + QPID_COMMON_EXTERN void decode(Buffer&, uint32_t=0); + QPID_COMMON_EXTERN void encodeStructBody(Buffer&) const; + QPID_COMMON_EXTERN void decodeStructBody(Buffer&, uint32_t=0); + QPID_COMMON_EXTERN uint32_t encodedSize() const; + QPID_COMMON_EXTERN uint32_t bodySize() const; + QPID_COMMON_EXTERN void print(std::ostream& out) const; +}; /* class #{classname} */ + +}} +EOS + } + cpp_file("qpid/framing/#{classname}.cpp") { + if (is_packed(s) || s.fields.size > 0 || execution_header?(s)) + buffer = "buffer" + else + buffer = "/*buffer*/" + end + gen <<EOS +#include "qpid/framing/#{classname}.h" +#include "qpid/framing/reply_exceptions.h" + +using namespace qpid::framing; + +EOS + + if (is_packed(s)) + define_packed_accessors(s) + end + gen <<EOS +void #{classname}::encodeStructBody(Buffer& #{buffer}) const +{ +EOS + if (execution_header?(s)) + genl "encodeHeader(buffer);" + end + + if (is_packed(s)) + indent {encode_packed_struct(s)} + else + indent { process_fields(s) { |f, combined| generate_encode(f, combined) } } + end + gen <<EOS +} + +void #{classname}::encode(Buffer& buffer) const +{ +EOS + indent { + if (s.kind_of? AmqpStruct) + if (s.code) + genl "buffer.put#{SizeType[s.size.to_i]}(bodySize() + 2/*typecode*/);" if s.size and s.size.to_i != 0 + genl "buffer.putShort(TYPE);" + else + genl "buffer.put#{SizeType[s.size.to_i]}(bodySize());" if s.size and s.size.to_i != 0 + end + end + genl "encodeStructBody(buffer);" + } + gen <<EOS +} + +void #{classname}::decodeStructBody(Buffer& #{buffer}, uint32_t /*size*/) +{ +EOS + if (execution_header?(s)) + genl "decodeHeader(buffer);" + end + + if (is_packed(s)) + indent {decode_packed_struct(s)} + else + indent { process_fields(s) { |f, combined| generate_decode(f, combined) } } + end + gen <<EOS +} + +void #{classname}::decode(Buffer& buffer, uint32_t /*size*/) +{ +EOS + indent { + if (s.kind_of? AmqpStruct) + genl "buffer.get#{SizeType[s.size.to_i]}();" if s.size and s.size.to_i != 0 + genl "if (TYPE != buffer.getShort()) throw FramingErrorException(\"Bad type code for struct\");" if s.code + end + genl "decodeStructBody(buffer);" + } + gen <<EOS +} + +uint32_t #{classname}::bodySize() const +{ + uint32_t total = 0; +EOS + if (execution_header?(s)) + genl "total += headerSize();" + end + + if (is_packed(s)) + indent {size_packed_struct(s)} + else + indent { process_fields(s) { |f, combined| generate_size(f, combined) } } + end + gen <<EOS + return total; +} + +uint32_t #{classname}::encodedSize() const { + uint32_t total = bodySize(); +EOS + if (s.kind_of? AmqpStruct) + genl "total += #{s.size}/*size field*/;" if s.size + genl "total += 2/*typecode*/;" if s.code + end + gen <<EOS + return total; +} + +void #{classname}::print(std::ostream& out) const +{ + out << "{#{classname}: "; +EOS + if (is_packed(s)) + indent {print_packed_struct(s)} + else + copy = Array.new(s.fields) + f = copy.shift + + indent { + genl "out << \"#{f.name}=\" << #{printable_form(f)};" if f + copy.each { |f| genl "out << \"; #{f.name}=\" << #{printable_form(f)};" } + } + end + gen <<EOS + out << "}"; +} +EOS + + if (s.kind_of? AmqpStruct) + gen <<EOS +namespace qpid{ +namespace framing{ + + std::ostream& operator<<(std::ostream& out, const #{classname}& s) + { + s.print(out); + return out; + } + +} +} +EOS + end +} + end + + def generate() + structs = @amqp.collect_all(AmqpStruct).select { |s| not ["command-fragment"].include?(s.name) } + structs.each { |s| define_struct(s) } + @amqp.methods_.each { |m| define_struct(m) } + #generate a single include file containing the list of structs for convenience + public_api("qpid/framing/amqp_structs.h") + h_file("qpid/framing/amqp_structs.h") { structs.each { |s| genl "#include \"qpid/framing/#{s.cppname}.h\"" } } + end +end + +StructGen.new($outdir, $amqp).generate() + diff --git a/qpid/cpp/rubygen/generate b/qpid/cpp/rubygen/generate new file mode 100755 index 0000000000..89b9b99520 --- /dev/null +++ b/qpid/cpp/rubygen/generate @@ -0,0 +1,160 @@ +#!/usr/bin/env ruby +# +# 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 'pathname' +require 'amqpgen' + +# +# Run a set of code generation templates. +# +if ARGV.size < 3 + puts <<EOS +Usage: #{ARGV[0]} SRCDIR APIDIR SPEC.xml [ ... ] TEMPLATE.rb [ ... ] +or: #{ARGV[0]} SRCDIR APIDIR SPEC.xml [ ... ] all [ makefragment.mk | makefragment.cmake ] + +Parse all SPEC.xml files to create an AMQP model, run each TEMPLATE +putting the resulting files under SRCDIR, public API files in APIdir. +Prints a list of files generated to standard output. + +If SRCDIR and APIDIR are '-' then just prints file list without generating files. +EOS + exit 1 +end + +# Create array of specs by version +def parse_specs(files) + lists=Hash.new { |h,k| h[k]=[] } + files.each { |f| + spec=AmqpRoot.new(File.new(f)) + lists[spec.version] << spec + } + specs={} + lists.each_pair { |k,l| + specs[k] = l.size==1 ? l.first : AmqpRoot.new(*l.map { |s| s.xml}) + } + return specs +end + +gendir=File.dirname(__FILE__) + +# Run selected templates +if ARGV.any? { |arg| arg=="all" } + templates=Dir["#{gendir}/*/*.rb"] +else +templates=ARGV.grep(/\.rb$/) + ARGV.each { |arg| + d=File.join gendir,arg + templates += Dir["#{d}/*.rb"] if File.directory? d + } +end + +$outdir=[ ARGV[0], ARGV[1] ] +$models=parse_specs(ARGV.grep(/\.xml$/)) + +templates.each { |t| + ver=Pathname.new(t).dirname.basename.to_s.split('.')[-1] + $amqp=$models[ver] + if $amqp + load t + else + puts "Warning: skipping #{t}, no spec file for version #{ver}." + end +} + +def cmake_continue(lines) lines.join(" \n "); end +def make_continue(lines) lines.join(" \\\n "); end + +# Generate makefile +makefile=ARGV.grep(/.mk$/)[0] +cmakefile=ARGV.grep(/.cmake$/)[0] +if cmakefile || makefile + srcdir,apidir=$outdir + dir=Dir.getwd + Dir.chdir File.dirname(__FILE__) + generator_files=Dir["**/*.rb"] << File.basename(__FILE__) + Dir.chdir dir + rgen_generator=generator_files.map{ |f| "$(rgen_dir)/#{f}" } + cmake_rgen_generator=generator_files.map{ |f| "${rgen_dir}/#{f}" } + rgen_srcs=GenFiles.get.map{ |f| "#{GenFiles.public_api?(f) ? apidir : srcdir}/#{f}" } + rgen_subdirs={} + rgen_srcs.each { |src| + if src.match(%r{(#{srcdir}|#{apidir})/qpid/([^/]+)/}) + subdir=$2 + rgen_subdirs[subdir] ||= [] + rgen_subdirs[subdir] << src + end + } + if (makefile) + File.open(makefile, 'w') { |out| + out << <<EOS +# Generated makefile fragment. +# Including makefile defines $(rgen_dir) $(rgen_cmd) and $(specs). + +rgen_generator=#{make_continue rgen_generator} +EOS + rgen_subdirs.each_key { |subdir| + out << "\nrgen_#{subdir}_srcs = #{make_continue(rgen_subdirs[subdir])}\n" + } + out << <<EOS +rgen_srcs=#{make_continue rgen_srcs} + +# Header file install rules. +EOS + ["amqp_0_10", "framing", "client/no_keyword","client", "broker"].each { |ns| + dir="qpid/#{ns}" + dir_ = dir.tr("/", "_") + regex=%r|#{dir}/[^/]+\.h$| + out << <<EOS +#{dir_}dir = $(includedir)/#{dir} +dist_#{dir_}_HEADERS = #{make_continue rgen_srcs.grep(regex)} + +EOS + } # each + } # File makefile + end # if (makefile) + + if (cmakefile) + File.open(cmakefile, 'w') { |out| + out << <<EOS +# Generated makefile fragment. +# Including makefile defines ${rgen_dir} ${rgen_cmd} and ${specs}. + +set(rgen_generator #{cmake_continue cmake_rgen_generator}) +EOS + rgen_subdirs.each_key { |subdir| + out << "\nset(rgen_#{subdir}_srcs #{cmake_continue(rgen_subdirs[subdir])})\n" + } + out << <<EOS +set(rgen_srcs #{cmake_continue rgen_srcs}) + +# Header file install rules. +EOS + ["amqp_0_10", "framing", "client/no_keyword","client", "broker"].each { |ns| + dir="qpid/#{ns}" + dir_ = dir.tr("/", "_") + regex=%r|#{dir}/[^/]+\.h$| + out << <<EOS +set(#{dir_}dir \${includedir}/#{dir}) +set(dist_#{dir_}_HEADERS #{cmake_continue rgen_srcs.grep(regex)}) + +EOS + } # each + } # File makefile + end # if (makefile) +end |