Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Newer
Older
100644 314 lines (280 sloc) 12.622 kb
be7923d Added LGPL licence headers
Elias Karakoulakis authored
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
8603286 Initial repo set-up
Elias Karakoulakis authored
28 # transform a server skeleton file into a fully operational server
29 # a.k.a. "fills in the blanks for you"
30 #
be7923d Added LGPL licence headers
Elias Karakoulakis authored
31 # ---------------------------
32
8603286 Initial repo set-up
Elias Karakoulakis authored
33 require 'rubygems'
34 require 'rbgccxml'
9f49774 use Ruby's GetoptLong for cmd line arguments
Elias Karakoulakis authored
35 require 'getoptlong'
36
adbdb4a remove all absolute library path dependencies from Makefile & create_ser...
Elias Karakoulakis authored
37 def abspath(path)
38 return (path[0] == ".") ? File.expand_path(path) : path
39 end
40
1d57943 updated OpenZWave API to r437
Elias Karakoulakis authored
41 #OZWRoot = abspath("../open-zwave")
42 #ThriftInc = abspath("/usr/local/include/thrift")
bde506c [Begin/Cancel]ControllerCommand implementation, callback skeleton mechan...
Elias Karakoulakis authored
43
9f49774 use Ruby's GetoptLong for cmd line arguments
Elias Karakoulakis authored
44 GetoptLong.new(
adbdb4a remove all absolute library path dependencies from Makefile & create_ser...
Elias Karakoulakis authored
45 [ "--ozwroot", GetoptLong::REQUIRED_ARGUMENT ],
46 [ "--thriftroot", GetoptLong::REQUIRED_ARGUMENT ],
bde506c [Begin/Cancel]ControllerCommand implementation, callback skeleton mechan...
Elias Karakoulakis authored
47 [ "--verbose", "-v", GetoptLong::NO_ARGUMENT ]
9f49774 use Ruby's GetoptLong for cmd line arguments
Elias Karakoulakis authored
48 ).each { |opt, arg|
49 case opt
adbdb4a remove all absolute library path dependencies from Makefile & create_ser...
Elias Karakoulakis authored
50 when '--ozwroot' then OZWRoot = abspath(arg)
51 when '--thriftroot' then ThriftInc = abspath(arg)
9f49774 use Ruby's GetoptLong for cmd line arguments
Elias Karakoulakis authored
52 when '--verbose' then $DEBUG = true
53 end
54 }
55
8603286 Initial repo set-up
Elias Karakoulakis authored
56 OverloadedRE = /([^_]*)(?:_(.*))/
57
58 MANAGER_INCLUDES = [
59 "gen_cpp",
9f49774 use Ruby's GetoptLong for cmd line arguments
Elias Karakoulakis authored
60 ThriftInc,
adbdb4a remove all absolute library path dependencies from Makefile & create_ser...
Elias Karakoulakis authored
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")
8603286 Initial repo set-up
Elias Karakoulakis authored
66 ]
67
1d57943 updated OpenZWave API to r437
Elias Karakoulakis authored
68 # API calls intentionally ignored by this script
69 MANAGER_API_IGNORE = %w{
70 Create
71 Get
72 Destroy
73 GetOptions
74 AddDriver
75 RemoveDriver
76 AddWatcher
77 RemoveWatcher
78 }
79
8603286 Initial repo set-up
Elias Karakoulakis authored
80 #
81 # must load all source files in a single batch (RbGCCXML gets confused otherwise...)
82 #
83 files = [
9f49774 use Ruby's GetoptLong for cmd line arguments
Elias Karakoulakis authored
84 File.join(Dir.getwd, "gen-cpp", "RemoteManager_server.skeleton.cpp"),
adbdb4a remove all absolute library path dependencies from Makefile & create_ser...
Elias Karakoulakis authored
85 File.join(OZWRoot, 'cpp', "src", "Manager.h")
8603286 Initial repo set-up
Elias Karakoulakis authored
86 ]
87 puts "Parsing:" + files.join("\n\t")
23788ce Elias Karakoulakis switched from PocoStomp to BoostStomp (just for kicks!)
authored
88 RootNode = RbGCCXML.parse(files, :includes => MANAGER_INCLUDES, :cxxflags => "-DHAVE_INTTYPES_H -DHAVE_NETINET_IN_H")
8603286 Initial repo set-up
Elias Karakoulakis authored
89
90 # read skeleton file in memory as an array
91 output = File.open("gen-cpp/RemoteManager_server.skeleton.cpp").readlines
92
93
bde506c [Begin/Cancel]ControllerCommand implementation, callback skeleton mechan...
Elias Karakoulakis authored
94 #
95 Callbacks = {}
96 #
97
8603286 Initial repo set-up
Elias Karakoulakis authored
98 # fix the constructor
34c2ebe More OpenZWave compatibility fixes
Elias Karakoulakis authored
99 #lineno = RootNode.classes("RemoteManagerHandler").constructors[1]['line'].to_i
8603286 Initial repo set-up
Elias Karakoulakis authored
100 #~ output[lineno] = Constructor
101
4951e1d create_server.rb: refactored code
Elias Karakoulakis authored
102 a = RootNode.classes("RemoteManagerHandler").methods.find(:access => :public)
103 b = RootNode.namespaces("OpenZWave").classes("Manager").methods.find(:access => :public)
1d57943 updated OpenZWave API to r437
Elias Karakoulakis authored
104 puts "RemoteManagerHandler: #{a.entries.size} public methods"
105 puts "OpenZWave::Manager: #{b.entries.size} public methods"
106 puts " --//-- ignored : #{MANAGER_API_IGNORE.size} methods"
bede65b updated API to rev.410 of openzwave's main trunk
Elias Karakoulakis authored
107 if (a.entries.size != b.entries.size) then
108 a_names = a.collect{ |meth|
1d57943 updated OpenZWave API to r437
Elias Karakoulakis authored
109 (md = OverloadedRE.match(meth.name))? md[1] : meth.name
bede65b updated API to rev.410 of openzwave's main trunk
Elias Karakoulakis authored
110 }.uniq
111 b_names = b.collect{ |meth| meth.name }.uniq
1d57943 updated OpenZWave API to r437
Elias Karakoulakis authored
112 missing = b_names - a_names - MANAGER_API_IGNORE
113 if missing.size > 0 then
114 puts "\n-----------------------------------------------------------------------"
115 puts " Missing OpenZWave::Manager method mappings from RemoteManagerHandler:"
116 puts "-----------------------------------------------------------------------"
cdcf187 Elias Karakoulakis updated demo files
authored
117 puts "\n\t" + missing.join("\n\t") + "\n\t"
1d57943 updated OpenZWave API to r437
Elias Karakoulakis authored
118 end
bede65b updated API to rev.410 of openzwave's main trunk
Elias Karakoulakis authored
119 end
4951e1d create_server.rb: refactored code
Elias Karakoulakis authored
120
8603286 Initial repo set-up
Elias Karakoulakis authored
121 RootNode.classes("RemoteManagerHandler").methods.each { |meth|
122 # find line number, insert critical section enter code
123 lineno = meth['line'].to_i
124 #
125 target_method = nil
126 target_method_name = nil
127 disambiguation_hint = nil
128
129 # skeleton function's name has underscore => Overloaded. Needs disambiguation.
130 if md = OverloadedRE.match(meth.name) then
131 target_method_name = md[1]
132 disambiguation_hint = md[2]
133 else
134 target_method_name = meth.name
135 end
136
137 #
138 # SEARCH FOR MATCHING FUNCTION IN OPENZWAVE::MANAGER
139 #
140 search_result = RootNode.namespaces("OpenZWave").classes("Manager").methods.find(:name => target_method_name, :access => :public)
141 #puts "search result: #{search_result.class.name}"
142 case search_result
143 when RbGCCXML::QueryResult then
24ac133 skip creation of extra functions not found in target library
Elias Karakoulakis authored
144 next if search_result.empty? # skip unknown functions (needed for "void SendAllValues()"
8603286 Initial repo set-up
Elias Karakoulakis authored
145 raise "#{target_method_name}(): no disambiguation hint given!!!" unless disambiguation_hint
146 #puts " ...Overloaded method: #{meth.name}"
147 search_result.each { |node|
148 # last argument's type must match disambiguation_hint
149 target_method = node if node.arguments[-1].cpp_type.to_cpp =~ Regexp.new(disambiguation_hint, Regexp::IGNORECASE)
150 # FIXME:: ListString => list<string>
151 }
152 when RbGCCXML::Method then
153 #puts " ...exact match for #{meth.name}"
154 target_method = search_result
155 end
156
157 raise "Unable to resolve target method! (#{meth.name})" unless target_method
158
159 #
160 # TIME TO BOOGEY
161 #
162
34c2ebe More OpenZWave compatibility fixes
Elias Karakoulakis authored
163 puts "CREATING MAPPING for (#{meth.return_type.to_cpp}) #{meth.name}" if $DEBUG
8603286 Initial repo set-up
Elias Karakoulakis authored
164
165 #Thrift transforms methods with complex return types (string, vector<...>, user-defined structs etc)
166 # example 1:
167 # (C++) string GetLibraryVersion( uint32 const _homeId );
168 # (thrift) string GetLibraryVersion( 1:i32 _homeId );
169 # (skeleton) void GetLibraryVersion(std::string& _return, const int32_t _homeId)
170 #
171 # example 2:
172 # (C++) uint32 GetNodeNeighbors( uint32 const _homeId, uint8 const _nodeId, uint8** _nodeNeighbors );
173 # (thrift) UInt32_NeighborMap GetNodeNeighbors( 1:i32 _homeId, 2:byte _nodeId);
174 # (skeleton) void GetNodeNeighbors(UInt32_ListByte& _return, const int32_t _homeId, const int8_t _nodeId)
175 # ozw_types.h: class UInt32_ListByte {
176 # int32_t retval;
177 # std::vector<int8_t> arg; *** notice manual copying needed from C-style pointer to pointers of uint8's (not very C++ish)
178 # }
179 #
180 # example 3:
181 # (C++) bool GetValueListItems( ValueID const& _id, vector<string>* o_value );
182 # (thrift) Bool_ListString GetValueListItems( 1:RemoteValueID _id );
183 # (skeleton) void GetValueListItems(Bool_ListString& _return, const RemoteValueID _id)
4951e1d create_server.rb: refactored code
Elias Karakoulakis authored
184 # where the Thrift definition for Bool_ListString is:
185 # (ozw_types.h):class Bool_ListString {
8603286 Initial repo set-up
Elias Karakoulakis authored
186 # bool retval;
187 # std::vector<std::string> arg;
188 # }
189 #
190
a41ce7d 1) fix Makefile to produce binaries in their directories
Elias Karakoulakis authored
191 #
4951e1d create_server.rb: refactored code
Elias Karakoulakis authored
192 # STEP 1. Map arguments from target (OpenZWave::Manager) to source (skeleton server)
a41ce7d 1) fix Makefile to produce binaries in their directories
Elias Karakoulakis authored
193 #
4951e1d create_server.rb: refactored code
Elias Karakoulakis authored
194 argmap = {}
195 # KEY: target argument node
196 # VALUE: hash with
197 # :descriptor => source argument DESCRIPTOR STRING (eg "_return._className")
198 # :node => the actual source argument node (Argument or Field)
199 target_method.arguments.each {|a|
200 # 1) match directly by name
201 if (arg = meth.arguments.find(:name => a.name )).is_a?RbGCCXML::Argument then
202 argmap[a] = {}
203 argmap[a][:descriptor] = arg.name
204 argmap[a][:node] = arg
205 # 2) else, match as a member of Thrift's special "_return" argument (class struct)
bde506c [Begin/Cancel]ControllerCommand implementation, callback skeleton mechan...
Elias Karakoulakis authored
206 elsif (_ret = meth.arguments.find(:name => "_return" )) and
207 (_ret.is_a?RbGCCXML::Node) and
208 (_ret.cpp_type.base_type.is_a?RbGCCXML::Class) and
209 (arg = _ret.cpp_type.base_type.variables.find(:name => a.name)).is_a?RbGCCXML::Field then
4951e1d create_server.rb: refactored code
Elias Karakoulakis authored
210 argmap[a] = {}
211 argmap[a][:descriptor] = "_return.#{a.name}"
212 argmap[a][:node] = arg
bde506c [Begin/Cancel]ControllerCommand implementation, callback skeleton mechan...
Elias Karakoulakis authored
213 # 3) else, check if is a _callback or _context argument (callbacks)
214 elsif (a.name =~ /callback/) then
215 cb_fun = "#{target_method.name}_callback"
216 puts "defining #{cb_fun}"
217 fntype = RbGCCXML::NodeCache.find(a['type']).base_type # => RbGCCXML::PointerType => RbGCCXML::FunctionType
218 i = 0
219 fntype_args = fntype.arguments.collect{ |arg| i=i+1; "#{arg.to_cpp} arg#{i}"}.join(', ')
220 cb = []
221 cb << fntype.base_type.return_type.to_cpp + " #{cb_fun}(#{fntype_args}) {"
222 cb << "\t// FIXME: fill in the blanks (sorry!)"
223 cb << "}"
224 Callbacks[cb_fun] = cb.join("\n")
225 argmap[a] = {}
226 argmap[a][:descriptor] = "&#{target_method.name}_callback"
227 #
228 elsif (a.name =~ /context/) then
229 # pass the Thrift server singleton instance as the callback context
230 argmap[a] = {}
231 argmap[a][:descriptor] = "(void*) this"
4951e1d create_server.rb: refactored code
Elias Karakoulakis authored
232 else
9f49774 use Ruby's GetoptLong for cmd line arguments
Elias Karakoulakis authored
233 raise "Reverse argument mapping: couldn't resolve argument '#{a.name}' in method '#{target_method.name}'!!!"
4951e1d create_server.rb: refactored code
Elias Karakoulakis authored
234 end
235 }
8603286 Initial repo set-up
Elias Karakoulakis authored
236
237 #
4951e1d create_server.rb: refactored code
Elias Karakoulakis authored
238 # STEP 2. Resolve the function call's return clause
8603286 Initial repo set-up
Elias Karakoulakis authored
239 #
4951e1d create_server.rb: refactored code
Elias Karakoulakis authored
240 function_return_clause = ''
241 if (_return = meth.arguments.find(:name => '_return')).is_a?RbGCCXML::Argument then
242 puts "Thrift special _return argument detected!" if $DEBUG
243 if (_return.cpp_type.base_type.is_a?RbGCCXML::Class) and
244 (retval = _return.cpp_type.base_type.variables.find(:name => 'retval')) and
245 (retval.is_a?RbGCCXML::Field) then
246 function_return_clause = "_return.retval = "
247 else
6a750a2 more documentation added
Elias Karakoulakis authored
248 unless target_method.return_type.name == "void" then
4951e1d create_server.rb: refactored code
Elias Karakoulakis authored
249 function_return_clause = "_return = "
6a750a2 more documentation added
Elias Karakoulakis authored
250 end
4951e1d create_server.rb: refactored code
Elias Karakoulakis authored
251 end
252 end
253
8603286 Initial repo set-up
Elias Karakoulakis authored
254 #
4951e1d create_server.rb: refactored code
Elias Karakoulakis authored
255 # STEP 3. Prepare argument array (ordered by target_method's argument order)
256 #
257 arg_array = []
258 target_method.arguments.each { |tgt_arg|
259 if (hsh = argmap[tgt_arg]) then
260 descriptor = hsh[:descriptor]
261 #puts " src=#{descriptor}\ttgt=#{tgt_arg.qualified_name}"
a41ce7d 1) fix Makefile to produce binaries in their directories
Elias Karakoulakis authored
262 ampersand = (tgt_arg.cpp_type.to_cpp.include?('*') ? '&' : '')
bde506c [Begin/Cancel]ControllerCommand implementation, callback skeleton mechan...
Elias Karakoulakis authored
263 if (src_arg = hsh[:node]) then
264 case src_arg.to_cpp
265 when /RemoteValueID/
266 arg_array << "#{descriptor}.toValueID()"
267 else
268 arg_array << "(#{tgt_arg.cpp_type.to_cpp}) #{ampersand}#{descriptor}"
269 size_src = src_arg.cpp_type.base_type['size'].to_i
270 size_tgt = tgt_arg.cpp_type.base_type['size'].to_i
271 # sanity check
272 puts "WARNING!!! method '#{meth.name}': Argument '#{descriptor}' size mismatch (src=#{size_src} tgt=#{size_tgt}) - CHECK GENERATED CODE!" unless size_src == size_tgt
273 end
274 else
275 puts "WARNING!!! target argument '#{tgt_arg.to_cpp}' not bound to a source node - needs patching..."
276 arg_array << descriptor
8603286 Initial repo set-up
Elias Karakoulakis authored
277 end
278 end
279 }
4951e1d create_server.rb: refactored code
Elias Karakoulakis authored
280
6a750a2 more documentation added
Elias Karakoulakis authored
281 # Get me the manager, and lock the criticalsection
282 output[lineno] = "\tManager* mgr = Manager::Get();\n\tg_criticalSection.lock();\n"
a41ce7d 1) fix Makefile to produce binaries in their directories
Elias Karakoulakis authored
283 fcall = "#{function_return_clause} mgr->#{target_method.name}(#{arg_array.compact.join(', ')})"
8603286 Initial repo set-up
Elias Karakoulakis authored
284 case meth.return_type.name
285 when "void"
286 output[lineno+1] = "\t#{fcall};\n"
287 else
288 output[lineno+1] = "\t#{meth.return_type.to_cpp} function_result = #{fcall};\n"
289 end
290 # unlock the critical section
291 output[lineno+1] << "\tg_criticalSection.unlock();\n"
292 # output return statement (unless rettype == void)
293 unless meth.return_type.name == "void"
294 output[lineno+1] << "\treturn(function_result);\n"
295 end
296
297 }
298
299 output[0] = "// Automatically generated OpenZWave::Manager_server wrapper\n"
300 output[1] = "// (c) 2011 Elias Karakoulakis <elias.karakoulakis@gmail.com>\n"
34c2ebe More OpenZWave compatibility fixes
Elias Karakoulakis authored
301 # comment out main()
302 ((RootNode.functions("main")["line"].to_i-1)..(output.size)).each{ |i|
303 output[i] = "// #{output[i]}"
304 }
bde506c [Begin/Cancel]ControllerCommand implementation, callback skeleton mechan...
Elias Karakoulakis authored
305
306 # add our callback sauce after the first constructor
307 lineno = RootNode.classes("RemoteManagerHandler")['line'].to_i - 2
308 output[lineno] = "\n" << Callbacks.values.join("\n") << "\n\n"
309
8603286 Initial repo set-up
Elias Karakoulakis authored
310 # write out the generated file
bde506c [Begin/Cancel]ControllerCommand implementation, callback skeleton mechan...
Elias Karakoulakis authored
311 HackedFile = "gen-cpp/RemoteManager_server.cpp"
312 puts "Writing generated server (#{HackedFile})...."
313 File.new(HackedFile, File::CREAT|File::TRUNC|File::RDWR, 0644) << output.join
4951e1d create_server.rb: refactored code
Elias Karakoulakis authored
314 puts "Done!"
Something went wrong with that request. Please try again.