Skip to content
This repository
Newer
Older
100644 295 lines (262 sloc) 12.059 kb
be7923d1 »
2011-12-27 Added LGPL licence headers
1 =begin
2 Thrift4OZW - An Apache Thrift wrapper for OpenZWave
3 ----------------------------------------------------
4 Copyright (c) 2011 Elias Karakoulakis <elias.karakoulakis@gmail.com>
5
6 SOFTWARE NOTICE AND LICENSE
7
8 Thrift4OZW is free software: you can redistribute it and/or modify
9 it under the terms of the GNU Lesser General Public License as published
10 by the Free Software Foundation, either version 3 of the License,
11 or (at your option) any later version.
12
13 Thrift4OZW is distributed in the hope that it will be useful,
14 but WITHOUT ANY WARRANTY; without even the implied warranty of
15 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 GNU Lesser General Public License for more details.
17
18 You should have received a copy of the GNU Lesser General Public License
19 along with Thrift4OZW. If not, see <http://www.gnu.org/licenses/>.
20
21 for more information on the LGPL, see:
22 http://en.wikipedia.org/wiki/GNU_Lesser_General_Public_License
23 =end
24
25 # --------------------------
26 #
27 # create_server.rb: a Thrift server generator for OpenZWave
86032865 »
2011-12-06 Initial repo set-up
28 # transform a server skeleton file into a fully operational server
29 # a.k.a. "fills in the blanks for you"
30 #
be7923d1 »
2011-12-27 Added LGPL licence headers
31 # ---------------------------
32
86032865 »
2011-12-06 Initial repo set-up
33 require 'rubygems'
34 require 'rbgccxml'
9f497748 »
2012-01-09 use Ruby's GetoptLong for cmd line arguments
35 require 'getoptlong'
36
adbdb4a5 »
2012-01-10 remove all absolute library path dependencies from Makefile & create_…
37 def abspath(path)
38 return (path[0] == ".") ? File.expand_path(path) : path
39 end
40
bde506cc »
2012-02-03 [Begin/Cancel]ControllerCommand implementation, callback skeleton mec…
41 OZWRoot = abspath("../open-zwave")
42 ThriftInc = abspath("/usr/local/include/thrift")
43
9f497748 »
2012-01-09 use Ruby's GetoptLong for cmd line arguments
44 GetoptLong.new(
adbdb4a5 »
2012-01-10 remove all absolute library path dependencies from Makefile & create_…
45 [ "--ozwroot", GetoptLong::REQUIRED_ARGUMENT ],
46 [ "--thriftroot", GetoptLong::REQUIRED_ARGUMENT ],
bde506cc »
2012-02-03 [Begin/Cancel]ControllerCommand implementation, callback skeleton mec…
47 [ "--verbose", "-v", GetoptLong::NO_ARGUMENT ]
9f497748 »
2012-01-09 use Ruby's GetoptLong for cmd line arguments
48 ).each { |opt, arg|
49 case opt
adbdb4a5 »
2012-01-10 remove all absolute library path dependencies from Makefile & create_…
50 when '--ozwroot' then OZWRoot = abspath(arg)
51 when '--thriftroot' then ThriftInc = abspath(arg)
9f497748 »
2012-01-09 use Ruby's GetoptLong for cmd line arguments
52 when '--verbose' then $DEBUG = true
53 end
54 }
55
86032865 »
2011-12-06 Initial repo set-up
56 OverloadedRE = /([^_]*)(?:_(.*))/
57
58 MANAGER_INCLUDES = [
59 "gen_cpp",
9f497748 »
2012-01-09 use Ruby's GetoptLong for cmd line arguments
60 ThriftInc,
adbdb4a5 »
2012-01-10 remove all absolute library path dependencies from Makefile & create_…
61 File.join(OZWRoot, 'cpp', "tinyxml"),
62 File.join(OZWRoot, 'cpp', "src"),
63 File.join(OZWRoot, 'cpp', "src", "value_classes"),
64 File.join(OZWRoot, 'cpp', "src", "command_classes"),
65 File.join(OZWRoot, 'cpp', "src", "platform")
86032865 »
2011-12-06 Initial repo set-up
66 ]
67
68 #
69 # must load all source files in a single batch (RbGCCXML gets confused otherwise...)
70 #
71 files = [
9f497748 »
2012-01-09 use Ruby's GetoptLong for cmd line arguments
72 File.join(Dir.getwd, "gen-cpp", "RemoteManager_server.skeleton.cpp"),
adbdb4a5 »
2012-01-10 remove all absolute library path dependencies from Makefile & create_…
73 File.join(OZWRoot, 'cpp', "src", "Manager.h")
86032865 »
2011-12-06 Initial repo set-up
74 ]
75 puts "Parsing:" + files.join("\n\t")
76 RootNode = RbGCCXML.parse(files, :includes => MANAGER_INCLUDES)
77
78 # read skeleton file in memory as an array
79 output = File.open("gen-cpp/RemoteManager_server.skeleton.cpp").readlines
80
81
bde506cc »
2012-02-03 [Begin/Cancel]ControllerCommand implementation, callback skeleton mec…
82 #
83 Callbacks = {}
84 #
85
86032865 »
2011-12-06 Initial repo set-up
86 # fix the constructor
34c2ebe3 »
2011-12-12 More OpenZWave compatibility fixes
87 #lineno = RootNode.classes("RemoteManagerHandler").constructors[1]['line'].to_i
86032865 »
2011-12-06 Initial repo set-up
88 #~ output[lineno] = Constructor
89
4951e1d9 »
2011-12-10 create_server.rb: refactored code
90 a = RootNode.classes("RemoteManagerHandler").methods.find(:access => :public)
91 b = RootNode.namespaces("OpenZWave").classes("Manager").methods.find(:access => :public)
34c2ebe3 »
2011-12-12 More OpenZWave compatibility fixes
92 puts "RemoteManagerHandler: #{a.entries.size} public methods, OpenZWave::Manager: #{b.entries.size} public methods"
bede65bc »
2012-02-06 updated API to rev.410 of openzwave's main trunk
93 if (a.entries.size != b.entries.size) then
94 a_names = a.collect{ |meth|
95 (md = OverloadedRE.match(meth.name))? md[1] : meth.name
96 }.uniq
97 b_names = b.collect{ |meth| meth.name }.uniq
98 puts " Missing OpenZWave::Manager method mappings from RemoteManagerHandler:"
99 puts "\t" + (b_names - a_names).join("\n\t")
100 end
4951e1d9 »
2011-12-10 create_server.rb: refactored code
101
86032865 »
2011-12-06 Initial repo set-up
102 RootNode.classes("RemoteManagerHandler").methods.each { |meth|
103 # find line number, insert critical section enter code
104 lineno = meth['line'].to_i
105 #
106 target_method = nil
107 target_method_name = nil
108 disambiguation_hint = nil
109
110 # skeleton function's name has underscore => Overloaded. Needs disambiguation.
111 if md = OverloadedRE.match(meth.name) then
112 target_method_name = md[1]
113 disambiguation_hint = md[2]
114 else
115 target_method_name = meth.name
116 end
117
118 #
119 # SEARCH FOR MATCHING FUNCTION IN OPENZWAVE::MANAGER
120 #
121 search_result = RootNode.namespaces("OpenZWave").classes("Manager").methods.find(:name => target_method_name, :access => :public)
122 #puts "search result: #{search_result.class.name}"
123 case search_result
124 when RbGCCXML::QueryResult then
24ac1337 »
2011-12-22 skip creation of extra functions not found in target library
125 next if search_result.empty? # skip unknown functions (needed for "void SendAllValues()"
86032865 »
2011-12-06 Initial repo set-up
126 raise "#{target_method_name}(): no disambiguation hint given!!!" unless disambiguation_hint
127 #puts " ...Overloaded method: #{meth.name}"
128 search_result.each { |node|
129 # last argument's type must match disambiguation_hint
130 target_method = node if node.arguments[-1].cpp_type.to_cpp =~ Regexp.new(disambiguation_hint, Regexp::IGNORECASE)
131 # FIXME:: ListString => list<string>
132 }
133 when RbGCCXML::Method then
134 #puts " ...exact match for #{meth.name}"
135 target_method = search_result
136 end
137
138 raise "Unable to resolve target method! (#{meth.name})" unless target_method
139
140 #
141 # TIME TO BOOGEY
142 #
143
34c2ebe3 »
2011-12-12 More OpenZWave compatibility fixes
144 puts "CREATING MAPPING for (#{meth.return_type.to_cpp}) #{meth.name}" if $DEBUG
86032865 »
2011-12-06 Initial repo set-up
145
146 #Thrift transforms methods with complex return types (string, vector<...>, user-defined structs etc)
147 # example 1:
148 # (C++) string GetLibraryVersion( uint32 const _homeId );
149 # (thrift) string GetLibraryVersion( 1:i32 _homeId );
150 # (skeleton) void GetLibraryVersion(std::string& _return, const int32_t _homeId)
151 #
152 # example 2:
153 # (C++) uint32 GetNodeNeighbors( uint32 const _homeId, uint8 const _nodeId, uint8** _nodeNeighbors );
154 # (thrift) UInt32_NeighborMap GetNodeNeighbors( 1:i32 _homeId, 2:byte _nodeId);
155 # (skeleton) void GetNodeNeighbors(UInt32_ListByte& _return, const int32_t _homeId, const int8_t _nodeId)
156 # ozw_types.h: class UInt32_ListByte {
157 # int32_t retval;
158 # std::vector<int8_t> arg; *** notice manual copying needed from C-style pointer to pointers of uint8's (not very C++ish)
159 # }
160 #
161 # example 3:
162 # (C++) bool GetValueListItems( ValueID const& _id, vector<string>* o_value );
163 # (thrift) Bool_ListString GetValueListItems( 1:RemoteValueID _id );
164 # (skeleton) void GetValueListItems(Bool_ListString& _return, const RemoteValueID _id)
4951e1d9 »
2011-12-10 create_server.rb: refactored code
165 # where the Thrift definition for Bool_ListString is:
166 # (ozw_types.h):class Bool_ListString {
86032865 »
2011-12-06 Initial repo set-up
167 # bool retval;
168 # std::vector<std::string> arg;
169 # }
170 #
171
a41ce7d6 »
2011-12-07 1) fix Makefile to produce binaries in their directories
172 #
4951e1d9 »
2011-12-10 create_server.rb: refactored code
173 # STEP 1. Map arguments from target (OpenZWave::Manager) to source (skeleton server)
a41ce7d6 »
2011-12-07 1) fix Makefile to produce binaries in their directories
174 #
4951e1d9 »
2011-12-10 create_server.rb: refactored code
175 argmap = {}
176 # KEY: target argument node
177 # VALUE: hash with
178 # :descriptor => source argument DESCRIPTOR STRING (eg "_return._className")
179 # :node => the actual source argument node (Argument or Field)
180 target_method.arguments.each {|a|
181 # 1) match directly by name
182 if (arg = meth.arguments.find(:name => a.name )).is_a?RbGCCXML::Argument then
183 argmap[a] = {}
184 argmap[a][:descriptor] = arg.name
185 argmap[a][:node] = arg
186 # 2) else, match as a member of Thrift's special "_return" argument (class struct)
bde506cc »
2012-02-03 [Begin/Cancel]ControllerCommand implementation, callback skeleton mec…
187 elsif (_ret = meth.arguments.find(:name => "_return" )) and
188 (_ret.is_a?RbGCCXML::Node) and
189 (_ret.cpp_type.base_type.is_a?RbGCCXML::Class) and
190 (arg = _ret.cpp_type.base_type.variables.find(:name => a.name)).is_a?RbGCCXML::Field then
4951e1d9 »
2011-12-10 create_server.rb: refactored code
191 argmap[a] = {}
192 argmap[a][:descriptor] = "_return.#{a.name}"
193 argmap[a][:node] = arg
bde506cc »
2012-02-03 [Begin/Cancel]ControllerCommand implementation, callback skeleton mec…
194 # 3) else, check if is a _callback or _context argument (callbacks)
195 elsif (a.name =~ /callback/) then
196 cb_fun = "#{target_method.name}_callback"
197 puts "defining #{cb_fun}"
198 fntype = RbGCCXML::NodeCache.find(a['type']).base_type # => RbGCCXML::PointerType => RbGCCXML::FunctionType
199 i = 0
200 fntype_args = fntype.arguments.collect{ |arg| i=i+1; "#{arg.to_cpp} arg#{i}"}.join(', ')
201 cb = []
202 cb << fntype.base_type.return_type.to_cpp + " #{cb_fun}(#{fntype_args}) {"
203 cb << "\t// FIXME: fill in the blanks (sorry!)"
204 cb << "}"
205 Callbacks[cb_fun] = cb.join("\n")
206 argmap[a] = {}
207 argmap[a][:descriptor] = "&#{target_method.name}_callback"
208 #
209 elsif (a.name =~ /context/) then
210 # pass the Thrift server singleton instance as the callback context
211 argmap[a] = {}
212 argmap[a][:descriptor] = "(void*) this"
4951e1d9 »
2011-12-10 create_server.rb: refactored code
213 else
9f497748 »
2012-01-09 use Ruby's GetoptLong for cmd line arguments
214 raise "Reverse argument mapping: couldn't resolve argument '#{a.name}' in method '#{target_method.name}'!!!"
4951e1d9 »
2011-12-10 create_server.rb: refactored code
215 end
216 }
86032865 »
2011-12-06 Initial repo set-up
217
218 #
4951e1d9 »
2011-12-10 create_server.rb: refactored code
219 # STEP 2. Resolve the function call's return clause
86032865 »
2011-12-06 Initial repo set-up
220 #
4951e1d9 »
2011-12-10 create_server.rb: refactored code
221 function_return_clause = ''
222 if (_return = meth.arguments.find(:name => '_return')).is_a?RbGCCXML::Argument then
223 puts "Thrift special _return argument detected!" if $DEBUG
224 if (_return.cpp_type.base_type.is_a?RbGCCXML::Class) and
225 (retval = _return.cpp_type.base_type.variables.find(:name => 'retval')) and
226 (retval.is_a?RbGCCXML::Field) then
227 function_return_clause = "_return.retval = "
228 else
6a750a29 »
2011-12-12 more documentation added
229 unless target_method.return_type.name == "void" then
4951e1d9 »
2011-12-10 create_server.rb: refactored code
230 function_return_clause = "_return = "
6a750a29 »
2011-12-12 more documentation added
231 end
4951e1d9 »
2011-12-10 create_server.rb: refactored code
232 end
233 end
234
86032865 »
2011-12-06 Initial repo set-up
235 #
4951e1d9 »
2011-12-10 create_server.rb: refactored code
236 # STEP 3. Prepare argument array (ordered by target_method's argument order)
237 #
238 arg_array = []
239 target_method.arguments.each { |tgt_arg|
240 if (hsh = argmap[tgt_arg]) then
241 descriptor = hsh[:descriptor]
242 #puts " src=#{descriptor}\ttgt=#{tgt_arg.qualified_name}"
a41ce7d6 »
2011-12-07 1) fix Makefile to produce binaries in their directories
243 ampersand = (tgt_arg.cpp_type.to_cpp.include?('*') ? '&' : '')
bde506cc »
2012-02-03 [Begin/Cancel]ControllerCommand implementation, callback skeleton mec…
244 if (src_arg = hsh[:node]) then
245 case src_arg.to_cpp
246 when /RemoteValueID/
247 arg_array << "#{descriptor}.toValueID()"
248 else
249 arg_array << "(#{tgt_arg.cpp_type.to_cpp}) #{ampersand}#{descriptor}"
250 size_src = src_arg.cpp_type.base_type['size'].to_i
251 size_tgt = tgt_arg.cpp_type.base_type['size'].to_i
252 # sanity check
253 puts "WARNING!!! method '#{meth.name}': Argument '#{descriptor}' size mismatch (src=#{size_src} tgt=#{size_tgt}) - CHECK GENERATED CODE!" unless size_src == size_tgt
254 end
255 else
256 puts "WARNING!!! target argument '#{tgt_arg.to_cpp}' not bound to a source node - needs patching..."
257 arg_array << descriptor
86032865 »
2011-12-06 Initial repo set-up
258 end
259 end
260 }
4951e1d9 »
2011-12-10 create_server.rb: refactored code
261
6a750a29 »
2011-12-12 more documentation added
262 # Get me the manager, and lock the criticalsection
263 output[lineno] = "\tManager* mgr = Manager::Get();\n\tg_criticalSection.lock();\n"
a41ce7d6 »
2011-12-07 1) fix Makefile to produce binaries in their directories
264 fcall = "#{function_return_clause} mgr->#{target_method.name}(#{arg_array.compact.join(', ')})"
86032865 »
2011-12-06 Initial repo set-up
265 case meth.return_type.name
266 when "void"
267 output[lineno+1] = "\t#{fcall};\n"
268 else
269 output[lineno+1] = "\t#{meth.return_type.to_cpp} function_result = #{fcall};\n"
270 end
271 # unlock the critical section
272 output[lineno+1] << "\tg_criticalSection.unlock();\n"
273 # output return statement (unless rettype == void)
274 unless meth.return_type.name == "void"
275 output[lineno+1] << "\treturn(function_result);\n"
276 end
277
278 }
279
280 output[0] = "// Automatically generated OpenZWave::Manager_server wrapper\n"
281 output[1] = "// (c) 2011 Elias Karakoulakis <elias.karakoulakis@gmail.com>\n"
34c2ebe3 »
2011-12-12 More OpenZWave compatibility fixes
282 # comment out main()
283 ((RootNode.functions("main")["line"].to_i-1)..(output.size)).each{ |i|
284 output[i] = "// #{output[i]}"
285 }
bde506cc »
2012-02-03 [Begin/Cancel]ControllerCommand implementation, callback skeleton mec…
286
287 # add our callback sauce after the first constructor
288 lineno = RootNode.classes("RemoteManagerHandler")['line'].to_i - 2
289 output[lineno] = "\n" << Callbacks.values.join("\n") << "\n\n"
290
86032865 »
2011-12-06 Initial repo set-up
291 # write out the generated file
bde506cc »
2012-02-03 [Begin/Cancel]ControllerCommand implementation, callback skeleton mec…
292 HackedFile = "gen-cpp/RemoteManager_server.cpp"
293 puts "Writing generated server (#{HackedFile})...."
294 File.new(HackedFile, File::CREAT|File::TRUNC|File::RDWR, 0644) << output.join
4951e1d9 »
2011-12-10 create_server.rb: refactored code
295 puts "Done!"
Something went wrong with that request. Please try again.